mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-20 15:39:25 +02:00
Compare commits
51 Commits
3fcd5d6924
...
344358989f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
344358989f | ||
|
|
716feab676 | ||
|
|
839132825a | ||
|
|
f93d3a1ad2 | ||
|
|
b3145d151d | ||
|
|
249395f14f | ||
|
|
5139255856 | ||
|
|
4455829366 | ||
|
|
345b1d6775 | ||
|
|
40b8cc92fe | ||
|
|
cd00841d96 | ||
|
|
714bb6bca3 | ||
|
|
585027fab7 | ||
|
|
571735cd57 | ||
|
|
b430118124 | ||
|
|
180be33d9d | ||
|
|
9e251dc397 | ||
|
|
92fa97c3aa | ||
|
|
ff001afd46 | ||
|
|
32d10080a4 | ||
|
|
1bbed177c8 | ||
|
|
a3ca438bef | ||
|
|
28ec9b34b8 | ||
|
|
a1f9ff4542 | ||
|
|
0afaf753c6 | ||
|
|
240bb2dfca | ||
|
|
4a63ee37e2 | ||
|
|
9bba4b78dd | ||
|
|
82a92f6296 | ||
|
|
8ca6e42f10 | ||
|
|
6294199343 | ||
|
|
1443668694 | ||
|
|
55c5d29e2d | ||
|
|
3a7e3e2719 | ||
|
|
cd2fe2f9a1 | ||
|
|
92081c9f1a | ||
|
|
34f34ef13d | ||
|
|
ab196cb532 | ||
|
|
c7b4b9aa80 | ||
|
|
2973083dda | ||
|
|
d0ba99f381 | ||
|
|
9c9c386af7 | ||
|
|
9d787ca0b4 | ||
|
|
0e0d9fbde2 | ||
|
|
d4f86764bb | ||
|
|
05e8f4d82c | ||
|
|
61dbae19dc | ||
|
|
05aebfea46 | ||
|
|
f1a2736a31 | ||
|
|
dbea1203b4 | ||
|
|
eb3c101959 |
51
.claude/settings.json
Normal file
51
.claude/settings.json
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(awk '-F[\\(\\),]' ' *)",
|
||||||
|
"Bash(xargs ls -lah)",
|
||||||
|
"Bash(py --version)",
|
||||||
|
"Bash(py -3 --version)",
|
||||||
|
"Bash(py scripts/import_cmangos_travel_nodes.py)",
|
||||||
|
"Read(//home/dev/azerothcore_installer/_server/azerothcore/modules/mod-playerbots/data/sql/playerbots/updates/**)",
|
||||||
|
"Bash(py scripts/fixup_id_collision.py)",
|
||||||
|
"Bash(py scripts/insert_ignore.py)",
|
||||||
|
"Bash(grep -nA 6 \"passFilter\" C:/Users/Admin/git/main/azerothcore-wotlk/deps/recastnavigation/Detour/Include/DetourNavMeshQuery.h)",
|
||||||
|
"Bash(grep -nA 5 \"dtQueryFilter::passFilter\" C:/Users/Admin/git/main/azerothcore-wotlk/deps/recastnavigation/Detour/Source/DetourNavMeshQuery.cpp)",
|
||||||
|
"Bash(grep -nB1 -A2 \"modAlmostUnwalkableTriangles\" C:/Users/Admin/git/main/azerothcore-wotlk/src/tools/mmaps_generator/MapBuilder.cpp)",
|
||||||
|
"Bash(xargs grep -l \"MoveFarTo\\\\|ResolveMovePath\\\\|DispatchMovement\")",
|
||||||
|
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" status)",
|
||||||
|
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" add src/server/game/Movement/MovementGenerators/PathGenerator.h)",
|
||||||
|
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" commit -m \"feat\\(Core/Movement\\): Expose dtQueryFilter::setAreaCost via PathGenerator\")",
|
||||||
|
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" push)",
|
||||||
|
"Bash(wait)",
|
||||||
|
"Bash(git -C \"c:/Users/Admin/git/main/azerothcore-wotlk/modules/mod-playerbots\" show 3710c35a)",
|
||||||
|
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" show 9cccc5d26)",
|
||||||
|
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" add src/server/game/Movement/MovementGenerators/PathGenerator.cpp)",
|
||||||
|
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" commit -m \"feat\\(Core/Movement\\): Bias NAV_WATER 10x in default Player path filter\")",
|
||||||
|
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" commit -m \"Revert \\\\\"feat\\(Core/Movement\\): Bias NAV_WATER 10x in default Player path filter\\\\\"\")",
|
||||||
|
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" commit -m \"Revert \\\\\"feat\\(Core/Movement\\): Expose dtQueryFilter::setAreaCost via PathGenerator\\\\\"\")",
|
||||||
|
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" log --oneline cf9c6e9f3353d386f398cbe7a821abfd8fe9a4b3..HEAD)",
|
||||||
|
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" diff cf9c6e9f3353d386f398cbe7a821abfd8fe9a4b3..HEAD --stat)",
|
||||||
|
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" reset --hard cf9c6e9f3353d386f398cbe7a821abfd8fe9a4b3)",
|
||||||
|
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" push --force-with-lease)",
|
||||||
|
"Bash(git cherry-pick *)",
|
||||||
|
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" status -s)",
|
||||||
|
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" diff --stat)",
|
||||||
|
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" add src/server/game/Movement/MovementGenerators/PathGenerator.cpp src/server/game/Movement/MovementGenerators/PathGenerator.h)",
|
||||||
|
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" commit -m \"feat\\(Core/Movement\\): Cap Player path filter at 50° + water bias under MOD_PLAYERBOTS\")",
|
||||||
|
"Bash(grep -n \"CollectIncludeDirectories\\\\|sourcePath\\\\|GLOB.*\\\\\\\\.cpp\\\\\\\\|target_include\" C:/Users/Admin/git/main/azerothcore-wotlk/modules/CMakeLists.txt)",
|
||||||
|
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" commit -m \"fix\\(Core/Movement\\): Gate playerbot path filter on WorldSession::IsBot\\(\\)\")",
|
||||||
|
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" fetch origin fix/mmaps-config-overrides-and-aliases)",
|
||||||
|
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" log --oneline HEAD..origin/fix/mmaps-config-overrides-and-aliases)",
|
||||||
|
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" rebase origin/fix/mmaps-config-overrides-and-aliases)",
|
||||||
|
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" log --oneline -3)",
|
||||||
|
"Bash(grep -v \"//\")",
|
||||||
|
"Bash(grep -v \"^//\")"
|
||||||
|
],
|
||||||
|
"additionalDirectories": [
|
||||||
|
"C:\\Users\\Admin\\git\\main\\azerothcore-wotlk\\src\\common\\Collision\\Maps",
|
||||||
|
"C:\\Users\\Admin\\git\\main\\azerothcore-wotlk\\src\\tools\\mmaps_generator",
|
||||||
|
"C:\\Users\\Admin\\git\\main\\azerothcore-wotlk\\src\\server\\game\\Movement\\MovementGenerators"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
92
.github/workflows/windows_build.yml
vendored
92
.github/workflows/windows_build.yml
vendored
@ -1,4 +1,5 @@
|
|||||||
name: windows-build
|
name: windows-build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "master", "test-staging" ]
|
branches: [ "master", "test-staging" ]
|
||||||
@ -6,7 +7,7 @@ on:
|
|||||||
branches: [ "master", "test-staging" ]
|
branches: [ "master", "test-staging" ]
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: "windows-build-${{ github.event.pull_request.number }}"
|
group: "windows-build-${{ github.event.pull_request.number || github.ref }}"
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@ -15,35 +16,108 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [windows-latest]
|
os: [windows-latest]
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
name: ${{ matrix.os }}
|
name: ${{ matrix.os }}
|
||||||
|
|
||||||
env:
|
env:
|
||||||
BOOST_ROOT: C:\local\boost_1_82_0
|
BOOST_ROOT: C:\local\boost_1_87_0
|
||||||
|
CMAKE_GENERATOR: Ninja
|
||||||
|
CTOOLS_BUILD: all
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout AzerothCore
|
- name: Checkout AzerothCore
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: 'mod-playerbots/azerothcore-wotlk'
|
repository: 'mod-playerbots/azerothcore-wotlk'
|
||||||
ref: ${{ (github.base_ref || github.ref_name) == 'test-staging' && 'test-staging' || 'Playerbot' }}
|
ref: ${{ (github.base_ref || github.ref_name) == 'test-staging' && 'test-staging' || 'Playerbot' }}
|
||||||
path: 'ac'
|
path: a
|
||||||
|
|
||||||
- name: Checkout Playerbot Module
|
- name: Checkout Playerbot Module
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: 'mod-playerbots/mod-playerbots'
|
repository: 'mod-playerbots/mod-playerbots'
|
||||||
#path: 'modules/mod-playerbots'
|
path: a/modules/mod-playerbots
|
||||||
path: ac/modules/mod-playerbots
|
|
||||||
|
- name: Move source tree to short path
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
if (Test-Path C:\ac) {
|
||||||
|
Remove-Item C:\ac -Recurse -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
New-Item -ItemType Directory -Path C:\ac | Out-Null
|
||||||
|
|
||||||
|
robocopy "${{ github.workspace }}\a" "C:\ac" /MIR
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -le 7) {
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
exit $LASTEXITCODE
|
||||||
|
|
||||||
|
- name: Install Ninja
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
choco install ninja -y
|
||||||
|
|
||||||
- name: ccache
|
- name: ccache
|
||||||
uses: hendrikmuhs/ccache-action@v1.2.13
|
uses: hendrikmuhs/ccache-action@v1.2.13
|
||||||
|
|
||||||
- name: Configure OS
|
- name: Configure OS
|
||||||
shell: bash
|
shell: bash
|
||||||
working-directory: ac
|
working-directory: C:\ac
|
||||||
env:
|
env:
|
||||||
CONTINUOUS_INTEGRATION: true
|
CONTINUOUS_INTEGRATION: true
|
||||||
run: |
|
run: |
|
||||||
./acore.sh install-deps
|
./acore.sh install-deps
|
||||||
|
|
||||||
|
- name: Create AzerothCore CI config
|
||||||
|
shell: bash
|
||||||
|
working-directory: C:\ac
|
||||||
|
run: |
|
||||||
|
cat > conf/config.sh <<'EOF'
|
||||||
|
CCOMPILERC="cl"
|
||||||
|
CCOMPILERCXX="cl"
|
||||||
|
|
||||||
|
CTYPE="Release"
|
||||||
|
CSCRIPTS="static"
|
||||||
|
CMODULES="static"
|
||||||
|
CTOOLS_BUILD="all"
|
||||||
|
|
||||||
|
CCUSTOMOPTIONS="-DCMAKE_RC_COMPILER=rc -DCMAKE_NINJA_FORCE_RESPONSE_FILE=ON -DCMAKE_NINJA_CMCLDEPS_RC=OFF -DCMAKE_C_USE_RESPONSE_FILE_FOR_OBJECTS=ON -DCMAKE_CXX_USE_RESPONSE_FILE_FOR_OBJECTS=ON -DCMAKE_C_USE_RESPONSE_FILE_FOR_INCLUDES=ON -DCMAKE_CXX_USE_RESPONSE_FILE_FOR_INCLUDES=ON -DCMAKE_C_USE_RESPONSE_FILE_FOR_LIBRARIES=ON -DCMAKE_CXX_USE_RESPONSE_FILE_FOR_LIBRARIES=ON"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat conf/config.sh
|
||||||
|
|
||||||
|
- name: Setup MSVC
|
||||||
|
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756
|
||||||
|
with:
|
||||||
|
arch: x64
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
shell: bash
|
shell: bash
|
||||||
working-directory: ac
|
working-directory: C:\ac
|
||||||
run: |
|
run: |
|
||||||
export CTOOLS_BUILD=all
|
export CTOOLS_BUILD=all
|
||||||
|
export CMAKE_GENERATOR=Ninja
|
||||||
|
|
||||||
|
export CC=cl
|
||||||
|
export CXX=cl
|
||||||
|
export RC=rc
|
||||||
|
|
||||||
|
cmake --version
|
||||||
|
ninja --version
|
||||||
|
|
||||||
|
echo "CMAKE_GENERATOR=$CMAKE_GENERATOR"
|
||||||
|
echo "CC=$CC"
|
||||||
|
echo "CXX=$CXX"
|
||||||
|
|
||||||
|
which cl || true
|
||||||
|
which rc || true
|
||||||
|
cl || true
|
||||||
|
rc || true
|
||||||
|
|
||||||
|
rm -rf var/build/obj
|
||||||
|
|
||||||
./acore.sh compiler build
|
./acore.sh compiler build
|
||||||
|
|||||||
10
README.md
10
README.md
@ -48,6 +48,16 @@ Then build the server following the platform-specific instructions in our **[Ins
|
|||||||
|
|
||||||
> **Testing branch:** A `test-staging` branch is available with the latest features and fixes before they are merged into `master`. To use it, clone with `--branch=test-staging` instead. Note that this branch may contain unstable or breaking changes — use it at your own risk and only if you are comfortable troubleshooting issues.
|
> **Testing branch:** A `test-staging` branch is available with the latest features and fixes before they are merged into `master`. To use it, clone with `--branch=test-staging` instead. Note that this branch may contain unstable or breaking changes — use it at your own risk and only if you are comfortable troubleshooting issues.
|
||||||
|
|
||||||
|
### Required server configuration
|
||||||
|
|
||||||
|
In `worldserver.conf` (AzerothCore core config), set:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
PreloadAllNonInstancedMapGrids = 1
|
||||||
|
```
|
||||||
|
|
||||||
|
This is required for `mod-playerbots`.
|
||||||
|
|
||||||
### Detailed Guides
|
### Detailed Guides
|
||||||
|
|
||||||
| Guide | Description |
|
| Guide | Description |
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
7973
data/sql/playerbots/updates/2026_04_28_00_playerbots_bis_gear.sql
Normal file
7973
data/sql/playerbots/updates/2026_04_28_00_playerbots_bis_gear.sql
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,183 @@
|
|||||||
|
-- #########################################################
|
||||||
|
-- Playerbots - Add /p autogear bis command texts
|
||||||
|
-- Localized for all WotLK locales (koKR, frFR, deDE, zhCN,
|
||||||
|
-- zhTW, esES, esMX, ruRU)
|
||||||
|
-- #########################################################
|
||||||
|
|
||||||
|
DELETE FROM ai_playerbot_texts WHERE name IN (
|
||||||
|
'bis_autogear_unavailable_error',
|
||||||
|
'bis_no_rows_fallback',
|
||||||
|
'bis_command_unavailable_error',
|
||||||
|
'bis_altbot_refused_error',
|
||||||
|
'bis_quality_floor_error',
|
||||||
|
'bis_pvp_refused_error',
|
||||||
|
'bis_invalid_arg_error',
|
||||||
|
'bis_arg_above_limit_error',
|
||||||
|
'bis_no_rows_autogear_msg',
|
||||||
|
'bis_closest_match_msg',
|
||||||
|
'bis_applying_msg',
|
||||||
|
'bis_applied_msg'
|
||||||
|
);
|
||||||
|
DELETE FROM ai_playerbot_texts_chance WHERE name IN (
|
||||||
|
'bis_autogear_unavailable_error',
|
||||||
|
'bis_no_rows_fallback',
|
||||||
|
'bis_command_unavailable_error',
|
||||||
|
'bis_altbot_refused_error',
|
||||||
|
'bis_quality_floor_error',
|
||||||
|
'bis_pvp_refused_error',
|
||||||
|
'bis_invalid_arg_error',
|
||||||
|
'bis_arg_above_limit_error',
|
||||||
|
'bis_no_rows_autogear_msg',
|
||||||
|
'bis_closest_match_msg',
|
||||||
|
'bis_applying_msg',
|
||||||
|
'bis_applied_msg'
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `ai_playerbot_texts`
|
||||||
|
(`id`, `name`, `text`, `say_type`, `reply_type`,
|
||||||
|
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
|
||||||
|
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
|
||||||
|
VALUES
|
||||||
|
(1767, 'bis_autogear_unavailable_error',
|
||||||
|
'autogear command is not allowed, please check the configuration.', 0, 0,
|
||||||
|
'자동 장비 명령이 허용되지 않습니다. 설정을 확인하세요.',
|
||||||
|
'La commande autogear n''est pas autorisée, veuillez vérifier la configuration.',
|
||||||
|
'Der autogear-Befehl ist nicht erlaubt, bitte überprüfe die Konfiguration.',
|
||||||
|
'自动装备命令未启用,请检查配置。',
|
||||||
|
'自動裝備指令未啟用,請檢查設定。',
|
||||||
|
'El comando autogear no está permitido, por favor revisa la configuración.',
|
||||||
|
'El comando autogear no está permitido, por favor revisa la configuración.',
|
||||||
|
'Команда autogear не разрешена, проверьте конфигурацию.'),
|
||||||
|
|
||||||
|
(1768, 'bis_no_rows_fallback',
|
||||||
|
'No BiS for your tier/spec/level, check cfg, running autogear instead', 0, 0,
|
||||||
|
'해당 등급/전문화/레벨에 BiS 목록이 없습니다. 설정을 확인하세요. 대신 자동 장비를 실행합니다.',
|
||||||
|
'Pas de BiS pour votre tier/spé/niveau, vérifiez la config, exécution d''autogear à la place.',
|
||||||
|
'Kein BiS für deinen Tier/Spec/Level, prüfe die Config, führe stattdessen autogear aus.',
|
||||||
|
'您的等级/天赋/级别没有BiS数据,请检查配置,改为运行autogear。',
|
||||||
|
'你的等級/天賦/級別沒有BiS資料,請檢查設定,改為執行autogear。',
|
||||||
|
'No hay BiS para tu tier/spec/nivel, revisa la configuración, ejecutando autogear en su lugar.',
|
||||||
|
'No hay BiS para tu tier/spec/nivel, revisa la configuración, ejecutando autogear en su lugar.',
|
||||||
|
'Нет BiS для вашего тира/спека/уровня, проверьте конфиг, запускаю autogear.'),
|
||||||
|
|
||||||
|
(1769, 'bis_command_unavailable_error',
|
||||||
|
'bis command is not allowed, please check the configuration.', 0, 0,
|
||||||
|
'bis 명령이 허용되지 않습니다. 설정을 확인하세요.',
|
||||||
|
'La commande bis n''est pas autorisée, veuillez vérifier la configuration.',
|
||||||
|
'Der bis-Befehl ist nicht erlaubt, bitte überprüfe die Konfiguration.',
|
||||||
|
'bis命令未启用,请检查配置。',
|
||||||
|
'bis指令未啟用,請檢查設定。',
|
||||||
|
'El comando bis no está permitido, por favor revisa la configuración.',
|
||||||
|
'El comando bis no está permitido, por favor revisa la configuración.',
|
||||||
|
'Команда bis не разрешена, проверьте конфигурацию.'),
|
||||||
|
|
||||||
|
(1770, 'bis_altbot_refused_error',
|
||||||
|
'You cannot use bis on alt bots.', 0, 0,
|
||||||
|
'부캐 봇에는 bis를 사용할 수 없습니다.',
|
||||||
|
'Vous ne pouvez pas utiliser bis sur des bots alternatifs.',
|
||||||
|
'Du kannst bis nicht auf Zweitbots verwenden.',
|
||||||
|
'你不能在副号机器人上使用bis。',
|
||||||
|
'你不能在副號機器人上使用bis。',
|
||||||
|
'No puedes usar bis en bots alternativos.',
|
||||||
|
'No puedes usar bis en bots alternativos.',
|
||||||
|
'Вы не можете использовать bis на дополнительных ботах.'),
|
||||||
|
|
||||||
|
(1771, 'bis_quality_floor_error',
|
||||||
|
'AutoGearQualityLimit must be 4 for BiS.', 0, 0,
|
||||||
|
'BiS를 사용하려면 AutoGearQualityLimit이 4여야 합니다.',
|
||||||
|
'AutoGearQualityLimit doit être à 4 pour utiliser BiS.',
|
||||||
|
'AutoGearQualityLimit muss für BiS auf 4 stehen.',
|
||||||
|
'BiS要求AutoGearQualityLimit设置为4。',
|
||||||
|
'BiS要求AutoGearQualityLimit設定為4。',
|
||||||
|
'AutoGearQualityLimit debe ser 4 para BiS.',
|
||||||
|
'AutoGearQualityLimit debe ser 4 para BiS.',
|
||||||
|
'AutoGearQualityLimit должен быть 4 для BiS.'),
|
||||||
|
|
||||||
|
(1772, 'bis_pvp_refused_error',
|
||||||
|
'bis is PvE only, bot is configured as PvP.', 0, 0,
|
||||||
|
'bis는 PvE 전용이며, 이 봇은 PvP로 설정되어 있습니다.',
|
||||||
|
'bis est uniquement pour le JcE, ce bot est configuré en JcJ.',
|
||||||
|
'bis ist nur für PvE, dieser Bot ist als PvP konfiguriert.',
|
||||||
|
'bis仅适用于PvE,此机器人配置为PvP。',
|
||||||
|
'bis僅適用於PvE,此機器人設定為PvP。',
|
||||||
|
'bis es solo para PvE, el bot está configurado como PvP.',
|
||||||
|
'bis es solo para PvE, el bot está configurado como PvP.',
|
||||||
|
'bis только для PvE, бот настроен на PvP.'),
|
||||||
|
|
||||||
|
(1773, 'bis_invalid_arg_error',
|
||||||
|
'Invalid BiS ilvl argument ''%param''. Use a positive integer.', 0, 0,
|
||||||
|
'잘못된 BiS 아이템 레벨 인수 ''%param''. 양의 정수를 사용하세요.',
|
||||||
|
'Argument iLvl BiS invalide ''%param''. Utilisez un entier positif.',
|
||||||
|
'Ungültiges BiS-iLvl-Argument ''%param''. Verwende eine positive ganze Zahl.',
|
||||||
|
'无效的BiS物品等级参数“%param”。请使用正整数。',
|
||||||
|
'無效的BiS物品等級參數「%param」。請使用正整數。',
|
||||||
|
'Argumento iLvl BiS inválido ''%param''. Usa un entero positivo.',
|
||||||
|
'Argumento iLvl BiS inválido ''%param''. Usa un entero positivo.',
|
||||||
|
'Неверный аргумент iLvl BiS ''%param''. Используйте положительное целое число.'),
|
||||||
|
|
||||||
|
(1774, 'bis_arg_above_limit_error',
|
||||||
|
'BiS ilvl %requested exceeds AutoGearScoreLimit %limit, refusing', 0, 0,
|
||||||
|
'BiS 아이템 레벨 %requested이(가) AutoGearScoreLimit %limit을(를) 초과합니다. 거부합니다.',
|
||||||
|
'iLvl BiS %requested dépasse AutoGearScoreLimit %limit, refusé.',
|
||||||
|
'BiS-iLvl %requested überschreitet AutoGearScoreLimit %limit, abgelehnt.',
|
||||||
|
'BiS物品等级%requested超过AutoGearScoreLimit %limit,已拒绝。',
|
||||||
|
'BiS物品等級%requested超過AutoGearScoreLimit %limit,已拒絕。',
|
||||||
|
'iLvl BiS %requested supera AutoGearScoreLimit %limit, rechazado.',
|
||||||
|
'iLvl BiS %requested supera AutoGearScoreLimit %limit, rechazado.',
|
||||||
|
'BiS iLvl %requested превышает AutoGearScoreLimit %limit, отказано.'),
|
||||||
|
|
||||||
|
(1775, 'bis_no_rows_autogear_msg',
|
||||||
|
'No BiS at ilvl %ilvl, using Autogear %ilvl instead', 0, 0,
|
||||||
|
'아이템 레벨 %ilvl의 BiS가 없어 대신 Autogear %ilvl을(를) 사용합니다.',
|
||||||
|
'Pas de BiS à l''iLvl %ilvl, utilisation d''Autogear %ilvl à la place.',
|
||||||
|
'Kein BiS auf iLvl %ilvl, verwende stattdessen Autogear %ilvl.',
|
||||||
|
'物品等级%ilvl没有BiS,改用Autogear %ilvl。',
|
||||||
|
'物品等級%ilvl沒有BiS,改用Autogear %ilvl。',
|
||||||
|
'No hay BiS en iLvl %ilvl, usando Autogear %ilvl en su lugar.',
|
||||||
|
'No hay BiS en iLvl %ilvl, usando Autogear %ilvl en su lugar.',
|
||||||
|
'Нет BiS на iLvl %ilvl, использую Autogear %ilvl.'),
|
||||||
|
|
||||||
|
(1776, 'bis_closest_match_msg',
|
||||||
|
'No BiS at ilvl %requested, using closest match at ilvl %resolved', 0, 0,
|
||||||
|
'아이템 레벨 %requested의 BiS가 없어 가장 가까운 아이템 레벨 %resolved을(를) 사용합니다.',
|
||||||
|
'Pas de BiS à l''iLvl %requested, utilisation de la correspondance la plus proche à l''iLvl %resolved.',
|
||||||
|
'Kein BiS auf iLvl %requested, verwende nächstliegende Übereinstimmung auf iLvl %resolved.',
|
||||||
|
'物品等级%requested没有BiS,使用最接近的物品等级%resolved。',
|
||||||
|
'物品等級%requested沒有BiS,使用最接近的物品等級%resolved。',
|
||||||
|
'No hay BiS en iLvl %requested, usando coincidencia más cercana en iLvl %resolved.',
|
||||||
|
'No hay BiS en iLvl %requested, usando coincidencia más cercana en iLvl %resolved.',
|
||||||
|
'Нет BiS на iLvl %requested, использую ближайшее совпадение на iLvl %resolved.'),
|
||||||
|
|
||||||
|
(1777, 'bis_applying_msg', 'Applying BiS gear', 0, 0,
|
||||||
|
'BiS 장비를 적용합니다.',
|
||||||
|
'Application de l''équipement BiS.',
|
||||||
|
'Wende BiS-Ausrüstung an.',
|
||||||
|
'正在装备BiS装备。',
|
||||||
|
'正在裝備BiS裝備。',
|
||||||
|
'Aplicando equipo BiS.',
|
||||||
|
'Aplicando equipo BiS.',
|
||||||
|
'Применяю BiS-снаряжение.'),
|
||||||
|
|
||||||
|
(1778, 'bis_applied_msg', 'BiS applied', 0, 0,
|
||||||
|
'BiS 장비가 적용되었습니다.',
|
||||||
|
'Équipement BiS appliqué.',
|
||||||
|
'BiS-Ausrüstung angewendet.',
|
||||||
|
'BiS装备已应用。',
|
||||||
|
'BiS裝備已套用。',
|
||||||
|
'Equipo BiS aplicado.',
|
||||||
|
'Equipo BiS aplicado.',
|
||||||
|
'BiS-снаряжение применено.');
|
||||||
|
|
||||||
|
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES
|
||||||
|
('bis_autogear_unavailable_error', 100),
|
||||||
|
('bis_no_rows_fallback', 100),
|
||||||
|
('bis_command_unavailable_error', 100),
|
||||||
|
('bis_altbot_refused_error', 100),
|
||||||
|
('bis_quality_floor_error', 100),
|
||||||
|
('bis_pvp_refused_error', 100),
|
||||||
|
('bis_invalid_arg_error', 100),
|
||||||
|
('bis_arg_above_limit_error', 100),
|
||||||
|
('bis_no_rows_autogear_msg', 100),
|
||||||
|
('bis_closest_match_msg', 100),
|
||||||
|
('bis_applying_msg', 100),
|
||||||
|
('bis_applied_msg', 100);
|
||||||
@ -0,0 +1,632 @@
|
|||||||
|
-- Imported from cmangos-playerbots ai_playerbot_travelnode (wotlk)
|
||||||
|
-- 626 new nodes (cmangos has them, we didn't)
|
||||||
|
-- Matched on (name, mapId) + closest position; remaining are unique to cmangos.
|
||||||
|
|
||||||
|
INSERT INTO `playerbots_travelnode` (`id`, `name`, `map_id`, `x`, `y`, `z`, `linked`) VALUES
|
||||||
|
(3781, ' portal', 0, 3476.3600, -4493.3600, 137.4900, 1),
|
||||||
|
(3782, ' portal', 530, 6107.2600, -6990.6400, 133.3170, 1),
|
||||||
|
(3783, ' spirithealer', 609, 1886.7800, -5784.5900, 102.8610, 1),
|
||||||
|
(3784, ' spirithealer', 609, 2116.1900, -5286.9400, 81.2151, 1),
|
||||||
|
(3785, ' spirithealer', 609, 2364.4200, -5771.3200, 151.3670, 1),
|
||||||
|
(3786, 'Absalan the Pious', 623, 36.0312, 40.4600, 25.0322, 1),
|
||||||
|
(3787, 'Acherus: The Ebon Hold flightMaster', 0, 2348.6300, -5669.2900, 382.3240, 1),
|
||||||
|
(3788, 'Acherus: The Ebon Hold spirithealer', 0, 2356.6500, -5663.0600, 382.2570, 1),
|
||||||
|
(3789, 'Alliance Log Ride 01 Begin', 571, 4274.5300, -3055.5500, 319.4630, 1),
|
||||||
|
(3790, 'Alliance PVP Barracks', 449, -9.1189, -4.2670, 5.5710, 1),
|
||||||
|
(3791, 'Alterac Mountains The Headland', 0, -80.9055, -331.6660, 136.4710, 1),
|
||||||
|
(3792, 'Alterac Mountains', 0, 498.3190, -1076.6400, 195.8960, 1),
|
||||||
|
(3793, 'Arathi Highlands The Sanctum', 0, -1532.4300, -1882.5600, 69.5533, 1),
|
||||||
|
(3794, 'Arathi Highlands The Tower of Arathor', 0, -1774.4800, -1518.3900, 75.2667, 1),
|
||||||
|
(3795, 'Archmage Pentarus', 603, -718.4560, -57.1132, 429.9240, 1),
|
||||||
|
(3796, 'Area 52 innkeeper', 530, 3062.1500, 3701.8200, 142.5620, 1),
|
||||||
|
(3797, 'Area52 Transporter', 530, 3092.5800, 3644.7200, 143.1360, 1),
|
||||||
|
(3798, 'Ashenvale Kargathia Keep', 1, 2437.0600, -3543.6300, 98.3115, 1),
|
||||||
|
(3799, 'Auberdine innkeeper', 1, 6406.5100, 515.3670, 8.7257, 1),
|
||||||
|
(3800, 'Auchindoun: Auchenai Crypts', 558, 63.4074, -175.2640, 15.4378, 1),
|
||||||
|
(3801, 'Auchindoun: Mana-Tombs', 557, -220.0700, -177.6620, -0.9810, 1),
|
||||||
|
(3802, 'Auchindoun: Sethekk Halls', 556, -102.4290, 177.5860, 0.0932, 1),
|
||||||
|
(3803, 'Auchindoun: Shadow Labyrinth', 555, -272.2950, -140.0660, 8.1563, 1),
|
||||||
|
(3804, 'Balzaphon', 329, 3733.2700, -3480.1100, 131.0400, 1),
|
||||||
|
(3805, 'Blackfathom Deeps', 48, -570.3640, 0.8977, -47.1378, 1),
|
||||||
|
(3806, 'Blackrock Depths', 230, 870.1800, -239.3270, -71.6776, 1),
|
||||||
|
(3807, 'Blackrock Spire', 229, -16.0931, -392.4040, 48.5157, 1),
|
||||||
|
(3808, 'Blades Edge Mountains Circle of Blood', 530, 2879.1300, 5979.4500, 6.2402, 1),
|
||||||
|
(3809, 'Blades Edge Mountains Vimgols Circle', 530, 3279.9000, 4640.3600, 216.5280, 1),
|
||||||
|
(3810, 'Blood elf start', 530, 10349.6000, -6357.2900, 33.4026, 1),
|
||||||
|
(3811, 'Bloodmyst Isle The Hidden Reef', 530, -1148.6600, -11127.4000, -76.0074, 1),
|
||||||
|
(3812, 'Borean Tundra Coldarra', 571, 3917.0600, 6817.9200, 150.5070, 1),
|
||||||
|
(3813, 'Borean Tundra Naxxanar', 571, 3750.9300, 3583.8600, 353.1330, 1),
|
||||||
|
(3814, 'Burning Steppes Blackrock Mountain', 0, -7996.6300, -1013.4100, 131.8670, 1),
|
||||||
|
(3815, 'Cannon Charging (Port)', 530, 1920.1300, 5581.9000, 269.7220, 1),
|
||||||
|
(3816, 'Cannon Prep', 0, -9569.6000, -13.7809, 63.9459, 1),
|
||||||
|
(3817, 'Cannon Prep', 1, -1327.6600, 85.9815, 130.2070, 1),
|
||||||
|
(3818, 'Cannon Prep', 530, -1742.2500, 5457.4000, -11.9282, 1),
|
||||||
|
(3819, 'Chief Thunder-Skins', 230, 847.8390, -181.1150, -49.6707, 1),
|
||||||
|
(3820, 'Coilfang: Serpentshrine Cavern', 548, 135.1620, -488.7410, 0.8400, 1),
|
||||||
|
(3821, 'Coilfang: The Slave Pens', 547, -71.6723, -322.2440, -1.5438, 1),
|
||||||
|
(3822, 'Coilfang: The Steamvault', 545, -91.9829, -255.4900, -12.5306, 1),
|
||||||
|
(3823, 'Coilfang: The Underbog', 546, 108.0610, -198.7420, 50.1184, 1),
|
||||||
|
(3824, 'Coilskar Witch', 585, 139.1010, -104.6440, -20.9245, 1),
|
||||||
|
(3825, 'Crystalsong Forest The Azure Front', 571, 5434.6500, 729.6780, 186.6960, 1),
|
||||||
|
(3826, 'Crystalsong Forest The Great Tree', 571, 5895.1000, 1022.6900, 185.5390, 1),
|
||||||
|
(3827, 'Crystalsong Forest The Twilight Rivulet', 571, 5508.9200, 432.9360, 161.7470, 1),
|
||||||
|
(3828, 'Dalaran Portal to Caverns of Time', 1, -8164.8000, -4768.5000, 34.3000, 1),
|
||||||
|
(3829, 'Dalaran Portal to Caverns of Time', 571, 5781.4800, 841.2580, 680.3790, 1),
|
||||||
|
(3830, 'Dalaran Portal to Darnassus', 1, 9656.5400, 2518.2600, 1331.6600, 1),
|
||||||
|
(3831, 'Dalaran Portal to Darnassus', 571, 5706.1600, 730.1020, 641.7450, 1),
|
||||||
|
(3832, 'Dalaran Portal to Exodar', 571, 5699.5800, 735.4690, 641.7690, 1),
|
||||||
|
(3833, 'Dalaran Portal to Ironforge', 0, -4613.7100, -915.2870, 501.0620, 1),
|
||||||
|
(3834, 'Dalaran Portal to Ironforge', 571, 5712.6800, 724.8450, 641.7360, 1),
|
||||||
|
(3835, 'Dalaran Portal to Orgrimmar', 1, 1470.0200, -4222.5900, 59.2213, 1),
|
||||||
|
(3836, 'Dalaran Portal to Orgrimmar', 571, 5925.8500, 593.2500, 640.5630, 1),
|
||||||
|
(3837, 'Dalaran Portal to Shattrath', 530, -1824.3200, 5417.2300, -12.4277, 1),
|
||||||
|
(3838, 'Dalaran Portal to Shattrath', 530, -1902.4900, 5442.8600, -12.4280, 1),
|
||||||
|
(3839, 'Dalaran Portal to Shattrath', 571, 5697.4900, 744.9120, 641.8190, 1),
|
||||||
|
(3840, 'Dalaran Portal to Shattrath', 571, 5941.6600, 584.8870, 640.5740, 1),
|
||||||
|
(3841, 'Dalaran Portal to Silvermoon', 530, 9998.4600, -7106.5500, 47.7054, 1),
|
||||||
|
(3842, 'Dalaran Portal to Silvermoon', 571, 5946.9800, 568.4790, 640.5730, 1),
|
||||||
|
(3843, 'Dalaran Portal to Stormwind', 571, 5719.1900, 719.6810, 641.7280, 1),
|
||||||
|
(3844, 'Dalaran Portal to Thunder Bluff', 1, -967.3750, 284.8200, 110.7730, 1),
|
||||||
|
(3845, 'Dalaran Portal to Thunder Bluff', 571, 5945.8100, 577.3570, 640.5740, 1),
|
||||||
|
(3846, 'Dalaran Portal to Undercity', 571, 5934.6600, 590.6880, 640.5750, 1),
|
||||||
|
(3847, 'Dalaran Violet Citadel Balcony', 571, 5865.0800, 840.2100, 846.3330, 1),
|
||||||
|
(3848, 'Darnassus The Temple Gardens', 1, 9814.7800, 2574.2100, 1314.4700, 1),
|
||||||
|
(3849, 'Darnassus Warriors Terrace', 1, 9976.2100, 2262.3700, 1334.5000, 1),
|
||||||
|
(3850, 'Darnassus innkeeper', 1, 9951.8800, 2282.8200, 1345.1600, 1),
|
||||||
|
(3851, 'Deadwind Pass Groshgok Compound', 0, -11121.6000, -2416.2600, 108.6530, 1),
|
||||||
|
(3852, 'Deadwind Pass Karazhan', 0, -11123.3000, -2006.7700, 47.2725, 1),
|
||||||
|
(3853, 'Deadwind Pass', 0, -10377.2000, -1785.4200, 94.3243, 1),
|
||||||
|
(3854, 'Deeprun Tram', 369, 13.6699, 80.6694, -4.2973, 1),
|
||||||
|
(3855, 'Desolace Bolgans Hole', 1, -2375.9500, 2469.7600, 74.4626, 1),
|
||||||
|
(3856, 'Desolace The Veiled Sea', 1, -1869.3900, 3404.3400, -50.7694, 1),
|
||||||
|
(3857, 'Dire Maul', 429, 135.5790, 192.0480, -3.3910, 1),
|
||||||
|
(3858, 'Doodad_CF_elevatorPlatform01entry', 548, 50.0000, -0.0071, -70.9017, 1),
|
||||||
|
(3859, 'Doodad_CF_elevatorPlatform_small01entry', 548, -59.1350, -98.7966, -51.7018, 1),
|
||||||
|
(3860, 'Doodad_FactoryElevator01entry', 554, 0.5438, -1.3935, -1.7012, 1),
|
||||||
|
(3861, 'Doodad_HF_Elevator_Gate01entry', 571, 254.1330, -5892.0000, 258.4950, 1),
|
||||||
|
(3862, 'Doodad_HF_Elevator_Gate02', 571, 263.1630, -5919.2500, 167.2690, 1),
|
||||||
|
(3863, 'Doodad_HF_Elevator_Gate02', 571, 263.1630, -5919.2500, 173.3200, 1),
|
||||||
|
(3864, 'Doodad_HF_Elevator_Gate02entry', 571, 275.2000, -5918.1300, 166.4950, 1),
|
||||||
|
(3865, 'Doodad_HF_Elevator_Gate03', 571, 135.1250, -5765.3500, 291.7530, 1),
|
||||||
|
(3866, 'Doodad_HF_Elevator_Gate03', 571, 135.1250, -5765.3500, 286.0490, 1),
|
||||||
|
(3867, 'Doodad_HF_Elevator_Gate03entry', 571, 133.3330, -5756.2700, 285.5940, 1),
|
||||||
|
(3868, 'Doodad_HF_Elevator_Lift01entry', 571, 259.9700, -5893.8300, 255.8280, 1),
|
||||||
|
(3869, 'Doodad_HF_Elevator_Lift01entry', 571, 261.1220, -5910.0100, 77.0138, 1),
|
||||||
|
(3870, 'Doodad_HF_Elevator_Lift01entry', 571, 261.9400, -5918.9400, 164.0950, 1),
|
||||||
|
(3871, 'Doodad_HF_Elevator_Lift_01entry', 571, 158.9330, -5760.0000, 38.3942, 1),
|
||||||
|
(3872, 'Doodad_ID_elevator01entry', 571, 3324.2300, -5134.7400, 300.5890, 1),
|
||||||
|
(3873, 'Doodad_ID_elevator02entry', 571, 3302.5400, -5103.7300, 300.5890, 1),
|
||||||
|
(3874, 'Doodad_ID_elevator03entry', 571, 3286.7600, -5135.8800, 300.5890, 1),
|
||||||
|
(3875, 'Doodad_LogRun_PumpElevator05', 571, 4264.2300, -3276.9500, 336.1010, 1),
|
||||||
|
(3876, 'Doodad_LogRun_PumpElevator05', 571, 4264.2300, -3276.9500, 330.6150, 1),
|
||||||
|
(3877, 'Doodad_LogRun_PumpElevator05entry', 571, 4264.2300, -3276.9500, 329.3720, 1),
|
||||||
|
(3878, 'Doodad_Nexus_Elevator_BaseStructure_01entry', 571, 3556.2500, 6928.7200, 251.3120, 1),
|
||||||
|
(3879, 'Doodad_Nexus_Elevator_BaseStructure_01entry', 571, 3979.2300, 7272.5600, 256.0160, 1),
|
||||||
|
(3880, 'Doodad_Nexus_Elevator_BaseStructure_01entry', 571, 4110.2800, 6755.6900, 249.1790, 1),
|
||||||
|
(3881, 'Doodad_Nexus_Elevator_BaseStructure_01entry', 578, 1213.8600, 1339.0600, 248.2530, 1),
|
||||||
|
(3882, 'Doodad_Nexus_Elevator_BaseStructure_01entry', 578, 1343.4300, 825.8630, 242.8180, 1),
|
||||||
|
(3883, 'Doodad_Nexus_Elevator_BaseStructure_01entry', 578, 789.8890, 992.0980, 251.2600, 1),
|
||||||
|
(3884, 'Doodad_Vrykul_Gondola01entry', 571, 698.9930, -3824.2500, 269.5360, 1),
|
||||||
|
(3885, 'Doodad_Vrykul_Gondola01entryentry', 571, 700.2670, -3823.5000, 269.3790, 1),
|
||||||
|
(3886, 'Doodad_Vrykul_Gondola_01entry', 575, -557.0330, 1543.4600, -288.9820, 1),
|
||||||
|
(3887, 'Doodad_icecrown_elevator02entry', 631, 4366.6900, 2365.6200, 358.4790, 1),
|
||||||
|
(3888, 'Doodad_mushroombase_elevator01entry', 530, 285.6000, 5927.2000, 26.6102, 1),
|
||||||
|
(3889, 'Doodad_org_arena_axe_pillar01entry', 618, 768.0000, -298.6670, 28.4867, 1),
|
||||||
|
(3890, 'Doodad_org_arena_lightning_pillar01entry', 618, 768.0000, -277.3330, 28.4867, 1),
|
||||||
|
(3891, 'Draenei start', 530, -3961.6400, -13931.2000, 100.6150, 1),
|
||||||
|
(3892, 'Dragonblight Frostmourne Cavern', 571, 4736.3900, -558.6370, 166.0830, 1),
|
||||||
|
(3893, 'Dragonblight Scarlet Tower', 571, 4692.0500, -356.1290, 178.7370, 1),
|
||||||
|
(3894, 'Dragonblight The Pit of Narjun', 571, 3740.0300, 2125.2500, 43.0709, 1),
|
||||||
|
(3895, 'Dragonblight Wintergarde Crypt', 571, 3584.5200, -781.0240, 155.6860, 1),
|
||||||
|
(3896, 'Dragonblight Wintergarde Mausoleum', 571, 3650.8400, -1123.3200, 89.2307, 1),
|
||||||
|
(3897, 'Drakuramas Teleport 02', 571, 6175.5900, -2000.6700, 241.7690, 1),
|
||||||
|
(3898, 'Dun Morogh Chill Breeze Valley', 0, -5550.9700, -76.1191, 426.9310, 1),
|
||||||
|
(3899, 'Dun Morogh Thunderbrew Distillery', 0, -5595.6700, -513.5950, 409.4150, 1),
|
||||||
|
(3900, 'Durotar Burning Blade Coven', 1, -88.3030, -4285.0100, 62.0652, 1),
|
||||||
|
(3901, 'Durotar Dustwind Cave', 1, 877.9880, -4745.6200, 30.4963, 1),
|
||||||
|
(3902, 'Durotar Razor Hill Barracks', 1, 319.0970, -4812.8800, 10.6054, 1),
|
||||||
|
(3903, 'Durotar The Den', 1, -588.7030, -4144.9400, 41.1033, 1),
|
||||||
|
(3904, 'Duskwood Forlorn Rowe', 0, -10320.8000, 368.2760, 60.5037, 1),
|
||||||
|
(3905, 'Duskwood Rolands Doom', 0, -11106.5000, -1160.6700, 42.2494, 1),
|
||||||
|
(3906, 'Dustwallow Marsh Emberstrifes Den', 1, -5103.3600, -3949.5300, 41.4934, 1),
|
||||||
|
(3907, 'Dustwallow Marsh Foothold Citadel', 1, -3721.7400, -4538.6000, 25.9170, 1),
|
||||||
|
(3908, 'Dustwallow Marsh North Point Tower', 1, -2885.3400, -3419.3200, 47.2420, 1),
|
||||||
|
(3909, 'Dustwallow Marsh The Great Sea', 1, -4026.5700, -4975.8200, 8.2153, 1),
|
||||||
|
(3910, 'Dustwallow to Stormwind Teleport', 0, -9008.7900, 851.3200, 105.8900, 1),
|
||||||
|
(3911, 'Eastern Plaguelands MazraAlor', 0, 3439.2500, -4980.9600, 195.8110, 1),
|
||||||
|
(3912, 'Eastern Plaguelands Terrorweb Tunnel', 0, 2903.1100, -2614.6700, 89.8071, 1),
|
||||||
|
(3913, 'Eastern Plaguelands The Noxious Glade', 0, 2714.6600, -5421.4700, 161.4070, 1),
|
||||||
|
(3914, 'Elevatorentry', 0, -5065.6200, 437.5100, 424.1080, 1),
|
||||||
|
(3915, 'Elevatorentry', 1, 2261.3300, -5565.5600, 34.2689, 1),
|
||||||
|
(3916, 'Elevatorentry', 530, 1914.5100, 5514.0200, 280.6880, 1),
|
||||||
|
(3917, 'Elevatorentry', 571, 2872.9500, 6234.9700, 104.9120, 1),
|
||||||
|
(3918, 'Elevatorentry', 571, 2894.6900, 6244.8900, 209.1970, 1),
|
||||||
|
(3919, 'Elevatorentry', 571, 4185.2900, 5283.6200, 39.6833, 1),
|
||||||
|
(3920, 'Elwynn Forest Thunder Falls', 0, -9291.1000, 677.6830, 131.7780, 1),
|
||||||
|
(3921, 'Entangle', 531, -8003.0000, 1222.9000, -82.1000, 1),
|
||||||
|
(3922, 'Entangle', 531, -8022.3000, 1149.0000, -89.1000, 1),
|
||||||
|
(3923, 'Entangle', 531, -8043.6000, 1254.1000, -84.3000, 1),
|
||||||
|
(3924, 'Eredar Soul-Eater', 552, 285.5190, 146.1550, 22.3118, 1),
|
||||||
|
(3925, 'Escape Voltarus', 571, 5875.4300, -1981.3700, 234.6710, 1),
|
||||||
|
(3926, 'Escape to the Isle of QuelDanas', 530, 12887.6000, -6869.2100, 10.1141, 1),
|
||||||
|
(3927, 'Escape to the Isle of QuelDanas', 585, 148.4010, 203.4430, -11.9579, 1),
|
||||||
|
(3928, 'Evergrove innkeeper', 530, 3022.9000, 5435.5900, 146.7010, 1),
|
||||||
|
(3929, 'Everlook Transporter', 1, 6755.2200, -4658.0400, 724.7950, 1),
|
||||||
|
(3930, 'Everlook innkeeper', 1, 6695.1500, -4673.0400, 721.6500, 1),
|
||||||
|
(3931, 'Eversong Woods Commons Hall', 530, 9822.0000, -6694.3700, 2.5945, 1),
|
||||||
|
(3932, 'Eversong Woods Falthrien Academy', 530, 10158.0000, -6026.5000, 63.7448, 1),
|
||||||
|
(3933, 'Eversong Woods Huntress of the Sun', 530, 9699.4300, -6701.3600, -0.2076, 1),
|
||||||
|
(3934, 'Exit Portal', 571, 3877.1400, 6980.8300, 152.0400, 1),
|
||||||
|
(3935, 'Feathermoon Stronghold innkeeper', 1, -4381.5900, 3289.4500, 13.6266, 1),
|
||||||
|
(3936, 'Felwood Bloodvenom River', 1, 5288.7400, -544.2570, 328.6870, 1),
|
||||||
|
(3937, 'Felwood Irontree Cavern', 1, 6353.3200, -1696.9200, 440.0420, 1),
|
||||||
|
(3938, 'Felwood Shrine of the Deceiver', 1, 4779.2600, -572.0400, 275.8440, 1),
|
||||||
|
(3939, 'Gadgetzan Transporter', 1, -7109.1200, -3825.2100, 10.1529, 1),
|
||||||
|
(3940, 'Gadgetzan innkeeper', 1, -7158.9600, -3841.6100, 8.8481, 1),
|
||||||
|
(3941, 'Gallows End Tavern innkeeper', 0, 2269.5100, 244.9440, 34.3402, 1),
|
||||||
|
(3942, 'Gates of AhnQiraj', 1, -8233.1200, 1535.7500, -0.3744, 1),
|
||||||
|
(3943, 'Ghostlands Deatholme', 530, 6465.7700, -6433.6600, 50.4155, 1),
|
||||||
|
(3944, 'Ghostlands Thalassian Pass', 530, 6554.0200, -6811.2000, 110.6000, 1),
|
||||||
|
(3945, 'Ghostly Baker', 532, -11057.7000, -1919.8300, 77.3515, 1),
|
||||||
|
(3946, 'Gnomeregan', 90, -617.4620, 370.6960, -247.1880, 1),
|
||||||
|
(3947, 'Gravity Lapse - Center Teleport', 585, 148.5000, 181.0000, -16.7000, 1),
|
||||||
|
(3948, 'Greater Shadowbat', 532, -10935.0000, -1999.5000, 49.4748, 1),
|
||||||
|
(3949, 'Grizzly Hills - Quest - Arugal Teleport Back', 571, 3841.4000, -3426.6500, 293.1040, 1),
|
||||||
|
(3950, 'Grizzly Hills Boulder Hills', 571, 5082.6400, -4724.1900, 287.5000, 1),
|
||||||
|
(3951, 'Grizzly Hills Duskhowl Den', 571, 3992.0100, -4523.4300, 195.6070, 1),
|
||||||
|
(3952, 'Grizzly Hills Ursocs Den', 571, 4692.7100, -3863.7500, 327.3770, 1),
|
||||||
|
(3953, 'Grizzly Hills Voldrune Dwelling', 571, 3005.7300, -2610.3100, 98.5530, 1),
|
||||||
|
(3954, 'Gruuls Lair', 565, 107.8710, 282.5120, 1.9718, 1),
|
||||||
|
(3955, 'Gunship Portal', 628, 747.0000, -1075.0000, 135.0000, 1),
|
||||||
|
(3956, 'Gunship Portal', 641, 16.4763, 0.0184, 20.4162, 1),
|
||||||
|
(3957, 'Gunship Portal', 642, 12.3199, 0.0963, 34.6508, 1),
|
||||||
|
(3958, 'Heart of the Deconstructor', 603, 886.2750, -12.0545, 409.6020, 1),
|
||||||
|
(3959, 'Hellfire Citadel: The Blood Furnace', 542, 331.0690, 28.0790, 9.7047, 1),
|
||||||
|
(3960, 'Hellfire Citadel: The Shattered Halls', 540, 203.6690, 157.2020, -42.3511, 1),
|
||||||
|
(3961, 'Hellfire Peninsula The Path of Anguish', 530, -525.2730, 2066.0700, 94.5205, 1),
|
||||||
|
(3962, 'Hex Lord Malacrass', 568, 117.3630, 923.5690, 33.9726, 1),
|
||||||
|
(3963, 'Hillsbrad Foothills Durnholde Keep', 0, -489.8100, -1480.9400, 88.1965, 1),
|
||||||
|
(3964, 'HiveZara Swarmer Teleport', 509, -9757.8700, 1416.7100, 76.7664, 1),
|
||||||
|
(3965, 'HiveZara Swarmer Teleport', 509, -9778.9100, 1419.9800, 61.0743, 1),
|
||||||
|
(3966, 'HiveZara Swarmer Teleport', 509, -9805.9500, 1422.8500, 77.5852, 1),
|
||||||
|
(3967, 'HiveZara Swarmer Teleport', 509, -9827.5800, 1506.2800, 82.3052, 1),
|
||||||
|
(3968, 'HiveZara Swarmer Teleport', 509, -9829.4200, 1456.3700, 90.7015, 1),
|
||||||
|
(3969, 'Horde Log Ride 01 Begin', 571, 4313.3700, -2958.1700, 318.4630, 1),
|
||||||
|
(3970, 'Howling Fjord New Agamand Inn', 571, 456.4610, -4519.2400, 244.9780, 1),
|
||||||
|
(3971, 'Howling Fjord Utgarde Keep', 571, 855.8150, -4831.8500, -115.7170, 1),
|
||||||
|
(3972, 'Howling Fjord Westguard Keep', 571, 1385.1700, -3244.6400, 162.8370, 1),
|
||||||
|
(3973, 'Howling Fjord Wildervar Mine', 571, 2604.4700, -5019.1400, 293.8260, 1),
|
||||||
|
(3974, 'Icecrown Jotunheim', 571, 7220.9200, 4113.1400, 630.8240, 1),
|
||||||
|
(3975, 'Icecrown Kulgalar Keep', 571, 6831.2300, 3817.0800, 621.1530, 1),
|
||||||
|
(3976, 'Icecrown Nazanak: The Forgotten Depths', 571, 5853.5100, 1807.1700, -344.9310, 1),
|
||||||
|
(3977, 'Icecrown The Fleshwerks', 571, 6610.5800, 3359.6700, 660.4740, 1),
|
||||||
|
(3978, 'Indisposed II', 571, 3454.1100, -2802.3700, 202.1400, 1),
|
||||||
|
(3979, 'Ironforge The Forlorn Cavern', 0, -4636.2200, -1110.0000, 501.4110, 1),
|
||||||
|
(3980, 'Ironforge flightMaster', 0, -4821.1300, -1152.4000, 502.2950, 1),
|
||||||
|
(3981, 'Ironforge innkeeper', 0, -4840.6700, -857.0940, 501.9970, 1),
|
||||||
|
(3982, 'Ironforge innkeeper', 0, -4908.1700, -970.9860, 505.2260, 1),
|
||||||
|
(3983, 'Ironforge', 0, -4832.2700, -1069.6400, 502.2680, 1),
|
||||||
|
(3984, 'Kalecgos', 585, 197.8630, -272.7440, -8.6516, 1),
|
||||||
|
(3985, 'Lady Faltheress', 129, 2583.1800, 695.8610, 56.8033, 1),
|
||||||
|
(3986, 'Lady Liadrin', 580, 1720.0200, 643.2330, 28.1335, 1),
|
||||||
|
(3987, 'Loch Modan Stoutlager Inn', 0, -5391.7300, -2974.8800, 325.9470, 1),
|
||||||
|
(3988, 'Lord Blackwood', 289, 200.2010, 150.8390, 109.8790, 1),
|
||||||
|
(3989, 'MaiKyl', 230, 842.7150, -181.5710, -49.6700, 1),
|
||||||
|
(3990, 'Majordomo Teleport', 409, 848.9330, -812.8750, -229.6010, 1),
|
||||||
|
(3991, 'Maraudon Portal Effect', 349, 386.2700, 33.4144, -130.9340, 1),
|
||||||
|
(3992, 'Maraudon', 349, 614.7090, -206.5360, -64.2963, 1),
|
||||||
|
(3993, 'Mesa Elevator', 209, 2600.8100, 1228.7200, -40.2788, 1),
|
||||||
|
(3994, 'Mesa Elevator', 209, 2600.8100, 1228.7200, 89.0211, 1),
|
||||||
|
(3995, 'Mesa Elevator', 209, 2617.5100, 1243.9200, -40.5284, 1),
|
||||||
|
(3996, 'Mesa Elevator', 209, 2617.5100, 1243.9200, 89.0253, 1),
|
||||||
|
(3997, 'Mesa Elevatorentry', 1, -1030.1400, -32.0337, 68.2329, 1),
|
||||||
|
(3998, 'Mesa Elevatorentry', 1, -1032.6600, -26.3241, 141.1470, 1),
|
||||||
|
(3999, 'Mesa Elevatorentry', 1, -1035.1900, -45.0725, 68.2586, 1),
|
||||||
|
(4000, 'Mesa Elevatorentry', 1, -1042.0500, -47.7792, 141.1470, 1),
|
||||||
|
(4001, 'Mesa Elevatorentry', 1, -1284.7700, 185.1110, 130.3230, 1),
|
||||||
|
(4002, 'Mesa Elevatorentry', 1, -1290.7100, 188.5430, 67.4105, 1),
|
||||||
|
(4003, 'Mesa Elevatorentry', 1, -1304.5600, 186.1990, 67.5106, 1),
|
||||||
|
(4004, 'Mesa Elevatorentry', 1, -1308.1800, 180.4040, 130.2800, 1),
|
||||||
|
(4005, 'Mesa Elevatorentry', 1, -4660.6000, -1828.2700, 85.6823, 1),
|
||||||
|
(4006, 'Mesa Elevatorentry', 1, -4666.2300, -1851.2600, 85.6813, 1),
|
||||||
|
(4007, 'Mesa Elevatorentry', 1, -4666.4800, -1832.0900, -45.3171, 1),
|
||||||
|
(4008, 'Mesa Elevatorentry', 1, -4670.1200, -1845.7300, -45.1181, 1),
|
||||||
|
(4009, 'Mesa Elevatorentry', 1, -5385.2000, -2485.3300, 89.4297, 1),
|
||||||
|
(4010, 'Mesa Elevatorentry', 1, -5385.2700, -2492.0100, -41.7147, 1),
|
||||||
|
(4011, 'Mesa Elevatorentry', 1, -5395.6800, -2501.7500, -41.5869, 1),
|
||||||
|
(4012, 'Mesa Elevatorentry', 1, -5402.5700, -2501.2400, 89.4297, 1),
|
||||||
|
(4013, 'Mesa Elevatorentry', 209, 2597.4300, 1232.1000, 89.4297, 1),
|
||||||
|
(4014, 'Mesa Elevatorentry', 209, 2604.3200, 1231.5800, -41.5870, 1),
|
||||||
|
(4015, 'Mesa Elevatorentry', 209, 2614.7300, 1241.3200, -41.7145, 1),
|
||||||
|
(4016, 'Mesa Elevatorentry', 209, 2614.8000, 1248.0000, 89.4297, 1),
|
||||||
|
(4017, 'Mesa Elevatorentry', 47, 1729.8800, 1354.2700, -45.1181, 1),
|
||||||
|
(4018, 'Mesa Elevatorentry', 47, 1733.5200, 1367.9100, -45.3171, 1),
|
||||||
|
(4019, 'Mesa Elevatorentry', 47, 1733.7700, 1348.7400, 85.6813, 1),
|
||||||
|
(4020, 'Mesa Elevatorentry', 47, 1739.4000, 1371.7300, 85.6823, 1),
|
||||||
|
(4021, 'Mole Machine Port to Grim Guzzler', 230, 901.0680, -143.9390, -49.7550, 1),
|
||||||
|
(4022, 'Molten Core', 409, 889.0990, -822.9660, -227.2440, 1),
|
||||||
|
(4023, 'Move Bind Sight', 230, 824.8090, -176.1660, -49.7551, 1),
|
||||||
|
(4024, 'Mudsprocket innkeeper', 1, -4629.4300, -3176.1400, 41.2339, 1),
|
||||||
|
(4025, 'Murta Grimgut', 209, 1891.0700, 1294.7800, 48.2347, 1),
|
||||||
|
(4026, 'Nagrand Abandoned Armory', 530, -2052.0900, 7432.7600, -24.9648, 1),
|
||||||
|
(4027, 'Naxxanar portal', 571, 3692.4300, 3577.9400, 473.3200, 1),
|
||||||
|
(4028, 'Naxxanar portal', 571, 3734.1400, 3571.1200, 341.6620, 1),
|
||||||
|
(4029, 'Naxxanar portal', 571, 3738.6400, 3567.6700, 294.5220, 1),
|
||||||
|
(4030, 'Naxxanar portal', 571, 3739.5000, 3567.0000, 337.5640, 1),
|
||||||
|
(4031, 'Naxxanar portal', 571, 3739.6600, 3567.2900, 286.7850, 1),
|
||||||
|
(4032, 'Naxxanar portal', 571, 3787.5700, 3558.1100, 469.3220, 1),
|
||||||
|
(4033, 'Naxxanar portal', 571, 3801.5000, 3586.0500, 49.5700, 1),
|
||||||
|
(4034, 'Naxxramas Teleport - Sapphiron Exit', 533, 3005.7300, -3414.7600, 297.0260, 1),
|
||||||
|
(4035, 'Netherstorm Manaforge Ara', 530, 3965.9500, 3911.9700, 178.4460, 1),
|
||||||
|
(4036, 'Netherstorm The Violet Tower', 530, 2243.2600, 2243.1400, 101.5590, 1),
|
||||||
|
(4037, 'Nexus Portal', 578, 1045.5700, 1104.2400, 361.0700, 1),
|
||||||
|
(4038, 'Nexus Portal', 578, 982.1730, 1055.4500, 359.9670, 1),
|
||||||
|
(4039, 'Nijels Point innkeeper', 1, 255.6150, 1253.7600, 192.2240, 1),
|
||||||
|
(4040, 'No Mans Land -> Out of Hyjal', 1, 5010.1700, -4554.9400, 852.1460, 1),
|
||||||
|
(4041, 'Onyxias Lair', 249, -90.7180, -106.2340, -38.1972, 1),
|
||||||
|
(4042, 'Orb of the Nexus', 578, 1048.2700, 991.3100, 361.0700, 1),
|
||||||
|
(4043, 'Orgrims Hammer', 622, 23.4324, 0.0201, 33.5795, 1),
|
||||||
|
(4044, 'Orgrims Hammerdock', 668, 5223.4700, 1537.8700, 645.6480, 1),
|
||||||
|
(4045, 'Orgrimmar flightMaster', 1, 1676.2500, -4313.4500, 61.9445, 1),
|
||||||
|
(4046, 'Out of Body Experience', 0, -465.6990, 1495.9800, 17.3595, 1),
|
||||||
|
(4047, 'Port to Mazthoril', 1, 5904.2000, -4045.9000, 596.4300, 1),
|
||||||
|
(4048, 'Portal Effect: Acherus', 609, 2400.0300, -5635.0000, 377.0170, 1),
|
||||||
|
(4049, 'Portal of Immolthar', 429, -37.5643, 813.4330, -7.4382, 1),
|
||||||
|
(4050, 'Portal to Blasted Lands', 0, -11708.4000, -3168.0000, -5.0700, 1),
|
||||||
|
(4051, 'Portal to Blasted Lands', 0, -4606.4400, -928.9970, 501.0700, 1),
|
||||||
|
(4052, 'Portal to Blasted Lands', 0, -9007.5800, 871.8700, 129.6920, 1),
|
||||||
|
(4053, 'Portal to Blasted Lands', 0, 1768.7700, 55.3698, -46.3194, 1),
|
||||||
|
(4054, 'Portal to Blasted Lands', 1, -944.0640, 274.8590, 111.7100, 1),
|
||||||
|
(4055, 'Portal to Blasted Lands', 1, 1472.0900, -4214.6300, 59.2208, 1),
|
||||||
|
(4056, 'Portal to Blasted Lands', 1, 9661.8300, 2509.6000, 1331.6300, 1),
|
||||||
|
(4057, 'Portal to Blasted Lands', 530, -4039.0700, -11555.1000, -138.3370, 1),
|
||||||
|
(4058, 'Portal to Blasted Lands', 530, 9984.0300, -7108.4300, 47.7049, 1),
|
||||||
|
(4059, 'Portal to Dalaran', 571, 5807.7500, 588.3470, 661.5050, 1),
|
||||||
|
(4060, 'Portal to Dalaran', 631, -72.7031, 2282.4500, 32.8673, 1),
|
||||||
|
(4061, 'Portal to Dalaran', 712, 4.3953, 13.6833, 20.8039, 1),
|
||||||
|
(4062, 'Portal to Dalaran', 713, 22.1770, 22.9527, 35.6576, 1),
|
||||||
|
(4063, 'Portal to Orgrimmar', 1, 1321.8100, -4383.1900, 26.2300, 1),
|
||||||
|
(4064, 'Portal to Orgrimmar', 1, 1921.3700, -4149.2400, 40.4075, 1),
|
||||||
|
(4065, 'Portal to Stormwind', 0, -8446.8300, 339.9310, 121.3290, 1),
|
||||||
|
(4066, 'Portal to Stormwind', 0, -9143.5800, 375.1030, 90.6907, 1),
|
||||||
|
(4067, 'Portal to The Purple Parlor', 571, 5822.3500, 837.0760, 680.6570, 1),
|
||||||
|
(4068, 'Portal to The Purple Parlor', 571, 5848.4800, 853.7060, 843.1820, 1),
|
||||||
|
(4069, 'Portal to The Violet Citadel', 571, 5413.0100, 2868.1800, 418.6750, 1),
|
||||||
|
(4070, 'Portal to The Violet Citadel', 571, 5819.2600, 829.7740, 680.2200, 1),
|
||||||
|
(4071, 'Portal to Undercity', 0, 1774.8000, 761.1270, 55.0477, 1),
|
||||||
|
(4072, 'Portal to Undercity', 0, 1962.6900, 235.9200, 39.7700, 1),
|
||||||
|
(4073, 'Portal: Moonglade Effect', 1, 7828.8400, -2245.6500, 463.7070, 1),
|
||||||
|
(4074, 'Portal: Return from Moonglade Effect', 571, 6215.5400, -8.4001, 410.1650, 1),
|
||||||
|
(4075, 'Portal: Valley of Echoes', 571, 6390.5300, 237.0120, 395.8130, 1),
|
||||||
|
(4076, 'Quest - Port Skettis Prisoner - Location 01', 530, -4106.6400, 3029.7600, 344.8770, 1),
|
||||||
|
(4077, 'Quest - Port Skettis Prisoner - Location 02', 530, -3720.3500, 3789.9100, 302.8880, 1),
|
||||||
|
(4078, 'Quest - Port Skettis Prisoner - Location 03', 530, -3669.5700, 3386.7400, 312.9550, 1),
|
||||||
|
(4079, 'Quest - Teleport: Caverns of Time', 1, -8380.5000, -4262.5600, -207.5340, 1),
|
||||||
|
(4080, 'Quetzlun', 571, 5716.2600, -4369.3400, 385.8850, 1),
|
||||||
|
(4081, 'Ragefire Chasm', 389, -223.4540, 87.2837, -24.9351, 1),
|
||||||
|
(4082, 'Raven', 209, 1886.6400, 1299.1100, 48.3146, 1),
|
||||||
|
(4083, 'Recall', 30, -1347.0000, -292.0000, 91.0000, 1),
|
||||||
|
(4084, 'Recall', 30, 648.0000, -34.0000, 47.0000, 1),
|
||||||
|
(4085, 'Redridge Mountains Renders Rock', 0, -8737.4300, -2213.5000, 149.9600, 1),
|
||||||
|
(4086, 'Redridge Mountains Stonewatch', 0, -9392.0000, -3026.6300, 136.7700, 1),
|
||||||
|
(4087, 'Return to Temper', 530, -3286.6300, -12908.6000, 11.7562, 1),
|
||||||
|
(4088, 'Revanchion', 429, -106.3030, 551.2760, -4.3970, 1),
|
||||||
|
(4089, 'Ritual Preparation', 575, 296.6890, -346.5040, 90.5482, 1),
|
||||||
|
(4090, 'Ritual of the Sword', 575, 296.6890, -346.5040, 108.5480, 1),
|
||||||
|
(4091, 'Rizzles Escape', 1, 3697.2000, -3967.1300, 28.3115, 1),
|
||||||
|
(4092, 'Sacrifice', 429, -25.9536, -448.2270, -36.0912, 1),
|
||||||
|
(4093, 'Sacrifice', 532, -11234.2000, -1698.4600, 179.2400, 1),
|
||||||
|
(4094, 'Sacrifice', 553, 4.0000, 608.0000, -13.8165, 1),
|
||||||
|
(4095, 'Samuro', 230, 847.6740, -175.8690, -49.6706, 1),
|
||||||
|
(4096, 'Scaffold Carsentry', 0, -6889.3100, -1211.7600, 240.4440, 1),
|
||||||
|
(4097, 'Scaffold Carsentry', 0, -6895.5000, -1338.1500, 240.2840, 1),
|
||||||
|
(4098, 'Scaffold Carsentry', 0, -6899.7000, -1207.6000, 240.4100, 1),
|
||||||
|
(4099, 'Scaffold Carsentry', 0, -6903.5800, -1201.6700, 193.8930, 1),
|
||||||
|
(4100, 'Scaffold Carsentry', 0, -6905.7300, -1211.8300, 214.0430, 1),
|
||||||
|
(4101, 'Scaffold Carsentry', 469, -6889.3100, -1211.7600, 240.4440, 1),
|
||||||
|
(4102, 'Scaffold Carsentry', 469, -6895.5000, -1338.1500, 240.2840, 1),
|
||||||
|
(4103, 'Scaffold Carsentry', 469, -6899.7000, -1207.6000, 240.4100, 1),
|
||||||
|
(4104, 'Scaffold Carsentry', 469, -6903.5800, -1201.6700, 193.8930, 1),
|
||||||
|
(4105, 'Scaffold Carsentry', 469, -6905.7300, -1211.8300, 214.0430, 1),
|
||||||
|
(4106, 'Scaffold Carsentry', 530, -3292.8900, 1901.2800, 142.1590, 1),
|
||||||
|
(4107, 'Scaffold Carsentry', 530, -3297.0700, 1898.6700, 101.3760, 1),
|
||||||
|
(4108, 'Scaffold Carsentry', 530, -3300.1400, 1902.2300, 168.4120, 1),
|
||||||
|
(4109, 'Scaffold Carsentry', 530, -3301.8300, 1895.4800, 122.1590, 1),
|
||||||
|
(4110, 'Scaffold Carsentry', 530, -3306.6700, 1902.6700, 168.5590, 1),
|
||||||
|
(4111, 'Scaffold Carsentry', 571, 8115.1400, -245.3940, 900.1890, 1),
|
||||||
|
(4112, 'Scaffold Carsentry', 571, 8118.4000, -380.0000, 1027.9200, 1),
|
||||||
|
(4113, 'Scaffold Carsentry', 571, 8118.7700, -386.8160, 981.7890, 1),
|
||||||
|
(4114, 'Scaffold Carsentry', 571, 8119.4000, -256.0000, 926.3570, 1),
|
||||||
|
(4115, 'Scaffold Carsentry', 571, 8119.6800, -379.0410, 1028.0500, 1),
|
||||||
|
(4116, 'Scaffold Carsentry', 571, 8122.6000, -247.5780, 926.3930, 1),
|
||||||
|
(4117, 'Scaffold Carsentry', 571, 8123.9300, -240.0710, 880.1890, 1),
|
||||||
|
(4118, 'Scaffold Carsentry', 571, 8127.2900, -380.4440, 1001.7900, 1),
|
||||||
|
(4119, 'Scroll of Recall', 0, -103.9880, -902.7950, 55.5340, 1),
|
||||||
|
(4120, 'Scroll of Recall', 0, -10446.9000, -3261.9100, 20.1790, 1),
|
||||||
|
(4121, 'Scroll of Recall', 0, -5506.3400, -704.3480, 392.6860, 1),
|
||||||
|
(4122, 'Scroll of Recall', 0, -9470.7600, 3.9090, 49.7940, 1),
|
||||||
|
(4123, 'Scroll of Recall', 0, 1804.8400, 196.3220, 70.3990, 1),
|
||||||
|
(4124, 'Scroll of Recall', 0, 286.3140, -2184.0900, 122.6120, 1),
|
||||||
|
(4125, 'Scroll of Recall', 1, -1060.2700, 23.1370, 141.4550, 1),
|
||||||
|
(4126, 'Scroll of Recall', 1, -506.2240, -2590.0800, 113.1500, 1),
|
||||||
|
(4127, 'Scroll of Recall', 1, -7135.7200, -3787.7700, 8.7990, 1),
|
||||||
|
(4128, 'Scroll of Recall', 1, 6395.7100, 433.2560, 33.2600, 1),
|
||||||
|
(4129, 'Scryers Tier innkeeper', 530, -2184.0400, 5399.6200, 51.9658, 1),
|
||||||
|
(4130, 'Searing Gorge Blackchar Cave', 0, -7249.7500, -814.5230, 296.6230, 1),
|
||||||
|
(4131, 'Searing Gorge Blackrock Mountain', 0, -7243.0600, -1122.9400, 274.6300, 1),
|
||||||
|
(4132, 'Searing Gorge The Slag Pit', 0, -6601.6300, -1250.7500, 188.0640, 1),
|
||||||
|
(4133, 'Sewer Teleport 01', 571, 5881.2000, 666.5000, 615.7940, 1),
|
||||||
|
(4134, 'Sewer Teleport 04', 571, 5815.3000, 714.6000, 619.0980, 1),
|
||||||
|
(4135, 'Shadow Port', 33, -103.4600, 2122.1000, 155.6550, 1),
|
||||||
|
(4136, 'Shadow Port', 33, -105.6540, 2154.9800, 156.4300, 1),
|
||||||
|
(4137, 'Shadow Port', 33, -84.9900, 2151.0100, 155.6200, 1),
|
||||||
|
(4138, 'Shadowblink', 469, -7469.9300, -1227.9300, 476.7770, 1),
|
||||||
|
(4139, 'Shadowblink', 469, -7486.3600, -1194.3200, 476.8000, 1),
|
||||||
|
(4140, 'Shadowblink', 469, -7500.7000, -1249.8900, 476.7980, 1),
|
||||||
|
(4141, 'Shadowblink', 469, -7506.5800, -1165.2600, 476.7960, 1),
|
||||||
|
(4142, 'Shadowblink', 469, -7524.3600, -1219.1200, 476.7940, 1),
|
||||||
|
(4143, 'Shadowblink', 469, -7538.6300, -1273.6400, 476.8000, 1),
|
||||||
|
(4144, 'Shadowblink', 469, -7542.4700, -1191.9200, 476.3550, 1),
|
||||||
|
(4145, 'Shadowblink', 469, -7561.5400, -1244.0100, 476.8000, 1),
|
||||||
|
(4146, 'Shadowblink', 469, -7581.1100, -1216.1900, 476.8000, 1),
|
||||||
|
(4147, 'Shadowmoon Darkcaster', 540, 72.4708, 184.4520, -13.2380, 1),
|
||||||
|
(4148, 'Shadowmoon Valley Oronoks Farm', 530, -2802.3200, 1286.4600, 77.3836, 1),
|
||||||
|
(4149, 'Shattrath Banish Teleport', 530, -1500.0300, 5217.1400, 32.4600, 1),
|
||||||
|
(4150, 'Shattrath City Aldor Rise', 530, -1751.2400, 5641.2300, 129.0710, 1),
|
||||||
|
(4151, 'Shattrath City Scryers Tier', 530, -2041.2900, 5561.9700, 54.4371, 1),
|
||||||
|
(4152, 'Shattrath City innkeeper', 530, -1903.7900, 5765.7000, 131.2960, 1),
|
||||||
|
(4153, 'Shattrath Portal to Darnassus', 530, -1790.9800, 5413.9800, -12.4282, 1),
|
||||||
|
(4154, 'Shattrath Portal to Exodar', 530, -1880.2800, 5357.5300, -12.4281, 1),
|
||||||
|
(4155, 'Shattrath Portal to Exodar', 530, -4031.2400, -11569.6000, -138.2990, 1),
|
||||||
|
(4156, 'Shattrath Portal to Ironforge', 530, -1795.7900, 5399.6300, -12.4281, 1),
|
||||||
|
(4157, 'Shattrath Portal to Orgrimmar', 530, -1934.4900, 5453.4800, -12.4279, 1),
|
||||||
|
(4158, 'Shattrath Portal to Silvermoon', 530, -1894.6900, 5362.3400, -12.4282, 1),
|
||||||
|
(4159, 'Shattrath Portal to Stormwind', 0, -8998.1400, 861.2540, 29.6206, 1),
|
||||||
|
(4160, 'Shattrath Portal to Stormwind', 530, -1792.7800, 5406.5400, -12.4279, 1),
|
||||||
|
(4161, 'Shattrath Portal to Thunder Bluff', 530, -1936.3200, 5445.9500, -12.4282, 1),
|
||||||
|
(4162, 'Shattrath Portal to Undercity', 0, 1773.4200, 61.7391, -46.3215, 1),
|
||||||
|
(4163, 'Shattrath Portal to Undercity', 530, -1931.4800, 5460.4900, -12.4281, 1),
|
||||||
|
(4164, 'Ship (The Bravery)dock', 0, -8644.6400, 1333.7100, 5.7993, 1),
|
||||||
|
(4165, 'Ship (The Bravery)dock', 1, 6419.1900, 818.6660, 6.1894, 1),
|
||||||
|
(4166, 'Ship (The Lady Mehley)dock', 0, -3898.2700, -597.7260, 5.5310, 1),
|
||||||
|
(4167, 'Ship (The Lady Mehley)dock', 1, -4006.9700, -4730.3300, 5.5366, 1),
|
||||||
|
(4168, 'Ship (The Maidens Fancy)dock', 0, -14279.5000, 571.2000, 6.0777, 1),
|
||||||
|
(4169, 'Ship (The Maidens Fancy)dock', 1, -997.3520, -3830.2500, 5.5803, 1),
|
||||||
|
(4170, 'Ship, Icebreaker (Northspear)dock', 571, 584.0140, -5098.6700, -6.4857, 1),
|
||||||
|
(4171, 'Ship, Night Elf (Elunes Blessing)dock', 1, 6546.9800, 927.7040, 6.1894, 1),
|
||||||
|
(4172, 'Ship, Night Elf (Elunes Blessing)dock', 530, -4264.1400, -11328.3000, 6.0035, 1),
|
||||||
|
(4173, 'Ship, Night Elf (Feathermoon Ferry)dock', 1, -4210.9100, 3280.7300, 5.7750, 1),
|
||||||
|
(4174, 'Ship, Night Elf (Moonspray)dock', 1, 6584.1600, 773.2750, 6.0986, 1),
|
||||||
|
(4175, 'Ship, Night Elf (Moonspray)dock', 1, 8545.0900, 1016.1700, 6.2912, 1),
|
||||||
|
(4176, 'Sholazar Basin Nesingwary Base Camp', 571, 5559.1100, 5765.4300, -77.9385, 1),
|
||||||
|
(4177, 'Sholazar Basin The Seabreach Flow', 571, 5527.8500, 5348.4900, -134.4620, 1),
|
||||||
|
(4178, 'Silithus Ortells Hideout', 1, -7578.7200, 196.9140, 11.5488, 1),
|
||||||
|
(4179, 'Silithus Twilights Run', 1, -6265.9300, 41.8866, 9.0625, 1),
|
||||||
|
(4180, 'Silverpine Forest Fenris Keep', 0, 993.8510, 690.0600, 74.8984, 1),
|
||||||
|
(4181, 'Sister Mercydock', 571, 254.1330, -3752.8000, 0.2291, 1),
|
||||||
|
(4182, 'Sister Mercydock', 571, 93.8904, -3681.4100, 0.2103, 1),
|
||||||
|
(4183, 'Skadi Teleport', 575, 476.7990, -511.1670, 104.7230, 1),
|
||||||
|
(4184, 'Skarthis the Summoner', 547, -76.9917, -157.0810, -2.1064, 1),
|
||||||
|
(4185, 'Socrethar Return Portal Effect', 530, 4778.4600, 3455.3600, 104.1300, 1),
|
||||||
|
(4186, 'Stonetalon Mountains Boulderslide Ravine', 1, -97.6734, 185.5910, 96.6757, 1),
|
||||||
|
(4187, 'Stonetalon Mountains Cragpool Lake', 1, 1605.9900, 96.7067, 98.5654, 1),
|
||||||
|
(4188, 'Stonetalon Mountains Windshear Mine', 1, 935.9090, -302.7410, 0.0220, 1),
|
||||||
|
(4189, 'Storm Peaks', 571, 8974.3700, -1280.5600, 1059.0100, 1),
|
||||||
|
(4190, 'Stormwind Stockade', 34, 131.1490, 3.3395, -25.5229, 1),
|
||||||
|
(4191, 'Stormwind to Dustwallow Teleport', 1, -3722.9100, -4413.9600, 26.1300, 1),
|
||||||
|
(4192, 'Stoutlager Inn innkeeper', 0, -5377.9100, -2973.9100, 323.2520, 1),
|
||||||
|
(4193, 'Stranglethorn Vale Kurzens Compound', 0, -11571.3000, -644.0530, 31.2554, 1),
|
||||||
|
(4194, 'Stranglethorn Vale The Vile Reef', 0, -12211.5000, 644.0560, -67.1350, 1),
|
||||||
|
(4195, 'Subwayentry', 369, -1.0668, -11.3475, -3.9157, 1),
|
||||||
|
(4196, 'Subwayentry', 369, -1.0668, 2470.3000, -3.9157, 1),
|
||||||
|
(4197, 'Subwayentry', 369, -39.7335, 9.7886, -3.9157, 1),
|
||||||
|
(4198, 'Subwayentry', 369, -39.7335, -10.3899, -3.9157, 1),
|
||||||
|
(4199, 'Subwayentry', 369, -39.7335, 30.3816, -3.9157, 1),
|
||||||
|
(4200, 'Subwayentry', 369, -50.9334, 2472.9300, -3.9157, 1),
|
||||||
|
(4201, 'Subwayentry', 369, -50.9334, 2512.1500, -3.9157, 1),
|
||||||
|
(4202, 'Subwayentry', 369, -51.0097, 2492.2300, -3.9157, 1),
|
||||||
|
(4203, 'Subwayentry', 369, 10.1333, 2510.4900, -3.9157, 1),
|
||||||
|
(4204, 'Subwayentry', 369, 10.1333, 2490.7400, -3.9157, 1),
|
||||||
|
(4205, 'Subwayentry', 369, 10.1333, 8.8000, -3.9157, 1),
|
||||||
|
(4206, 'Subwayentry', 369, 10.1896, 28.7706, -3.9157, 1),
|
||||||
|
(4207, 'Summon Menagerie', 578, 1116.1100, 1075.1700, 508.3490, 1),
|
||||||
|
(4208, 'Summon Menagerie', 578, 1163.7200, 1170.9900, 527.3220, 1),
|
||||||
|
(4209, 'Summon Menagerie', 578, 968.7080, 1042.4900, 527.3220, 1),
|
||||||
|
(4210, 'Sunken Temple', 109, -455.3780, 76.5158, -93.3827, 1),
|
||||||
|
(4211, 'Surface Portal', 571, 5795.8800, 2070.8700, -344.0460, 1),
|
||||||
|
(4212, 'Surface Portal', 571, 6203.8700, 2262.1700, 497.1970, 1),
|
||||||
|
(4213, 'Surge Needle Teleporter', 571, 3444.3800, 2361.8700, 38.7409, 1),
|
||||||
|
(4214, 'Swamp of Sorrows Stagalbog Cave', 0, -10919.3000, -3673.0500, 11.0245, 1),
|
||||||
|
(4215, 'Swamp of Sorrows Stonard', 0, -10437.8000, -3307.0400, 20.4499, 1),
|
||||||
|
(4216, 'Teldrassil Banethil Barrow Den', 1, 9794.8400, 1549.1500, 1262.8000, 1),
|
||||||
|
(4217, 'Teldrassil Fel Rock', 1, 10124.0000, 1115.9100, 1322.8200, 1),
|
||||||
|
(4218, 'Teldrassil Shadowthread Cave', 1, 10889.4000, 917.5860, 1326.6400, 1),
|
||||||
|
(4219, 'Teleport - Coldarra, Transitus Shield to Amber Ledge', 571, 3594.1800, 5997.5100, 136.2150, 1),
|
||||||
|
(4220, 'Teleport - Dalaran to Wintergrasp', 571, 5325.0600, 2843.3600, 409.2850, 1),
|
||||||
|
(4221, 'Teleport Darkshire', 0, -10566.0000, -1189.0000, 28.0000, 1),
|
||||||
|
(4222, 'Teleport Defenders', 607, 1226.4300, -71.9616, 70.0842, 1),
|
||||||
|
(4223, 'Teleport Duskwood', 0, -10368.0000, -422.0000, 64.1214, 1),
|
||||||
|
(4224, 'Teleport Elwynn', 0, -9104.0000, -70.0000, 83.0000, 1),
|
||||||
|
(4225, 'Teleport Goldshire', 0, -9464.0000, 62.0000, 56.0000, 1),
|
||||||
|
(4226, 'Teleport Left', 533, 2692.0000, -3399.2700, 267.6860, 1),
|
||||||
|
(4227, 'Teleport Moonbrook', 0, -11020.0000, 1436.0000, 43.6892, 1),
|
||||||
|
(4228, 'Teleport Players on Victory', 631, -548.9830, 2211.2400, 539.2900, 1),
|
||||||
|
(4229, 'Teleport Return', 533, 2685.0600, -3502.3700, 261.3150, 1),
|
||||||
|
(4230, 'Teleport Right', 533, 2692.0000, -3321.8600, 267.6860, 1),
|
||||||
|
(4231, 'Teleport Sanctum Moon - Down', 530, 7513.6300, -6388.9300, 23.8000, 1),
|
||||||
|
(4232, 'Teleport Sanctum Sun - Down', 530, 7199.4000, -7097.3600, 66.9700, 1),
|
||||||
|
(4233, 'Teleport To Zone In', 616, 728.0550, 1329.0300, 275.0000, 1),
|
||||||
|
(4234, 'Teleport Violet Citadel Spire Down', 571, 5790.0000, 734.0000, 640.0000, 1),
|
||||||
|
(4235, 'Teleport Westfall', 0, -10643.0000, 1052.0000, 33.8437, 1),
|
||||||
|
(4236, 'Teleport and Transform', 580, 1667.6400, 633.4660, 28.0500, 1),
|
||||||
|
(4237, 'Teleport back to Main Room', 603, 1970.6100, -25.5988, 324.5500, 1),
|
||||||
|
(4238, 'Teleport from Azshara Tower', 1, 3641.0000, -4702.0000, 121.0000, 1),
|
||||||
|
(4239, 'Teleport to Ashtongue NPCs', 564, 702.2200, 200.3000, 125.0100, 1),
|
||||||
|
(4240, 'Teleport to Center', 568, -34.3160, 1149.6400, 19.1550, 1),
|
||||||
|
(4241, 'Teleport to Chamber Illusion', 603, 2043.1200, -25.6981, 239.7210, 1),
|
||||||
|
(4242, 'Teleport to CoT Stratholme Phase 4', 595, 2071.5500, 1287.6800, 141.6870, 1),
|
||||||
|
(4243, 'Teleport to Council', 564, 603.4200, 305.9820, 271.9000, 1),
|
||||||
|
(4244, 'Teleport to Final Chamber Effect DND', 531, -8632.8400, 2055.8700, 108.8600, 1),
|
||||||
|
(4245, 'Teleport to Gnomeregan', 0, -5095.0000, 757.0000, 261.0000, 1),
|
||||||
|
(4246, 'Teleport to Hall of Command', 0, 2402.6200, -5633.2800, 377.0210, 1),
|
||||||
|
(4247, 'Teleport to Heart of Acherus', 609, 2419.9100, -5620.4800, 420.6440, 1),
|
||||||
|
(4248, 'Teleport to Icecrown Illusion', 603, 1949.1300, -80.6744, 239.9900, 1),
|
||||||
|
(4249, 'Teleport to Inside Violet Hold', 608, 1857.2400, 803.8770, 44.0085, 1),
|
||||||
|
(4250, 'Teleport to Lake Wintergrasp', 571, 4561.5800, 2835.3300, 389.7900, 1),
|
||||||
|
(4251, 'Teleport to Lake Wintergrasp', 571, 5025.7100, 3673.4100, 362.6870, 1),
|
||||||
|
(4252, 'Teleport to Lake Wintergrasp', 571, 5094.6700, 2170.3300, 365.6010, 1),
|
||||||
|
(4253, 'Teleport to Lake Wintergrasp', 571, 5386.0500, 2840.9700, 418.6750, 1),
|
||||||
|
(4254, 'Teleport to Molten Core DND', 409, 1080.0000, -483.0000, -108.0000, 1),
|
||||||
|
(4255, 'Teleport to Stormwind Illusion', 603, 1954.1400, 21.5220, 239.7180, 1),
|
||||||
|
(4256, 'Teleport to Sunwell Plateau', 580, 1861.4500, 495.1250, 82.9059, 1),
|
||||||
|
(4257, 'Teleport to Twin Emps Effect DND', 531, -8971.8100, 1321.4700, -104.2490, 1),
|
||||||
|
(4258, 'Teleport to Violet Stand', 571, 5724.6200, 1013.1700, 174.4800, 1),
|
||||||
|
(4259, 'Teleport to the Silvermoon', 530, 10021.1000, -7014.8700, 49.7100, 1),
|
||||||
|
(4260, 'Teleport to the Undercity', 0, 1805.9300, 335.6600, 70.3900, 1),
|
||||||
|
(4261, 'Teleport', 1, -3891.8000, -4609.9700, 9.5011, 1),
|
||||||
|
(4262, 'Teleport', 409, 736.5160, -1176.3500, -119.0060, 1),
|
||||||
|
(4263, 'Teleport', 531, -8306.6800, 2060.8400, 133.0620, 1),
|
||||||
|
(4264, 'Teleport', 531, -8330.6300, 2123.1400, 133.0620, 1),
|
||||||
|
(4265, 'Teleport', 533, 2633.4900, -3529.5600, 274.1110, 1),
|
||||||
|
(4266, 'Teleport', 533, 2905.6300, -3769.9600, 273.6200, 1),
|
||||||
|
(4267, 'Teleport', 571, 3574.2200, 6652.1300, 195.1850, 1),
|
||||||
|
(4268, 'Teleport', 571, 3646.7400, 5893.2000, 174.4830, 1),
|
||||||
|
(4269, 'Teleport', 571, 4590.9400, -5711.2400, 184.5070, 1),
|
||||||
|
(4270, 'Teleport', 576, 504.7420, 88.9122, -16.1245, 1),
|
||||||
|
(4271, 'Teleport', 578, 1103.4700, 1049.5700, 512.0000, 1),
|
||||||
|
(4272, 'Teleport', 600, -369.0000, -601.0000, 2.0000, 1),
|
||||||
|
(4273, 'Teleport: Argent Tournament', 571, 8480.5500, 1092.9000, 554.4850, 1),
|
||||||
|
(4274, 'Teleport: Black Temple', 530, -3560.5200, 583.3530, 10.9431, 1),
|
||||||
|
(4275, 'Teleport: Darnassus', 1, 9664.1400, 2526.3600, 1332.6900, 1),
|
||||||
|
(4276, 'Teleport: Moonglade', 1, 7992.6200, -2680.0400, 512.0990, 1),
|
||||||
|
(4277, 'Teleport: Shattrath', 530, -1842.0700, 5497.1700, -12.4306, 1),
|
||||||
|
(4278, 'Teleport: Stonard', 0, -10469.0000, -3331.5400, 25.4716, 1),
|
||||||
|
(4279, 'Teleport: Theramore', 1, -3748.1100, -4440.2100, 30.5688, 1),
|
||||||
|
(4280, 'Tempest Keep', 550, 411.4090, -39.8267, 20.1802, 1);
|
||||||
|
INSERT INTO `playerbots_travelnode` (`id`, `name`, `map_id`, `x`, `y`, `z`, `linked`) VALUES
|
||||||
|
(4281, 'Tempest Keep: The Arcatraz', 552, 278.6480, -12.6903, 22.4479, 1),
|
||||||
|
(4282, 'Tempest Keep: The Botanica', 553, 17.5470, 404.8610, -27.1645, 1),
|
||||||
|
(4283, 'Tempest Keep: The Mechanar', 554, 169.2420, -12.2941, -0.0010, 1),
|
||||||
|
(4284, 'Terokkar Forest Skettis', 530, -3944.9300, 3664.0900, 287.9900, 1),
|
||||||
|
(4285, 'Terokkar Forest Terokks Rest', 530, -3868.3500, 3521.8800, 278.5610, 1),
|
||||||
|
(4286, 'Test of Lore', 1, -2354.0300, -1902.0700, 95.7800, 1),
|
||||||
|
(4287, 'The Barrens Baeldun Keep', 1, -4071.7300, -2381.6400, 126.2140, 1),
|
||||||
|
(4288, 'The Barrens Dreadmist Den', 1, 318.8870, -2225.7300, 212.5040, 1),
|
||||||
|
(4289, 'The Chilled Quagmire spiritguide', 571, 5103.1300, 3462.1300, 368.5680, 1),
|
||||||
|
(4290, 'The Chilled Quagmire spirithealer', 571, 5099.0300, 3469.6700, 368.4850, 1),
|
||||||
|
(4291, 'The Nexus', 576, 545.9580, -125.9330, -24.9367, 1),
|
||||||
|
(4292, 'The Skybreaker', 623, 5.2403, 0.2579, 20.8691, 1),
|
||||||
|
(4293, 'The Storm Peaks Frostfloe Deep', 571, 8300.4300, -2564.8600, 1153.5900, 1),
|
||||||
|
(4294, 'The Storm Peaks Frostgrips Hollow', 571, 6956.1400, -2.5143, 808.5300, 1),
|
||||||
|
(4295, 'The Storm Peaks Gimoraks Den', 571, 7920.2400, -1625.2100, 910.8520, 1),
|
||||||
|
(4296, 'The Storm Peaks Hibernal Cavern', 571, 7241.0300, -2075.5900, 763.0780, 1),
|
||||||
|
(4297, 'The Storm Peaks Plain of Echoes', 571, 8109.8900, -2815.7900, 1135.3200, 1),
|
||||||
|
(4298, 'The Storm Peaks The Forlorn Mine', 571, 6929.4000, -1315.7700, 831.3460, 1),
|
||||||
|
(4299, 'The Storm Peaks The Frozen Mine', 571, 7768.5000, -16.3215, 864.4010, 1),
|
||||||
|
(4300, 'The Storm Peaks', 571, 7268.5100, -2125.3800, 778.1580, 1),
|
||||||
|
(4301, 'The Sunken Ring spiritguide', 571, 5104.7500, 2300.9500, 368.5680, 1),
|
||||||
|
(4302, 'The Zephyrdock', 1, -1026.8600, 358.4000, 133.3400, 1),
|
||||||
|
(4303, 'The Zephyrdock', 1, 1139.7300, -4142.9300, 52.0080, 1),
|
||||||
|
(4304, 'Thousand Needles Darkcloud Pinnacle', 1, -4904.6700, -1970.1300, 86.8811, 1),
|
||||||
|
(4305, 'Thousand Needles Roguefeather Den', 1, -5533.0400, -1602.8800, 29.1719, 1),
|
||||||
|
(4306, 'Thousand Needles Splithoof Hold', 1, -4952.9400, -2337.3000, -56.5321, 1),
|
||||||
|
(4307, 'Thousand Needles The Weathered Nook', 1, -5217.6500, -2788.9900, -7.4459, 1),
|
||||||
|
(4308, 'Thrall', 0, 1953.8400, 233.8350, 41.8800, 1),
|
||||||
|
(4309, 'Thrall', 1, 1920.0100, -4123.9500, 43.6300, 1),
|
||||||
|
(4310, 'Thunderbrew Distillery innkeeper', 0, -5601.6000, -531.2030, 399.7370, 1),
|
||||||
|
(4311, 'Tirisfal Glades Agamand Family Crypt', 0, 3043.6500, 681.8670, 67.0126, 1),
|
||||||
|
(4312, 'Tirisfal Glades Gallows End Tavern', 0, 2262.2600, 244.2570, 33.7170, 1),
|
||||||
|
(4313, 'To Icecrown Airship - Player - Aura - Teleport to Dalaran Trigger', 571, 5831.5300, 497.0880, 657.4660, 1),
|
||||||
|
(4314, 'Toshleys Station Transporter', 530, 2054.0500, 5569.1600, 263.5710, 1),
|
||||||
|
(4315, 'Translocate', 0, 1805.0000, 327.0000, 70.5000, 1),
|
||||||
|
(4316, 'Translocate', 530, -2259.7400, 3215.0300, -4.0500, 1),
|
||||||
|
(4317, 'Translocate', 530, -2307.3500, 3123.9200, 13.6900, 1),
|
||||||
|
(4318, 'Translocate', 530, -589.0000, 4079.0000, 143.3000, 1),
|
||||||
|
(4319, 'Translocate', 530, 12780.9000, -6877.5000, 22.7861, 1),
|
||||||
|
(4320, 'Translocate', 530, 9334.5000, -7880.7600, 74.9094, 1),
|
||||||
|
(4321, 'Translocation', 530, -594.0000, 4079.0000, 94.0000, 1),
|
||||||
|
(4322, 'Trespasser!', 571, 5773.0000, 703.5000, 641.6000, 1),
|
||||||
|
(4323, 'Trespasser!', 571, 5846.5000, 605.5000, 650.9000, 1),
|
||||||
|
(4324, 'Trespasser!', 571, 8460.0000, 700.0000, 547.4000, 1),
|
||||||
|
(4325, 'Trespasser!', 571, 8573.0000, 703.9000, 547.3000, 1),
|
||||||
|
(4326, 'Turtle (Green Island)dock', 571, 2649.6000, 844.8000, 1.7773, 1),
|
||||||
|
(4327, 'Turtle (Walker of Waves)dock', 571, 2638.1300, 938.4000, 1.7773, 1),
|
||||||
|
(4328, 'Uldaman', 70, -42.3058, 263.8750, -48.9355, 1),
|
||||||
|
(4329, 'Ulduar spirithealer', 571, 9025.6600, -1178.5700, 1060.0800, 1),
|
||||||
|
(4330, 'Undervatorentry', 0, 1552.8000, 240.7730, 55.4904, 1),
|
||||||
|
(4331, 'Undervatorentry', 0, 1596.1500, 283.2000, 55.4904, 1),
|
||||||
|
(4332, 'Undervatorentry', 0, 1596.2000, 197.1040, 55.4904, 1),
|
||||||
|
(4333, 'Undervatorentryentry', 0, 1564.0000, 240.6560, 55.7571, 1),
|
||||||
|
(4334, 'Undervatorentryentry', 0, 1595.3800, 197.7060, 55.4904, 1),
|
||||||
|
(4335, 'Undervatorentryentry', 0, 1595.3800, 213.3330, 57.8905, 1),
|
||||||
|
(4336, 'Undervatorentryentry', 0, 1595.6500, 271.8890, 55.7571, 1),
|
||||||
|
(4337, 'Use Legion Teleporter', 530, -2833.0900, 1949.8900, 201.2560, 1),
|
||||||
|
(4338, 'Vampiric Shadowbat', 532, -10930.9000, -1995.7500, 49.4768, 1),
|
||||||
|
(4339, 'Vanish', 309, -11516.1000, -1605.3100, 41.3000, 1),
|
||||||
|
(4340, 'Vator2entry', 90, -800.3290, 314.9250, -272.4900, 1),
|
||||||
|
(4341, 'Vatorentry', 0, -5163.7700, 655.3050, 348.5880, 1),
|
||||||
|
(4342, 'Vatorentry', 0, -5164.2400, 650.3540, 247.9780, 1),
|
||||||
|
(4343, 'Vortex', 616, 755.0000, 1301.0000, 280.0000, 1),
|
||||||
|
(4344, 'Watery Grave', 548, 337.6900, -732.8700, -13.7400, 1),
|
||||||
|
(4345, 'Watery Grave', 548, 365.5300, -737.1200, -14.0000, 1),
|
||||||
|
(4346, 'Watery Grave', 548, 366.2700, -709.4000, -13.9200, 1),
|
||||||
|
(4347, 'Watery Grave', 548, 372.8500, -690.8400, -13.9100, 1),
|
||||||
|
(4348, 'Weegli Blastfuse', 209, 1881.0500, 1297.3600, 48.4190, 1),
|
||||||
|
(4349, 'Western Plaguelands Mardenholde Keep', 0, 2936.4100, -1395.9000, 166.0270, 1),
|
||||||
|
(4350, 'Westfall The Cooper Residence', 0, -11023.1000, 1547.4800, 44.4936, 1),
|
||||||
|
(4351, 'Winterspring Everlook', 1, 6768.3400, -4668.5200, 723.7480, 1),
|
||||||
|
(4352, 'Winterspring Moon Horror Den', 1, 7124.2700, -4637.7800, 639.6550, 1),
|
||||||
|
(4353, 'Wyrmrest Temple flightMaster', 571, 3647.2600, 244.0510, 52.3397, 1),
|
||||||
|
(4354, 'Wyrmrest Temple spirithealer', 571, 3546.8600, 272.9880, 45.5817, 1),
|
||||||
|
(4355, 'Zeppelin (The Iron Eagle)dock', 0, -12452.0000, 221.0670, 31.7681, 1),
|
||||||
|
(4356, 'Zeppelin (The Iron Eagle)dock', 1, 1359.5700, -4632.2000, 53.6569, 1),
|
||||||
|
(4357, 'Zeppelin (The Purple Princess)dock', 0, -12407.5000, 211.8380, 31.5014, 1),
|
||||||
|
(4358, 'Zeppelin (The Purple Princess)dock', 0, 2061.4200, 235.5580, 100.1520, 1),
|
||||||
|
(4359, 'Zeppelin (The Thundercaller)dock', 0, 2064.2400, 291.6030, 97.0904, 1),
|
||||||
|
(4360, 'Zeppelin (The Thundercaller)dock', 1, 1319.3200, -4658.0900, 53.8358, 1),
|
||||||
|
(4361, 'Zeppelindock', 571, 1412.9900, -3092.5800, 166.2270, 1),
|
||||||
|
(4362, 'ZulDrak Amphitheater of Anguish', 571, 5715.7900, -2945.0100, 296.5510, 1),
|
||||||
|
(4363, 'ZulDrak ZimTorga', 571, 5756.5800, -3567.6300, 387.0390, 1),
|
||||||
|
(4364, '[PH] Teleport to Auberdine', 1, 6581.0500, 767.5000, 5.7843, 1),
|
||||||
|
(4365, '[PH] Teleport to Booty Bay', 0, -14457.0000, 496.4500, 39.1392, 1),
|
||||||
|
(4366, '[PH] Teleport to Felwood', 1, 5483.9000, -749.8810, 334.6210, 1),
|
||||||
|
(4367, '[PH] Teleport to GromGol', 0, -12415.0000, 207.6180, 31.5017, 1),
|
||||||
|
(4368, '[PH] Teleport to Menethil Harbor', 0, -3752.8100, -851.5580, 10.1153, 1),
|
||||||
|
(4369, '[PH] Teleport to Orgrimmar', 1, 1552.5000, -4420.6600, 8.9480, 1),
|
||||||
|
(4370, '[PH] Teleport to Theramore', 1, -3615.4900, -4467.3400, 21.6032, 1),
|
||||||
|
(4371, 'c-Maraudon', 1, -1424.0400, 2945.0100, 134.5400, 1),
|
||||||
|
(4372, 'c-Tauren start', 1, -3034.7000, 144.0400, 70.8700, 1),
|
||||||
|
(4373, 'c1-Blackfathom Deeps', 1, 4158.0100, 877.6000, -20.6800, 1),
|
||||||
|
(4374, 'c1-Blackrock Mountain', 0, -7502.2000, -1152.9800, 269.5500, 1),
|
||||||
|
(4375, 'c1-Coilfang', 530, 571.1000, 6938.9700, -16.8100, 1),
|
||||||
|
(4376, 'c1-Deadmine exit', 0, -11367.5000, 1617.1000, 71.2200, 1),
|
||||||
|
(4377, 'c1-Dire Maul', 1, -3626.3900, 917.3700, 150.1300, 1),
|
||||||
|
(4378, 'c1-Ebon Hold', 609, 2390.0200, -5640.9100, 377.0900, 1),
|
||||||
|
(4379, 'c1-Sunken Temple', 0, -10416.5000, -3832.5300, -36.9200, 1),
|
||||||
|
(4380, 'c1-The Noxious Pass', 609, 2528.2200, -5580.4400, 162.0200, 1),
|
||||||
|
(4381, 'c1-Timbermaw Hold', 1, 7016.7500, -2153.8400, 595.0900, 1),
|
||||||
|
(4382, 'c1-Wailing Caverns', 1, -588.5300, -2037.6900, 57.6000, 1),
|
||||||
|
(4383, 'c2-Blackfathom Deeps', 1, 4156.6000, 909.8900, -20.9700, 1),
|
||||||
|
(4384, 'c2-Blackrock Mountain', 0, -7591.3100, -1114.4400, 249.9100, 1),
|
||||||
|
(4385, 'c2-Coilfang', 530, 571.1000, 6938.9700, -15.2000, 1),
|
||||||
|
(4386, 'c2-Deadmine exit', 0, -11367.1000, 1610.4800, 76.6300, 1),
|
||||||
|
(4387, 'c2-Dire Maul', 1, -3628.0800, 919.5500, 137.8400, 1),
|
||||||
|
(4388, 'c2-Ebon Hold', 609, 2383.6500, -5645.2000, 420.7700, 1),
|
||||||
|
(4389, 'c2-Sunken Temple', 0, -10408.7000, -3834.2900, -44.6900, 1),
|
||||||
|
(4390, 'c2-The Noxious Pass', 609, 2538.3500, -5573.0500, 162.4600, 1),
|
||||||
|
(4391, 'c3-Blackfathom Deeps', 1, 4157.4600, 916.4400, -17.4000, 1),
|
||||||
|
(4392, 'c3-Coilfang', 530, 651.0700, 6865.3700, -82.3400, 1),
|
||||||
|
(4393, 'c3-Deadmine exit', 0, -11381.5000, 1584.1100, 82.1000, 1),
|
||||||
|
(4394, 'c3-Ebon Hold', 609, 2325.0300, -5659.6000, 382.2400, 1),
|
||||||
|
(4395, 'c3-Scarlet Enclave', 609, 2409.0900, -5722.3700, 154.0000, 1),
|
||||||
|
(4396, 'c3-Sunken Temple', 0, -10325.2000, -3865.8600, -44.4500, 1),
|
||||||
|
(4397, 'c3-The Noxious Pass', 609, 2546.6100, -5563.8900, 162.8800, 1),
|
||||||
|
(4398, 'c4-Coilfang', 530, 607.1700, 6908.6800, -49.2000, 1),
|
||||||
|
(4399, 'c4-Deadmine exit', 0, -11379.6000, 1578.7900, 87.8400, 1),
|
||||||
|
(4400, 'c4-Ebon Hold', 609, 2348.5800, -5695.3500, 382.2400, 1),
|
||||||
|
(4401, 'c4-Scarlet Enclave', 609, 2402.8600, -5727.0300, 154.0000, 1),
|
||||||
|
(4402, 'c4-The Noxious Pass', 609, 2563.5200, -5547.7900, 163.2700, 1),
|
||||||
|
(4403, 'c5-Coilfang', 530, 574.6800, 6942.9300, -37.7200, 1),
|
||||||
|
(4404, 'c5-Deadmine exit', 0, -11340.2000, 1571.6100, 94.4400, 1),
|
||||||
|
(4405, 'c6-Coilfang', 530, 723.7400, 6865.7800, -74.1000, 1),
|
||||||
|
(4406, 'c7-Coilfang', 530, 731.5700, 6866.0100, -70.4700, 1);
|
||||||
File diff suppressed because it is too large
Load Diff
143899
data/sql/playerbots/updates/2026_05_09_03_travelnode_path_cmangos_import.sql
Normal file
143899
data/sql/playerbots/updates/2026_05_09_03_travelnode_path_cmangos_import.sql
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,489 @@
|
|||||||
|
DELETE FROM ai_playerbot_texts WHERE name IN (
|
||||||
|
'quest_accept_debug',
|
||||||
|
'quest_already_have_error',
|
||||||
|
'quest_cant_take_error',
|
||||||
|
'arena_team_already_in_team',
|
||||||
|
'arena_team_thanks_for_invite',
|
||||||
|
'area_trigger_follow_too_far_error',
|
||||||
|
'area_trigger_wait_for_me',
|
||||||
|
'attack_no_target_error',
|
||||||
|
'attack_target_not_in_world_error',
|
||||||
|
'attack_in_flight_error',
|
||||||
|
'attack_pvp_prohibited_error',
|
||||||
|
'attack_target_friendly_error',
|
||||||
|
'attack_target_dead_error',
|
||||||
|
'attack_target_not_in_sight_error',
|
||||||
|
'attack_already_attacking_error',
|
||||||
|
'attack_invalid_target_error',
|
||||||
|
'bank_no_banker_nearby_error',
|
||||||
|
'move_from_group',
|
||||||
|
'running_away',
|
||||||
|
'clean_quest_log_started',
|
||||||
|
'quest_trivial_will_remove',
|
||||||
|
'quest_has_been_removed',
|
||||||
|
'quest_not_trivial_kept',
|
||||||
|
'quest_removed_debug',
|
||||||
|
'quest_removed_with_name',
|
||||||
|
'guild_accept_inviter_not_in_guild',
|
||||||
|
'guild_accept_already_in_guild',
|
||||||
|
'guild_accept_declined',
|
||||||
|
'outfit_usage_add',
|
||||||
|
'outfit_usage_remove',
|
||||||
|
'outfit_usage_equip',
|
||||||
|
'outfit_set_as',
|
||||||
|
'outfit_equipping',
|
||||||
|
'outfit_replace_current',
|
||||||
|
'outfit_resetting',
|
||||||
|
'outfit_updating_current',
|
||||||
|
'outfit_item_removed_from',
|
||||||
|
'outfit_item_added_to',
|
||||||
|
'release_spirit_not_dead_wait',
|
||||||
|
'release_spirit_already_spirit',
|
||||||
|
'release_spirit_releasing',
|
||||||
|
'release_spirit_meet_graveyard',
|
||||||
|
'send_mail_no_mailbox_nearby',
|
||||||
|
'send_mail_one_item_only',
|
||||||
|
'send_mail_cannot_send_money',
|
||||||
|
'send_mail_not_enough_money',
|
||||||
|
'send_mail_sending_to',
|
||||||
|
'send_mail_cannot_send_item',
|
||||||
|
'send_mail_item_not_for_sale',
|
||||||
|
'send_mail_sent_to',
|
||||||
|
'craft_reset',
|
||||||
|
'craft_usage',
|
||||||
|
'craft_cannot_craft',
|
||||||
|
'craft_summary',
|
||||||
|
'set_home_success',
|
||||||
|
'set_home_no_innkeeper_error',
|
||||||
|
'quest_shared',
|
||||||
|
'tame_invalid_id_error',
|
||||||
|
'tame_usage_error',
|
||||||
|
'tame_pet_changed',
|
||||||
|
'tame_pet_changed_initialized',
|
||||||
|
'tame_exotic_requires_beast_mastery',
|
||||||
|
'tame_no_pet_by_name',
|
||||||
|
'tame_no_pet_by_id',
|
||||||
|
'tame_no_pet_by_family',
|
||||||
|
'tame_no_pet_to_rename',
|
||||||
|
'tame_pet_name_length_error',
|
||||||
|
'tame_pet_name_alpha_error',
|
||||||
|
'tame_pet_name_forbidden_error',
|
||||||
|
'tame_pet_renamed',
|
||||||
|
'tame_pet_rename_refresh_hint',
|
||||||
|
'tame_only_hunters_level_10',
|
||||||
|
'tame_creature_template_not_found',
|
||||||
|
'tame_create_pet_failed',
|
||||||
|
'tame_pet_abandoned',
|
||||||
|
'tame_no_hunter_pet_to_abandon',
|
||||||
|
'taxi_ready_next_flight',
|
||||||
|
'taxi_cant_fly_with_you',
|
||||||
|
'taxi_no_flightmaster_nearby',
|
||||||
|
'trade_busy_now',
|
||||||
|
'trade_disabled',
|
||||||
|
'trade_thank_you_player',
|
||||||
|
'trade_selling_disabled',
|
||||||
|
'trade_buying_disabled',
|
||||||
|
'trade_item_not_for_sale',
|
||||||
|
'trade_item_not_needed',
|
||||||
|
'trade_no_items_error',
|
||||||
|
'trade_discount_buy_only',
|
||||||
|
'trade_success_pleasure',
|
||||||
|
'trade_success_fair_trade',
|
||||||
|
'trade_success_thanks',
|
||||||
|
'trade_success_off_with_you',
|
||||||
|
'trade_want_money_for_this',
|
||||||
|
'use_item_none_available',
|
||||||
|
'use_gameobject',
|
||||||
|
'socket_does_not_fit',
|
||||||
|
'use_item_on_target',
|
||||||
|
'use_item',
|
||||||
|
'socketing_item_with_gem',
|
||||||
|
'meeting_stone_in_combat',
|
||||||
|
'meeting_stone_welcome',
|
||||||
|
'meeting_stone_none_nearby',
|
||||||
|
'meeting_stone_none_near_you',
|
||||||
|
'meeting_stone_no_hearthstone_self',
|
||||||
|
'meeting_stone_no_hearthstone_you',
|
||||||
|
'meeting_stone_hearthstone_not_ready_self',
|
||||||
|
'meeting_stone_hearthstone_not_ready_you',
|
||||||
|
'meeting_stone_no_innkeepers_nearby',
|
||||||
|
'meeting_stone_no_innkeepers_near_you',
|
||||||
|
'meeting_stone_cannot_summon_vehicle',
|
||||||
|
'meeting_stone_cannot_summon_master_in_combat',
|
||||||
|
'meeting_stone_cannot_summon_master_dead',
|
||||||
|
'meeting_stone_cannot_summon_bot_dead',
|
||||||
|
'meeting_stone_revived',
|
||||||
|
'meeting_stone_not_enough_space',
|
||||||
|
'new_rpg_quest_accepted',
|
||||||
|
'new_rpg_quest_rewarded',
|
||||||
|
'new_rpg_quest_dropped',
|
||||||
|
'rpg_item_better_for_player',
|
||||||
|
'rpg_start_trade_with_player'
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM ai_playerbot_texts_chance WHERE name IN (
|
||||||
|
'quest_accept_debug',
|
||||||
|
'quest_already_have_error',
|
||||||
|
'quest_cant_take_error',
|
||||||
|
'arena_team_already_in_team',
|
||||||
|
'arena_team_thanks_for_invite',
|
||||||
|
'area_trigger_follow_too_far_error',
|
||||||
|
'area_trigger_wait_for_me',
|
||||||
|
'attack_no_target_error',
|
||||||
|
'attack_target_not_in_world_error',
|
||||||
|
'attack_in_flight_error',
|
||||||
|
'attack_pvp_prohibited_error',
|
||||||
|
'attack_target_friendly_error',
|
||||||
|
'attack_target_dead_error',
|
||||||
|
'attack_target_not_in_sight_error',
|
||||||
|
'attack_already_attacking_error',
|
||||||
|
'attack_invalid_target_error',
|
||||||
|
'bank_no_banker_nearby_error',
|
||||||
|
'move_from_group',
|
||||||
|
'running_away',
|
||||||
|
'clean_quest_log_started',
|
||||||
|
'quest_trivial_will_remove',
|
||||||
|
'quest_has_been_removed',
|
||||||
|
'quest_not_trivial_kept',
|
||||||
|
'quest_removed_debug',
|
||||||
|
'quest_removed_with_name',
|
||||||
|
'guild_accept_inviter_not_in_guild',
|
||||||
|
'guild_accept_already_in_guild',
|
||||||
|
'guild_accept_declined',
|
||||||
|
'outfit_usage_add',
|
||||||
|
'outfit_usage_remove',
|
||||||
|
'outfit_usage_equip',
|
||||||
|
'outfit_set_as',
|
||||||
|
'outfit_equipping',
|
||||||
|
'outfit_replace_current',
|
||||||
|
'outfit_resetting',
|
||||||
|
'outfit_updating_current',
|
||||||
|
'outfit_item_removed_from',
|
||||||
|
'outfit_item_added_to',
|
||||||
|
'release_spirit_not_dead_wait',
|
||||||
|
'release_spirit_already_spirit',
|
||||||
|
'release_spirit_releasing',
|
||||||
|
'release_spirit_meet_graveyard',
|
||||||
|
'send_mail_no_mailbox_nearby',
|
||||||
|
'send_mail_one_item_only',
|
||||||
|
'send_mail_cannot_send_money',
|
||||||
|
'send_mail_not_enough_money',
|
||||||
|
'send_mail_sending_to',
|
||||||
|
'send_mail_cannot_send_item',
|
||||||
|
'send_mail_item_not_for_sale',
|
||||||
|
'send_mail_sent_to',
|
||||||
|
'craft_reset',
|
||||||
|
'craft_usage',
|
||||||
|
'craft_cannot_craft',
|
||||||
|
'craft_summary',
|
||||||
|
'set_home_success',
|
||||||
|
'set_home_no_innkeeper_error',
|
||||||
|
'quest_shared',
|
||||||
|
'tame_invalid_id_error',
|
||||||
|
'tame_usage_error',
|
||||||
|
'tame_pet_changed',
|
||||||
|
'tame_pet_changed_initialized',
|
||||||
|
'tame_exotic_requires_beast_mastery',
|
||||||
|
'tame_no_pet_by_name',
|
||||||
|
'tame_no_pet_by_id',
|
||||||
|
'tame_no_pet_by_family',
|
||||||
|
'tame_no_pet_to_rename',
|
||||||
|
'tame_pet_name_length_error',
|
||||||
|
'tame_pet_name_alpha_error',
|
||||||
|
'tame_pet_name_forbidden_error',
|
||||||
|
'tame_pet_renamed',
|
||||||
|
'tame_pet_rename_refresh_hint',
|
||||||
|
'tame_only_hunters_level_10',
|
||||||
|
'tame_creature_template_not_found',
|
||||||
|
'tame_create_pet_failed',
|
||||||
|
'tame_pet_abandoned',
|
||||||
|
'tame_no_hunter_pet_to_abandon',
|
||||||
|
'taxi_ready_next_flight',
|
||||||
|
'taxi_cant_fly_with_you',
|
||||||
|
'taxi_no_flightmaster_nearby',
|
||||||
|
'trade_busy_now',
|
||||||
|
'trade_disabled',
|
||||||
|
'trade_thank_you_player',
|
||||||
|
'trade_selling_disabled',
|
||||||
|
'trade_buying_disabled',
|
||||||
|
'trade_item_not_for_sale',
|
||||||
|
'trade_item_not_needed',
|
||||||
|
'trade_no_items_error',
|
||||||
|
'trade_discount_buy_only',
|
||||||
|
'trade_success_pleasure',
|
||||||
|
'trade_success_fair_trade',
|
||||||
|
'trade_success_thanks',
|
||||||
|
'trade_success_off_with_you',
|
||||||
|
'trade_want_money_for_this',
|
||||||
|
'use_item_none_available',
|
||||||
|
'use_gameobject',
|
||||||
|
'socket_does_not_fit',
|
||||||
|
'use_item_on_target',
|
||||||
|
'use_item',
|
||||||
|
'socketing_item_with_gem',
|
||||||
|
'meeting_stone_in_combat',
|
||||||
|
'meeting_stone_welcome',
|
||||||
|
'meeting_stone_none_nearby',
|
||||||
|
'meeting_stone_none_near_you',
|
||||||
|
'meeting_stone_no_hearthstone_self',
|
||||||
|
'meeting_stone_no_hearthstone_you',
|
||||||
|
'meeting_stone_hearthstone_not_ready_self',
|
||||||
|
'meeting_stone_hearthstone_not_ready_you',
|
||||||
|
'meeting_stone_no_innkeepers_nearby',
|
||||||
|
'meeting_stone_no_innkeepers_near_you',
|
||||||
|
'meeting_stone_cannot_summon_vehicle',
|
||||||
|
'meeting_stone_cannot_summon_master_in_combat',
|
||||||
|
'meeting_stone_cannot_summon_master_dead',
|
||||||
|
'meeting_stone_cannot_summon_bot_dead',
|
||||||
|
'meeting_stone_revived',
|
||||||
|
'meeting_stone_not_enough_space',
|
||||||
|
'new_rpg_quest_accepted',
|
||||||
|
'new_rpg_quest_rewarded',
|
||||||
|
'new_rpg_quest_dropped',
|
||||||
|
'rpg_item_better_for_player',
|
||||||
|
'rpg_start_trade_with_player'
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO ai_playerbot_texts (id, name, text, say_type, reply_type, text_loc1, text_loc2, text_loc3, text_loc4, text_loc5, text_loc6, text_loc7, text_loc8) VALUES
|
||||||
|
(1779, 'quest_accept_debug', 'Quest [%quest] accepted', 0, 0, '퀘스트 [%quest] 수락', 'Quête [%quest] acceptée', 'Quest [%quest] angenommen', '任务 [%quest] 已接受', '任務 [%quest] 已接受', 'Misión [%quest] aceptada', 'Misión [%quest] aceptada', 'Задание [%quest] принято'),
|
||||||
|
(1780, 'quest_already_have_error', 'I have this quest', 0, 0, '이미 이 퀘스트를 가지고 있습니다', 'J''ai déjà cette quête', 'Ich habe diese Quest bereits', '我已经有这个任务了', '我已經有這個任務了', 'Ya tengo esta misión', 'Ya tengo esta misión', 'У меня уже есть это задание'),
|
||||||
|
(1781, 'quest_cant_take_error', 'I can''t take this quest', 0, 0, '이 퀘스트를 받을 수 없습니다', 'Je ne peux pas prendre cette quête', 'Ich kann diese Quest nicht annehmen', '我无法接受这个任务', '我無法接受這個任務', 'No puedo aceptar esta misión', 'No puedo aceptar esta misión', 'Я не могу взять это задание'),
|
||||||
|
(1782, 'arena_team_already_in_team', 'Sorry, I am already in such team', 0, 0, '죄송하지만 이미 그런 팀에 속해 있습니다', 'Désolé, je suis déjà dans une telle équipe', 'Entschuldigung, ich bin bereits in so einem Team', '抱歉,我已经在这样的队伍里了', '抱歉,我已經在這樣的隊伍裡了', 'Lo siento, ya estoy en un equipo así', 'Lo siento, ya estoy en un equipo así', 'Извините, я уже состою в такой команде'),
|
||||||
|
(1783, 'arena_team_thanks_for_invite', 'Thanks for the invite!', 0, 0, '초대해 주셔서 감사합니다!', 'Merci pour l''invitation !', 'Danke für die Einladung!', '谢谢你的邀请!', '謝謝你的邀請!', 'Gracias por la invitación!', 'Gracias por la invitación!', 'Спасибо за приглашение!'),
|
||||||
|
(1784, 'area_trigger_follow_too_far_error', 'I won''t follow: too far away', 0, 0, '너무 멀어서 따라가지 않겠습니다', 'Je ne suivrai pas : c''est trop loin', 'Ich werde nicht folgen: zu weit entfernt', '我不会跟随:距离太远了', '我不會跟隨:距離太遠了', 'No te seguiré: estás demasiado lejos', 'No te seguiré: estás demasiado lejos', 'Я не пойду следом: слишком далеко'),
|
||||||
|
(1785, 'area_trigger_wait_for_me', 'Wait for me', 0, 0, '잠깐만 기다려 주세요', 'Attendez-moi', 'Warte auf mich', '等等我', '等等我', 'Espérame', 'Espérame', 'Подожди меня'),
|
||||||
|
(1786, 'attack_no_target_error', 'I have no target', 0, 0, '대상이 없습니다', 'Je n''ai pas de cible', 'Ich habe kein Ziel', '我没有目标', '我沒有目標', 'No tengo objetivo', 'No tengo objetivo', 'У меня нет цели'),
|
||||||
|
(1787, 'attack_target_not_in_world_error', '%target is no longer in the world.', 0, 0, '%target은(는) 더 이상 월드에 없습니다.', '%target n''est plus dans le monde.', '%target ist nicht mehr in der Welt.', '%target 已不在世界中。', '%target 已不在世界中。', '%target ya no está en el mundo.', '%target ya no está en el mundo.', '%target больше не находится в мире.'),
|
||||||
|
(1788, 'attack_in_flight_error', 'I cannot attack in flight', 0, 0, '비행 중에는 공격할 수 없습니다', 'Je ne peux pas attaquer en vol', 'Ich kann im Flug nicht angreifen', '飞行中无法攻击', '飛行中無法攻擊', 'No puedo atacar en vuelo', 'No puedo atacar en vuelo', 'Я не могу атаковать в полёте'),
|
||||||
|
(1789, 'attack_pvp_prohibited_error', 'I cannot attack other players in PvP prohibited areas.', 0, 0, 'PvP 금지 지역에서는 다른 플레이어를 공격할 수 없습니다.', 'Je ne peux pas attaquer d''autres joueurs dans les zones où le PvP est interdit.', 'Ich kann andere Spieler in PvP-verbotenen Gebieten nicht angreifen.', '我不能在禁止 PvP 的区域攻击其他玩家。', '我不能在禁止 PvP 的區域攻擊其他玩家。', 'No puedo atacar a otros jugadores en zonas donde el JcJ está prohibido.', 'No puedo atacar a otros jugadores en zonas donde el JcJ está prohibido.', 'Я не могу атаковать других игроков в зонах, где PvP запрещено.'),
|
||||||
|
(1790, 'attack_target_friendly_error', '%target is friendly to me.', 0, 0, '%target은(는) 우호적인 대상입니다.', '%target m''est amical.', '%target ist mir gegenüber freundlich.', '%target 对我是友方目标。', '%target 對我是友方目標。', '%target es amistoso conmigo.', '%target es amistoso conmigo.', '%target дружелюбен ко мне.'),
|
||||||
|
(1791, 'attack_target_dead_error', '%target is dead.', 0, 0, '%target은(는) 죽었습니다.', '%target est mort.', '%target ist tot.', '%target 已经死了。', '%target 已經死了。', '%target está muerto.', '%target está muerto.', '%target мёртв.'),
|
||||||
|
(1792, 'attack_target_not_in_sight_error', '%target is not in my sight.', 0, 0, '%target이(가) 시야에 없습니다.', '%target n''est pas dans mon champ de vision.', '%target ist nicht in Sichtweite.', '%target 不在我的视野中。', '%target 不在我的視野中。', '%target no está a mi vista.', '%target no está a mi vista.', '%target вне поля моего зрения.'),
|
||||||
|
(1793, 'attack_already_attacking_error', 'I am already attacking %target.', 0, 0, '이미 %target을(를) 공격하고 있습니다.', 'J''attaque déjà %target.', 'Ich greife %target bereits an.', '我已经在攻击 %target。', '我已經在攻擊 %target。', 'Ya estoy atacando a %target.', 'Ya estoy atacando a %target.', 'Я уже атакую %target.'),
|
||||||
|
(1794, 'attack_invalid_target_error', 'I cannot attack an invalid target.', 0, 0, '유효하지 않은 대상은 공격할 수 없습니다.', 'Je ne peux pas attaquer une cible invalide.', 'Ich kann kein ungültiges Ziel angreifen.', '我不能攻击无效目标。', '我不能攻擊無效目標。', 'No puedo atacar un objetivo no válido.', 'No puedo atacar un objetivo no válido.', 'Я не могу атаковать недопустимую цель.'),
|
||||||
|
(1795, 'bank_no_banker_nearby_error', 'Cannot find banker nearby', 0, 0, '근처에 은행원이 없습니다', 'Impossible de trouver un banquier à proximité', 'Kein Bankier in der Nähe gefunden', '附近找不到银行职员', '附近找不到銀行職員', 'No encuentro un banquero cerca', 'No encuentro un banquero cerca', 'Поблизости нет банкира'),
|
||||||
|
(1796, 'move_from_group', 'Moving away from group', 0, 0, '파티에서 멀어지는 중입니다', 'Je m''éloigne du groupe', 'Ich entferne mich von der Gruppe', '正在远离队伍', '正在遠離隊伍', 'Me estoy alejando del grupo', 'Me estoy alejando del grupo', 'Отхожу от группы'),
|
||||||
|
(1797, 'running_away', 'Running away', 0, 0, '도망치는 중입니다', 'Je m''enfuis', 'Ich laufe weg', '正在逃跑', '正在逃跑', 'Estoy huyendo', 'Estoy huyendo', 'Убегаю'),
|
||||||
|
(1798, 'clean_quest_log_started', 'Clean Quest Log command received, removing grey/trivial quests...', 0, 0, '퀘스트 로그 정리 명령을 받았습니다. 회색/사소한 퀘스트를 제거하는 중입니다...', 'Commande de nettoyage du journal des quêtes reçue, suppression des quêtes grises/triviales...', 'Befehl zum Bereinigen des Questlogs erhalten, graue/triviale Quests werden entfernt...', '已收到清理任务日志命令,正在移除灰色/琐碎任务...', '已收到清理任務日誌命令,正在移除灰色/瑣碎任務...', 'Comando para limpiar el registro de misiones recibido, eliminando misiones grises/triviales...', 'Comando para limpiar el registro de misiones recibido, eliminando misiones grises/triviales...', 'Получена команда очистки журнала заданий, удаляю серые/простые задания...'),
|
||||||
|
(1799, 'quest_trivial_will_remove', 'Quest [%title] will be removed because it is trivial (grey).', 0, 0, '퀘스트 [%title]은(는) 사소한(회색) 퀘스트이므로 제거됩니다.', 'La quête [%title] sera supprimée car elle est triviale (grise).', 'Quest [%title] wird entfernt, weil sie trivial (grau) ist.', '任务 [%title] 将被移除,因为它是琐碎的(灰色)任务。', '任務 [%title] 將被移除,因為它是瑣碎的(灰色)任務。', 'La misión [%title] se eliminará porque es trivial (gris).', 'La misión [%title] se eliminará porque es trivial (gris).', 'Задание [%title] будет удалено, так как оно простое (серое).'),
|
||||||
|
(1800, 'quest_has_been_removed', 'Quest [%title] has been removed.', 0, 0, '퀘스트 [%title]이(가) 제거되었습니다.', 'La quête [%title] a été supprimée.', 'Quest [%title] wurde entfernt.', '任务 [%title] 已被移除。', '任務 [%title] 已被移除。', 'La misión [%title] ha sido eliminada.', 'La misión [%title] ha sido eliminada.', 'Задание [%title] было удалено.'),
|
||||||
|
(1801, 'quest_not_trivial_kept', 'Quest [%title] is not trivial and will be kept.', 0, 0, '퀘스트 [%title]은(는) 사소하지 않으므로 유지됩니다.', 'La quête [%title] n''est pas triviale et sera conservée.', 'Quest [%title] ist nicht trivial und wird behalten.', '任务 [%title] 并不琐碎,将被保留。', '任務 [%title] 並不瑣碎,將被保留。', 'La misión [%title] no es trivial y se conservará.', 'La misión [%title] no es trivial y se conservará.', 'Задание [%title] не является простым и будет сохранено.'),
|
||||||
|
(1802, 'quest_removed_debug', 'Quest [%quest] removed', 0, 0, '퀘스트 [%quest] 제거됨', 'Quête [%quest] supprimée', 'Quest [%quest] entfernt', '任务 [%quest] 已移除', '任務 [%quest] 已移除', 'Misión [%quest] eliminada', 'Misión [%quest] eliminada', 'Задание [%quest] удалено'),
|
||||||
|
(1803, 'quest_removed_with_name', 'Quest removed %quest', 0, 0, '퀘스트 제거됨 %quest', 'Quête supprimée %quest', 'Quest entfernt %quest', '任务已移除 %quest', '任務已移除 %quest', 'Misión eliminada %quest', 'Misión eliminada %quest', 'Задание удалено %quest'),
|
||||||
|
(1804, 'guild_accept_inviter_not_in_guild', 'You are not in a guild!', 0, 0, '당신은 길드에 속해 있지 않습니다!', 'Vous n''êtes pas dans une guilde !', 'Du bist in keiner Gilde!', '你不在公会中!', '你不在公會中!', 'No estás en un gremio!', 'No estás en un gremio!', 'Вы не состоите в гильдии!'),
|
||||||
|
(1805, 'guild_accept_already_in_guild', 'Sorry, I am in a guild already', 0, 0, '죄송하지만 이미 길드에 속해 있습니다', 'Désolé, je suis déjà dans une guilde', 'Entschuldigung, ich bin bereits in einer Gilde', '抱歉,我已经在公会里了', '抱歉,我已經在公會裡了', 'Lo siento, ya estoy en un gremio', 'Lo siento, ya estoy en un gremio', 'Извините, я уже в гильдии'),
|
||||||
|
(1806, 'guild_accept_declined', 'Sorry, I don''t want to join your guild :(', 0, 0, '죄송하지만 당신의 길드에 가입하고 싶지 않습니다 :(', 'Désolé, je ne veux pas rejoindre votre guilde :(', 'Entschuldigung, ich möchte deiner Gilde nicht beitreten :(', '抱歉,我不想加入你的公会 :(', '抱歉,我不想加入你的公會 :(', 'Lo siento, no quiero unirme a tu gremio :(', 'Lo siento, no quiero unirme a tu gremio :(', 'Извините, я не хочу вступать в вашу гильдию :('),
|
||||||
|
(1807, 'outfit_usage_add', 'outfit <name> +[item] to add items', 0, 0, '아이템을 추가하려면 outfit <name> +[item]', 'outfit <name> +[item] pour ajouter des objets', 'outfit <name> +[item], um Gegenstände hinzuzufügen', '使用 outfit <name> +[item] 添加物品', '使用 outfit <name> +[item] 添加物品', 'outfit <name> +[item] para agregar objetos', 'outfit <name> +[item] para agregar objetos', 'outfit <name> +[item], чтобы добавить предметы'),
|
||||||
|
(1808, 'outfit_usage_remove', 'outfit <name> -[item] to remove items', 0, 0, '아이템을 제거하려면 outfit <name> -[item]', 'outfit <name> -[item] pour retirer des objets', 'outfit <name> -[item], um Gegenstände zu entfernen', '使用 outfit <name> -[item] 移除物品', '使用 outfit <name> -[item] 移除物品', 'outfit <name> -[item] para eliminar objetos', 'outfit <name> -[item] para eliminar objetos', 'outfit <name> -[item], чтобы убрать предметы'),
|
||||||
|
(1809, 'outfit_usage_equip', 'outfit <name> equip/replace to equip items', 0, 0, '아이템을 장착하려면 outfit <name> equip/replace', 'outfit <name> equip/replace pour équiper des objets', 'outfit <name> equip/replace, um Gegenstände anzulegen', '使用 outfit <name> equip/replace 装备物品', '使用 outfit <name> equip/replace 裝備物品', 'outfit <name> equip/replace para equipar objetos', 'outfit <name> equip/replace para equipar objetos', 'outfit <name> equip/replace, чтобы экипировать предметы'),
|
||||||
|
(1810, 'outfit_set_as', 'Setting outfit %name as %param', 0, 0, '복장 %name을(를) %param(으)로 설정하는 중입니다', 'Définition de la tenue %name comme %param', 'Setze Outfit %name als %param', '正在将装备方案 %name 设置为 %param', '正在將裝備方案 %name 設定為 %param', 'Estableciendo el atuendo %name como %param', 'Estableciendo el atuendo %name como %param', 'Устанавливаю комплект %name как %param'),
|
||||||
|
(1811, 'outfit_equipping', 'Equipping outfit %name', 0, 0, '복장 %name을(를) 장착하는 중입니다', 'Équipement de la tenue %name', 'Outfit %name wird angelegt', '正在装备方案 %name', '正在裝備方案 %name', 'Equipando el atuendo %name', 'Equipando el atuendo %name', 'Экипирую комплект %name'),
|
||||||
|
(1812, 'outfit_replace_current', 'Replacing current equip with outfit %name', 0, 0, '현재 장비를 복장 %name으로 교체하는 중입니다', 'Remplacement de l''équipement actuel par la tenue %name', 'Aktuelle Ausrüstung wird durch Outfit %name ersetzt', '正在用装备方案 %name 替换当前装备', '正在用裝備方案 %name 替換目前裝備', 'Reemplazando el equipo actual con el atuendo %name', 'Reemplazando el equipo actual con el atuendo %name', 'Заменяю текущую экипировку комплектом %name'),
|
||||||
|
(1813, 'outfit_resetting', 'Resetting outfit %name', 0, 0, '복장 %name을(를) 초기화하는 중입니다', 'Réinitialisation de la tenue %name', 'Outfit %name wird zurückgesetzt', '正在重置装备方案 %name', '正在重置裝備方案 %name', 'Restableciendo el atuendo %name', 'Restableciendo el atuendo %name', 'Сбрасываю комплект %name'),
|
||||||
|
(1814, 'outfit_updating_current', 'Updating with current items outfit %name', 0, 0, '현재 아이템으로 복장 %name을(를) 업데이트하는 중입니다', 'Mise à jour de la tenue %name avec les objets actuels', 'Outfit %name wird mit der aktuellen Ausrüstung aktualisiert', '正在用当前物品更新装备方案 %name', '正在用目前物品更新裝備方案 %name', 'Actualizando el atuendo %name con los objetos actuales', 'Actualizando el atuendo %name con los objetos actuales', 'Обновляю комплект %name текущими предметами'),
|
||||||
|
(1815, 'outfit_item_removed_from', '%item removed from %name', 0, 0, '%name에서 %item이(가) 제거되었습니다', '%item retiré de %name', '%item aus %name entfernt', '已从 %name 中移除 %item', '已從 %name 中移除 %item', '%item eliminado de %name', '%item eliminado de %name', '%item удалён из %name'),
|
||||||
|
(1816, 'outfit_item_added_to', '%item added to %name', 0, 0, '%item이(가) %name에 추가되었습니다', '%item ajouté à %name', '%item zu %name hinzugefügt', '已将 %item 添加到 %name', '已將 %item 添加到 %name', '%item agregado a %name', '%item agregado a %name', '%item добавлен в %name'),
|
||||||
|
(1817, 'release_spirit_not_dead_wait', 'I am not dead, will wait here', 0, 0, '저는 죽지 않았습니다. 여기서 기다리겠습니다', 'Je ne suis pas mort, j''attendrai ici', 'Ich bin nicht tot und werde hier warten', '我还没死,会在这里等', '我還沒死,會在這裡等', 'No estoy muerto, esperaré aquí', 'No estoy muerto, esperaré aquí', 'Я не мёртв, буду ждать здесь'),
|
||||||
|
(1818, 'release_spirit_already_spirit', 'I am already a spirit', 0, 0, '저는 이미 유령입니다', 'Je suis déjà un esprit', 'Ich bin bereits ein Geist', '我已经是灵魂状态了', '我已經是靈魂狀態了', 'Ya soy un espíritu', 'Ya soy un espíritu', 'Я уже дух'),
|
||||||
|
(1819, 'release_spirit_releasing', 'Releasing...', 0, 0, '해방 중...', 'Je libère mon esprit...', 'Geist wird freigesetzt...', '正在释放灵魂...', '正在釋放靈魂...', 'Liberando espíritu...', 'Liberando espíritu...', 'Освобождаю дух...'),
|
||||||
|
(1820, 'release_spirit_meet_graveyard', 'Meet me at the graveyard', 0, 0, '묘지에서 만나요', 'Retrouvez-moi au cimetière', 'Triff mich auf dem Friedhof', '墓地见', '墓地見', 'Encuéntrame en el cementerio', 'Encuéntrame en el cementerio', 'Встречаемся на кладбище'),
|
||||||
|
(1821, 'send_mail_no_mailbox_nearby', 'There is no mailbox nearby', 0, 0, '근처에 우체통이 없습니다', 'Il n''y a pas de boîte aux lettres à proximité', 'Kein Briefkasten in der Nähe', '附近没有邮箱', '附近沒有郵箱', 'No hay un buzón cerca', 'No hay un buzón cerca', 'Поблизости нет почтового ящика'),
|
||||||
|
(1822, 'send_mail_one_item_only', 'You can not request more than one item', 0, 0, '둘 이상의 아이템은 요청할 수 없습니다', 'Vous ne pouvez pas demander plus d''un objet', 'Du kannst nicht mehr als einen Gegenstand anfordern', '你不能请求多于一件物品', '你不能要求超過一件物品', 'No puedes solicitar más de un objeto', 'No puedes solicitar más de un objeto', 'Нельзя запрашивать больше одного предмета'),
|
||||||
|
(1823, 'send_mail_cannot_send_money', 'I cannot send money', 0, 0, '돈은 보낼 수 없습니다', 'Je ne peux pas envoyer d''argent', 'Ich kann kein Geld senden', '我不能寄送钱', '我不能寄送金錢', 'No puedo enviar dinero', 'No puedo enviar dinero', 'Я не могу отправить деньги'),
|
||||||
|
(1824, 'send_mail_not_enough_money', 'I don''t have enough money', 0, 0, '돈이 충분하지 않습니다', 'Je n''ai pas assez d''argent', 'Ich habe nicht genug Geld', '我没有足够的钱', '我沒有足夠的錢', 'No tengo suficiente dinero', 'No tengo suficiente dinero', 'У меня недостаточно денег'),
|
||||||
|
(1825, 'send_mail_sending_to', 'Sending mail to %receiver', 0, 0, '%receiver에게 우편을 보내는 중입니다', 'Envoi du courrier à %receiver', 'Sende Post an %receiver', '正在向 %receiver 发送邮件', '正在向 %receiver 發送郵件', 'Enviando correo a %receiver', 'Enviando correo a %receiver', 'Отправляю почту %receiver'),
|
||||||
|
(1826, 'send_mail_cannot_send_item', 'Cannot send %item', 0, 0, '%item을(를) 보낼 수 없습니다', 'Impossible d''envoyer %item', '%item kann nicht gesendet werden', '无法发送 %item', '無法發送 %item', 'No se puede enviar %item', 'No se puede enviar %item', 'Невозможно отправить %item'),
|
||||||
|
(1827, 'send_mail_item_not_for_sale', '%item: it is not for sale', 0, 0, '%item: 판매 대상이 아닙니다', '%item : ce n''est pas à vendre', '%item: Das steht nicht zum Verkauf', '%item:这不是出售物品', '%item:這不是出售物品', '%item: esto no está en venta', '%item: esto no está en venta', '%item: это не продаётся'),
|
||||||
|
(1828, 'send_mail_sent_to', 'Sent mail to %receiver', 0, 0, '%receiver에게 우편을 보냈습니다', 'Courrier envoyé à %receiver', 'Post an %receiver gesendet', '已向 %receiver 发送邮件', '已向 %receiver 發送郵件', 'Correo enviado a %receiver', 'Correo enviado a %receiver', 'Почта отправлена %receiver'),
|
||||||
|
(1829, 'craft_reset', 'I will not craft anything', 0, 0, '아무것도 제작하지 않겠습니다', 'Je ne fabriquerai rien', 'Ich werde nichts herstellen', '我不会制作任何东西', '我不會製作任何東西', 'No fabricaré nada', 'No fabricaré nada', 'Я ничего не буду создавать'),
|
||||||
|
(1830, 'craft_usage', 'Usage: ''craft [itemId]'' or ''craft reset''', 0, 0, '사용법: ''craft [itemId]'' 또는 ''craft reset''', 'Utilisation : ''craft [itemId]'' ou ''craft reset''', 'Verwendung: ''craft [itemId]'' oder ''craft reset''', '用法:''craft [itemId]'' 或 ''craft reset''', '用法:''craft [itemId]'' 或 ''craft reset''', 'Uso: ''craft [itemId]'' o ''craft reset''', 'Uso: ''craft [itemId]'' o ''craft reset''', 'Использование: ''craft [itemId]'' или ''craft reset'''),
|
||||||
|
(1831, 'craft_cannot_craft', 'I cannot craft this', 0, 0, '이것은 제작할 수 없습니다', 'Je ne peux pas fabriquer ceci', 'Ich kann das nicht herstellen', '我无法制作这个', '我無法製作這個', 'No puedo fabricar esto', 'No puedo fabricar esto', 'Я не могу это создать'),
|
||||||
|
(1832, 'craft_summary', 'I will craft %item using reagents: %reagents (craft fee: %money)', 0, 0, '재료 %reagents을(를) 사용해 %item을(를) 제작하겠습니다 (제작 수수료: %money)', 'Je fabriquerai %item en utilisant les composants : %reagents (frais de fabrication : %money)', 'Ich werde %item mit folgenden Reagenzien herstellen: %reagents (Herstellungsgebühr: %money)', '我将使用材料 %reagents 制作 %item(制作费用:%money)', '我將使用材料 %reagents 製作 %item(製作費用:%money)', 'Fabricaré %item usando los materiales: %reagents (tarifa de fabricación: %money)', 'Fabricaré %item usando los materiales: %reagents (tarifa de fabricación: %money)', 'Я изготовлю %item, используя реагенты: %reagents (плата за изготовление: %money)'),
|
||||||
|
(1833, 'set_home_success', 'This inn is my new home', 0, 0, '이 여관을 새로운 집으로 설정했습니다', 'Cette auberge est ma nouvelle maison', 'Dieses Gasthaus ist mein neues Zuhause', '这家旅店是我的新家', '這間旅店是我的新家', 'Esta posada es mi nuevo hogar', 'Esta posada es mi nuevo hogar', 'Этот трактир теперь мой новый дом'),
|
||||||
|
(1834, 'set_home_no_innkeeper_error', 'Can''t find any innkeeper around', 0, 0, '주변에서 여관주인을 찾을 수 없습니다', 'Impossible de trouver un aubergiste aux alentours', 'Kein Gastwirt in der Nähe gefunden', '附近找不到旅店老板', '附近找不到旅店老闆', 'No encuentro ningún posadero cerca', 'No encuentro ningún posadero cerca', 'Не удалось найти поблизости трактирщика'),
|
||||||
|
(1835, 'quest_shared', 'Quest shared', 0, 0, '퀘스트 공유됨', 'Quête partagée', 'Quest geteilt', '任务已共享', '任務已共享', 'Misión compartida', 'Misión compartida', 'Задание поделено'),
|
||||||
|
(1836, 'tame_invalid_id_error', 'Invalid tame id.', 0, 0, '잘못된 길들이기 ID입니다.', 'Identifiant de dressage invalide.', 'Ungültige Zähm-ID.', '无效的驯服 ID。', '無效的馴服 ID。', 'ID de domesticación no válido.', 'ID de domesticación no válido.', 'Недопустимый ID приручения.'),
|
||||||
|
(1837, 'tame_usage_error', 'Usage: tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon', 0, 0, '사용법: tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon', 'Utilisation : tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon', 'Verwendung: tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon', '用法:tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon', '用法:tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon', 'Uso: tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon', 'Uso: tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon', 'Использование: tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon'),
|
||||||
|
(1838, 'tame_pet_changed', 'Pet changed to %name, ID: %id.', 0, 0, '소환수가 %name, ID: %id(으)로 변경되었습니다.', 'Familier changé en %name, ID : %id.', 'Begleiter geändert zu %name, ID: %id.', '宠物已更改为 %name,ID:%id。', '寵物已更改為 %name,ID:%id。', 'Mascota cambiada a %name, ID: %id.', 'Mascota cambiada a %name, ID: %id.', 'Питомец изменён на %name, ID: %id.'),
|
||||||
|
(1839, 'tame_pet_changed_initialized', 'Pet changed and initialized!', 0, 0, '소환수가 변경되고 초기화되었습니다!', 'Familier changé et initialisé !', 'Begleiter geändert und initialisiert!', '宠物已更改并初始化!', '寵物已更改並初始化!', 'La mascota ha sido cambiada e inicializada!', 'La mascota ha sido cambiada e inicializada!', 'Питомец изменён и инициализирован!'),
|
||||||
|
(1840, 'tame_exotic_requires_beast_mastery', 'I cannot use exotic pets unless I have the Beast Mastery talent.', 0, 0, '야수 지배 특성이 없으면 특수 야수 소환수를 사용할 수 없습니다.', 'Je ne peux pas utiliser de familiers exotiques sans le talent Maîtrise des bêtes.', 'Ich kann ohne das Talent Tierherrschaft keine exotischen Begleiter benutzen.', '如果没有野兽掌握天赋,我无法使用异种宠物。', '如果沒有野獸控制天賦,我無法使用異種寵物。', 'No puedo usar mascotas exóticas a menos que tenga el talento Dominio de bestias.', 'No puedo usar mascotas exóticas a menos que tenga el talento Dominio de bestias.', 'Я не могу использовать экзотических питомцев без таланта Повелитель зверей.'),
|
||||||
|
(1841, 'tame_no_pet_by_name', 'No tameable pet found with name: %name', 0, 0, '이름이 %name인 길들일 수 있는 소환수를 찾을 수 없습니다', 'Aucun familier apprivoisable trouvé avec le nom : %name', 'Kein zähmbares Tier mit dem Namen %name gefunden', '未找到名为 %name 的可驯服宠物', '未找到名為 %name 的可馴服寵物', 'No se encontró ninguna mascota domesticable con el nombre: %name', 'No se encontró ninguna mascota domesticable con el nombre: %name', 'Не найден приручаемый питомец с именем: %name'),
|
||||||
|
(1842, 'tame_no_pet_by_id', 'No tameable pet found with id: %id', 0, 0, 'ID가 %id인 길들일 수 있는 소환수를 찾을 수 없습니다', 'Aucun familier apprivoisable trouvé avec l''id : %id', 'Kein zähmbares Tier mit der ID %id gefunden', '未找到 ID 为 %id 的可驯服宠物', '未找到 ID 為 %id 的可馴服寵物', 'No se encontró ninguna mascota domesticable con el id: %id', 'No se encontró ninguna mascota domesticable con el id: %id', 'Не найден приручаемый питомец с id: %id'),
|
||||||
|
(1843, 'tame_no_pet_by_family', 'No tameable pet found with family: %family', 0, 0, '계열이 %family인 길들일 수 있는 소환수를 찾을 수 없습니다', 'Aucun familier apprivoisable trouvé avec la famille : %family', 'Kein zähmbares Tier mit der Familie %family gefunden', '未找到家族为 %family 的可驯服宠物', '未找到家族為 %family 的可馴服寵物', 'No se encontró ninguna mascota domesticable con la familia: %family', 'No se encontró ninguna mascota domesticable con la familia: %family', 'Не найден приручаемый питомец семейства: %family'),
|
||||||
|
(1844, 'tame_no_pet_to_rename', 'You have no pet to rename.', 0, 0, '이름을 바꿀 소환수가 없습니다.', 'Vous n''avez pas de familier à renommer.', 'Du hast kein Tier zum Umbenennen.', '你没有可重命名的宠物。', '你沒有可重新命名的寵物。', 'No tienes ninguna mascota para renombrar.', 'No tienes ninguna mascota para renombrar.', 'У вас нет питомца для переименования.'),
|
||||||
|
(1845, 'tame_pet_name_length_error', 'Pet name must be between 1 and 12 alphabetic characters.', 0, 0, '소환수 이름은 1자에서 12자의 알파벳 문자여야 합니다.', 'Le nom du familier doit contenir entre 1 et 12 caractères alphabétiques.', 'Der Name des Begleiters muss zwischen 1 und 12 alphabetischen Zeichen lang sein.', '宠物名字必须为 1 到 12 个字母字符。', '寵物名字必須為 1 到 12 個字母字元。', 'El nombre de la mascota debe tener entre 1 y 12 caracteres alfabéticos.', 'El nombre de la mascota debe tener entre 1 y 12 caracteres alfabéticos.', 'Имя питомца должно содержать от 1 до 12 буквенных символов.'),
|
||||||
|
(1846, 'tame_pet_name_alpha_error', 'Pet name must only contain alphabetic characters (A-Z, a-z).', 0, 0, '소환수 이름에는 알파벳 문자(A-Z, a-z)만 포함될 수 있습니다.', 'Le nom du familier ne doit contenir que des caractères alphabétiques (A-Z, a-z).', 'Der Name des Begleiters darf nur alphabetische Zeichen (A-Z, a-z) enthalten.', '宠物名字只能包含字母字符(A-Z, a-z)。', '寵物名字只能包含字母字元(A-Z, a-z)。', 'El nombre de la mascota solo puede contener caracteres alfabéticos (A-Z, a-z).', 'El nombre de la mascota solo puede contener caracteres alfabéticos (A-Z, a-z).', 'Имя питомца должно содержать только буквенные символы (A-Z, a-z).'),
|
||||||
|
(1847, 'tame_pet_name_forbidden_error', 'That pet name is forbidden. Please choose another name.', 0, 0, '그 소환수 이름은 사용할 수 없습니다. 다른 이름을 선택해 주세요.', 'Ce nom de familier est interdit. Veuillez en choisir un autre.', 'Dieser Name für den Begleiter ist verboten. Bitte wähle einen anderen Namen.', '该宠物名称被禁止使用。请选择其他名字。', '該寵物名稱被禁止使用。請選擇其他名字。', 'Ese nombre de mascota está prohibido. Por favor, elige otro nombre.', 'Ese nombre de mascota está prohibido. Por favor, elige otro nombre.', 'Это имя для питомца запрещено. Пожалуйста, выберите другое имя.'),
|
||||||
|
(1848, 'tame_pet_renamed', 'Your pet has been renamed to %name!', 0, 0, '당신의 소환수 이름이 %name(으)로 변경되었습니다!', 'Votre familier a été renommé en %name !', 'Dein Begleiter wurde in %name umbenannt!', '你的宠物已重命名为 %name!', '你的寵物已重新命名為 %name!', 'Tu mascota ha sido renombrada a %name!', 'Tu mascota ha sido renombrada a %name!', 'Ваш питомец был переименован в %name!'),
|
||||||
|
(1849, 'tame_pet_rename_refresh_hint', 'If you do not see the new name, please dismiss and recall your pet.', 0, 0, '새 이름이 보이지 않으면 소환수를 해제했다가 다시 소환해 주세요.', 'Si vous ne voyez pas le nouveau nom, veuillez renvoyer puis rappeler votre familier.', 'Wenn du den neuen Namen nicht siehst, schicke deinen Begleiter weg und rufe ihn erneut.', '如果你看不到新名字,请先解散再召回你的宠物。', '如果你看不到新名字,請先解散再召回你的寵物。', 'Si no ves el nuevo nombre, por favor retira y vuelve a invocar a tu mascota.', 'Si no ves el nuevo nombre, por favor retira y vuelve a invocar a tu mascota.', 'Если вы не видите новое имя, отпустите и снова призовите питомца.'),
|
||||||
|
(1850, 'tame_only_hunters_level_10', 'Only level 10+ hunters can have pets.', 0, 0, '10레벨 이상의 사냥꾼만 소환수를 가질 수 있습니다.', 'Seuls les chasseurs de niveau 10+ peuvent avoir des familiers.', 'Nur Jäger ab Stufe 10 können Begleiter haben.', '只有 10 级以上的猎人才能拥有宠物。', '只有 10 級以上的獵人才能擁有寵物。', 'Solo los cazadores de nivel 10 o superior pueden tener mascotas.', 'Solo los cazadores de nivel 10 o superior pueden tener mascotas.', 'Только охотники 10 уровня и выше могут иметь питомцев.'),
|
||||||
|
(1851, 'tame_creature_template_not_found', 'Creature template not found.', 0, 0, '생물 템플릿을 찾을 수 없습니다.', 'Modèle de créature introuvable.', 'Kreaturvorlage nicht gefunden.', '未找到生物模板。', '未找到生物範本。', 'No se encontró la plantilla de criatura.', 'No se encontró la plantilla de criatura.', 'Шаблон существа не найден.'),
|
||||||
|
(1852, 'tame_create_pet_failed', 'Failed to create pet.', 0, 0, '소환수 생성에 실패했습니다.', 'Échec de la création du familier.', 'Erstellen des Begleiters fehlgeschlagen.', '创建宠物失败。', '建立寵物失敗。', 'No se pudo crear la mascota.', 'No se pudo crear la mascota.', 'Не удалось создать питомца.'),
|
||||||
|
(1853, 'tame_pet_abandoned', 'Your pet has been abandoned.', 0, 0, '당신의 소환수를 버렸습니다.', 'Votre familier a été abandonné.', 'Dein Begleiter wurde freigelassen.', '你的宠物已被放弃。', '你的寵物已被放棄。', 'Tu mascota ha sido abandonada.', 'Tu mascota ha sido abandonada.', 'Ваш питомец был брошен.'),
|
||||||
|
(1854, 'tame_no_hunter_pet_to_abandon', 'You have no hunter pet to abandon.', 0, 0, '버릴 사냥꾼 소환수가 없습니다.', 'Vous n''avez pas de familier de chasseur à abandonner.', 'Du hast kein Jägertier zum Freilassen.', '你没有可放弃的猎人宠物。', '你沒有可放棄的獵人寵物。', 'No tienes ninguna mascota de cazador que abandonar.', 'No tienes ninguna mascota de cazador que abandonar.', 'У вас нет питомца охотника, которого можно бросить.'),
|
||||||
|
(1855, 'taxi_ready_next_flight', 'I am ready for the next flight', 0, 0, '다음 비행을 탈 준비가 되었습니다', 'Je suis prêt pour le prochain vol', 'Ich bin bereit für den nächsten Flug', '我已准备好进行下一次飞行', '我已準備好進行下一次飛行', 'Estoy listo para el siguiente vuelo', 'Estoy listo para el siguiente vuelo', 'Я готов к следующему перелёту'),
|
||||||
|
(1856, 'taxi_cant_fly_with_you', 'I can''t fly with you', 0, 0, '당신과 함께 날 수 없습니다', 'Je ne peux pas voler avec vous', 'Ich kann nicht mit dir fliegen', '我不能和你一起飞', '我不能和你一起飛', 'No puedo volar contigo', 'No puedo volar contigo', 'Я не могу лететь с вами'),
|
||||||
|
(1857, 'taxi_no_flightmaster_nearby', 'Cannot find any flightmaster to talk', 0, 0, '대화할 비행 조련사를 찾을 수 없습니다', 'Impossible de trouver un maître de vol à qui parler', 'Keinen Flugmeister zum Ansprechen gefunden', '找不到可以交谈的飞行管理员', '找不到可以交談的飛行管理員', 'No encuentro ningún maestro de vuelo con quien hablar', 'No encuentro ningún maestro de vuelo con quien hablar', 'Не могу найти распорядителя полётов, с которым можно поговорить'),
|
||||||
|
(1858, 'trade_busy_now', 'I''m kind of busy now', 0, 0, '지금은 좀 바쁩니다', 'Je suis un peu occupé en ce moment', 'Ich bin gerade etwas beschäftigt', '我现在有点忙', '我現在有點忙', 'Ahora estoy un poco ocupado', 'Ahora estoy un poco ocupado', 'Сейчас я немного занят'),
|
||||||
|
(1859, 'trade_disabled', 'Trading is disabled', 0, 0, '거래가 비활성화되어 있습니다', 'L''échange est désactivé', 'Handel ist deaktiviert', '交易已禁用', '交易已停用', 'El comercio está deshabilitado', 'El comercio está deshabilitado', 'Торговля отключена'),
|
||||||
|
(1860, 'trade_thank_you_player', 'Thank you %player', 0, 0, '고마워요 %player', 'Merci %player', 'Danke %player', '谢谢你 %player', '謝謝你 %player', 'Gracias %player', 'Gracias %player', 'Спасибо, %player'),
|
||||||
|
(1861, 'trade_selling_disabled', 'Selling is disabled.', 0, 0, '판매가 비활성화되어 있습니다.', 'La vente est désactivée.', 'Verkaufen ist deaktiviert.', '出售已禁用。', '出售已停用。', 'La venta está deshabilitada.', 'La venta está deshabilitada.', 'Продажа отключена.'),
|
||||||
|
(1862, 'trade_buying_disabled', 'Buying is disabled.', 0, 0, '구매가 비활성화되어 있습니다.', 'L''achat est désactivé.', 'Kaufen ist deaktiviert.', '购买已禁用。', '購買已停用。', 'La compra está deshabilitada.', 'La compra está deshabilitada.', 'Покупка отключена.'),
|
||||||
|
(1863, 'trade_item_not_for_sale', '%item - This is not for sale', 0, 0, '%item - 이것은 판매용이 아닙니다', '%item - Ceci n''est pas à vendre', '%item - Das steht nicht zum Verkauf', '%item - 这不是出售物品', '%item - 這不是出售物品', '%item - Esto no está en venta', '%item - Esto no está en venta', '%item - это не продаётся'),
|
||||||
|
(1864, 'trade_item_not_needed', '%item - I don''t need this', 0, 0, '%item - 저는 이것이 필요 없습니다', '%item - Je n''en ai pas besoin', '%item - Das brauche ich nicht', '%item - 我不需要这个', '%item - 我不需要這個', '%item - No necesito esto', '%item - No necesito esto', '%item - мне это не нужно'),
|
||||||
|
(1865, 'trade_no_items_error', 'There are no items to trade', 0, 0, '거래할 아이템이 없습니다', 'Il n''y a aucun objet à échanger', 'Es gibt keine Gegenstände zum Handeln', '没有可交易的物品', '沒有可交易的物品', 'No hay objetos para comerciar', 'No hay objetos para comerciar', 'Нет предметов для обмена'),
|
||||||
|
(1866, 'trade_discount_buy_only', 'You can use discount to buy items only', 0, 0, '할인은 아이템 구매에만 사용할 수 있습니다', 'Vous ne pouvez utiliser la réduction que pour acheter des objets', 'Rabatte können nur zum Kauf von Gegenständen verwendet werden', '折扣只能用于购买物品', '折扣只能用於購買物品', 'Solo puedes usar el descuento para comprar objetos', 'Solo puedes usar el descuento para comprar objetos', 'Скидку можно использовать только для покупки предметов'),
|
||||||
|
(1867, 'trade_success_pleasure', 'A pleasure doing business with you', 0, 0, '당신과 거래해서 즐거웠습니다', 'Un plaisir de faire affaire avec vous', 'Es war mir ein Vergnügen, mit dir Geschäfte zu machen', '很高兴和你做生意', '很高興和你做生意', 'Un placer hacer negocios contigo', 'Un placer hacer negocios contigo', 'Приятно было иметь с вами дело'),
|
||||||
|
(1868, 'trade_success_fair_trade', 'Fair trade', 0, 0, '공정한 거래입니다', 'Échange équitable', 'Fairer Handel', '公平交易', '公平交易', 'Intercambio justo', 'Intercambio justo', 'Честная сделка'),
|
||||||
|
(1869, 'trade_success_thanks', 'Thanks', 0, 0, '감사합니다', 'Merci', 'Danke', '谢谢', '謝謝', 'Gracias', 'Gracias', 'Спасибо'),
|
||||||
|
(1870, 'trade_success_off_with_you', 'Off with you', 0, 0, '이제 가보세요', 'Allez-vous-en maintenant', 'Nun geh deiner Wege', '走吧你', '走吧你', 'Ahora vete', 'Ahora vete', 'Иди уже'),
|
||||||
|
(1871, 'trade_want_money_for_this', 'I want %money for this', 0, 0, '이것에 대해 %money을(를) 원합니다', 'Je veux %money pour ceci', 'Ich möchte %money dafür', '这个我想要 %money', '這個我想要 %money', 'Quiero %money por esto', 'Quiero %money por esto', 'Я хочу %money за это'),
|
||||||
|
(1872, 'use_item_none_available', 'No items (or game objects) available', 0, 0, '사용할 수 있는 아이템(또는 게임 오브젝트)이 없습니다', 'Aucun objet (ou objet interactif) disponible', 'Keine Gegenstände (oder Spielobjekte) verfügbar', '没有可用的物品(或游戏物体)', '沒有可用的物品(或遊戲物件)', 'No hay objetos (ni objetos del juego) disponibles', 'No hay objetos (ni objetos del juego) disponibles', 'Нет доступных предметов (или игровых объектов)'),
|
||||||
|
(1873, 'use_gameobject', 'Using %gameobject', 0, 0, '%gameobject 사용 중', 'Utilisation de %gameobject', 'Benutze %gameobject', '正在使用 %gameobject', '正在使用 %gameobject', 'Usando %gameobject', 'Usando %gameobject', 'Использую %gameobject'),
|
||||||
|
(1874, 'socket_does_not_fit', 'Socket does not fit', 0, 0, '소켓이 맞지 않습니다', 'La châsse ne correspond pas', 'Sockel passt nicht', '插槽不匹配', '插槽不匹配', 'La ranura no encaja', 'La ranura no encaja', 'Гнездо не подходит'),
|
||||||
|
(1875, 'use_item_on_target', 'Using %item on %target', 0, 0, '%target에게 %item을(를) 사용하는 중', 'Utilisation de %item sur %target', 'Benutze %item auf %target', '正在对 %target 使用 %item', '正在對 %target 使用 %item', 'Usando %item en %target', 'Usando %item en %target', 'Использую %item на %target'),
|
||||||
|
(1876, 'use_item', 'Using %item', 0, 0, '%item 사용 중', 'Utilisation de %item', 'Benutze %item', '正在使用 %item', '正在使用 %item', 'Usando %item', 'Usando %item', 'Использую %item'),
|
||||||
|
(1877, 'socketing_item_with_gem', 'Socketing %item with %gem', 0, 0, '%item에 %gem 보석을 장착하는 중', 'Sertissage de %item avec %gem', 'Sockele %item mit %gem', '正在将 %gem 镶嵌到 %item 上', '正在將 %gem 鑲嵌到 %item 上', 'Engarzando %item con %gem', 'Engarzando %item con %gem', 'Вставляю %gem в %item'),
|
||||||
|
(1878, 'meeting_stone_in_combat', 'I am in combat', 0, 0, '전투 중입니다', 'Je suis en combat', 'Ich bin im Kampf', '我在战斗中', '我在戰鬥中', 'Estoy en combate', 'Estoy en combate', 'Я в бою'),
|
||||||
|
(1879, 'meeting_stone_welcome', 'Welcome!', 0, 0, '환영합니다!', 'Bienvenue !', 'Willkommen!', '欢迎!', '歡迎!', 'Bienvenido!', 'Bienvenido!', 'Добро пожаловать!'),
|
||||||
|
(1880, 'meeting_stone_none_nearby', 'There is no meeting stone nearby', 0, 0, '근처에 집결의 돌이 없습니다', 'Il n''y a pas de pierre de rencontre à proximité', 'Es gibt keinen Beschwörungsstein in der Nähe', '附近没有集合石', '附近沒有集合石', 'No hay ninguna piedra de encuentro cerca', 'No hay ninguna piedra de encuentro cerca', 'Поблизости нет камня встреч'),
|
||||||
|
(1881, 'meeting_stone_none_near_you', 'There is no meeting stone near you', 0, 0, '당신 근처에 집결의 돌이 없습니다', 'Il n''y a pas de pierre de rencontre près de vous', 'Es gibt keinen Beschwörungsstein in deiner Nähe', '你附近没有集合石', '你附近沒有集合石', 'No hay ninguna piedra de encuentro cerca de ti', 'No hay ninguna piedra de encuentro cerca de ti', 'Рядом с вами нет камня встреч'),
|
||||||
|
(1882, 'meeting_stone_no_hearthstone_self', 'I have no hearthstone', 0, 0, '귀환석이 없습니다', 'Je n''ai pas de pierre de foyer', 'Ich habe keinen Ruhestein', '我没有炉石', '我沒有爐石', 'No tengo piedra de hogar', 'No tengo piedra de hogar', 'У меня нет камня возвращения'),
|
||||||
|
(1883, 'meeting_stone_no_hearthstone_you', 'You have no hearthstone', 0, 0, '당신에게 귀환석이 없습니다', 'Vous n''avez pas de pierre de foyer', 'Du hast keinen Ruhestein', '你没有炉石', '你沒有爐石', 'No tienes piedra de hogar', 'No tienes piedra de hogar', 'У вас нет камня возвращения'),
|
||||||
|
(1884, 'meeting_stone_hearthstone_not_ready_self', 'My hearthstone is not ready', 0, 0, '제 귀환석은 아직 준비되지 않았습니다', 'Ma pierre de foyer n''est pas prête', 'Mein Ruhestein ist nicht bereit', '我的炉石还没准备好', '我的爐石還沒準備好', 'Mi piedra de hogar no está lista', 'Mi piedra de hogar no está lista', 'Мой камень возвращения ещё не готов'),
|
||||||
|
(1885, 'meeting_stone_hearthstone_not_ready_you', 'Your hearthstone is not ready', 0, 0, '당신의 귀환석은 아직 준비되지 않았습니다', 'Votre pierre de foyer n''est pas prête', 'Dein Ruhestein ist nicht bereit', '你的炉石还没准备好', '你的爐石還沒準備好', 'Tu piedra de hogar no está lista', 'Tu piedra de hogar no está lista', 'Ваш камень возвращения ещё не готов'),
|
||||||
|
(1886, 'meeting_stone_no_innkeepers_nearby', 'There are no innkeepers nearby', 0, 0, '근처에 여관주인이 없습니다', 'Il n''y a pas d''aubergistes à proximité', 'Es gibt keine Gastwirte in der Nähe', '附近没有旅店老板', '附近沒有旅店老闆', 'No hay posaderos cerca', 'No hay posaderos cerca', 'Поблизости нет трактирщиков'),
|
||||||
|
(1887, 'meeting_stone_no_innkeepers_near_you', 'There are no innkeepers near you', 0, 0, '당신 근처에 여관주인이 없습니다', 'Il n''y a pas d''aubergistes près de vous', 'Es gibt keine Gastwirte in deiner Nähe', '你附近没有旅店老板', '你附近沒有旅店老闆', 'No hay posaderos cerca de ti', 'No hay posaderos cerca de ti', 'Рядом с вами нет трактирщиков'),
|
||||||
|
(1888, 'meeting_stone_cannot_summon_vehicle', 'You cannot summon me while I''m on a vehicle', 0, 0, '제가 탈것/차량에 타고 있는 동안에는 소환할 수 없습니다', 'Vous ne pouvez pas m''invoquer tant que je suis sur un véhicule', 'Du kannst mich nicht beschwören, solange ich auf einem Fahrzeug bin', '当我在载具上时,你不能召唤我', '當我在載具上時,你不能召喚我', 'No puedes invocarme mientras esté en un vehículo', 'No puedes invocarme mientras esté en un vehículo', 'Вы не можете призвать меня, пока я на транспорте'),
|
||||||
|
(1889, 'meeting_stone_cannot_summon_master_in_combat', 'You cannot summon me while you''re in combat', 0, 0, '당신이 전투 중일 때는 저를 소환할 수 없습니다', 'Vous ne pouvez pas m''invoquer pendant que vous êtes en combat', 'Du kannst mich nicht beschwören, solange du im Kampf bist', '当你在战斗中时,不能召唤我', '當你在戰鬥中時,不能召喚我', 'No puedes invocarme mientras estés en combate', 'No puedes invocarme mientras estés en combate', 'Вы не можете призвать меня, пока вы в бою'),
|
||||||
|
(1890, 'meeting_stone_cannot_summon_master_dead', 'You cannot summon me while you''re dead', 0, 0, '당신이 죽어 있는 동안에는 저를 소환할 수 없습니다', 'Vous ne pouvez pas m''invoquer pendant que vous êtes mort', 'Du kannst mich nicht beschwören, solange du tot bist', '当你死亡时,不能召唤我', '當你死亡時,不能召喚我', 'No puedes invocarme mientras estés muerto', 'No puedes invocarme mientras estés muerto', 'Вы не можете призвать меня, пока вы мертвы'),
|
||||||
|
(1891, 'meeting_stone_cannot_summon_bot_dead', 'You cannot summon me while I''m dead, you need to release my spirit first', 0, 0, '제가 죽어 있는 동안에는 저를 소환할 수 없습니다. 먼저 제 영혼을 해방해야 합니다', 'Vous ne pouvez pas m''invoquer tant que je suis mort, vous devez d''abord libérer mon esprit', 'Du kannst mich nicht beschwören, solange ich tot bin; du musst zuerst meinen Geist freisetzen', '当我死亡时,你不能召唤我,你需要先释放我的灵魂', '當我死亡時,你不能召喚我,你需要先釋放我的靈魂', 'No puedes invocarme mientras esté muerto, primero debes liberar mi espíritu', 'No puedes invocarme mientras esté muerto, primero debes liberar mi espíritu', 'Вы не можете призвать меня, пока я мёртв, сначала нужно освободить мой дух'),
|
||||||
|
(1892, 'meeting_stone_revived', 'I live, again!', 0, 0, '다시 살아났습니다!', 'Je vis à nouveau !', 'Ich lebe wieder!', '我又活了!', '我又活了!', 'Vivo de nuevo!', 'Vivo de nuevo!', 'Я снова жив!'),
|
||||||
|
(1893, 'meeting_stone_not_enough_space', 'Not enough place to summon', 0, 0, '소환할 공간이 부족합니다', 'Pas assez de place pour invoquer', 'Nicht genug Platz zum Beschwören', '没有足够的空间进行召唤', '沒有足夠的空間進行召喚', 'No hay suficiente espacio para invocar', 'No hay suficiente espacio para invocar', 'Недостаточно места для призыва'),
|
||||||
|
(1894, 'new_rpg_quest_accepted', 'Quest accepted %quest', 0, 0, '퀘스트 수락 %quest', 'Quête acceptée %quest', 'Quest angenommen %quest', '任务已接受 %quest', '任務已接受 %quest', 'Misión aceptada %quest', 'Misión aceptada %quest', 'Задание принято %quest'),
|
||||||
|
(1895, 'new_rpg_quest_rewarded', 'Quest rewarded %quest', 0, 0, '퀘스트 보상 받음 %quest', 'Quête récompensée %quest', 'Quest abgeschlossen %quest', '任务已奖励 %quest', '任務已獎勵 %quest', 'Misión completada %quest', 'Misión completada %quest', 'Награда за задание получена %quest'),
|
||||||
|
(1896, 'new_rpg_quest_dropped', 'Quest dropped %quest', 0, 0, '퀘스트 포기 %quest', 'Quête abandonnée %quest', 'Quest abgebrochen %quest', '任务已放弃 %quest', '任務已放棄 %quest', 'Misión abandonada %quest', 'Misión abandonada %quest', 'Задание отменено %quest'),
|
||||||
|
(1897, 'rpg_item_better_for_player', 'You can use this %item better than me, %player.', 0, 0, '%player님, 이 %item은(는) 저보다 당신에게 더 잘 맞습니다.', 'Vous pouvez mieux utiliser cet objet %item que moi, %player.', 'Du kannst diesen %item besser gebrauchen als ich, %player.', '%player,这个 %item 比我更适合你使用。', '%player,這個 %item 比我更適合你使用。', 'Tú puedes usar este %item mejor que yo, %player.', 'Tú puedes usar este %item mejor que yo, %player.', '%player, ты можешь использовать %item лучше, чем я.'),
|
||||||
|
(1898, 'rpg_start_trade_with_player', 'Start trade with %player', 0, 0, '%player와(과) 거래를 시작합니다', 'Début de l''échange avec %player', 'Beginne Handel mit %player', '开始与 %player 交易', '開始與 %player 交易', 'Iniciando intercambio con %player', 'Iniciando intercambio con %player', 'Начинаю обмен с %player');
|
||||||
|
|
||||||
|
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES
|
||||||
|
('quest_accept_debug', 100),
|
||||||
|
('quest_already_have_error', 100),
|
||||||
|
('quest_cant_take_error', 100),
|
||||||
|
('arena_team_already_in_team', 100),
|
||||||
|
('arena_team_thanks_for_invite', 100),
|
||||||
|
('area_trigger_follow_too_far_error', 100),
|
||||||
|
('area_trigger_wait_for_me', 100),
|
||||||
|
('attack_no_target_error', 100),
|
||||||
|
('attack_target_not_in_world_error', 100),
|
||||||
|
('attack_in_flight_error', 100),
|
||||||
|
('attack_pvp_prohibited_error', 100),
|
||||||
|
('attack_target_friendly_error', 100),
|
||||||
|
('attack_target_dead_error', 100),
|
||||||
|
('attack_target_not_in_sight_error', 100),
|
||||||
|
('attack_already_attacking_error', 100),
|
||||||
|
('attack_invalid_target_error', 100),
|
||||||
|
('bank_no_banker_nearby_error', 100),
|
||||||
|
('move_from_group', 100),
|
||||||
|
('running_away', 100),
|
||||||
|
('clean_quest_log_started', 100),
|
||||||
|
('quest_trivial_will_remove', 100),
|
||||||
|
('quest_has_been_removed', 100),
|
||||||
|
('quest_not_trivial_kept', 100),
|
||||||
|
('quest_removed_debug', 100),
|
||||||
|
('quest_removed_with_name', 100),
|
||||||
|
('guild_accept_inviter_not_in_guild', 100),
|
||||||
|
('guild_accept_already_in_guild', 100),
|
||||||
|
('guild_accept_declined', 100),
|
||||||
|
('outfit_usage_add', 100),
|
||||||
|
('outfit_usage_remove', 100),
|
||||||
|
('outfit_usage_equip', 100),
|
||||||
|
('outfit_set_as', 100),
|
||||||
|
('outfit_equipping', 100),
|
||||||
|
('outfit_replace_current', 100),
|
||||||
|
('outfit_resetting', 100),
|
||||||
|
('outfit_updating_current', 100),
|
||||||
|
('outfit_item_removed_from', 100),
|
||||||
|
('outfit_item_added_to', 100),
|
||||||
|
('release_spirit_not_dead_wait', 100),
|
||||||
|
('release_spirit_already_spirit', 100),
|
||||||
|
('release_spirit_releasing', 100),
|
||||||
|
('release_spirit_meet_graveyard', 100),
|
||||||
|
('send_mail_no_mailbox_nearby', 100),
|
||||||
|
('send_mail_one_item_only', 100),
|
||||||
|
('send_mail_cannot_send_money', 100),
|
||||||
|
('send_mail_not_enough_money', 100),
|
||||||
|
('send_mail_sending_to', 100),
|
||||||
|
('send_mail_cannot_send_item', 100),
|
||||||
|
('send_mail_item_not_for_sale', 100),
|
||||||
|
('send_mail_sent_to', 100),
|
||||||
|
('craft_reset', 100),
|
||||||
|
('craft_usage', 100),
|
||||||
|
('craft_cannot_craft', 100),
|
||||||
|
('craft_summary', 100),
|
||||||
|
('set_home_success', 100),
|
||||||
|
('set_home_no_innkeeper_error', 100),
|
||||||
|
('quest_shared', 100),
|
||||||
|
('tame_invalid_id_error', 100),
|
||||||
|
('tame_usage_error', 100),
|
||||||
|
('tame_pet_changed', 100),
|
||||||
|
('tame_pet_changed_initialized', 100),
|
||||||
|
('tame_exotic_requires_beast_mastery', 100),
|
||||||
|
('tame_no_pet_by_name', 100),
|
||||||
|
('tame_no_pet_by_id', 100),
|
||||||
|
('tame_no_pet_by_family', 100),
|
||||||
|
('tame_no_pet_to_rename', 100),
|
||||||
|
('tame_pet_name_length_error', 100),
|
||||||
|
('tame_pet_name_alpha_error', 100),
|
||||||
|
('tame_pet_name_forbidden_error', 100),
|
||||||
|
('tame_pet_renamed', 100),
|
||||||
|
('tame_pet_rename_refresh_hint', 100),
|
||||||
|
('tame_only_hunters_level_10', 100),
|
||||||
|
('tame_creature_template_not_found', 100),
|
||||||
|
('tame_create_pet_failed', 100),
|
||||||
|
('tame_pet_abandoned', 100),
|
||||||
|
('tame_no_hunter_pet_to_abandon', 100),
|
||||||
|
('taxi_ready_next_flight', 100),
|
||||||
|
('taxi_cant_fly_with_you', 100),
|
||||||
|
('taxi_no_flightmaster_nearby', 100),
|
||||||
|
('trade_busy_now', 100),
|
||||||
|
('trade_disabled', 100),
|
||||||
|
('trade_thank_you_player', 100),
|
||||||
|
('trade_selling_disabled', 100),
|
||||||
|
('trade_buying_disabled', 100),
|
||||||
|
('trade_item_not_for_sale', 100),
|
||||||
|
('trade_item_not_needed', 100),
|
||||||
|
('trade_no_items_error', 100),
|
||||||
|
('trade_discount_buy_only', 100),
|
||||||
|
('trade_success_pleasure', 100),
|
||||||
|
('trade_success_fair_trade', 100),
|
||||||
|
('trade_success_thanks', 100),
|
||||||
|
('trade_success_off_with_you', 100),
|
||||||
|
('trade_want_money_for_this', 100),
|
||||||
|
('use_item_none_available', 100),
|
||||||
|
('use_gameobject', 100),
|
||||||
|
('socket_does_not_fit', 100),
|
||||||
|
('use_item_on_target', 100),
|
||||||
|
('use_item', 100),
|
||||||
|
('socketing_item_with_gem', 100),
|
||||||
|
('meeting_stone_in_combat', 100),
|
||||||
|
('meeting_stone_welcome', 100),
|
||||||
|
('meeting_stone_none_nearby', 100),
|
||||||
|
('meeting_stone_none_near_you', 100),
|
||||||
|
('meeting_stone_no_hearthstone_self', 100),
|
||||||
|
('meeting_stone_no_hearthstone_you', 100),
|
||||||
|
('meeting_stone_hearthstone_not_ready_self', 100),
|
||||||
|
('meeting_stone_hearthstone_not_ready_you', 100),
|
||||||
|
('meeting_stone_no_innkeepers_nearby', 100),
|
||||||
|
('meeting_stone_no_innkeepers_near_you', 100),
|
||||||
|
('meeting_stone_cannot_summon_vehicle', 100),
|
||||||
|
('meeting_stone_cannot_summon_master_in_combat', 100),
|
||||||
|
('meeting_stone_cannot_summon_master_dead', 100),
|
||||||
|
('meeting_stone_cannot_summon_bot_dead', 100),
|
||||||
|
('meeting_stone_revived', 100),
|
||||||
|
('meeting_stone_not_enough_space', 100),
|
||||||
|
('new_rpg_quest_accepted', 100),
|
||||||
|
('new_rpg_quest_rewarded', 100),
|
||||||
|
('new_rpg_quest_dropped', 100),
|
||||||
|
('rpg_item_better_for_player', 100),
|
||||||
|
('rpg_start_trade_with_player', 100);
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
DELETE FROM ai_playerbot_texts WHERE name IN (
|
||||||
|
'send_mail_disabled'
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM ai_playerbot_texts_chance WHERE name IN (
|
||||||
|
'send_mail_disabled'
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO ai_playerbot_texts (id, name, text, say_type, reply_type, text_loc1, text_loc2, text_loc3, text_loc4, text_loc5, text_loc6, text_loc7, text_loc8) VALUES
|
||||||
|
(1899, 'send_mail_disabled', 'I cannot send mail', 0, 0, '우편을 보낼 수 없습니다', 'Je ne peux pas envoyer de courrier', 'Ich kann keine Post senden', '我不能寄送邮件', '我不能寄送郵件', 'No puedo enviar correo', 'No puedo enviar correo', 'Я не могу отправить почту');
|
||||||
|
|
||||||
|
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES
|
||||||
|
('send_mail_disabled', 100);
|
||||||
351
extract_ac_client_data.sh
Normal file
351
extract_ac_client_data.sh
Normal file
@ -0,0 +1,351 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# =============================================================================
|
||||||
|
# AzerothCore client-data extraction for mod-playerbots.
|
||||||
|
# Run from anywhere — output lands in $SERVER_DATA_DIR.
|
||||||
|
# =============================================================================
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ─── PATHS ──────────────────────────────────────────────────────────────────
|
||||||
|
WOW_CLIENT_DATA="/home/dev/wow_client_data"
|
||||||
|
TOOLS_DIR="/home/dev/azerothcore_installer/_server/azerothcore/env/dist/bin"
|
||||||
|
SERVER_DATA_DIR="$TOOLS_DIR"
|
||||||
|
|
||||||
|
# ─── TOGGLES ────────────────────────────────────────────────────────────────
|
||||||
|
EXTRACT_DBC_AND_MAPS=true
|
||||||
|
EXTRACT_VMAPS=true
|
||||||
|
EXTRACT_MMAPS=true
|
||||||
|
|
||||||
|
MMAP_THREADS=0 # 0 = auto-detect (each thread uses 1-2 GB RAM)
|
||||||
|
MMAP_SINGLE_MAP="" # e.g. "489" for Warsong Gulch only
|
||||||
|
|
||||||
|
# Verbatim copy of azerothcore-wotlk/master:src/tools/mmaps_generator/mmaps-config.yaml
|
||||||
|
# (also the same config the mod-playerbots fork ships).
|
||||||
|
MMAPS_CONFIG_YAML=$(cat <<'YAML_EOF'
|
||||||
|
mmapsConfig:
|
||||||
|
skipLiquid: false
|
||||||
|
skipContinents: false
|
||||||
|
skipJunkMaps: true
|
||||||
|
skipBattlegrounds: false
|
||||||
|
|
||||||
|
# Path to the directory containing navigation data files.
|
||||||
|
# This directory should contain the "maps" and "vmaps" folders,
|
||||||
|
# and is also where the "mmaps" folder will be created or located.
|
||||||
|
dataDir: "./"
|
||||||
|
|
||||||
|
meshSettings:
|
||||||
|
# Here we have global config for recast navigation.
|
||||||
|
# It's possible to override these data on map or tile level (see mapsOverrides).
|
||||||
|
|
||||||
|
# Maximum slope angle (in degrees) NPCs can walk on.
|
||||||
|
# Surfaces steeper than this will be considered unwalkable.
|
||||||
|
walkableSlopeAngle: 50
|
||||||
|
|
||||||
|
# --- Cell Size Calculation ---
|
||||||
|
# Many parameters below are defined in "cell units".
|
||||||
|
# In RecastDemo, you often work with world units instead of cell units.
|
||||||
|
# The actual generator uses (src/tools/mmaps_generator/Config.cpp:28):
|
||||||
|
#
|
||||||
|
# cellSize = MMAP::GRID_SIZE / vertexPerMapEdge
|
||||||
|
#
|
||||||
|
# Where:
|
||||||
|
# MMAP::GRID_SIZE = 533.3333f (the size of one map tile in world units)
|
||||||
|
# vertexPerMapEdge = number of vertices along one edge of the full map grid
|
||||||
|
#
|
||||||
|
# Example (AC stock):
|
||||||
|
# vertexPerMapEdge = 2000 → cellSize ≈ 533.3333 / 2000 ≈ 0.2667 yd
|
||||||
|
#
|
||||||
|
# IMPORTANT: when changing vertexPerMapEdge, the per-cell parameters
|
||||||
|
# below (walkableHeight, walkableClimb, walkableRadius) must be re-scaled
|
||||||
|
# to preserve their world-unit semantics. Doubling vertexPerMapEdge
|
||||||
|
# halves cellSize, so cell counts must double to keep the same yd value.
|
||||||
|
#
|
||||||
|
# To convert a value from cell units to world units (e.g., walkableClimb),
|
||||||
|
# multiply by cellSize. For example, a walkableClimb of 6 at 2000 resolution:
|
||||||
|
# 6 × 0.2667 ≈ 1.60 yd
|
||||||
|
|
||||||
|
# Minimum ceiling height (in cell units) NPCs need to pass under an obstacle.
|
||||||
|
# Controls how much vertical clearance is required.
|
||||||
|
# To convert to world units, multiply by cellSize (see "Cell Size Calculation").
|
||||||
|
# 6 cells × 0.2667 yd ≈ 1.60 yd — matches WoW player capsule height at
|
||||||
|
# 2000 resolution (AC stock). Preserves the 1.60 yd world-unit
|
||||||
|
# ceiling-clearance requirement.
|
||||||
|
walkableHeight: 6
|
||||||
|
|
||||||
|
# Maximum height difference (in cell units) NPCs can step up or down.
|
||||||
|
# Higher values allow walking over fences, ledges, or steps.
|
||||||
|
# To convert to world units, multiply by cellSize (see "Cell Size Calculation").
|
||||||
|
#
|
||||||
|
# Vanilla WotLK uses 6, which allows creatures to "jump" over fences.
|
||||||
|
# Classic WotLK uses 4, which forces creatures to walk around fences.
|
||||||
|
# 6 cells × 0.2667 yd ≈ 1.60 yd — Vanilla-WotLK step semantics at
|
||||||
|
# 2000 resolution. Preserves the 1.60 yd world-unit step. The mmap
|
||||||
|
# is shared with every creature, NPC patrol, escort, and quest mob;
|
||||||
|
# tightening below stock breaks patrols that cross 1.5y ledges.
|
||||||
|
walkableClimb: 4
|
||||||
|
|
||||||
|
# Minimum distance (in cell units) around walkable surfaces.
|
||||||
|
# Helps prevent NPCs from clipping into walls and narrow gaps.
|
||||||
|
# To convert to world units, multiply by cellSize (see "Cell Size Calculation").
|
||||||
|
# 2 cells × 0.2667 yd ≈ 0.53 yd — AC stock world-unit buffer at
|
||||||
|
# 2000 resolution. Tested wider (= 0.71y world units): erodes polys
|
||||||
|
# near mountains/cliffs so pathfinder routes through surviving
|
||||||
|
# (higher/worse) polys → bot climbs mountains.
|
||||||
|
walkableRadius: 2
|
||||||
|
|
||||||
|
# Number of vertices along one edge of the entire map's navmesh grid.
|
||||||
|
# Higher values increase mesh resolution but also CPU/memory usage.
|
||||||
|
# 2000 = AC stock baseline. cellSize ≈ 0.2667 yd.
|
||||||
|
vertexPerMapEdge: 2000
|
||||||
|
|
||||||
|
# Number of vertices along one edge of each tile chunk.
|
||||||
|
# Must divide vertexPerMapEdge evenly — the generator uses integer
|
||||||
|
# division: tilesPerMapEdge = vertexPerMap / vertexPerTile
|
||||||
|
# (src/tools/mmaps_generator/Config.cpp:144).
|
||||||
|
# A higher vertex count per tile means fewer total tiles,
|
||||||
|
# reducing runtime work to load, unload, and manage tiles.
|
||||||
|
# 80 = AC stock baseline. 2000 / 80 = 25 tiles per map edge, 625
|
||||||
|
# tiles per map (~21y per tile). Lots of small tiles, low per-tile
|
||||||
|
# RAM, more seams to stitch across.
|
||||||
|
vertexPerTileEdge: 80
|
||||||
|
|
||||||
|
# Tolerance for how much a polygon can deviate from the original geometry when simplified.
|
||||||
|
# Higher values produce simpler (faster) meshes but can reduce accuracy.
|
||||||
|
# 0.8 (vs the AC stock 1.8 and recast canonical 1.3) keeps polygon
|
||||||
|
# edges close to real terrain. Targets "merged step into ramp"
|
||||||
|
# simplification artifacts that produce corner-cuts and false NOPATH.
|
||||||
|
maxSimplificationError: 0.8
|
||||||
|
|
||||||
|
# You can override any global parameter for a specific map by specifying its map ID.
|
||||||
|
# Inside each map override, you can also override parameters per individual tile,
|
||||||
|
# identified by a string "tileX,tileY" (coordinates).
|
||||||
|
#
|
||||||
|
# Overrides cascade: global settings → map overrides → tile overrides.
|
||||||
|
# For example:
|
||||||
|
#
|
||||||
|
# mapsOverrides:
|
||||||
|
# "0": # Map ID 0 overrides
|
||||||
|
# walkableRadius: 5 # Override global climb height for entire map 0
|
||||||
|
#
|
||||||
|
# tilesOverrides:
|
||||||
|
# "50,70": # Tile at coordinates (50,70) on map 0
|
||||||
|
# walkableSlopeAngle: 70 # Override slope angle locally just here
|
||||||
|
# walkableClimb: 4 # Also override climb height for this tile only
|
||||||
|
#
|
||||||
|
# "51,71":
|
||||||
|
# walkableClimb: 3 # Override climb height for tile (51,71)
|
||||||
|
#
|
||||||
|
# "48,32":
|
||||||
|
# walkableClimb: 1 # Even smaller climb for tile (48,32)
|
||||||
|
#
|
||||||
|
# "1": # Map ID 1 overrides example
|
||||||
|
# walkableHeight: 8 # Increase clearance for whole map 1
|
||||||
|
#
|
||||||
|
# tilesOverrides:
|
||||||
|
# "100,100":
|
||||||
|
# maxSimplificationError: 2.5 # Looser mesh simplification for this tile only
|
||||||
|
#
|
||||||
|
# "101,101":
|
||||||
|
# walkableRadius: 1 # Smaller NPC radius here for tight corridors
|
||||||
|
#
|
||||||
|
# This approach allows very fine-grained control of navigation mesh parameters
|
||||||
|
# on a per-map and per-tile basis, optimizing pathfinding quality and performance.
|
||||||
|
#
|
||||||
|
# All parameters defined globally are eligible for override.
|
||||||
|
# Just specify the parameter name and new value in the override section.
|
||||||
|
mapsOverrides:
|
||||||
|
"562": # Blade's Edge Arena
|
||||||
|
walkableRadius: 0 # This allows walking on the ropes to the pillars
|
||||||
|
|
||||||
|
"48": # Blackfathom Deeps
|
||||||
|
cellSizeVertical: 0.5334 # ch*2 = 0.2667 * 2 ≈ 0.5334. Reduce the chance to have underground levels.
|
||||||
|
|
||||||
|
"529": # Arathi Basin
|
||||||
|
tilesOverrides:
|
||||||
|
"30,29": # Lumber Mill
|
||||||
|
# Make sure that Fear will not drop players rom cliff -
|
||||||
|
# https://github.com/azerothcore/azerothcore-wotlk/pull/22462#issuecomment-3067024680
|
||||||
|
walkableSlopeAngle: 45
|
||||||
|
|
||||||
|
"530": # Outland
|
||||||
|
tilesOverrides:
|
||||||
|
"32,30": # Dark portal
|
||||||
|
walkableSlopeAngle: 45 # https://github.com/chromiecraft/chromiecraft/issues/8404#issuecomment-3476012660
|
||||||
|
|
||||||
|
# debugOutput generates debug files in the `meshes` directory for use with RecastDemo.
|
||||||
|
# This is useful for inspecting and debugging mmap generation visually.
|
||||||
|
#
|
||||||
|
# My workflow:
|
||||||
|
# 1. Install RecastDemo. I'm building it from the source of this fork: https://github.com/jackpoz/recastnavigation
|
||||||
|
# 2. In-game, move your character to the area you want to debug.
|
||||||
|
# 3. Type `.mmap loc` in chat. This will output:
|
||||||
|
# - The current tile file name (e.g., `04832.mmtile`)
|
||||||
|
# - The Recast config values used to generate that tile
|
||||||
|
# 4. Enable `debugOutput` and regenerate mmaps (preferably just the tile from step 3).
|
||||||
|
# - To regenerate only one tile, delete it from the `mmaps` folder.
|
||||||
|
# 5. After generation, you will find debug files in the `meshes` folder, including an OBJ file (e.g., `map0004832.obj`)
|
||||||
|
# 6. Copy these debug files to the `Meshes` folder used by RecastDemo.
|
||||||
|
# - RecastDemo expects this folder to be in the same directory as its executable.
|
||||||
|
# 7. In RecastDemo:
|
||||||
|
# - Click "Input Mesh" and select the `.obj` file
|
||||||
|
# - Choose "Solo Mesh" in the Sample selector
|
||||||
|
# 8. (Optional) Reuse the Recast config values from step 3:
|
||||||
|
# - `cellSizeHorizontal` → "Cell Size"
|
||||||
|
# - `walkableSlopeAngle` → "Max Slope"
|
||||||
|
# - `walkableClimb` → "Max Climb"
|
||||||
|
# - and so on
|
||||||
|
# 9. Scroll to the bottom of RecastDemo UI and press "Build" to generate the navigation mesh
|
||||||
|
debugOutput: false
|
||||||
|
YAML_EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# ─── DO NOT EDIT BELOW ──────────────────────────────────────────────────────
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
[ -n "$SERVER_DATA_DIR" ] || { echo "SERVER_DATA_DIR is not set"; exit 1; }
|
||||||
|
[ -n "$WOW_CLIENT_DATA" ] || { echo "WOW_CLIENT_DATA is not set"; exit 1; }
|
||||||
|
mkdir -p "$SERVER_DATA_DIR"
|
||||||
|
cd "$SERVER_DATA_DIR"
|
||||||
|
|
||||||
|
# ─── SAFETY: source MPQs are READ-ONLY to this script ──────────────────────
|
||||||
|
# Resolve both paths to canonical form and refuse to run if the output dir
|
||||||
|
# is inside the source. Combined with safe_rm() below, this script cannot
|
||||||
|
# touch any file inside WOW_CLIENT_DATA.
|
||||||
|
SERVER_DATA_DIR_REAL="$(cd "$SERVER_DATA_DIR" && pwd -P)"
|
||||||
|
WOW_CLIENT_DATA_REAL="$(cd "$WOW_CLIENT_DATA" && pwd -P 2>/dev/null || echo "$WOW_CLIENT_DATA")"
|
||||||
|
case "$SERVER_DATA_DIR_REAL/" in
|
||||||
|
"$WOW_CLIENT_DATA_REAL"/|"$WOW_CLIENT_DATA_REAL"/*)
|
||||||
|
echo "ERROR: SERVER_DATA_DIR ($SERVER_DATA_DIR_REAL) is inside WOW_CLIENT_DATA — refusing." >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Refuses to remove anything outside SERVER_DATA_DIR. Resolves the parent
|
||||||
|
# to absolute path so a symlink inside cwd can't trick us into traversing
|
||||||
|
# into the source. Use this for every cleanup in this script.
|
||||||
|
safe_rm() {
|
||||||
|
local target="$1"
|
||||||
|
local parent_abs base
|
||||||
|
parent_abs="$(cd "$(dirname -- "$target")" 2>/dev/null && pwd -P)" || return 0
|
||||||
|
base="$(basename -- "$target")"
|
||||||
|
local abs="$parent_abs/$base"
|
||||||
|
case "$abs/" in
|
||||||
|
"$SERVER_DATA_DIR_REAL"/|"$SERVER_DATA_DIR_REAL"/*) ;;
|
||||||
|
*)
|
||||||
|
echo "REFUSING to rm path outside SERVER_DATA_DIR: $target → $abs" >&2
|
||||||
|
exit 1 ;;
|
||||||
|
esac
|
||||||
|
rm -rf -- "$target"
|
||||||
|
}
|
||||||
|
|
||||||
|
[ "$MMAP_THREADS" -eq 0 ] && MMAP_THREADS=$(nproc 2>/dev/null || echo 4)
|
||||||
|
|
||||||
|
echo "Working dir : $(pwd)"
|
||||||
|
echo "Tools dir : $TOOLS_DIR"
|
||||||
|
echo "Threads : $MMAP_THREADS"
|
||||||
|
echo "Steps : maps=$EXTRACT_DBC_AND_MAPS vmaps=$EXTRACT_VMAPS mmaps=$EXTRACT_MMAPS"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# ─── Symlink Data/ → MPQ source (only when extracting from client) ──────────
|
||||||
|
if [ "$EXTRACT_DBC_AND_MAPS" = true ] || [ "$EXTRACT_VMAPS" = true ]; then
|
||||||
|
has_mpqs() { find "$1" -maxdepth 1 -iname "*.mpq" -print -quit 2>/dev/null | grep -q .; }
|
||||||
|
|
||||||
|
if has_mpqs "$WOW_CLIENT_DATA"; then
|
||||||
|
MPQ_DIR="$WOW_CLIENT_DATA"
|
||||||
|
elif has_mpqs "$WOW_CLIENT_DATA/Data"; then
|
||||||
|
MPQ_DIR="$WOW_CLIENT_DATA/Data"
|
||||||
|
else
|
||||||
|
echo "ERROR: no .mpq files in $WOW_CLIENT_DATA" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
MPQ_DIR="$(cd "$MPQ_DIR" && pwd)"
|
||||||
|
|
||||||
|
# Symlink only — refuse to clobber an existing real directory.
|
||||||
|
if [ -e Data ] && [ ! -L Data ]; then
|
||||||
|
echo "ERROR: Data/ exists in $(pwd) but is not a symlink" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
ln -sfn "$MPQ_DIR" Data
|
||||||
|
echo "Data/ → $MPQ_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── STEP 1: DBCs + Maps ────────────────────────────────────────────────────
|
||||||
|
if [ "$EXTRACT_DBC_AND_MAPS" = true ]; then
|
||||||
|
echo
|
||||||
|
echo "[1/3] Extracting DBCs + Maps..."
|
||||||
|
# Clean slate — map_extractor refuses to run if these dirs already exist.
|
||||||
|
safe_rm dbc
|
||||||
|
safe_rm maps
|
||||||
|
safe_rm Cameras
|
||||||
|
# -e 7 = bitfield MAP(1)|DBC(2)|CAMERA(4) — extract everything.
|
||||||
|
# The old "-e 2" was DBC-only and skipped maps + cameras entirely.
|
||||||
|
"$TOOLS_DIR/map_extractor" -e 7 -f 0
|
||||||
|
if [ ! -d maps ] || [ -z "$(ls -A maps 2>/dev/null)" ]; then
|
||||||
|
echo "ERROR: map_extractor finished but maps/ is empty — check its output above" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── STEP 2: VMaps ──────────────────────────────────────────────────────────
|
||||||
|
if [ "$EXTRACT_VMAPS" = true ]; then
|
||||||
|
echo
|
||||||
|
echo "[2/3] Extracting VMaps..."
|
||||||
|
# Clean slate — vmap4_extractor refuses to run if these dirs already exist.
|
||||||
|
safe_rm Buildings
|
||||||
|
safe_rm vmaps
|
||||||
|
"$TOOLS_DIR/vmap4_extractor" -l -d ./Data
|
||||||
|
mkdir -p vmaps
|
||||||
|
"$TOOLS_DIR/vmap4_assembler" Buildings vmaps
|
||||||
|
safe_rm Buildings
|
||||||
|
if [ ! -d vmaps ] || [ -z "$(ls -A vmaps 2>/dev/null)" ]; then
|
||||||
|
echo "ERROR: vmap4_assembler finished but vmaps/ is empty — check output above" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── STEP 3: MMaps ──────────────────────────────────────────────────────────
|
||||||
|
if [ "$EXTRACT_MMAPS" = true ]; then
|
||||||
|
if [ ! -d maps ]; then
|
||||||
|
echo "ERROR: maps/ missing in $(pwd) — run with EXTRACT_DBC_AND_MAPS=true once" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ ! -d vmaps ]; then
|
||||||
|
echo "ERROR: vmaps/ missing in $(pwd) — run with EXTRACT_VMAPS=true once" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "[3/3] Generating MMaps... (do not interrupt)"
|
||||||
|
printf '%s\n' "$MMAPS_CONFIG_YAML" > mmaps-config.yaml
|
||||||
|
|
||||||
|
# Wipe any existing tiles before regenerating. Mixed tiles from
|
||||||
|
# previous runs (different cellSize / verticesPerTileEdge / etc.)
|
||||||
|
# would otherwise be silently kept and mixed with new ones,
|
||||||
|
# producing a corrupt navmesh. Clean slate every mmap run.
|
||||||
|
safe_rm mmaps
|
||||||
|
mkdir -p mmaps
|
||||||
|
|
||||||
|
# Workaround: some mmaps_generator builds write a few tiles to /mmaps
|
||||||
|
# via an absolute path. Pre-create it so the writes don't fail, then
|
||||||
|
# fold the strays into our local mmaps/ at the end.
|
||||||
|
sudo rm -rf /mmaps
|
||||||
|
sudo mkdir -p /mmaps && sudo chmod 777 /mmaps
|
||||||
|
|
||||||
|
CMD=("$TOOLS_DIR/mmaps_generator" --config mmaps-config.yaml --threads "$MMAP_THREADS")
|
||||||
|
[ -n "$MMAP_SINGLE_MAP" ] && CMD+=("$MMAP_SINGLE_MAP")
|
||||||
|
|
||||||
|
START=$(date +%s)
|
||||||
|
"${CMD[@]}"
|
||||||
|
ELAPSED=$(( $(date +%s) - START ))
|
||||||
|
|
||||||
|
if compgen -G "/mmaps/*.mmtile" >/dev/null; then
|
||||||
|
cp /mmaps/*.mmtile mmaps/ && rm -f /mmaps/*.mmtile
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "MMap done in $((ELAPSED / 60))m $((ELAPSED % 60))s"
|
||||||
|
echo "Tiles: $(ls mmaps/*.mmtile 2>/dev/null | wc -l)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Done. Restart worldserver to pick up changes."
|
||||||
@ -9,6 +9,7 @@
|
|||||||
#include "ObjectAccessor.h"
|
#include "ObjectAccessor.h"
|
||||||
#include "PlayerbotAIConfig.h"
|
#include "PlayerbotAIConfig.h"
|
||||||
#include "PlayerbotSecurity.h"
|
#include "PlayerbotSecurity.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "WorldPacket.h"
|
#include "WorldPacket.h"
|
||||||
|
|
||||||
@ -55,7 +56,7 @@ bool AcceptInvitationAction::Execute(Event event)
|
|||||||
botAI->ChangeStrategy("+follow,-lfg,-bg", BOT_STATE_NON_COMBAT);
|
botAI->ChangeStrategy("+follow,-lfg,-bg", BOT_STATE_NON_COMBAT);
|
||||||
botAI->Reset();
|
botAI->Reset();
|
||||||
|
|
||||||
botAI->TellMaster("Hello");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault("hello", "Hello", {}));
|
||||||
|
|
||||||
if (sPlayerbotAIConfig.summonWhenGroup && bot->GetDistance(inviter) > sPlayerbotAIConfig.sightDistance)
|
if (sPlayerbotAIConfig.summonWhenGroup && bot->GetDistance(inviter) > sPlayerbotAIConfig.sightDistance)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
#include "AcceptQuestAction.h"
|
#include "AcceptQuestAction.h"
|
||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
bool AcceptAllQuestsAction::ProcessQuest(Quest const* quest, Object* questGiver)
|
bool AcceptAllQuestsAction::ProcessQuest(Quest const* quest, Object* questGiver)
|
||||||
@ -18,7 +19,11 @@ bool AcceptAllQuestsAction::ProcessQuest(Quest const* quest, Object* questGiver)
|
|||||||
if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT) || botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT) || botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
||||||
{
|
{
|
||||||
LOG_INFO("playerbots", "{} => Quest [{}] accepted", bot->GetName(), quest->GetTitle());
|
LOG_INFO("playerbots", "{} => Quest [{}] accepted", bot->GetName(), quest->GetTitle());
|
||||||
bot->Say("Quest [" + text_quest + "] accepted", LANG_UNIVERSAL);
|
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_accept_debug",
|
||||||
|
"Quest [%quest] accepted",
|
||||||
|
{{"%quest", text_quest}});
|
||||||
|
bot->Say(text, LANG_UNIVERSAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -113,7 +118,8 @@ bool AcceptQuestShareAction::Execute(Event event)
|
|||||||
if (bot->HasQuest(quest))
|
if (bot->HasQuest(quest))
|
||||||
{
|
{
|
||||||
bot->SetDivider(ObjectGuid::Empty);
|
bot->SetDivider(ObjectGuid::Empty);
|
||||||
botAI->TellError("I have this quest");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_already_have_error", "I have this quest", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +127,8 @@ bool AcceptQuestShareAction::Execute(Event event)
|
|||||||
{
|
{
|
||||||
// can't take quest
|
// can't take quest
|
||||||
bot->SetDivider(ObjectGuid::Empty);
|
bot->SetDivider(ObjectGuid::Empty);
|
||||||
botAI->TellError("I can't take this quest");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_cant_take_error", "I can't take this quest", {}));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -149,7 +156,8 @@ bool AcceptQuestShareAction::Execute(Event event)
|
|||||||
bot->CastSpell(bot, qInfo->GetSrcSpell(), true);
|
bot->CastSpell(bot, qInfo->GetSrcSpell(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
botAI->TellMaster("Quest accepted");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_accept", "Quest accepted", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "LastMovementValue.h"
|
#include "LastMovementValue.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "Transport.h"
|
#include "Transport.h"
|
||||||
|
|
||||||
@ -36,7 +37,8 @@ bool ReachAreaTriggerAction::Execute(Event event)
|
|||||||
|
|
||||||
if (bot->GetMapId() != at->map)
|
if (bot->GetMapId() != at->map)
|
||||||
{
|
{
|
||||||
botAI->TellError("I won't follow: too far away");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"area_trigger_follow_too_far_error", "I won't follow: too far away", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +53,8 @@ bool ReachAreaTriggerAction::Execute(Event event)
|
|||||||
|
|
||||||
float distance = bot->GetDistance(at->x, at->y, at->z);
|
float distance = bot->GetDistance(at->x, at->y, at->z);
|
||||||
float delay = 1000.0f * distance / bot->GetSpeed(MOVE_RUN) + sPlayerbotAIConfig.reactDelay;
|
float delay = 1000.0f * distance / bot->GetSpeed(MOVE_RUN) + sPlayerbotAIConfig.reactDelay;
|
||||||
botAI->TellError("Wait for me");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"area_trigger_wait_for_me", "Wait for me", {}));
|
||||||
botAI->SetNextCheckDelay(delay);
|
botAI->SetNextCheckDelay(delay);
|
||||||
context->GetValue<LastMovement&>("last area trigger")->Get().lastAreaTrigger = triggerId;
|
context->GetValue<LastMovement&>("last area trigger")->Get().lastAreaTrigger = triggerId;
|
||||||
|
|
||||||
@ -76,6 +79,6 @@ bool AreaTriggerAction::Execute(Event /*event*/)
|
|||||||
p.rpos(0);
|
p.rpos(0);
|
||||||
bot->GetSession()->HandleAreaTriggerOpcode(p);
|
bot->GetSession()->HandleAreaTriggerOpcode(p);
|
||||||
|
|
||||||
botAI->TellMaster("Hello");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault("hello", "Hello", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
#include "ArenaTeamActions.h"
|
#include "ArenaTeamActions.h"
|
||||||
|
|
||||||
#include "ArenaTeamMgr.h"
|
#include "ArenaTeamMgr.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
bool ArenaTeamAcceptAction::Execute(Event event)
|
bool ArenaTeamAcceptAction::Execute(Event event)
|
||||||
@ -31,7 +32,9 @@ bool ArenaTeamAcceptAction::Execute(Event event)
|
|||||||
if (bot->GetArenaTeamId(at->GetSlot()))
|
if (bot->GetArenaTeamId(at->GetSlot()))
|
||||||
{
|
{
|
||||||
// bot is already in an arena team
|
// bot is already in an arena team
|
||||||
bot->Say("Sorry, I am already in such team", LANG_UNIVERSAL);
|
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"arena_team_already_in_team", "Sorry, I am already in such team", {});
|
||||||
|
bot->Say(text, LANG_UNIVERSAL);
|
||||||
accept = false;
|
accept = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +42,9 @@ bool ArenaTeamAcceptAction::Execute(Event event)
|
|||||||
{
|
{
|
||||||
WorldPacket data(CMSG_ARENA_TEAM_ACCEPT);
|
WorldPacket data(CMSG_ARENA_TEAM_ACCEPT);
|
||||||
bot->GetSession()->HandleArenaTeamAcceptOpcode(data);
|
bot->GetSession()->HandleArenaTeamAcceptOpcode(data);
|
||||||
bot->Say("Thanks for the invite!", LANG_UNIVERSAL);
|
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"arena_team_thanks_for_invite", "Thanks for the invite!", {});
|
||||||
|
bot->Say(text, LANG_UNIVERSAL);
|
||||||
LOG_INFO("playerbots", "Bot {} <{}> accepts Arena Team invite", bot->GetGUID().ToString().c_str(),
|
LOG_INFO("playerbots", "Bot {} <{}> accepts Arena Team invite", bot->GetGUID().ToString().c_str(),
|
||||||
bot->GetName().c_str());
|
bot->GetName().c_str());
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
#include "LastMovementValue.h"
|
#include "LastMovementValue.h"
|
||||||
#include "LootObjectStack.h"
|
#include "LootObjectStack.h"
|
||||||
#include "PlayerbotAI.h"
|
#include "PlayerbotAI.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "ServerFacade.h"
|
#include "ServerFacade.h"
|
||||||
#include "SharedDefines.h"
|
#include "SharedDefines.h"
|
||||||
@ -38,7 +39,8 @@ bool AttackMyTargetAction::Execute(Event /*event*/)
|
|||||||
if (!guid)
|
if (!guid)
|
||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError("You have no target");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"pull_no_target_error", "You have no target", {}));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -56,7 +58,8 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
|||||||
if (!target)
|
if (!target)
|
||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError("I have no target");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"attack_no_target_error", "I have no target", {}));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -64,7 +67,10 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
|||||||
if (!target->IsInWorld())
|
if (!target->IsInWorld())
|
||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError(std::string(target->GetName()) + " is no longer in the world.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"attack_target_not_in_world_error",
|
||||||
|
"%target is no longer in the world.",
|
||||||
|
{{"%target", target->GetName()}}));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -73,7 +79,8 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
|||||||
bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
|
bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
|
||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError("I cannot attack in flight");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"attack_in_flight_error", "I cannot attack in flight", {}));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -85,7 +92,10 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
|||||||
sPlayerbotAIConfig.IsPvpProhibited(target->GetZoneId(), target->GetAreaId())))
|
sPlayerbotAIConfig.IsPvpProhibited(target->GetZoneId(), target->GetAreaId())))
|
||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError("I cannot attack other players in PvP prohibited areas.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"attack_pvp_prohibited_error",
|
||||||
|
"I cannot attack other players in PvP prohibited areas.",
|
||||||
|
{}));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -93,7 +103,10 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
|||||||
if (bot->IsFriendlyTo(target))
|
if (bot->IsFriendlyTo(target))
|
||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError(std::string(target->GetName()) + " is friendly to me.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"attack_target_friendly_error",
|
||||||
|
"%target is friendly to me.",
|
||||||
|
{{"%target", target->GetName()}}));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -101,7 +114,10 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
|||||||
if (target->isDead())
|
if (target->isDead())
|
||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError(std::string(target->GetName()) + " is dead.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"attack_target_dead_error",
|
||||||
|
"%target is dead.",
|
||||||
|
{{"%target", target->GetName()}}));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -109,7 +125,10 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
|||||||
if (!bot->IsWithinLOSInMap(target))
|
if (!bot->IsWithinLOSInMap(target))
|
||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError(std::string(target->GetName()) + " is not in my sight.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"attack_target_not_in_sight_error",
|
||||||
|
"%target is not in my sight.",
|
||||||
|
{{"%target", target->GetName()}}));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -129,7 +148,10 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
|||||||
if (sameTarget && inCombat && sameAttackMode)
|
if (sameTarget && inCombat && sameAttackMode)
|
||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError("I am already attacking " + std::string(target->GetName()) + ".");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"attack_already_attacking_error",
|
||||||
|
"I am already attacking %target.",
|
||||||
|
{{"%target", target->GetName()}}));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -137,7 +159,8 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
|||||||
if (!bot->IsValidAttackTarget(target))
|
if (!bot->IsValidAttackTarget(target))
|
||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError("I cannot attack an invalid target.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"attack_invalid_target_error", "I cannot attack an invalid target.", {}));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,8 +12,8 @@ bool AutoMaintenanceOnLevelupAction::Execute(Event /*event*/)
|
|||||||
{
|
{
|
||||||
AutoPickTalents();
|
AutoPickTalents();
|
||||||
AutoLearnSpell();
|
AutoLearnSpell();
|
||||||
AutoUpgradeEquip();
|
|
||||||
AutoTeleportForLevel();
|
AutoTeleportForLevel();
|
||||||
|
AutoUpgradeEquip();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -21,13 +21,11 @@ bool AutoMaintenanceOnLevelupAction::Execute(Event /*event*/)
|
|||||||
void AutoMaintenanceOnLevelupAction::AutoTeleportForLevel()
|
void AutoMaintenanceOnLevelupAction::AutoTeleportForLevel()
|
||||||
{
|
{
|
||||||
if (!sPlayerbotAIConfig.autoTeleportForLevel || !sRandomPlayerbotMgr.IsRandomBot(bot))
|
if (!sPlayerbotAIConfig.autoTeleportForLevel || !sRandomPlayerbotMgr.IsRandomBot(bot))
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
if (botAI->HasRealPlayerMaster())
|
if (botAI->HasRealPlayerMaster())
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
sRandomPlayerbotMgr.RandomTeleportForLevel(bot);
|
sRandomPlayerbotMgr.RandomTeleportForLevel(bot);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -89,21 +87,17 @@ void AutoMaintenanceOnLevelupAction::LearnQuestSpells(std::ostringstream* out)
|
|||||||
{
|
{
|
||||||
Quest const* quest = i->second;
|
Quest const* quest = i->second;
|
||||||
|
|
||||||
// only process class-specific quests to learn class-related spells, cuz
|
if (!quest->GetRequiredClasses() || quest->IsRepeatable() || quest->GetMinLevel() < 10 ||
|
||||||
// we don't want all these bunch of entries to be handled!
|
quest->GetMinLevel() > bot->GetLevel())
|
||||||
if (!quest->GetRequiredClasses())
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// skip quests that are repeatable, too low level, or above bots' level
|
|
||||||
if (quest->IsRepeatable() || quest->GetMinLevel() < 10 || quest->GetMinLevel() > bot->GetLevel())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// skip if bot doesnt satisfy class, race, or skill requirements
|
|
||||||
if (!bot->SatisfyQuestClass(quest, false) || !bot->SatisfyQuestRace(quest, false) ||
|
if (!bot->SatisfyQuestClass(quest, false) || !bot->SatisfyQuestRace(quest, false) ||
|
||||||
!bot->SatisfyQuestSkill(quest, false))
|
!bot->SatisfyQuestSkill(quest, false))
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
// use the same logic and impl from Player::learnQuestRewardedSpells
|
|
||||||
|
|
||||||
int32 spellId = quest->GetRewSpellCast();
|
int32 spellId = quest->GetRewSpellCast();
|
||||||
if (!spellId)
|
if (!spellId)
|
||||||
@ -113,31 +107,26 @@ void AutoMaintenanceOnLevelupAction::LearnQuestSpells(std::ostringstream* out)
|
|||||||
if (!spellInfo)
|
if (!spellInfo)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// xinef: find effect with learn spell and check if we have this spell
|
|
||||||
bool found = false;
|
bool found = false;
|
||||||
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
||||||
{
|
{
|
||||||
if (spellInfo->Effects[i].Effect == SPELL_EFFECT_LEARN_SPELL && spellInfo->Effects[i].TriggerSpell &&
|
if (spellInfo->Effects[i].Effect == SPELL_EFFECT_LEARN_SPELL && spellInfo->Effects[i].TriggerSpell &&
|
||||||
!bot->HasSpell(spellInfo->Effects[i].TriggerSpell))
|
!bot->HasSpell(spellInfo->Effects[i].TriggerSpell))
|
||||||
{
|
{
|
||||||
// pusywizard: don't re-add profession specialties!
|
|
||||||
if (SpellInfo const* triggeredInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[i].TriggerSpell))
|
if (SpellInfo const* triggeredInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[i].TriggerSpell))
|
||||||
if (triggeredInfo->Effects[0].Effect == SPELL_EFFECT_TRADE_SKILL)
|
if (triggeredInfo->Effects[0].Effect == SPELL_EFFECT_TRADE_SKILL)
|
||||||
break; // pussywizard: break and not cast the spell (found is false)
|
break;
|
||||||
|
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// xinef: we know the spell, continue
|
|
||||||
if (!found)
|
if (!found)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
bot->CastSpell(bot, spellId, true);
|
bot->CastSpell(bot, spellId, true);
|
||||||
|
|
||||||
// Check if RewardDisplaySpell is set to output the proper spell learned
|
|
||||||
// after processing quests. Output the original RewardSpell otherwise.
|
|
||||||
uint32 rewSpellId = quest->GetRewSpell();
|
uint32 rewSpellId = quest->GetRewSpell();
|
||||||
if (rewSpellId)
|
if (rewSpellId)
|
||||||
{
|
{
|
||||||
@ -167,12 +156,11 @@ std::string const AutoMaintenanceOnLevelupAction::FormatSpell(SpellInfo const* s
|
|||||||
|
|
||||||
void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip()
|
void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip()
|
||||||
{
|
{
|
||||||
if (!sPlayerbotAIConfig.autoUpgradeEquip || !sRandomPlayerbotMgr.IsRandomBot(bot))
|
if (!sRandomPlayerbotMgr.IsRandomBot(bot))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
PlayerbotFactory factory(bot, bot->GetLevel());
|
PlayerbotFactory factory(bot, bot->GetLevel());
|
||||||
|
|
||||||
// Clean up old consumables before adding new ones
|
|
||||||
factory.CleanupConsumables();
|
factory.CleanupConsumables();
|
||||||
|
|
||||||
factory.InitAmmo();
|
factory.InitAmmo();
|
||||||
@ -181,9 +169,6 @@ void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip()
|
|||||||
factory.InitConsumables();
|
factory.InitConsumables();
|
||||||
factory.InitPotions();
|
factory.InitPotions();
|
||||||
|
|
||||||
if (!sPlayerbotAIConfig.equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig.equipmentPersistenceLevel)
|
if (sPlayerbotAIConfig.autoUpgradeEquip)
|
||||||
{
|
|
||||||
if (sPlayerbotAIConfig.incrementalGearInit)
|
|
||||||
factory.InitEquipment(true);
|
factory.InitEquipment(true);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "ItemCountValue.h"
|
#include "ItemCountValue.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
bool BankAction::Execute(Event event)
|
bool BankAction::Execute(Event event)
|
||||||
@ -23,7 +24,8 @@ bool BankAction::Execute(Event event)
|
|||||||
return ExecuteBank(text, npc);
|
return ExecuteBank(text, npc);
|
||||||
}
|
}
|
||||||
|
|
||||||
botAI->TellError("Cannot find banker nearby");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bank_no_banker_nearby_error", "Cannot find banker nearby", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1299,7 +1299,7 @@ std::string const BGTactics::HandleConsoleCommandPrivate(WorldSession* session,
|
|||||||
Player* player = session->GetPlayer();
|
Player* player = session->GetPlayer();
|
||||||
if (!player)
|
if (!player)
|
||||||
return "Error - session player not found";
|
return "Error - session player not found";
|
||||||
if (player->GetSession()->GetSecurity() < SEC_GAMEMASTER)
|
if (!player->CanBeGameMaster())
|
||||||
return "Command can only be used by a GM";
|
return "Command can only be used by a GM";
|
||||||
Battleground* bg = player->GetBattleground();
|
Battleground* bg = player->GetBattleground();
|
||||||
if (!bg)
|
if (!bg)
|
||||||
|
|||||||
@ -49,7 +49,7 @@ bool ChangeNonCombatStrategyAction::Execute(Event event)
|
|||||||
|
|
||||||
uint32 account = bot->GetSession()->GetAccountId();
|
uint32 account = bot->GetSession()->GetAccountId();
|
||||||
if (sPlayerbotAIConfig.IsInRandomAccountList(account) && botAI->GetMaster() &&
|
if (sPlayerbotAIConfig.IsInRandomAccountList(account) && botAI->GetMaster() &&
|
||||||
botAI->GetMaster()->GetSession()->GetSecurity() < SEC_GAMEMASTER)
|
!botAI->GetMaster()->CanBeGameMaster())
|
||||||
{
|
{
|
||||||
if (text.find("loot") != std::string::npos || text.find("gather") != std::string::npos)
|
if (text.find("loot") != std::string::npos || text.find("gather") != std::string::npos)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "Formations.h"
|
#include "Formations.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "PositionValue.h"
|
#include "PositionValue.h"
|
||||||
|
|
||||||
@ -85,7 +86,8 @@ bool FollowChatShortcutAction::Execute(Event /*event*/)
|
|||||||
|
|
||||||
if (moved)
|
if (moved)
|
||||||
{
|
{
|
||||||
botAI->TellMaster("Following");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"following", "Following", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,7 +110,8 @@ bool FollowChatShortcutAction::Execute(Event /*event*/)
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
botAI->TellMaster("Following");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"following", "Following", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +128,8 @@ bool StayChatShortcutAction::Execute(Event /*event*/)
|
|||||||
SetReturnPosition(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
|
SetReturnPosition(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
|
||||||
SetStayPosition(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
|
SetStayPosition(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
|
||||||
|
|
||||||
botAI->TellMaster("Staying");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"staying", "Staying", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +144,8 @@ bool MoveFromGroupChatShortcutAction::Execute(Event /*event*/)
|
|||||||
botAI->ChangeStrategy("+move from group", BOT_STATE_NON_COMBAT);
|
botAI->ChangeStrategy("+move from group", BOT_STATE_NON_COMBAT);
|
||||||
botAI->ChangeStrategy("+move from group", BOT_STATE_COMBAT);
|
botAI->ChangeStrategy("+move from group", BOT_STATE_COMBAT);
|
||||||
|
|
||||||
botAI->TellMaster("Moving away from group");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"move_from_group", "Moving away from group", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,11 +164,13 @@ bool FleeChatShortcutAction::Execute(Event /*event*/)
|
|||||||
|
|
||||||
if (bot->GetMapId() != master->GetMapId() || bot->GetDistance(master) > sPlayerbotAIConfig.sightDistance)
|
if (bot->GetMapId() != master->GetMapId() || bot->GetDistance(master) > sPlayerbotAIConfig.sightDistance)
|
||||||
{
|
{
|
||||||
botAI->TellError("I will not flee with you - too far away");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"fleeing_far", "I will not flee with you - too far away", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
botAI->TellMaster("Fleeing");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"fleeing", "Fleeing", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,7 +187,8 @@ bool GoawayChatShortcutAction::Execute(Event /*event*/)
|
|||||||
ResetReturnPosition();
|
ResetReturnPosition();
|
||||||
ResetStayPosition();
|
ResetStayPosition();
|
||||||
|
|
||||||
botAI->TellMaster("Running away");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"running_away", "Running away", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,7 +204,8 @@ bool GrindChatShortcutAction::Execute(Event /*event*/)
|
|||||||
ResetReturnPosition();
|
ResetReturnPosition();
|
||||||
ResetStayPosition();
|
ResetStayPosition();
|
||||||
|
|
||||||
botAI->TellMaster("Grinding");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"grinding", "Grinding", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,7 +225,8 @@ bool TankAttackChatShortcutAction::Execute(Event /*event*/)
|
|||||||
ResetReturnPosition();
|
ResetReturnPosition();
|
||||||
ResetStayPosition();
|
ResetStayPosition();
|
||||||
|
|
||||||
botAI->TellMaster("Attacking");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"attacking", "Attacking", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,10 +6,10 @@
|
|||||||
#include "CheckValuesAction.h"
|
#include "CheckValuesAction.h"
|
||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
|
#include "ObjectGuid.h"
|
||||||
#include "ServerFacade.h"
|
#include "ServerFacade.h"
|
||||||
|
|
||||||
#include "PlayerbotAI.h"
|
#include "PlayerbotAI.h"
|
||||||
#include "TravelNode.h"
|
|
||||||
#include "AiObjectContext.h"
|
#include "AiObjectContext.h"
|
||||||
|
|
||||||
CheckValuesAction::CheckValuesAction(PlayerbotAI* botAI) : Action(botAI, "check values") {}
|
CheckValuesAction::CheckValuesAction(PlayerbotAI* botAI) : Action(botAI, "check values") {}
|
||||||
@ -21,11 +21,6 @@ bool CheckValuesAction::Execute(Event /*event*/)
|
|||||||
botAI->Ping(bot->GetPositionX(), bot->GetPositionY());
|
botAI->Ping(bot->GetPositionX(), bot->GetPositionY());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (botAI->HasStrategy("map", BOT_STATE_NON_COMBAT) || botAI->HasStrategy("map full", BOT_STATE_NON_COMBAT))
|
|
||||||
{
|
|
||||||
TravelNodeMap::instance().manageNodes(bot, botAI->HasStrategy("map full", BOT_STATE_NON_COMBAT));
|
|
||||||
}
|
|
||||||
|
|
||||||
GuidVector possible_targets = *context->GetValue<GuidVector>("possible targets");
|
GuidVector possible_targets = *context->GetValue<GuidVector>("possible targets");
|
||||||
GuidVector all_targets = *context->GetValue<GuidVector>("all targets");
|
GuidVector all_targets = *context->GetValue<GuidVector>("all targets");
|
||||||
GuidVector npcs = *context->GetValue<GuidVector>("nearest npcs");
|
GuidVector npcs = *context->GetValue<GuidVector>("nearest npcs");
|
||||||
|
|||||||
@ -76,7 +76,7 @@ bool DebugAction::Execute(Event event)
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
std::vector<WorldPosition> beginPath, endPath;
|
std::vector<WorldPosition> beginPath, endPath;
|
||||||
TravelNodeRoute route = TravelNodeMap::instance().getRoute(botPos, *points.front(), beginPath, bot);
|
TravelNodeRoute route = TravelNodeMap::instance().FindRouteNearestNodes(botPos, *points.front(), beginPath, bot);
|
||||||
|
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << "Traveling to " << dest->getTitle() << ": ";
|
out << "Traveling to " << dest->getTitle() << ": ";
|
||||||
@ -196,18 +196,18 @@ bool DebugAction::Execute(Event event)
|
|||||||
{
|
{
|
||||||
WorldPosition pos(bot);
|
WorldPosition pos(bot);
|
||||||
|
|
||||||
std::string const name = "USER:" + text.substr(9);
|
std::string suffix = text.size() > 9 ? text.substr(9) : pos.getAreaName();
|
||||||
|
std::string const name = "USER:" + suffix;
|
||||||
|
|
||||||
/* TravelNode* startNode = */ TravelNodeMap::instance().addNode(pos, name, false, false); // startNode not used, but addNode as side effect, fragment marked for removal.
|
{
|
||||||
|
std::lock_guard<std::shared_timed_mutex> lock(TravelNodeMap::instance().m_nMapMtx);
|
||||||
|
TravelNodeMap::instance().addNode(pos, name, false, true);
|
||||||
|
|
||||||
for (auto& endNode : TravelNodeMap::instance().getNodes(pos, 2000))
|
for (auto& endNode : TravelNodeMap::instance().getNodes(pos, 2000))
|
||||||
{
|
|
||||||
endNode->setLinked(false);
|
endNode->setLinked(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
botAI->TellMasterNoFacing("Node " + name + " created.");
|
botAI->TellMasterNoFacing("Node " + name + " created. Use console command '.playerbots travel generatenode' to connect nodes.");
|
||||||
|
|
||||||
TravelNodeMap::instance().setHasToGen();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -223,14 +223,15 @@ bool DebugAction::Execute(Event event)
|
|||||||
if (startNode->isImportant())
|
if (startNode->isImportant())
|
||||||
{
|
{
|
||||||
botAI->TellMasterNoFacing("Node can not be removed.");
|
botAI->TellMasterNoFacing("Node can not be removed.");
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
TravelNodeMap::instance().m_nMapMtx.lock();
|
{
|
||||||
|
std::lock_guard<std::shared_timed_mutex> lock(TravelNodeMap::instance().m_nMapMtx);
|
||||||
TravelNodeMap::instance().removeNode(startNode);
|
TravelNodeMap::instance().removeNode(startNode);
|
||||||
botAI->TellMasterNoFacing("Node removed.");
|
}
|
||||||
TravelNodeMap::instance().m_nMapMtx.unlock();
|
|
||||||
|
|
||||||
TravelNodeMap::instance().setHasToGen();
|
botAI->TellMasterNoFacing("Node removed. Use console command '.playerbots travel generatenode' to finalize nodes.");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -247,15 +248,17 @@ bool DebugAction::Execute(Event event)
|
|||||||
node->removeLinkTo(path.first, true);
|
node->removeLinkTo(path.first, true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (text.find("gen node") != std::string::npos)
|
else if (text.find("gen node") != std::string::npos ||
|
||||||
|
text.find("gen path") != std::string::npos)
|
||||||
{
|
{
|
||||||
// Pathfinder
|
// Disabled: generateAll() touches Map / grid / mmap state that is only
|
||||||
TravelNodeMap::instance().generateNodes();
|
// safe to mutate on the world thread. Running it from a detached worker
|
||||||
return true;
|
// (or from a bot tick on a MapUpdater thread) races with world updates
|
||||||
}
|
// and freezes the server. Use the console command instead, which runs
|
||||||
else if (text.find("gen path") != std::string::npos)
|
// synchronously on the world thread:
|
||||||
{
|
// .playerbots travel generatenode
|
||||||
TravelNodeMap::instance().generatePaths();
|
botAI->TellMasterNoFacing(
|
||||||
|
"Disabled in chat. Run '.playerbots travel generatenode' from the server console.");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (text.find("crop path") != std::string::npos)
|
else if (text.find("crop path") != std::string::npos)
|
||||||
@ -275,7 +278,7 @@ bool DebugAction::Execute(Event event)
|
|||||||
[]
|
[]
|
||||||
{
|
{
|
||||||
TravelNodeMap::instance().removeNodes();
|
TravelNodeMap::instance().removeNodes();
|
||||||
TravelNodeMap::instance().loadNodeStore();
|
TravelNodeMap::instance().LoadNodeStore();
|
||||||
});
|
});
|
||||||
|
|
||||||
t.detach();
|
t.detach();
|
||||||
@ -297,7 +300,7 @@ bool DebugAction::Execute(Event event)
|
|||||||
|
|
||||||
// uint32 time = 60 * IN_MILLISECONDS; //not used, line marked for removal.
|
// uint32 time = 60 * IN_MILLISECONDS; //not used, line marked for removal.
|
||||||
|
|
||||||
std::vector<WorldPosition> ppath = l.second->getPath();
|
std::vector<WorldPosition> ppath = l.second->GetPath();
|
||||||
|
|
||||||
for (auto p : ppath)
|
for (auto p : ppath)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -29,6 +29,10 @@ void DestroyItemAction::DestroyItem(FindItemVisitor* visitor)
|
|||||||
std::vector<Item*> items = visitor->GetResult();
|
std::vector<Item*> items = visitor->GetResult();
|
||||||
for (Item* item : items)
|
for (Item* item : items)
|
||||||
{
|
{
|
||||||
|
// backstop: never drop an active quest item
|
||||||
|
if (bot->HasQuestForItem(item->GetTemplate()->ItemId))
|
||||||
|
continue;
|
||||||
|
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << chat->FormatItem(item->GetTemplate()) << " destroyed";
|
out << chat->FormatItem(item->GetTemplate()) << " destroyed";
|
||||||
botAI->TellMaster(out);
|
botAI->TellMaster(out);
|
||||||
@ -67,18 +71,11 @@ bool SmartDestroyItemAction::Execute(Event /*event*/)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ITEM_USAGE_QUEST is excluded — those are still-needed quest items
|
||||||
std::vector<uint32> bestToDestroy = {ITEM_USAGE_NONE}; // First destroy anything useless.
|
std::vector<uint32> bestToDestroy = {ITEM_USAGE_NONE}; // First destroy anything useless.
|
||||||
|
|
||||||
if (!AI_VALUE(bool, "can sell") &&
|
|
||||||
AI_VALUE(
|
|
||||||
bool,
|
|
||||||
"should get money")) // We need money so quest items are less important since they can't directly be sold.
|
|
||||||
bestToDestroy.push_back(ITEM_USAGE_QUEST);
|
|
||||||
else // We don't need money so destroy the cheapest stuff.
|
|
||||||
{
|
|
||||||
bestToDestroy.push_back(ITEM_USAGE_VENDOR);
|
bestToDestroy.push_back(ITEM_USAGE_VENDOR);
|
||||||
bestToDestroy.push_back(ITEM_USAGE_AH);
|
bestToDestroy.push_back(ITEM_USAGE_AH);
|
||||||
}
|
|
||||||
|
|
||||||
// If we still need room
|
// If we still need room
|
||||||
bestToDestroy.push_back(
|
bestToDestroy.push_back(
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "ChatHelper.h"
|
#include "ChatHelper.h"
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
bool DropQuestAction::Execute(Event event)
|
bool DropQuestAction::Execute(Event event)
|
||||||
@ -51,10 +52,15 @@ bool DropQuestAction::Execute(Event event)
|
|||||||
const Quest* pQuest = sObjectMgr->GetQuestTemplate(entry);
|
const Quest* pQuest = sObjectMgr->GetQuestTemplate(entry);
|
||||||
const std::string text_quest = ChatHelper::FormatQuest(pQuest);
|
const std::string text_quest = ChatHelper::FormatQuest(pQuest);
|
||||||
LOG_INFO("playerbots", "{} => Quest [ {} ] removed", bot->GetName(), pQuest->GetTitle());
|
LOG_INFO("playerbots", "{} => Quest [ {} ] removed", bot->GetName(), pQuest->GetTitle());
|
||||||
bot->Say("Quest [ " + text_quest + " ] removed", LANG_UNIVERSAL);
|
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_removed_debug",
|
||||||
|
"Quest [%quest] removed",
|
||||||
|
{{"%quest", text_quest}});
|
||||||
|
bot->Say(text, LANG_UNIVERSAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
botAI->TellMaster("Quest removed");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_remove", "Quest removed", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +75,10 @@ bool CleanQuestLogAction::Execute(Event event)
|
|||||||
|
|
||||||
// Only output this message if "debug rpg" strategy is enabled
|
// Only output this message if "debug rpg" strategy is enabled
|
||||||
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
||||||
botAI->TellMaster("Clean Quest Log command received, removing grey/trivial quests...");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"clean_quest_log_started",
|
||||||
|
"Clean Quest Log command received, removing grey/trivial quests...",
|
||||||
|
{}));
|
||||||
|
|
||||||
uint8 botLevel = bot->GetLevel(); // Get bot's level
|
uint8 botLevel = bot->GetLevel(); // Get bot's level
|
||||||
|
|
||||||
@ -103,7 +112,10 @@ bool CleanQuestLogAction::Execute(Event event)
|
|||||||
{
|
{
|
||||||
// Output only if "debug rpg" strategy is enabled
|
// Output only if "debug rpg" strategy is enabled
|
||||||
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
||||||
botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] will be removed because it is trivial (grey).");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_trivial_will_remove",
|
||||||
|
"Quest [%title] will be removed because it is trivial (grey).",
|
||||||
|
{{"%title", quest->GetTitle()}}));
|
||||||
|
|
||||||
// Remove quest
|
// Remove quest
|
||||||
botAI->rpgStatistic.questDropped++;
|
botAI->rpgStatistic.questDropped++;
|
||||||
@ -116,17 +128,27 @@ bool CleanQuestLogAction::Execute(Event event)
|
|||||||
{
|
{
|
||||||
const std::string text_quest = ChatHelper::FormatQuest(quest);
|
const std::string text_quest = ChatHelper::FormatQuest(quest);
|
||||||
LOG_INFO("playerbots", "{} => Quest [ {} ] removed", bot->GetName(), quest->GetTitle());
|
LOG_INFO("playerbots", "{} => Quest [ {} ] removed", bot->GetName(), quest->GetTitle());
|
||||||
bot->Say("Quest [ " + text_quest + " ] removed", LANG_UNIVERSAL);
|
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_removed_debug",
|
||||||
|
"Quest [%quest] removed",
|
||||||
|
{{"%quest", text_quest}});
|
||||||
|
bot->Say(text, LANG_UNIVERSAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
||||||
botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] has been removed.");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_has_been_removed",
|
||||||
|
"Quest [%title] has been removed.",
|
||||||
|
{{"%title", quest->GetTitle()}}));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Only output if "debug rpg" strategy is enabled
|
// Only output if "debug rpg" strategy is enabled
|
||||||
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
||||||
botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] is not trivial and will be kept.");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_not_trivial_kept",
|
||||||
|
"Quest [%title] is not trivial and will be kept.",
|
||||||
|
{{"%title", quest->GetTitle()}}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,9 +226,16 @@ void CleanQuestLogAction::DropQuestType(uint8& numQuest, uint8 wantNum, bool isG
|
|||||||
{
|
{
|
||||||
const std::string text_quest = ChatHelper::FormatQuest(quest);
|
const std::string text_quest = ChatHelper::FormatQuest(quest);
|
||||||
LOG_INFO("playerbots", "{} => Quest [ {} ] removed", bot->GetName(), quest->GetTitle());
|
LOG_INFO("playerbots", "{} => Quest [ {} ] removed", bot->GetName(), quest->GetTitle());
|
||||||
bot->Say("Quest [ " + text_quest + " ] removed", LANG_UNIVERSAL);
|
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_removed_debug",
|
||||||
|
"Quest [%quest] removed",
|
||||||
|
{{"%quest", text_quest}});
|
||||||
|
bot->Say(text, LANG_UNIVERSAL);
|
||||||
}
|
}
|
||||||
botAI->TellMaster("Quest removed" + chat->FormatQuest(quest));
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_removed_with_name",
|
||||||
|
"Quest removed %quest",
|
||||||
|
{{"%quest", chat->FormatQuest(quest)}}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -154,9 +154,11 @@ void EquipAction::EquipItem(Item* item)
|
|||||||
calculator.SetOverflowPenalty(false);
|
calculator.SetOverflowPenalty(false);
|
||||||
|
|
||||||
// Calculate item scores once and store them
|
// Calculate item scores once and store them
|
||||||
float newItemScore = calculator.CalculateItem(itemId);
|
float newItemScore = calculator.CalculateItem(itemId, item->GetItemRandomPropertyId());
|
||||||
float mainHandScore = mainHandItem ? calculator.CalculateItem(mainHandItem->GetTemplate()->ItemId) : 0.0f;
|
float mainHandScore = mainHandItem
|
||||||
float offHandScore = offHandItem ? calculator.CalculateItem(offHandItem->GetTemplate()->ItemId) : 0.0f;
|
? calculator.CalculateItem(mainHandItem->GetTemplate()->ItemId, mainHandItem->GetItemRandomPropertyId()) : 0.0f;
|
||||||
|
float offHandScore = offHandItem
|
||||||
|
? calculator.CalculateItem(offHandItem->GetTemplate()->ItemId, offHandItem->GetItemRandomPropertyId()) : 0.0f;
|
||||||
|
|
||||||
// Determine where this weapon can go
|
// Determine where this weapon can go
|
||||||
bool canGoMain = (invType == INVTYPE_WEAPON ||
|
bool canGoMain = (invType == INVTYPE_WEAPON ||
|
||||||
|
|||||||
@ -19,83 +19,8 @@
|
|||||||
#include "Transport.h"
|
#include "Transport.h"
|
||||||
#include "Map.h"
|
#include "Map.h"
|
||||||
|
|
||||||
namespace
|
// Transport helpers (GetTransportForPosTolerant, FindBoardingPointOnTransport,
|
||||||
{
|
// BoardTransport) are now on MovementAction — inherited by FollowAction.
|
||||||
Transport* GetTransportForPosTolerant(Map* map, WorldObject* ref, uint32 phaseMask, float x, float y, float z)
|
|
||||||
{
|
|
||||||
if (!map || !ref)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
std::array<float, 4> const probes = { z, z + 0.5f, z + 1.5f, z - 0.5f };
|
|
||||||
for (float const pz : probes)
|
|
||||||
{
|
|
||||||
if (Transport* t = map->GetTransportForPos(phaseMask, x, y, pz, ref))
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempts to find a point on the leader's transport that is closer to the bot,
|
|
||||||
// by probing along the segment from master -> bot and returning the last point
|
|
||||||
// that is still detected as being on the expected transport.
|
|
||||||
bool FindBoardingPointOnTransport(Map* map, Transport* expectedTransport, WorldObject* ref,
|
|
||||||
float masterX, float masterY, float masterZ,
|
|
||||||
float botX, float botY, float botZ,
|
|
||||||
float& outX, float& outY, float& outZ)
|
|
||||||
{
|
|
||||||
if (!map || !expectedTransport || !ref)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
uint32 const phaseMask = ref->GetPhaseMask();
|
|
||||||
|
|
||||||
// Ensure master is actually detected on that transport (tolerant).
|
|
||||||
if (GetTransportForPosTolerant(map, ref, phaseMask, masterX, masterY, masterZ) != expectedTransport)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// The raycast in GetTransportForPos starts at (z + 2). Probe with a safe Z.
|
|
||||||
float const probeZ = std::max(masterZ, botZ);
|
|
||||||
|
|
||||||
// Adaptive step count: small platforms need tighter sampling.
|
|
||||||
float const dx2 = botX - masterX;
|
|
||||||
float const dy2 = botY - masterY;
|
|
||||||
float const dist2d = std::sqrt(dx2 * dx2 + dy2 * dy2);
|
|
||||||
int32 const steps = std::clamp(static_cast<int32>(dist2d / 0.75f), 10, 28);
|
|
||||||
|
|
||||||
float const dx = (botX - masterX) / static_cast<float>(steps);
|
|
||||||
float const dy = (botY - masterY) / static_cast<float>(steps);
|
|
||||||
|
|
||||||
// Master must actually be on the expected transport for this to work.
|
|
||||||
if (map->GetTransportForPos(ref->GetPhaseMask(), masterX, masterY, probeZ, ref) != expectedTransport)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
float lastX = masterX;
|
|
||||||
float lastY = masterY;
|
|
||||||
bool found = false;
|
|
||||||
|
|
||||||
for (int32 i = 1; i <= steps; ++i)
|
|
||||||
{
|
|
||||||
float const px = masterX + dx * i;
|
|
||||||
float const py = masterY + dy * i;
|
|
||||||
|
|
||||||
Transport* const t = GetTransportForPosTolerant(map, ref, phaseMask, px, py, probeZ);
|
|
||||||
if (t != expectedTransport)
|
|
||||||
break;
|
|
||||||
|
|
||||||
lastX = px;
|
|
||||||
lastY = py;
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
outX = lastX;
|
|
||||||
outY = lastY;
|
|
||||||
outZ = masterZ; // keep deck-level Z to encourage stepping onto the platform/boat
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FollowAction::Execute(Event /*event*/)
|
bool FollowAction::Execute(Event /*event*/)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
#include "Pet.h"
|
#include "Pet.h"
|
||||||
#include "PlayerbotAIConfig.h"
|
#include "PlayerbotAIConfig.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "CreatureAI.h"
|
#include "CreatureAI.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "CharmInfo.h"
|
#include "CharmInfo.h"
|
||||||
@ -49,7 +50,10 @@ bool MeleeAction::isUseful()
|
|||||||
if (botAI->IsInVehicle() && !botAI->IsInVehicle(false, false, true))
|
if (botAI->IsInVehicle() && !botAI->IsInVehicle(false, false, true))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
// Do not start autoattack while prowled — let opener spells break stealth intentionally.
|
||||||
|
// Future rogue stealth implementation should use this instead:
|
||||||
|
// return !(botAI->HasAura("stealth", bot) || botAI->HasAura("prowl", bot));
|
||||||
|
return !botAI->HasAura("prowl", bot);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TogglePetSpellAutoCastAction::Execute(Event /*event*/)
|
bool TogglePetSpellAutoCastAction::Execute(Event /*event*/)
|
||||||
@ -82,7 +86,7 @@ bool TogglePetSpellAutoCastAction::Execute(Event /*event*/)
|
|||||||
|
|
||||||
uint32 spellId = itr->first;
|
uint32 spellId = itr->first;
|
||||||
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||||
if (!spellInfo->IsAutocastable())
|
if (!spellInfo || !spellInfo->IsAutocastable())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
bool shouldApply = true;
|
bool shouldApply = true;
|
||||||
@ -178,7 +182,8 @@ bool SetPetStanceAction::Execute(Event /*event*/)
|
|||||||
// If there are no controlled pets or guardians, notify the player and exit
|
// If there are no controlled pets or guardians, notify the player and exit
|
||||||
if (targets.empty())
|
if (targets.empty())
|
||||||
{
|
{
|
||||||
botAI->TellError("You have no pet or guardian pet.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"pet_no_pet_error", "You have no pet or guardian pet.", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
#include "GenericSpellActions.h"
|
#include "GenericSpellActions.h"
|
||||||
|
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "ItemTemplate.h"
|
#include "ItemTemplate.h"
|
||||||
@ -23,11 +24,119 @@
|
|||||||
using ai::buff::MakeAuraQualifierForBuff;
|
using ai::buff::MakeAuraQualifierForBuff;
|
||||||
using ai::spell::HasSpellOrCategoryCooldown;
|
using ai::spell::HasSpellOrCategoryCooldown;
|
||||||
|
|
||||||
CastSpellAction::CastSpellAction(PlayerbotAI* botAI, std::string const spell)
|
namespace
|
||||||
: Action(botAI, spell), range(botAI->GetRange("spell")), spell(spell)
|
|
||||||
{
|
{
|
||||||
|
std::unordered_set<uint32> const& GetMixedTriggerTrinketSpellIds()
|
||||||
|
{
|
||||||
|
static std::unordered_set<uint32> const mixedTriggerSpellIds = []()
|
||||||
|
{
|
||||||
|
std::unordered_set<uint32> onUseSpellIds;
|
||||||
|
std::unordered_set<uint32> onEquipSpellIds;
|
||||||
|
std::unordered_set<uint32> mixedSpellIds;
|
||||||
|
|
||||||
|
auto const* itemTemplates = sObjectMgr->GetItemTemplateStore();
|
||||||
|
if (!itemTemplates)
|
||||||
|
return mixedSpellIds;
|
||||||
|
|
||||||
|
auto const markSpellId = [&](int32 spellId, uint8 spellTrigger)
|
||||||
|
{
|
||||||
|
if (spellId <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (spellTrigger == ITEM_SPELLTRIGGER_ON_USE)
|
||||||
|
{
|
||||||
|
if (onEquipSpellIds.find(spellId) != onEquipSpellIds.end())
|
||||||
|
mixedSpellIds.insert(spellId);
|
||||||
|
|
||||||
|
onUseSpellIds.insert(spellId);
|
||||||
|
}
|
||||||
|
else if (spellTrigger == ITEM_SPELLTRIGGER_ON_EQUIP)
|
||||||
|
{
|
||||||
|
if (onUseSpellIds.find(spellId) != onUseSpellIds.end())
|
||||||
|
mixedSpellIds.insert(spellId);
|
||||||
|
|
||||||
|
onEquipSpellIds.insert(spellId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto const& itr : *itemTemplates)
|
||||||
|
{
|
||||||
|
ItemTemplate const& proto = itr.second;
|
||||||
|
if (proto.InventoryType != INVTYPE_TRINKET)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (uint8 spellIndex = 0; spellIndex < MAX_ITEM_PROTO_SPELLS; ++spellIndex)
|
||||||
|
{
|
||||||
|
auto const& spellData = proto.Spells[spellIndex];
|
||||||
|
markSpellId(spellData.SpellId, spellData.SpellTrigger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mixedSpellIds;
|
||||||
|
}();
|
||||||
|
|
||||||
|
return mixedTriggerSpellIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsManaRestoreEffect(SpellEffectInfo const& effectInfo)
|
||||||
|
{
|
||||||
|
return (effectInfo.Effect == SPELL_EFFECT_ENERGIZE &&
|
||||||
|
effectInfo.MiscValue == POWER_MANA) ||
|
||||||
|
(effectInfo.Effect == SPELL_EFFECT_APPLY_AURA &&
|
||||||
|
effectInfo.ApplyAuraName == SPELL_AURA_PERIODIC_ENERGIZE &&
|
||||||
|
effectInfo.MiscValue == POWER_MANA);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsManaEfficiencyEffect(SpellEffectInfo const& effectInfo)
|
||||||
|
{
|
||||||
|
return effectInfo.Effect == SPELL_EFFECT_APPLY_AURA &&
|
||||||
|
(((effectInfo.ApplyAuraName == SPELL_AURA_MOD_POWER_REGEN ||
|
||||||
|
effectInfo.ApplyAuraName == SPELL_AURA_MOD_POWER_REGEN_PERCENT) &&
|
||||||
|
effectInfo.MiscValue == POWER_MANA) ||
|
||||||
|
effectInfo.ApplyAuraName == SPELL_AURA_MOD_POWER_COST_SCHOOL ||
|
||||||
|
effectInfo.ApplyAuraName == SPELL_AURA_MOD_POWER_COST_SCHOOL_PCT ||
|
||||||
|
effectInfo.ApplyAuraName == SPELL_AURA_MOD_MANA_REGEN_INTERRUPT);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsDefensiveTankEffect(SpellEffectInfo const& effectInfo)
|
||||||
|
{
|
||||||
|
if (effectInfo.Effect != SPELL_EFFECT_APPLY_AURA)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
uint32 const tankRatingsMask =
|
||||||
|
(1u << CR_DEFENSE_SKILL) |
|
||||||
|
(1u << CR_DODGE) |
|
||||||
|
(1u << CR_PARRY) |
|
||||||
|
(1u << CR_BLOCK) |
|
||||||
|
(1u << CR_HIT_TAKEN_MELEE) |
|
||||||
|
(1u << CR_HIT_TAKEN_RANGED) |
|
||||||
|
(1u << CR_HIT_TAKEN_SPELL) |
|
||||||
|
(1u << CR_CRIT_TAKEN_MELEE) |
|
||||||
|
(1u << CR_CRIT_TAKEN_RANGED) |
|
||||||
|
(1u << CR_CRIT_TAKEN_SPELL);
|
||||||
|
|
||||||
|
switch (effectInfo.ApplyAuraName)
|
||||||
|
{
|
||||||
|
case SPELL_AURA_MOD_RESISTANCE:
|
||||||
|
return (effectInfo.MiscValue & SPELL_SCHOOL_MASK_NORMAL) != 0;
|
||||||
|
case SPELL_AURA_MOD_RATING:
|
||||||
|
return (effectInfo.MiscValue & tankRatingsMask) != 0;
|
||||||
|
case SPELL_AURA_MOD_INCREASE_HEALTH:
|
||||||
|
case SPELL_AURA_MOD_INCREASE_HEALTH_PERCENT:
|
||||||
|
case SPELL_AURA_MOD_PARRY_PERCENT:
|
||||||
|
case SPELL_AURA_MOD_DODGE_PERCENT:
|
||||||
|
case SPELL_AURA_MOD_BLOCK_PERCENT:
|
||||||
|
case SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CastSpellAction::CastSpellAction(PlayerbotAI* botAI, std::string const spell)
|
||||||
|
: Action(botAI, spell), range(botAI->GetRange("spell")), spell(spell) {}
|
||||||
|
|
||||||
bool CastSpellAction::Execute(Event /*event*/)
|
bool CastSpellAction::Execute(Event /*event*/)
|
||||||
{
|
{
|
||||||
if (spell == "conjure food" || spell == "conjure water")
|
if (spell == "conjure food" || spell == "conjure water")
|
||||||
@ -53,18 +162,12 @@ bool CastSpellAction::Execute(Event /*event*/)
|
|||||||
|
|
||||||
wstrToLower(wnamepart);
|
wstrToLower(wnamepart);
|
||||||
|
|
||||||
if (!Utf8FitTo(spell, wnamepart))
|
if (!Utf8FitTo(spell, wnamepart) || spellInfo->Effects[0].Effect != SPELL_EFFECT_CREATE_ITEM)
|
||||||
continue;
|
|
||||||
|
|
||||||
if (spellInfo->Effects[0].Effect != SPELL_EFFECT_CREATE_ITEM)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
uint32 itemId = spellInfo->Effects[0].ItemType;
|
uint32 itemId = spellInfo->Effects[0].ItemType;
|
||||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||||
if (!proto)
|
if (!proto || bot->CanUseItem(proto) != EQUIP_ERR_OK)
|
||||||
continue;
|
|
||||||
|
|
||||||
if (bot->CanUseItem(proto) != EQUIP_ERR_OK)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (spellInfo->Id > castId)
|
if (spellInfo->Id > castId)
|
||||||
@ -92,10 +195,7 @@ bool CastSpellAction::isUseful()
|
|||||||
}
|
}
|
||||||
|
|
||||||
Unit* spellTarget = GetTarget();
|
Unit* spellTarget = GetTarget();
|
||||||
if (!spellTarget)
|
if (!spellTarget || !spellTarget->IsInWorld() || spellTarget->GetMapId() != bot->GetMapId())
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!spellTarget->IsInWorld() || spellTarget->GetMapId() != bot->GetMapId())
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// float combatReach = bot->GetCombatReach() + target->GetCombatReach();
|
// float combatReach = bot->GetCombatReach() + target->GetCombatReach();
|
||||||
@ -143,10 +243,7 @@ CastMeleeSpellAction::CastMeleeSpellAction(
|
|||||||
bool CastMeleeSpellAction::isUseful()
|
bool CastMeleeSpellAction::isUseful()
|
||||||
{
|
{
|
||||||
Unit* target = GetTarget();
|
Unit* target = GetTarget();
|
||||||
if (!target)
|
if (!target || !bot->IsWithinMeleeRange(target))
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!bot->IsWithinMeleeRange(target))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return CastSpellAction::isUseful();
|
return CastSpellAction::isUseful();
|
||||||
@ -162,10 +259,7 @@ CastMeleeDebuffSpellAction::CastMeleeDebuffSpellAction(
|
|||||||
bool CastMeleeDebuffSpellAction::isUseful()
|
bool CastMeleeDebuffSpellAction::isUseful()
|
||||||
{
|
{
|
||||||
Unit* target = GetTarget();
|
Unit* target = GetTarget();
|
||||||
if (!target)
|
if (!target || !bot->IsWithinMeleeRange(target))
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!bot->IsWithinMeleeRange(target))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return CastDebuffSpellAction::isUseful();
|
return CastDebuffSpellAction::isUseful();
|
||||||
@ -175,14 +269,55 @@ bool CastAuraSpellAction::isUseful()
|
|||||||
{
|
{
|
||||||
if (!GetTarget() || !CastSpellAction::isUseful())
|
if (!GetTarget() || !CastSpellAction::isUseful())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Aura* aura = botAI->GetAura(spell, GetTarget(), isOwner, checkDuration);
|
Aura* aura = botAI->GetAura(spell, GetTarget(), isOwner, checkDuration);
|
||||||
if (!aura)
|
if (!aura || (beforeDuration && aura->GetDuration() < beforeDuration))
|
||||||
return true;
|
|
||||||
if (beforeDuration && aura->GetDuration() < beforeDuration)
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CastBuffSpellAction::isUseful()
|
||||||
|
{
|
||||||
|
Unit* target = GetTarget();
|
||||||
|
if (!target || !CastSpellAction::isUseful())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Aura* aura = botAI->GetAura(spell, target, isOwner, checkDuration);
|
||||||
|
return !aura || (beforeDuration && aura->GetDuration() < beforeDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CastBuffSpellAction::Execute(Event /*event*/)
|
||||||
|
{
|
||||||
|
return botAI->CastSpell(spell, GetTarget());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GroupBuffSpellAction::isUseful()
|
||||||
|
{
|
||||||
|
Unit* target = GetTarget();
|
||||||
|
if (!target || !CastSpellAction::isUseful())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (ai::buff::IsGroupVariantEnabled(bot, spell))
|
||||||
|
{
|
||||||
|
std::string const groupVariant = ai::buff::GroupVariantFor(spell);
|
||||||
|
if (!groupVariant.empty() && botAI->HasAura(groupVariant, target, false, isOwner, -1, checkDuration))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Aura* aura = botAI->GetAura(spell, target, isOwner, checkDuration);
|
||||||
|
if (!aura || (beforeDuration && aura->GetDuration() < beforeDuration))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GroupBuffSpellAction::Execute(Event /*event*/)
|
||||||
|
{
|
||||||
|
std::string const castName = ai::buff::UpgradeToGroupIfAppropriate(bot, botAI, spell);
|
||||||
|
return botAI->CastSpell(castName, GetTarget());
|
||||||
|
}
|
||||||
|
|
||||||
CastEnchantItemMainHandAction::CastEnchantItemMainHandAction(
|
CastEnchantItemMainHandAction::CastEnchantItemMainHandAction(
|
||||||
PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) {}
|
PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) {}
|
||||||
|
|
||||||
@ -248,25 +383,16 @@ Value<Unit*>* CurePartyMemberAction::GetTargetValue()
|
|||||||
return context->GetValue<Unit*>("party member to dispel", dispelType);
|
return context->GetValue<Unit*>("party member to dispel", dispelType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make Bots Paladin, druid, mage use the greater buff rank spell
|
|
||||||
// TODO Priest doen't verify il he have components
|
|
||||||
Value<Unit*>* BuffOnPartyAction::GetTargetValue()
|
Value<Unit*>* BuffOnPartyAction::GetTargetValue()
|
||||||
|
{
|
||||||
|
return context->GetValue<Unit*>("party member without aura", spell);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value<Unit*>* GroupBuffOnPartyAction::GetTargetValue()
|
||||||
{
|
{
|
||||||
return context->GetValue<Unit*>("party member without aura", MakeAuraQualifierForBuff(spell));
|
return context->GetValue<Unit*>("party member without aura", MakeAuraQualifierForBuff(spell));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BuffOnPartyAction::Execute(Event /*event*/)
|
|
||||||
{
|
|
||||||
std::string castName = spell; // default = mono
|
|
||||||
|
|
||||||
auto SendGroupRP = ai::chat::MakeGroupAnnouncer(bot);
|
|
||||||
castName = ai::buff::UpgradeToGroupIfAppropriate(
|
|
||||||
bot, botAI, castName, /*announceOnMissing=*/true, SendGroupRP);
|
|
||||||
|
|
||||||
return botAI->CastSpell(castName, GetTarget());
|
|
||||||
}
|
|
||||||
// End greater buff fix
|
|
||||||
|
|
||||||
CastShootAction::CastShootAction(
|
CastShootAction::CastShootAction(
|
||||||
PlayerbotAI* botAI) : CastSpellAction(botAI, "shoot"), shootSpellId(0)
|
PlayerbotAI* botAI) : CastSpellAction(botAI, "shoot"), shootSpellId(0)
|
||||||
{
|
{
|
||||||
@ -365,16 +491,7 @@ bool CastVehicleSpellAction::Execute(Event /*event*/)
|
|||||||
bool CastEveryManForHimselfAction::isPossible()
|
bool CastEveryManForHimselfAction::isPossible()
|
||||||
{
|
{
|
||||||
uint32 spellId = AI_VALUE2(uint32, "spell id", spell);
|
uint32 spellId = AI_VALUE2(uint32, "spell id", spell);
|
||||||
if (!spellId)
|
return spellId && bot->HasSpell(spellId) && !HasSpellOrCategoryCooldown(bot, spellId);
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!bot->HasSpell(spellId))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (HasSpellOrCategoryCooldown(bot, spellId))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CastEveryManForHimselfAction::isUseful()
|
bool CastEveryManForHimselfAction::isUseful()
|
||||||
@ -390,16 +507,7 @@ bool CastEveryManForHimselfAction::isUseful()
|
|||||||
bool CastWillOfTheForsakenAction::isPossible()
|
bool CastWillOfTheForsakenAction::isPossible()
|
||||||
{
|
{
|
||||||
uint32 spellId = AI_VALUE2(uint32, "spell id", spell);
|
uint32 spellId = AI_VALUE2(uint32, "spell id", spell);
|
||||||
if (!spellId)
|
return spellId && bot->HasSpell(spellId) && !HasSpellOrCategoryCooldown(bot, spellId);
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!bot->HasSpell(spellId))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (HasSpellOrCategoryCooldown(bot, spellId))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CastWillOfTheForsakenAction::isUseful()
|
bool CastWillOfTheForsakenAction::isUseful()
|
||||||
@ -427,70 +535,141 @@ bool UseTrinketAction::Execute(Event /*event*/)
|
|||||||
|
|
||||||
bool UseTrinketAction::UseTrinket(Item* item)
|
bool UseTrinketAction::UseTrinket(Item* item)
|
||||||
{
|
{
|
||||||
if (bot->CanUseItem(item) != EQUIP_ERR_OK)
|
if (bot->CanUseItem(item) != EQUIP_ERR_OK || bot->IsNonMeleeSpellCast(true))
|
||||||
return false;
|
|
||||||
|
|
||||||
if (bot->IsNonMeleeSpellCast(true))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
uint8 bagIndex = item->GetBagSlot();
|
uint8 bagIndex = item->GetBagSlot();
|
||||||
uint8 slot = item->GetSlot();
|
uint8 slot = item->GetSlot();
|
||||||
// uint8 spell_index = 0; //not used, line marked for removal.
|
|
||||||
uint8 cast_count = 1;
|
uint8 cast_count = 1;
|
||||||
ObjectGuid item_guid = item->GetGUID();
|
ObjectGuid item_guid = item->GetGUID();
|
||||||
uint32 glyphIndex = 0;
|
uint32 glyphIndex = 0;
|
||||||
uint8 castFlags = 0;
|
uint8 castFlags = 0;
|
||||||
uint32 targetFlag = TARGET_FLAG_NONE;
|
uint32 targetFlag = TARGET_FLAG_NONE;
|
||||||
uint32 spellId = 0;
|
uint32 spellId = 0;
|
||||||
|
int32 itemSpellCooldown = 0;
|
||||||
|
uint32 itemSpellCategory = 0;
|
||||||
|
int32 itemSpellCategoryCooldown = 0;
|
||||||
|
|
||||||
for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
|
for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
|
||||||
{
|
{
|
||||||
if (item->GetTemplate()->Spells[i].SpellId > 0 &&
|
if (item->GetTemplate()->Spells[i].SpellId > 0 &&
|
||||||
item->GetTemplate()->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_USE)
|
item->GetTemplate()->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_USE)
|
||||||
{
|
{
|
||||||
spellId = item->GetTemplate()->Spells[i].SpellId;
|
spellId = item->GetTemplate()->Spells[i].SpellId;
|
||||||
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
itemSpellCooldown = item->GetTemplate()->Spells[i].SpellCooldown;
|
||||||
|
itemSpellCategory = item->GetTemplate()->Spells[i].SpellCategory;
|
||||||
|
itemSpellCategoryCooldown = item->GetTemplate()->Spells[i].SpellCategoryCooldown;
|
||||||
|
uint64 const itemCooldownKey = (static_cast<uint64>(item->GetEntry()) << 32) | spellId;
|
||||||
|
uint32 const now = getMSTime();
|
||||||
|
|
||||||
|
if (itemSpellCooldown > 0)
|
||||||
|
{
|
||||||
|
auto const itemCooldownItr = trinketItemCooldownExpiries.find(itemCooldownKey);
|
||||||
|
if (itemCooldownItr != trinketItemCooldownExpiries.end())
|
||||||
|
{
|
||||||
|
if (itemCooldownItr->second > now)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
trinketItemCooldownExpiries.erase(itemCooldownItr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemSpellCategory && itemSpellCategoryCooldown > 0)
|
||||||
|
{
|
||||||
|
auto const categoryCooldownItr = trinketCategoryCooldownExpiries.find(itemSpellCategory);
|
||||||
|
if (categoryCooldownItr != trinketCategoryCooldownExpiries.end())
|
||||||
|
{
|
||||||
|
if (categoryCooldownItr->second > now)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
trinketCategoryCooldownExpiries.erase(categoryCooldownItr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||||
if (!spellInfo || !spellInfo->IsPositive())
|
if (!spellInfo || !spellInfo->IsPositive())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
bool applyAura = false;
|
bool applyAura = false;
|
||||||
|
bool restoresMana = false;
|
||||||
|
bool improvesManaEfficiency = false;
|
||||||
|
bool defensiveTankEffect = false;
|
||||||
for (int i = 0; i < MAX_SPELL_EFFECTS; i++)
|
for (int i = 0; i < MAX_SPELL_EFFECTS; i++)
|
||||||
{
|
{
|
||||||
const SpellEffectInfo& effectInfo = spellInfo->Effects[i];
|
const SpellEffectInfo& effectInfo = spellInfo->Effects[i];
|
||||||
if (effectInfo.Effect == SPELL_EFFECT_APPLY_AURA)
|
if (effectInfo.Effect == SPELL_EFFECT_APPLY_AURA)
|
||||||
{
|
|
||||||
applyAura = true;
|
applyAura = true;
|
||||||
break;
|
|
||||||
}
|
restoresMana = restoresMana || IsManaRestoreEffect(effectInfo);
|
||||||
|
improvesManaEfficiency = improvesManaEfficiency || IsManaEfficiencyEffect(effectInfo);
|
||||||
|
defensiveTankEffect = defensiveTankEffect || IsDefensiveTankEffect(effectInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!applyAura)
|
if (!applyAura && !restoresMana)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
uint32 spellProcFlag = spellInfo->ProcFlags;
|
if (restoresMana || improvesManaEfficiency)
|
||||||
|
{
|
||||||
|
if (!AI_VALUE2(bool, "has mana", "self target"))
|
||||||
|
return false;
|
||||||
|
|
||||||
// Handle items with procflag "if you kill a target that grants honor or experience"
|
uint8 const manaPct = AI_VALUE2(uint8, "mana", "self target");
|
||||||
// Bots will "learn" the trinket proc, so CanCastSpell() will be true
|
if ((restoresMana && manaPct >= sPlayerbotAIConfig.mediumMana) ||
|
||||||
// e.g. on Item https://www.wowhead.com/wotlk/item=44074/oracle-talisman-of-ablution leading to
|
manaPct >= sPlayerbotAIConfig.highMana)
|
||||||
// constant casting of the proc spell onto themselfes https://www.wowhead.com/wotlk/spell=59787/oracle-ablutions
|
|
||||||
// This will lead to multiple hundreds of entries in m_appliedAuras -> Once killing an enemy -> Big diff time spikes
|
|
||||||
if (spellProcFlag != 0) return false;
|
|
||||||
|
|
||||||
if (!botAI->CanCastSpell(spellId, bot, false))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defensiveTankEffect)
|
||||||
|
{
|
||||||
|
uint8 const healthPct = AI_VALUE2(uint8, "health", "self target");
|
||||||
|
if (healthPct > sPlayerbotAIConfig.lowHealth)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const& mixedTriggerTrinketSpellIds = GetMixedTriggerTrinketSpellIds();
|
||||||
|
// Exclude trinkets that expose the same spell as both ON_EQUIP and ON_USE across
|
||||||
|
// item templates. Those are equip/proc effects leaking into the active-use path,
|
||||||
|
// as seen with the error versions of Oracle Talisman of Ablution (44870) and
|
||||||
|
// Frenzyheart Insignia of Fury (44869).
|
||||||
|
if (mixedTriggerTrinketSpellIds.find(spellId) != mixedTriggerTrinketSpellIds.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!botAI->CanCastSpell(spellId, bot, false, nullptr, item))
|
||||||
|
return false;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!spellId)
|
if (!spellId)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
WorldPacket packet(CMSG_USE_ITEM);
|
WorldPacket packet(CMSG_USE_ITEM);
|
||||||
packet << bagIndex << slot << cast_count << spellId << item_guid << glyphIndex << castFlags;
|
packet << bagIndex << slot << cast_count << spellId << item_guid << glyphIndex << castFlags;
|
||||||
|
|
||||||
targetFlag = TARGET_FLAG_NONE;
|
targetFlag = TARGET_FLAG_NONE;
|
||||||
packet << targetFlag << bot->GetPackGUID();
|
packet << targetFlag << bot->GetPackGUID();
|
||||||
|
|
||||||
bot->GetSession()->HandleUseItemOpcode(packet);
|
bot->GetSession()->HandleUseItemOpcode(packet);
|
||||||
|
|
||||||
|
uint32 const now = getMSTime();
|
||||||
|
uint32 const cooldownDelay = bot->GetSpellCooldownDelay(spellId);
|
||||||
|
if (cooldownDelay > 0)
|
||||||
|
{
|
||||||
|
if (itemSpellCooldown > 0)
|
||||||
|
{
|
||||||
|
uint64 const itemCooldownKey = (static_cast<uint64>(item->GetEntry()) << 32) | spellId;
|
||||||
|
trinketItemCooldownExpiries[itemCooldownKey] = now + static_cast<uint32>(itemSpellCooldown);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemSpellCategory && itemSpellCategoryCooldown > 0)
|
||||||
|
{
|
||||||
|
trinketCategoryCooldownExpiries[itemSpellCategory] = now + static_cast<uint32>(itemSpellCategoryCooldown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -500,9 +679,8 @@ bool CastDebuffSpellAction::isUseful()
|
|||||||
{
|
{
|
||||||
Unit* target = GetTarget();
|
Unit* target = GetTarget();
|
||||||
if (!target || !target->IsAlive() || !target->IsInWorld())
|
if (!target || !target->IsAlive() || !target->IsInWorld())
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
return CastAuraSpellAction::isUseful() &&
|
return CastAuraSpellAction::isUseful() &&
|
||||||
(target->GetHealth() / AI_VALUE(float, "estimated group dps")) >= needLifeTime;
|
(target->GetHealth() / AI_VALUE(float, "estimated group dps")) >= needLifeTime;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,9 +69,7 @@ class CastDebuffSpellAction : public CastAuraSpellAction
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CastDebuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool isOwner = false, float needLifeTime = 8.0f)
|
CastDebuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool isOwner = false, float needLifeTime = 8.0f)
|
||||||
: CastAuraSpellAction(botAI, spell, isOwner), needLifeTime(needLifeTime)
|
: CastAuraSpellAction(botAI, spell, isOwner), needLifeTime(needLifeTime) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
bool isUseful() override;
|
bool isUseful() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -90,9 +88,7 @@ class CastDebuffSpellOnAttackerAction : public CastDebuffSpellAction
|
|||||||
public:
|
public:
|
||||||
CastDebuffSpellOnAttackerAction(PlayerbotAI* botAI, std::string const spell, bool isOwner = true,
|
CastDebuffSpellOnAttackerAction(PlayerbotAI* botAI, std::string const spell, bool isOwner = true,
|
||||||
float needLifeTime = 8.0f)
|
float needLifeTime = 8.0f)
|
||||||
: CastDebuffSpellAction(botAI, spell, isOwner, needLifeTime)
|
: CastDebuffSpellAction(botAI, spell, isOwner, needLifeTime) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Value<Unit*>* GetTargetValue() override;
|
Value<Unit*>* GetTargetValue() override;
|
||||||
std::string const getName() override { return spell + " on attacker"; }
|
std::string const getName() override { return spell + " on attacker"; }
|
||||||
@ -104,9 +100,7 @@ class CastDebuffSpellOnMeleeAttackerAction : public CastDebuffSpellAction
|
|||||||
public:
|
public:
|
||||||
CastDebuffSpellOnMeleeAttackerAction(PlayerbotAI* botAI, std::string const spell, bool isOwner = true,
|
CastDebuffSpellOnMeleeAttackerAction(PlayerbotAI* botAI, std::string const spell, bool isOwner = true,
|
||||||
float needLifeTime = 8.0f)
|
float needLifeTime = 8.0f)
|
||||||
: CastDebuffSpellAction(botAI, spell, isOwner, needLifeTime)
|
: CastDebuffSpellAction(botAI, spell, isOwner, needLifeTime) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Value<Unit*>* GetTargetValue() override;
|
Value<Unit*>* GetTargetValue() override;
|
||||||
std::string const getName() override { return spell + " on attacker"; }
|
std::string const getName() override { return spell + " on attacker"; }
|
||||||
@ -119,6 +113,19 @@ public:
|
|||||||
CastBuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner = false, uint32 beforeDuration = 0);
|
CastBuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner = false, uint32 beforeDuration = 0);
|
||||||
|
|
||||||
std::string const GetTargetName() override { return "self target"; }
|
std::string const GetTargetName() override { return "self target"; }
|
||||||
|
bool isUseful() override;
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GroupBuffSpellAction : public CastBuffSpellAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GroupBuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner = false,
|
||||||
|
uint32 beforeDuration = 0)
|
||||||
|
: CastBuffSpellAction(botAI, spell, checkIsOwner, beforeDuration) {}
|
||||||
|
|
||||||
|
bool isUseful() override;
|
||||||
|
bool Execute(Event event) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastEnchantItemMainHandAction : public CastSpellAction
|
class CastEnchantItemMainHandAction : public CastSpellAction
|
||||||
@ -151,8 +158,6 @@ public:
|
|||||||
// Yunfan: Mana efficiency tell the bot how to save mana. The higher the better.
|
// Yunfan: Mana efficiency tell the bot how to save mana. The higher the better.
|
||||||
HealingManaEfficiency manaEfficiency;
|
HealingManaEfficiency manaEfficiency;
|
||||||
uint8 estAmount;
|
uint8 estAmount;
|
||||||
|
|
||||||
// protected:
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastAoeHealSpellAction : public CastHealingSpellAction
|
class CastAoeHealSpellAction : public CastHealingSpellAction
|
||||||
@ -192,9 +197,7 @@ class HealPartyMemberAction : public CastHealingSpellAction, public PartyMemberA
|
|||||||
public:
|
public:
|
||||||
HealPartyMemberAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount = 15.0f,
|
HealPartyMemberAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount = 15.0f,
|
||||||
HealingManaEfficiency manaEfficiency = HealingManaEfficiency::MEDIUM, bool isOwner = true)
|
HealingManaEfficiency manaEfficiency = HealingManaEfficiency::MEDIUM, bool isOwner = true)
|
||||||
: CastHealingSpellAction(botAI, spell, estAmount, manaEfficiency, isOwner), PartyMemberActionNameSupport(spell)
|
: CastHealingSpellAction(botAI, spell, estAmount, manaEfficiency, isOwner), PartyMemberActionNameSupport(spell) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string const GetTargetName() override { return "party member to heal"; }
|
std::string const GetTargetName() override { return "party member to heal"; }
|
||||||
std::string const getName() override { return PartyMemberActionNameSupport::getName(); }
|
std::string const getName() override { return PartyMemberActionNameSupport::getName(); }
|
||||||
@ -219,9 +222,7 @@ class CurePartyMemberAction : public CastSpellAction, public PartyMemberActionNa
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CurePartyMemberAction(PlayerbotAI* botAI, std::string const spell, uint32 dispelType)
|
CurePartyMemberAction(PlayerbotAI* botAI, std::string const spell, uint32 dispelType)
|
||||||
: CastSpellAction(botAI, spell), PartyMemberActionNameSupport(spell), dispelType(dispelType)
|
: CastSpellAction(botAI, spell), PartyMemberActionNameSupport(spell), dispelType(dispelType) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Value<Unit*>* GetTargetValue() override;
|
Value<Unit*>* GetTargetValue() override;
|
||||||
std::string const getName() override { return PartyMemberActionNameSupport::getName(); }
|
std::string const getName() override { return PartyMemberActionNameSupport::getName(); }
|
||||||
@ -230,18 +231,25 @@ protected:
|
|||||||
uint32 dispelType;
|
uint32 dispelType;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Make Bots Paladin, druid, mage use the greater buff rank spell
|
|
||||||
class BuffOnPartyAction : public CastBuffSpellAction, public PartyMemberActionNameSupport
|
class BuffOnPartyAction : public CastBuffSpellAction, public PartyMemberActionNameSupport
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
BuffOnPartyAction(PlayerbotAI* botAI, std::string const spell)
|
BuffOnPartyAction(PlayerbotAI* botAI, std::string const spell)
|
||||||
: CastBuffSpellAction(botAI, spell), PartyMemberActionNameSupport(spell) { }
|
: CastBuffSpellAction(botAI, spell), PartyMemberActionNameSupport(spell) {}
|
||||||
|
|
||||||
|
Value<Unit*>* GetTargetValue() override;
|
||||||
|
std::string const getName() override { return PartyMemberActionNameSupport::getName(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class GroupBuffOnPartyAction : public GroupBuffSpellAction, public PartyMemberActionNameSupport
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GroupBuffOnPartyAction(PlayerbotAI* botAI, std::string const spell)
|
||||||
|
: GroupBuffSpellAction(botAI, spell), PartyMemberActionNameSupport(spell) {}
|
||||||
|
|
||||||
Value<Unit*>* GetTargetValue() override;
|
Value<Unit*>* GetTargetValue() override;
|
||||||
bool Execute(Event event) override;
|
|
||||||
std::string const getName() override { return PartyMemberActionNameSupport::getName(); }
|
std::string const getName() override { return PartyMemberActionNameSupport::getName(); }
|
||||||
};
|
};
|
||||||
// End Fix
|
|
||||||
|
|
||||||
class CastShootAction : public CastSpellAction
|
class CastShootAction : public CastSpellAction
|
||||||
{
|
{
|
||||||
@ -323,8 +331,13 @@ class UseTrinketAction : public Action
|
|||||||
public:
|
public:
|
||||||
UseTrinketAction(PlayerbotAI* botAI) : Action(botAI, "use trinket") {}
|
UseTrinketAction(PlayerbotAI* botAI) : Action(botAI, "use trinket") {}
|
||||||
bool Execute(Event event) override;
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool UseTrinket(Item* trinket);
|
bool UseTrinket(Item* trinket);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<uint64, uint32> trinketItemCooldownExpiries;
|
||||||
|
std::unordered_map<uint32, uint32> trinketCategoryCooldownExpiries;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastSpellOnEnemyHealerAction : public CastSpellAction
|
class CastSpellOnEnemyHealerAction : public CastSpellAction
|
||||||
@ -461,12 +474,11 @@ class BuffOnMainTankAction : public CastBuffSpellAction, public MainTankActionNa
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
BuffOnMainTankAction(PlayerbotAI* ai, std::string spell, bool checkIsOwner = false)
|
BuffOnMainTankAction(PlayerbotAI* ai, std::string spell, bool checkIsOwner = false)
|
||||||
: CastBuffSpellAction(ai, spell, checkIsOwner), MainTankActionNameSupport(spell)
|
: CastBuffSpellAction(ai, spell, checkIsOwner), MainTankActionNameSupport(spell) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual Value<Unit*>* GetTargetValue();
|
virtual Value<Unit*>* GetTargetValue();
|
||||||
virtual std::string const getName() { return MainTankActionNameSupport::getName(); }
|
virtual std::string const getName() { return MainTankActionNameSupport::getName(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "GuildPackets.h"
|
#include "GuildPackets.h"
|
||||||
#include "PlayerbotSecurity.h"
|
#include "PlayerbotSecurity.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
bool GuildAcceptAction::Execute(Event event)
|
bool GuildAcceptAction::Execute(Event event)
|
||||||
@ -28,17 +29,20 @@ bool GuildAcceptAction::Execute(Event event)
|
|||||||
uint32 guildId = inviter->GetGuildId();
|
uint32 guildId = inviter->GetGuildId();
|
||||||
if (!guildId)
|
if (!guildId)
|
||||||
{
|
{
|
||||||
botAI->TellError("You are not in a guild!");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"guild_accept_inviter_not_in_guild", "You are not in a guild!", {}));
|
||||||
accept = false;
|
accept = false;
|
||||||
}
|
}
|
||||||
else if (bot->GetGuildId())
|
else if (bot->GetGuildId())
|
||||||
{
|
{
|
||||||
botAI->TellError("Sorry, I am in a guild already");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"guild_accept_already_in_guild", "Sorry, I am in a guild already", {}));
|
||||||
accept = false;
|
accept = false;
|
||||||
}
|
}
|
||||||
else if (!botAI->GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_INVITE, false, inviter, true))
|
else if (!botAI->GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_INVITE, false, inviter, true))
|
||||||
{
|
{
|
||||||
botAI->TellError("Sorry, I don't want to join your guild :(");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"guild_accept_declined", "Sorry, I don't want to join your guild :(", {}));
|
||||||
accept = false;
|
accept = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -308,9 +308,8 @@ bool LfgAction::Execute(Event event)
|
|||||||
allowedRoles[BOT_ROLE_HEALER] = 1;
|
allowedRoles[BOT_ROLE_HEALER] = 1;
|
||||||
allowedRoles[BOT_ROLE_DPS] = 3;
|
allowedRoles[BOT_ROLE_DPS] = 3;
|
||||||
|
|
||||||
BotRoles role = botAI->IsTank(requester, false)
|
BotRoles role = botAI->IsTank(requester, true) ? BOT_ROLE_TANK
|
||||||
? BOT_ROLE_TANK
|
: (botAI->IsHeal(requester, true) ? BOT_ROLE_HEALER : BOT_ROLE_DPS);
|
||||||
: (botAI->IsHeal(requester, false) ? BOT_ROLE_HEALER : BOT_ROLE_DPS);
|
|
||||||
Classes cls = (Classes)requester->getClass();
|
Classes cls = (Classes)requester->getClass();
|
||||||
|
|
||||||
if (group)
|
if (group)
|
||||||
@ -383,8 +382,8 @@ bool LfgAction::Execute(Event event)
|
|||||||
if (!botAI->IsSafe(player))
|
if (!botAI->IsSafe(player))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
role = botAI->IsTank(player, false) ? BOT_ROLE_TANK
|
role = botAI->IsTank(player, true) ? BOT_ROLE_TANK
|
||||||
: (botAI->IsHeal(player, false) ? BOT_ROLE_HEALER : BOT_ROLE_DPS);
|
: (botAI->IsHeal(player, true) ? BOT_ROLE_HEALER : BOT_ROLE_DPS);
|
||||||
cls = (Classes)player->getClass();
|
cls = (Classes)player->getClass();
|
||||||
|
|
||||||
if (allowedRoles[role] > 0)
|
if (allowedRoles[role] > 0)
|
||||||
@ -403,7 +402,7 @@ bool LfgAction::Execute(Event event)
|
|||||||
allowedClassNr[cls][role]--;
|
allowedClassNr[cls][role]--;
|
||||||
}
|
}
|
||||||
|
|
||||||
role = botAI->IsTank(bot, false) ? BOT_ROLE_TANK : (botAI->IsHeal(bot, false) ? BOT_ROLE_HEALER : BOT_ROLE_DPS);
|
role = botAI->IsTank(bot, true) ? BOT_ROLE_TANK : (botAI->IsHeal(bot, true) ? BOT_ROLE_HEALER : BOT_ROLE_DPS);
|
||||||
cls = (Classes)bot->getClass();
|
cls = (Classes)bot->getClass();
|
||||||
|
|
||||||
if (allowedRoles[role] == 0)
|
if (allowedRoles[role] == 0)
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "PlayerbotAIConfig.h"
|
#include "PlayerbotAIConfig.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
bool LeaveGroupAction::Execute(Event event)
|
bool LeaveGroupAction::Execute(Event event)
|
||||||
@ -86,7 +87,9 @@ bool LeaveGroupAction::Leave()
|
|||||||
|
|
||||||
Player* master = botAI -> GetMaster();
|
Player* master = botAI -> GetMaster();
|
||||||
if (master)
|
if (master)
|
||||||
botAI->TellMaster("Goodbye!", PLAYERBOT_SECURITY_TALK);
|
botAI->TellMaster(
|
||||||
|
PlayerbotTextMgr::instance().GetBotTextOrDefault("goodbye", "Goodbye!", {}),
|
||||||
|
PLAYERBOT_SECURITY_TALK);
|
||||||
|
|
||||||
botAI->LeaveOrDisbandGroup();
|
botAI->LeaveOrDisbandGroup();
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -5,6 +5,9 @@
|
|||||||
|
|
||||||
#include "LootAction.h"
|
#include "LootAction.h"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include "Bag.h"
|
||||||
#include "ChatHelper.h"
|
#include "ChatHelper.h"
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "GuildMgr.h"
|
#include "GuildMgr.h"
|
||||||
@ -76,7 +79,11 @@ bool OpenLootAction::Execute(Event /*event*/)
|
|||||||
bool result = DoLoot(lootObject);
|
bool result = DoLoot(lootObject);
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
AI_VALUE(LootObjectStack*, "available loot")->Remove(lootObject.guid);
|
// MarkCompleted (not Remove) — "add all loot" reads
|
||||||
|
// "nearest corpses" without a lootable filter, so a plain
|
||||||
|
// Remove lets the same corpse re-enter the stack on the next
|
||||||
|
// tick. The completed set blocks re-add for ~5 min.
|
||||||
|
AI_VALUE(LootObjectStack*, "available loot")->MarkCompleted(lootObject.guid);
|
||||||
context->GetValue<LootObject>("loot target")->Set(LootObject());
|
context->GetValue<LootObject>("loot target")->Set(LootObject());
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@ -139,8 +146,9 @@ bool OpenLootAction::DoLoot(LootObject& lootObject)
|
|||||||
if (go && (go->GetGoState() != GO_STATE_READY))
|
if (go && (go->GetGoState() != GO_STATE_READY))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// This prevents dungeon chests like Tribunal Chest (Halls of Stone) from being ninja'd by the bots
|
// Block event-gated chests (Tribunal Chest, Gunship Armory) but allow
|
||||||
if (go && go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_INTERACT_COND))
|
// wild quest GOs (Moonpetal Lily etc.) when the bot is on the quest.
|
||||||
|
if (go && go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_INTERACT_COND) && !lootObject.isNeededQuestItem)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// This prevents raid chests like Gunship Armory (ICC) from being ninja'd by the bots
|
// This prevents raid chests like Gunship Armory (ICC) from being ninja'd by the bots
|
||||||
@ -377,6 +385,12 @@ bool StoreLootAction::Execute(Event event)
|
|||||||
// bot->GetSession()->HandleLootMoneyOpcode(packet);
|
// bot->GetSession()->HandleLootMoneyOpcode(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// one make-room destroy per loot packet — CanStoreNewItem after a junk
|
||||||
|
// destroy can still report full while CMSG_AUTOSTORE_LOOT_ITEM is
|
||||||
|
// queued, so a multi-quest-item packet would otherwise destroy more
|
||||||
|
// junk than necessary
|
||||||
|
bool destroyedThisPacket = false;
|
||||||
|
|
||||||
for (uint8 i = 0; i < items; ++i)
|
for (uint8 i = 0; i < items; ++i)
|
||||||
{
|
{
|
||||||
uint32 itemid;
|
uint32 itemid;
|
||||||
@ -402,7 +416,9 @@ bool StoreLootAction::Execute(Event event)
|
|||||||
if (!proto)
|
if (!proto)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!botAI->HasActivePlayerMaster() && AI_VALUE(uint8, "bag space") > 80)
|
// bags >80%: skip non-stackable junk (quest items exempt)
|
||||||
|
if (!botAI->HasActivePlayerMaster() && AI_VALUE(uint8, "bag space") > 80 &&
|
||||||
|
!bot->HasQuestForItem(itemid))
|
||||||
{
|
{
|
||||||
uint32 maxStack = proto->GetMaxStackSize();
|
uint32 maxStack = proto->GetMaxStackSize();
|
||||||
if (maxStack == 1)
|
if (maxStack == 1)
|
||||||
@ -438,6 +454,55 @@ bool StoreLootAction::Execute(Event event)
|
|||||||
GuildTaskMgr::instance().CheckItemTask(itemid, itemcount, ref->GetSource(), bot);
|
GuildTaskMgr::instance().CheckItemTask(itemid, itemcount, ref->GetSource(), bot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bags full + quest item: make room by dropping cheapest junk
|
||||||
|
if (!destroyedThisPacket && bot->HasQuestForItem(itemid))
|
||||||
|
{
|
||||||
|
ItemPosCountVec dest;
|
||||||
|
InventoryResult can =
|
||||||
|
bot->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemid, itemcount);
|
||||||
|
if (can == EQUIP_ERR_INVENTORY_FULL || can == EQUIP_ERR_BAG_FULL)
|
||||||
|
{
|
||||||
|
// picked by usage, not quality — high-level bots have no grays
|
||||||
|
Item* victim = nullptr;
|
||||||
|
uint32 minPrice = std::numeric_limits<uint32>::max();
|
||||||
|
auto consider = [&](uint8 bag, uint8 slot)
|
||||||
|
{
|
||||||
|
Item* it = bot->GetItemByPos(bag, slot);
|
||||||
|
if (!it)
|
||||||
|
return;
|
||||||
|
ItemTemplate const* tpl = it->GetTemplate();
|
||||||
|
if (!tpl)
|
||||||
|
return;
|
||||||
|
if (bot->HasQuestForItem(tpl->ItemId))
|
||||||
|
return;
|
||||||
|
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", tpl->ItemId);
|
||||||
|
if (usage != ITEM_USAGE_NONE && usage != ITEM_USAGE_VENDOR &&
|
||||||
|
usage != ITEM_USAGE_BAD_EQUIP && usage != ITEM_USAGE_BROKEN_EQUIP)
|
||||||
|
return;
|
||||||
|
if (tpl->SellPrice < minPrice)
|
||||||
|
{
|
||||||
|
minPrice = tpl->SellPrice;
|
||||||
|
victim = it;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; ++slot)
|
||||||
|
consider(INVENTORY_SLOT_BAG_0, slot);
|
||||||
|
for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag)
|
||||||
|
{
|
||||||
|
Bag* pBag = bot->GetBagByPos(bag);
|
||||||
|
if (!pBag)
|
||||||
|
continue;
|
||||||
|
for (uint32 slot = 0; slot < pBag->GetBagSize(); ++slot)
|
||||||
|
consider(bag, static_cast<uint8>(slot));
|
||||||
|
}
|
||||||
|
if (victim)
|
||||||
|
{
|
||||||
|
bot->DestroyItem(victim->GetBagSlot(), victim->GetSlot(), true);
|
||||||
|
destroyedThisPacket = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
WorldPacket* packet = new WorldPacket(CMSG_AUTOSTORE_LOOT_ITEM, 1);
|
WorldPacket* packet = new WorldPacket(CMSG_AUTOSTORE_LOOT_ITEM, 1);
|
||||||
*packet << itemindex;
|
*packet << itemindex;
|
||||||
bot->GetSession()->QueuePacket(packet);
|
bot->GetSession()->QueuePacket(packet);
|
||||||
@ -453,7 +518,7 @@ bool StoreLootAction::Execute(Event event)
|
|||||||
BroadcastHelper::BroadcastLootingItem(botAI, bot, proto);
|
BroadcastHelper::BroadcastLootingItem(botAI, bot, proto);
|
||||||
}
|
}
|
||||||
|
|
||||||
AI_VALUE(LootObjectStack*, "available loot")->Remove(guid);
|
AI_VALUE(LootObjectStack*, "available loot")->MarkCompleted(guid);
|
||||||
|
|
||||||
// release loot
|
// release loot
|
||||||
WorldPacket* packet = new WorldPacket(CMSG_LOOT_RELEASE, 8);
|
WorldPacket* packet = new WorldPacket(CMSG_LOOT_RELEASE, 8);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include "Action.h"
|
#include "Action.h"
|
||||||
#include "LastMovementValue.h"
|
#include "LastMovementValue.h"
|
||||||
|
#include "PathGenerator.h"
|
||||||
#include "PlayerbotAIConfig.h"
|
#include "PlayerbotAIConfig.h"
|
||||||
|
|
||||||
class Player;
|
class Player;
|
||||||
@ -22,12 +23,33 @@ class Position;
|
|||||||
#define ANGLE_90_DEG M_PI_2
|
#define ANGLE_90_DEG M_PI_2
|
||||||
#define ANGLE_120_DEG (2.f * static_cast<float>(M_PI) / 3.f)
|
#define ANGLE_120_DEG (2.f * static_cast<float>(M_PI) / 3.f)
|
||||||
|
|
||||||
|
// Default acceptable path types for GeneratePath
|
||||||
|
constexpr uint32 DEFAULT_PATH_ACCEPT_MASK = PATHFIND_NORMAL | PATHFIND_INCOMPLETE;
|
||||||
|
constexpr uint32 RELAXED_PATH_ACCEPT_MASK = PATHFIND_NORMAL | PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY;
|
||||||
|
|
||||||
|
struct PathResult
|
||||||
|
{
|
||||||
|
Movement::PointsArray points;
|
||||||
|
G3D::Vector3 actualEnd;
|
||||||
|
G3D::Vector3 end;
|
||||||
|
PathType pathType;
|
||||||
|
bool reachable;
|
||||||
|
};
|
||||||
|
|
||||||
class MovementAction : public Action
|
class MovementAction : public Action
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MovementAction(PlayerbotAI* botAI, std::string const name);
|
MovementAction(PlayerbotAI* botAI, std::string const name);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
// Emit a one-line trace describing the imminent movement. No-op
|
||||||
|
// unless the bot has the "debug move" non-combat strategy.
|
||||||
|
// Subclasses (e.g. NewRpgBaseAction) may override to append richer
|
||||||
|
// context such as RPG status and target name. Optional `extra`
|
||||||
|
// is appended verbatim (use it to attach hop labels like
|
||||||
|
// "node:Stormwind innkeeper" or fallback reasons).
|
||||||
|
virtual void EmitDebugMove(char const* method, char const* generator, float x, float y, float z, char const* extra = nullptr);
|
||||||
|
|
||||||
bool JumpTo(uint32 mapId, float x, float y, float z, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
bool JumpTo(uint32 mapId, float x, float y, float z, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
||||||
bool MoveNear(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig.contactDistance,
|
bool MoveNear(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig.contactDistance,
|
||||||
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
||||||
@ -66,6 +88,31 @@ protected:
|
|||||||
bool FleePosition(Position pos, float radius, uint32 minInterval = 1000);
|
bool FleePosition(Position pos, float radius, uint32 minInterval = 1000);
|
||||||
bool CheckLastFlee(float curAngle, std::list<FleeInfo>& infoList);
|
bool CheckLastFlee(float curAngle, std::list<FleeInfo>& infoList);
|
||||||
|
|
||||||
|
PathResult GeneratePath(float x, float y, float z, uint32 acceptMask = DEFAULT_PATH_ACCEPT_MASK, bool forceDestination = false);
|
||||||
|
|
||||||
|
bool GetTravelPlan(TravelPlan& plan, WorldPosition destination);
|
||||||
|
bool ExecuteTravelPlan(TravelPlan& state);
|
||||||
|
|
||||||
|
// Transport boarding helpers (shared by FollowAction and travel plan)
|
||||||
|
static Transport* GetTransportForPosTolerant(Map* map, WorldObject* ref,
|
||||||
|
uint32 phaseMask, float x, float y, float z);
|
||||||
|
static bool FindBoardingPointOnTransport(Map* map, Transport* transport,
|
||||||
|
WorldObject* ref, float refX, float refY, float refZ,
|
||||||
|
float botX, float botY, float botZ,
|
||||||
|
float& outX, float& outY, float& outZ);
|
||||||
|
bool BoardTransport(Transport* transport);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool LaunchWalkSpline(TravelPlan& state);
|
||||||
|
bool CheckSplineProgress(TravelPlan& state);
|
||||||
|
bool MoveToSpline(TravelPlan& state, WorldPosition target);
|
||||||
|
// Per-segment mmap refinement of a travel-node-graph walk batch.
|
||||||
|
// The graph stores offline-baked coords whose straight-line
|
||||||
|
// interpolation may pass through geometry the bot can't actually
|
||||||
|
// traverse. Returns false if any segment is unwalkable per the
|
||||||
|
// live navmesh, in which case the caller should abort the plan.
|
||||||
|
bool RefineWalkPoints(std::vector<G3D::Vector3>& walkPoints);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
struct CheckAngle
|
struct CheckAngle
|
||||||
{
|
{
|
||||||
@ -74,10 +121,6 @@ protected:
|
|||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// float SearchBestGroundZForPath(float x, float y, float z, bool generatePath, float range = 20.0f, bool
|
|
||||||
// normal_only = false, float step = 8.0f);
|
|
||||||
const Movement::PointsArray SearchForBestPath(float x, float y, float z, float& modified_z, int maxSearchCount = 5,
|
|
||||||
bool normal_only = false, float step = 8.0f);
|
|
||||||
bool wasMovementRestricted = false;
|
bool wasMovementRestricted = false;
|
||||||
void DoMovePoint(Unit* unit, float x, float y, float z, bool generatePath, bool backwards);
|
void DoMovePoint(Unit* unit, float x, float y, float z, bool generatePath, bool backwards);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "ItemVisitors.h"
|
#include "ItemVisitors.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
|
#include "PlayerbotRepository.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "ItemPackets.h"
|
#include "ItemPackets.h"
|
||||||
|
|
||||||
@ -17,9 +19,12 @@ bool OutfitAction::Execute(Event event)
|
|||||||
if (param == "?")
|
if (param == "?")
|
||||||
{
|
{
|
||||||
List();
|
List();
|
||||||
botAI->TellMaster("outfit <name> +[item] to add items");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
botAI->TellMaster("outfit <name> -[item] to remove items");
|
"outfit_usage_add", "outfit <name> +[item] to add items", {}));
|
||||||
botAI->TellMaster("outfit <name> equip/replace to equip items");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"outfit_usage_remove", "outfit <name> -[item] to remove items", {}));
|
||||||
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"outfit_usage_equip", "outfit <name> equip/replace to equip items", {}));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -28,10 +33,13 @@ bool OutfitAction::Execute(Event event)
|
|||||||
if (!name.empty())
|
if (!name.empty())
|
||||||
{
|
{
|
||||||
Save(name, items);
|
Save(name, items);
|
||||||
|
PlayerbotRepository::instance().Save(botAI);
|
||||||
|
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << "Setting outfit " << name << " as " << param;
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
botAI->TellMaster(out);
|
"outfit_set_as",
|
||||||
|
"Setting outfit %name as %param",
|
||||||
|
{{"%name", name}, {"%param", param}}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,18 +55,20 @@ bool OutfitAction::Execute(Event event)
|
|||||||
std::string const command = param.substr(space + 1);
|
std::string const command = param.substr(space + 1);
|
||||||
if (command == "equip")
|
if (command == "equip")
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
out << "Equipping outfit " << name;
|
"outfit_equipping",
|
||||||
botAI->TellMaster(out);
|
"Equipping outfit %name",
|
||||||
|
{{"%name", name}}));
|
||||||
|
|
||||||
EquipItems(outfit);
|
EquipItems(outfit);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (command == "replace")
|
else if (command == "replace")
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
out << "Replacing current equip with outfit " << name;
|
"outfit_replace_current",
|
||||||
botAI->TellMaster(out);
|
"Replacing current equip with outfit %name",
|
||||||
|
{{"%name", name}}));
|
||||||
|
|
||||||
for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; slot++)
|
for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; slot++)
|
||||||
{
|
{
|
||||||
@ -81,20 +91,24 @@ bool OutfitAction::Execute(Event event)
|
|||||||
}
|
}
|
||||||
else if (command == "reset")
|
else if (command == "reset")
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
out << "Resetting outfit " << name;
|
"outfit_resetting",
|
||||||
botAI->TellMaster(out);
|
"Resetting outfit %name",
|
||||||
|
{{"%name", name}}));
|
||||||
|
|
||||||
Save(name, ItemIds());
|
Save(name, ItemIds());
|
||||||
|
PlayerbotRepository::instance().Save(botAI);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (command == "update")
|
else if (command == "update")
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
out << "Updating with current items outfit " << name;
|
"outfit_updating_current",
|
||||||
botAI->TellMaster(out);
|
"Updating with current items outfit %name",
|
||||||
|
{{"%name", name}}));
|
||||||
|
|
||||||
Update(name);
|
Update(name);
|
||||||
|
PlayerbotRepository::instance().Save(botAI);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,27 +117,29 @@ bool OutfitAction::Execute(Event event)
|
|||||||
{
|
{
|
||||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemid);
|
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemid);
|
||||||
|
|
||||||
std::ostringstream out;
|
|
||||||
out << chat->FormatItem(proto);
|
|
||||||
if (remove)
|
if (remove)
|
||||||
{
|
{
|
||||||
std::set<uint32>::iterator j = outfit.find(itemid);
|
std::set<uint32>::iterator j = outfit.find(itemid);
|
||||||
if (j != outfit.end())
|
if (j != outfit.end())
|
||||||
outfit.erase(j);
|
outfit.erase(j);
|
||||||
|
|
||||||
out << " removed from ";
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"outfit_item_removed_from",
|
||||||
|
"%item removed from %name",
|
||||||
|
{{"%item", chat->FormatItem(proto)}, {"%name", name}}));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
outfit.insert(itemid);
|
outfit.insert(itemid);
|
||||||
out << " added to ";
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"outfit_item_added_to",
|
||||||
|
"%item added to %name",
|
||||||
|
{{"%item", chat->FormatItem(proto)}, {"%name", name}}));
|
||||||
}
|
}
|
||||||
|
|
||||||
out << name;
|
|
||||||
botAI->TellMaster(out.str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Save(name, outfit);
|
Save(name, outfit);
|
||||||
|
PlayerbotRepository::instance().Save(botAI);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
#include "NearestNpcsValue.h"
|
#include "NearestNpcsValue.h"
|
||||||
#include "ObjectDefines.h"
|
#include "ObjectDefines.h"
|
||||||
#include "ObjectGuid.h"
|
#include "ObjectGuid.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "ServerFacade.h"
|
#include "ServerFacade.h"
|
||||||
#include "Corpse.h"
|
#include "Corpse.h"
|
||||||
@ -22,7 +23,8 @@ bool ReleaseSpiritAction::Execute(Event event)
|
|||||||
{
|
{
|
||||||
if (!bot->InBattleground())
|
if (!bot->InBattleground())
|
||||||
{
|
{
|
||||||
botAI->TellMasterNoFacing("I am not dead, will wait here");
|
botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"release_spirit_not_dead_wait", "I am not dead, will wait here", {}));
|
||||||
// -follow in bg is overwriten each tick with +follow
|
// -follow in bg is overwriten each tick with +follow
|
||||||
// +stay in bg causes stuttering effect as bot is cycled between +stay and +follow each tick
|
// +stay in bg causes stuttering effect as bot is cycled between +stay and +follow each tick
|
||||||
botAI->ChangeStrategy("-follow,+stay", BOT_STATE_NON_COMBAT);
|
botAI->ChangeStrategy("-follow,+stay", BOT_STATE_NON_COMBAT);
|
||||||
@ -33,14 +35,15 @@ bool ReleaseSpiritAction::Execute(Event event)
|
|||||||
|
|
||||||
if (bot->GetCorpse() && bot->HasPlayerFlag(PLAYER_FLAGS_GHOST))
|
if (bot->GetCorpse() && bot->HasPlayerFlag(PLAYER_FLAGS_GHOST))
|
||||||
{
|
{
|
||||||
botAI->TellMasterNoFacing("I am already a spirit");
|
botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"release_spirit_already_spirit", "I am already a spirit", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WorldPacket& packet = event.getPacket();
|
const WorldPacket& packet = event.getPacket();
|
||||||
const std::string message = !packet.empty() && packet.GetOpcode() == CMSG_REPOP_REQUEST
|
const std::string message = !packet.empty() && packet.GetOpcode() == CMSG_REPOP_REQUEST
|
||||||
? "Releasing..."
|
? PlayerbotTextMgr::instance().GetBotTextOrDefault("release_spirit_releasing", "Releasing...", {})
|
||||||
: "Meet me at the graveyard";
|
: PlayerbotTextMgr::instance().GetBotTextOrDefault("release_spirit_meet_graveyard", "Meet me at the graveyard", {});
|
||||||
botAI->TellMasterNoFacing(message);
|
botAI->TellMasterNoFacing(message);
|
||||||
|
|
||||||
IncrementDeathCount();
|
IncrementDeathCount();
|
||||||
|
|||||||
@ -44,6 +44,21 @@ bool ResetAiAction::Execute(Event event)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (Player* master = botAI->GetMaster())
|
||||||
|
{
|
||||||
|
Group* botGroup = bot->GetGroup();
|
||||||
|
Group* masterGroup = master->GetGroup();
|
||||||
|
if (botGroup && (!masterGroup || masterGroup != botGroup))
|
||||||
|
botAI->SetMaster(nullptr);
|
||||||
|
}
|
||||||
|
if (sRandomPlayerbotMgr.IsRandomBot(bot) && !bot->InBattleground())
|
||||||
|
{
|
||||||
|
if (bot->GetGroup() && (!botAI->GetMaster() || GET_PLAYERBOT_AI(botAI->GetMaster())))
|
||||||
|
{
|
||||||
|
if (Player* newMaster = botAI->FindNewMaster())
|
||||||
|
botAI->SetMaster(newMaster);
|
||||||
|
}
|
||||||
|
}
|
||||||
PlayerbotRepository::instance().Reset(botAI);
|
PlayerbotRepository::instance().Reset(botAI);
|
||||||
botAI->ResetStrategies(false);
|
botAI->ResetStrategies(false);
|
||||||
botAI->TellMaster("AI was reset to defaults");
|
botAI->TellMaster("AI was reset to defaults");
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
#include "FleeManager.h"
|
#include "FleeManager.h"
|
||||||
#include "GameGraveyard.h"
|
#include "GameGraveyard.h"
|
||||||
#include "MapMgr.h"
|
#include "MapMgr.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "RandomPlayerbotMgr.h"
|
#include "RandomPlayerbotMgr.h"
|
||||||
#include "ServerFacade.h"
|
#include "ServerFacade.h"
|
||||||
@ -322,7 +323,7 @@ bool SpiritHealerAction::Execute(Event /*event*/)
|
|||||||
bot->SpawnCorpseBones();
|
bot->SpawnCorpseBones();
|
||||||
context->GetValue<Unit*>("current target")->Set(nullptr);
|
context->GetValue<Unit*>("current target")->Set(nullptr);
|
||||||
bot->SetTarget();
|
bot->SetTarget();
|
||||||
botAI->TellMaster("Hello");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault("hello", "Hello", {}));
|
||||||
|
|
||||||
if (dCount > 20)
|
if (dCount > 20)
|
||||||
context->GetValue<uint32>("death count")->Set(0);
|
context->GetValue<uint32>("death count")->Set(0);
|
||||||
|
|||||||
@ -12,7 +12,7 @@ bool SecurityCheckAction::isUseful()
|
|||||||
{
|
{
|
||||||
return RandomPlayerbotMgr::instance().IsRandomBot(bot)
|
return RandomPlayerbotMgr::instance().IsRandomBot(bot)
|
||||||
&& botAI->GetMaster()
|
&& botAI->GetMaster()
|
||||||
&& botAI->GetMaster()->GetSession()->GetSecurity() < SEC_GAMEMASTER
|
&& !botAI->GetMaster()->CanBeGameMaster()
|
||||||
&& !GET_PLAYERBOT_AI(botAI->GetMaster());
|
&& !GET_PLAYERBOT_AI(botAI->GetMaster());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "ItemVisitors.h"
|
#include "ItemVisitors.h"
|
||||||
#include "Mail.h"
|
#include "Mail.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
bool SendMailAction::Execute(Event event)
|
bool SendMailAction::Execute(Event event)
|
||||||
@ -33,34 +34,37 @@ bool SendMailAction::Execute(Event event)
|
|||||||
Player* receiver = GetMaster();
|
Player* receiver = GetMaster();
|
||||||
Player* tellTo = receiver;
|
Player* tellTo = receiver;
|
||||||
|
|
||||||
std::vector<std::string> ss = split(text, ' ');
|
|
||||||
if (ss.size() > 1)
|
|
||||||
{
|
|
||||||
if (Player* p = ObjectAccessor::FindPlayer(ObjectGuid(uint64(ss[ss.size() - 1].c_str()))))
|
|
||||||
receiver = p;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!receiver)
|
if (!receiver)
|
||||||
receiver = event.getOwner();
|
receiver = event.getOwner();
|
||||||
|
|
||||||
if (!receiver || receiver == bot)
|
if (!receiver || receiver == bot)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
if (!tellTo)
|
if (!tellTo)
|
||||||
tellTo = receiver;
|
tellTo = receiver;
|
||||||
|
|
||||||
|
if (!sPlayerbotAIConfig.botSendMailEnabled)
|
||||||
|
{
|
||||||
|
bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"send_mail_disabled", "I cannot send mail", {}),
|
||||||
|
LANG_UNIVERSAL, tellTo);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!mailboxFound && !randomBot)
|
if (!mailboxFound && !randomBot)
|
||||||
{
|
{
|
||||||
bot->Whisper("There is no mailbox nearby", LANG_UNIVERSAL, tellTo);
|
bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"send_mail_no_mailbox_nearby", "There is no mailbox nearby", {}),
|
||||||
|
LANG_UNIVERSAL, tellTo);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemIds ids = chat->parseItems(text);
|
ItemIds ids = chat->parseItems(text);
|
||||||
if (ids.size() > 1)
|
if (ids.size() > 1)
|
||||||
{
|
{
|
||||||
bot->Whisper("You can not request more than one item", LANG_UNIVERSAL, tellTo);
|
bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"send_mail_one_item_only", "You can not request more than one item", {}),
|
||||||
|
LANG_UNIVERSAL, tellTo);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,13 +76,16 @@ bool SendMailAction::Execute(Event event)
|
|||||||
|
|
||||||
if (randomBot)
|
if (randomBot)
|
||||||
{
|
{
|
||||||
bot->Whisper("I cannot send money", LANG_UNIVERSAL, tellTo);
|
bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"send_mail_cannot_send_money", "I cannot send money", {}),
|
||||||
|
LANG_UNIVERSAL, tellTo);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bot->GetMoney() < money)
|
if (bot->GetMoney() < money)
|
||||||
{
|
{
|
||||||
botAI->TellError("I don't have enough money");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"send_mail_not_enough_money", "I don't have enough money", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,8 +107,10 @@ bool SendMailAction::Execute(Event event)
|
|||||||
CharacterDatabase.CommitTransaction(trans);
|
CharacterDatabase.CommitTransaction(trans);
|
||||||
|
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << "Sending mail to " << receiver->GetName();
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
botAI->TellMaster(out.str());
|
"send_mail_sending_to",
|
||||||
|
"Sending mail to %receiver",
|
||||||
|
{{"%receiver", receiver->GetName()}}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +134,10 @@ bool SendMailAction::Execute(Event event)
|
|||||||
if (item->IsSoulBound() || item->IsConjuredConsumable())
|
if (item->IsSoulBound() || item->IsConjuredConsumable())
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << "Cannot send " << ChatHelper::FormatItem(item->GetTemplate());
|
out << PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"send_mail_cannot_send_item",
|
||||||
|
"Cannot send %item",
|
||||||
|
{{"%item", ChatHelper::FormatItem(item->GetTemplate())}});
|
||||||
bot->Whisper(out.str(), LANG_UNIVERSAL, tellTo);
|
bot->Whisper(out.str(), LANG_UNIVERSAL, tellTo);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -140,7 +152,10 @@ bool SendMailAction::Execute(Event event)
|
|||||||
if (!price)
|
if (!price)
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << ChatHelper::FormatItem(item->GetTemplate()) << ": it is not for sale";
|
out << PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"send_mail_item_not_for_sale",
|
||||||
|
"%item: it is not for sale",
|
||||||
|
{{"%item", ChatHelper::FormatItem(item->GetTemplate())}});
|
||||||
bot->Whisper(out.str(), LANG_UNIVERSAL, tellTo);
|
bot->Whisper(out.str(), LANG_UNIVERSAL, tellTo);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -160,7 +175,10 @@ bool SendMailAction::Execute(Event event)
|
|||||||
CharacterDatabase.CommitTransaction(trans);
|
CharacterDatabase.CommitTransaction(trans);
|
||||||
|
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << "Sent mail to " << receiver->GetName();
|
out << PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"send_mail_sent_to",
|
||||||
|
"Sent mail to %receiver",
|
||||||
|
{{"%receiver", receiver->GetName()}});
|
||||||
bot->Whisper(out.str(), LANG_UNIVERSAL, tellTo);
|
bot->Whisper(out.str(), LANG_UNIVERSAL, tellTo);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
#include "ChatHelper.h"
|
#include "ChatHelper.h"
|
||||||
#include "CraftValue.h"
|
#include "CraftValue.h"
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
std::map<uint32, SkillLineAbilityEntry const*> SetCraftAction::skillSpells;
|
std::map<uint32, SkillLineAbilityEntry const*> SetCraftAction::skillSpells;
|
||||||
@ -24,7 +25,8 @@ bool SetCraftAction::Execute(Event event)
|
|||||||
if (link == "reset")
|
if (link == "reset")
|
||||||
{
|
{
|
||||||
data.Reset();
|
data.Reset();
|
||||||
botAI->TellMaster("I will not craft anything");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"craft_reset", "I will not craft anything", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +39,8 @@ bool SetCraftAction::Execute(Event event)
|
|||||||
ItemIds itemIds = chat->parseItems(link);
|
ItemIds itemIds = chat->parseItems(link);
|
||||||
if (itemIds.empty())
|
if (itemIds.empty())
|
||||||
{
|
{
|
||||||
botAI->TellMaster("Usage: 'craft [itemId]' or 'craft reset'");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"craft_usage", "Usage: 'craft [itemId]' or 'craft reset'", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,7 +97,8 @@ bool SetCraftAction::Execute(Event event)
|
|||||||
|
|
||||||
if (data.required.empty())
|
if (data.required.empty())
|
||||||
{
|
{
|
||||||
botAI->TellMaster("I cannot craft this");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"craft_cannot_craft", "I cannot craft this", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +113,8 @@ void SetCraftAction::TellCraft()
|
|||||||
CraftData& data = AI_VALUE(CraftData&, "craft");
|
CraftData& data = AI_VALUE(CraftData&, "craft");
|
||||||
if (data.IsEmpty())
|
if (data.IsEmpty())
|
||||||
{
|
{
|
||||||
botAI->TellMaster("I will not craft anything");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"craft_reset", "I will not craft anything", {}));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,8 +122,7 @@ void SetCraftAction::TellCraft()
|
|||||||
if (!proto)
|
if (!proto)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::ostringstream out;
|
std::ostringstream reagentsOut;
|
||||||
out << "I will craft " << chat->FormatItem(proto) << " using reagents: ";
|
|
||||||
|
|
||||||
bool first = true;
|
bool first = true;
|
||||||
for (std::map<uint32, uint32>::iterator i = data.required.begin(); i != data.required.end(); ++i)
|
for (std::map<uint32, uint32>::iterator i = data.required.begin(); i != data.required.end(); ++i)
|
||||||
@ -130,20 +134,23 @@ void SetCraftAction::TellCraft()
|
|||||||
{
|
{
|
||||||
if (first)
|
if (first)
|
||||||
first = false;
|
first = false;
|
||||||
|
|
||||||
else
|
else
|
||||||
out << ", ";
|
reagentsOut << ", ";
|
||||||
|
|
||||||
out << chat->FormatItem(reagent, required);
|
reagentsOut << chat->FormatItem(reagent, required);
|
||||||
|
|
||||||
uint32 given = data.obtained[item];
|
uint32 given = data.obtained[item];
|
||||||
if (given)
|
if (given)
|
||||||
out << "|cffffff00(x" << given << " given)|r ";
|
reagentsOut << "|cffffff00(x" << given << " given)|r ";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out << " (craft fee: " << chat->formatMoney(GetCraftFee(data)) << ")";
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
botAI->TellMaster(out.str());
|
"craft_summary",
|
||||||
|
"I will craft %item using reagents: %reagents (craft fee: %money)",
|
||||||
|
{{"%item", chat->FormatItem(proto)},
|
||||||
|
{"%reagents", reagentsOut.str()},
|
||||||
|
{"%money", chat->formatMoney(GetCraftFee(data))}}));
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 SetCraftAction::GetCraftFee(CraftData& data)
|
uint32 SetCraftAction::GetCraftFee(CraftData& data)
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
#include "SetHomeAction.h"
|
#include "SetHomeAction.h"
|
||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
bool SetHomeAction::Execute(Event /*event*/)
|
bool SetHomeAction::Execute(Event /*event*/)
|
||||||
@ -28,7 +29,8 @@ bool SetHomeAction::Execute(Event /*event*/)
|
|||||||
{
|
{
|
||||||
Creature* creature = botAI->GetCreature(selection);
|
Creature* creature = botAI->GetCreature(selection);
|
||||||
bot->GetSession()->SendBindPoint(creature);
|
bot->GetSession()->SendBindPoint(creature);
|
||||||
botAI->TellMaster("This inn is my new home");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"set_home_success", "This inn is my new home", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,10 +42,12 @@ bool SetHomeAction::Execute(Event /*event*/)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
bot->GetSession()->SendBindPoint(unit);
|
bot->GetSession()->SendBindPoint(unit);
|
||||||
botAI->TellMaster("This inn is my new home");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"set_home_success", "This inn is my new home", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
botAI->TellError("Can't find any innkeeper around");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"set_home_no_innkeeper_error", "Can't find any innkeeper around", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
#include "ShareQuestAction.h"
|
#include "ShareQuestAction.h"
|
||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
bool ShareQuestAction::Execute(Event event)
|
bool ShareQuestAction::Execute(Event event)
|
||||||
@ -32,7 +33,8 @@ bool ShareQuestAction::Execute(Event event)
|
|||||||
WorldPacket p;
|
WorldPacket p;
|
||||||
p << entry;
|
p << entry;
|
||||||
bot->GetSession()->HandlePushQuestToParty(p);
|
bot->GetSession()->HandlePushQuestToParty(p);
|
||||||
botAI->TellMaster("Quest shared");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_shared", "Quest shared", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -98,7 +100,8 @@ bool AutoShareQuestAction::Execute(Event /*event*/)
|
|||||||
WorldPacket p;
|
WorldPacket p;
|
||||||
p << logQuest;
|
p << logQuest;
|
||||||
bot->GetSession()->HandlePushQuestToParty(p);
|
bot->GetSession()->HandlePushQuestToParty(p);
|
||||||
botAI->TellMaster("Quest shared");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_shared", "Quest shared", {}));
|
||||||
shared = true;
|
shared = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
#include "PlayerbotAI.h"
|
#include "PlayerbotAI.h"
|
||||||
#include "PlayerbotFactory.h"
|
#include "PlayerbotFactory.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "SpellMgr.h"
|
#include "SpellMgr.h"
|
||||||
#include "WorldSession.h"
|
#include "WorldSession.h"
|
||||||
|
|
||||||
@ -123,7 +124,8 @@ bool TameAction::Execute(Event event)
|
|||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
botAI->TellError("Invalid tame id.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_invalid_id_error", "Invalid tame id.", {}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (mode == "family" && !value.empty())
|
else if (mode == "family" && !value.empty())
|
||||||
@ -137,8 +139,10 @@ bool TameAction::Execute(Event event)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Unrecognized command or missing argument; show usage
|
// Unrecognized command or missing argument; show usage
|
||||||
botAI->TellError(
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
"Usage: tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon");
|
"tame_usage_error",
|
||||||
|
"Usage: tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,12 +161,15 @@ bool TameAction::Execute(Event event)
|
|||||||
if (!lastPetName.empty() && lastPetId != 0)
|
if (!lastPetName.empty() && lastPetId != 0)
|
||||||
{
|
{
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss << "Pet changed to " << lastPetName << ", ID: " << lastPetId << ".";
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
botAI->TellMaster(oss.str());
|
"tame_pet_changed",
|
||||||
|
"Pet changed to %name, ID: %id.",
|
||||||
|
{{"%name", lastPetName}, {"%id", std::to_string(lastPetId)}}));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
botAI->TellMaster("Pet changed and initialized!");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_pet_changed_initialized", "Pet changed and initialized!", {}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,7 +204,10 @@ bool TameAction::SetPetByName(const std::string& name)
|
|||||||
// If the creature is exotic and the bot doesn't have Beast Mastery, show error and fail
|
// If the creature is exotic and the bot doesn't have Beast Mastery, show error and fail
|
||||||
if (IsExoticPet(&creature) && !HasBeastMastery(bot))
|
if (IsExoticPet(&creature) && !HasBeastMastery(bot))
|
||||||
{
|
{
|
||||||
botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_exotic_requires_beast_mastery",
|
||||||
|
"I cannot use exotic pets unless I have the Beast Mastery talent.",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,7 +224,8 @@ bool TameAction::SetPetByName(const std::string& name)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If no suitable pet found, show an error and return failure
|
// If no suitable pet found, show an error and return failure
|
||||||
botAI->TellError("No tameable pet found with name: " + name);
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_no_pet_by_name", "No tameable pet found with name: %name", {{"%name", name}}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,21 +242,26 @@ bool TameAction::SetPetById(uint32 id)
|
|||||||
if (!creature->IsTameable(true))
|
if (!creature->IsTameable(true))
|
||||||
{
|
{
|
||||||
// If not tameable at all, show an error and fail
|
// If not tameable at all, show an error and fail
|
||||||
botAI->TellError("No tameable pet found with id: " + std::to_string(id));
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_no_pet_by_id", "No tameable pet found with id: %id", {{"%id", std::to_string(id)}}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's an exotic pet, make sure the bot has the Beast Mastery talent
|
// If it's an exotic pet, make sure the bot has the Beast Mastery talent
|
||||||
if (IsExoticPet(creature) && !HasBeastMastery(bot))
|
if (IsExoticPet(creature) && !HasBeastMastery(bot))
|
||||||
{
|
{
|
||||||
botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_exotic_requires_beast_mastery",
|
||||||
|
"I cannot use exotic pets unless I have the Beast Mastery talent.",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the bot is actually allowed to tame this pet (honoring exotic pet rules)
|
// Check if the bot is actually allowed to tame this pet (honoring exotic pet rules)
|
||||||
if (!creature->IsTameable(bot->CanTameExoticPets()))
|
if (!creature->IsTameable(bot->CanTameExoticPets()))
|
||||||
{
|
{
|
||||||
botAI->TellError("No tameable pet found with id: " + std::to_string(id));
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_no_pet_by_id", "No tameable pet found with id: %id", {{"%id", std::to_string(id)}}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,7 +273,8 @@ bool TameAction::SetPetById(uint32 id)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If no valid creature was found by id, show an error
|
// If no valid creature was found by id, show an error
|
||||||
botAI->TellError("No tameable pet found with id: " + std::to_string(id));
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_no_pet_by_id", "No tameable pet found with id: %id", {{"%id", std::to_string(id)}}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,9 +332,13 @@ bool TameAction::SetPetByFamily(const std::string& family)
|
|||||||
if (candidates.empty())
|
if (candidates.empty())
|
||||||
{
|
{
|
||||||
if (foundExotic && !HasBeastMastery(bot))
|
if (foundExotic && !HasBeastMastery(bot))
|
||||||
botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_exotic_requires_beast_mastery",
|
||||||
|
"I cannot use exotic pets unless I have the Beast Mastery talent.",
|
||||||
|
{}));
|
||||||
else
|
else
|
||||||
botAI->TellError("No tameable pet found with family: " + family);
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_no_pet_by_family", "No tameable pet found with family: %family", {{"%family", family}}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,14 +363,18 @@ bool TameAction::RenamePet(const std::string& newName)
|
|||||||
// Check if the bot currently has a pet
|
// Check if the bot currently has a pet
|
||||||
if (!pet)
|
if (!pet)
|
||||||
{
|
{
|
||||||
botAI->TellError("You have no pet to rename.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_no_pet_to_rename", "You have no pet to rename.", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the new name: must not be empty and max 12 characters
|
// Validate the new name: must not be empty and max 12 characters
|
||||||
if (newName.empty() || newName.length() > 12)
|
if (newName.empty() || newName.length() > 12)
|
||||||
{
|
{
|
||||||
botAI->TellError("Pet name must be between 1 and 12 alphabetic characters.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_pet_name_length_error",
|
||||||
|
"Pet name must be between 1 and 12 alphabetic characters.",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,7 +383,10 @@ bool TameAction::RenamePet(const std::string& newName)
|
|||||||
{
|
{
|
||||||
if (!std::isalpha(static_cast<unsigned char>(c)))
|
if (!std::isalpha(static_cast<unsigned char>(c)))
|
||||||
{
|
{
|
||||||
botAI->TellError("Pet name must only contain alphabetic characters (A-Z, a-z).");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_pet_name_alpha_error",
|
||||||
|
"Pet name must only contain alphabetic characters (A-Z, a-z).",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -372,7 +400,10 @@ bool TameAction::RenamePet(const std::string& newName)
|
|||||||
// Check if the new name is reserved or forbidden
|
// Check if the new name is reserved or forbidden
|
||||||
if (sObjectMgr->IsReservedName(normalized))
|
if (sObjectMgr->IsReservedName(normalized))
|
||||||
{
|
{
|
||||||
botAI->TellError("That pet name is forbidden. Please choose another name.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_pet_name_forbidden_error",
|
||||||
|
"That pet name is forbidden. Please choose another name.",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -382,8 +413,12 @@ bool TameAction::RenamePet(const std::string& newName)
|
|||||||
bot->GetSession()->SendPetNameQuery(pet->GetGUID(), pet->GetEntry());
|
bot->GetSession()->SendPetNameQuery(pet->GetGUID(), pet->GetEntry());
|
||||||
|
|
||||||
// Notify the master about the rename and give a tip to update the client name display
|
// Notify the master about the rename and give a tip to update the client name display
|
||||||
botAI->TellMaster("Your pet has been renamed to " + normalized + "!");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
botAI->TellMaster("If you do not see the new name, please dismiss and recall your pet.");
|
"tame_pet_renamed", "Your pet has been renamed to %name!", {{"%name", normalized}}));
|
||||||
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_pet_rename_refresh_hint",
|
||||||
|
"If you do not see the new name, please dismiss and recall your pet.",
|
||||||
|
{}));
|
||||||
|
|
||||||
// Remove the current pet and (re-)cast Call Pet spell if the bot is a hunter
|
// Remove the current pet and (re-)cast Call Pet spell if the bot is a hunter
|
||||||
bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT, true);
|
bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT, true);
|
||||||
@ -401,7 +436,8 @@ bool TameAction::CreateAndSetPet(uint32 creatureEntry)
|
|||||||
// Ensure the player is a hunter and at least level 10 (required for pets)
|
// Ensure the player is a hunter and at least level 10 (required for pets)
|
||||||
if (bot->getClass() != CLASS_HUNTER || bot->GetLevel() < 10)
|
if (bot->getClass() != CLASS_HUNTER || bot->GetLevel() < 10)
|
||||||
{
|
{
|
||||||
botAI->TellError("Only level 10+ hunters can have pets.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_only_hunters_level_10", "Only level 10+ hunters can have pets.", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,7 +445,8 @@ bool TameAction::CreateAndSetPet(uint32 creatureEntry)
|
|||||||
CreatureTemplate const* creature = sObjectMgr->GetCreatureTemplate(creatureEntry);
|
CreatureTemplate const* creature = sObjectMgr->GetCreatureTemplate(creatureEntry);
|
||||||
if (!creature)
|
if (!creature)
|
||||||
{
|
{
|
||||||
botAI->TellError("Creature template not found.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_creature_template_not_found", "Creature template not found.", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -430,7 +467,8 @@ bool TameAction::CreateAndSetPet(uint32 creatureEntry)
|
|||||||
Pet* pet = bot->CreateTamedPetFrom(creatureEntry, 0);
|
Pet* pet = bot->CreateTamedPetFrom(creatureEntry, 0);
|
||||||
if (!pet)
|
if (!pet)
|
||||||
{
|
{
|
||||||
botAI->TellError("Failed to create pet.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_create_pet_failed", "Failed to create pet.", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -485,13 +523,15 @@ bool TameAction::AbandonPet()
|
|||||||
// Remove the pet from the bot and mark it as deleted in the database
|
// Remove the pet from the bot and mark it as deleted in the database
|
||||||
bot->RemovePet(pet, PET_SAVE_AS_DELETED);
|
bot->RemovePet(pet, PET_SAVE_AS_DELETED);
|
||||||
// Inform the bot's master/player that the pet was abandoned
|
// Inform the bot's master/player that the pet was abandoned
|
||||||
botAI->TellMaster("Your pet has been abandoned.");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_pet_abandoned", "Your pet has been abandoned.", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// If there is no hunter pet, show an error message
|
// If there is no hunter pet, show an error message
|
||||||
botAI->TellError("You have no hunter pet to abandon.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_no_hunter_pet_to_abandon", "You have no hunter pet to abandon.", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "LastMovementValue.h"
|
#include "LastMovementValue.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "PlayerbotAIConfig.h"
|
#include "PlayerbotAIConfig.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
@ -24,7 +25,8 @@ bool TaxiAction::Execute(Event event)
|
|||||||
{
|
{
|
||||||
movement.taxiNodes.clear();
|
movement.taxiNodes.clear();
|
||||||
movement.Set(nullptr);
|
movement.Set(nullptr);
|
||||||
botAI->TellMaster("I am ready for the next flight");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"taxi_ready_next_flight", "I am ready for the next flight", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,13 +122,15 @@ bool TaxiAction::Execute(Event event)
|
|||||||
{
|
{
|
||||||
movement.taxiNodes.clear();
|
movement.taxiNodes.clear();
|
||||||
movement.Set(nullptr);
|
movement.Set(nullptr);
|
||||||
botAI->TellError("I can't fly with you");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"taxi_cant_fly_with_you", "I can't fly with you", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
botAI->TellError("Cannot find any flightmaster to talk");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"taxi_no_flightmaster_nearby", "Cannot find any flightmaster to talk", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
#include "ItemVisitors.h"
|
#include "ItemVisitors.h"
|
||||||
#include "PlayerbotMgr.h"
|
#include "PlayerbotMgr.h"
|
||||||
#include "PlayerbotSecurity.h"
|
#include "PlayerbotSecurity.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "RandomPlayerbotMgr.h"
|
#include "RandomPlayerbotMgr.h"
|
||||||
#include "SetCraftAction.h"
|
#include "SetCraftAction.h"
|
||||||
@ -28,13 +29,17 @@ bool TradeStatusAction::Execute(Event event)
|
|||||||
// Allow the master and group members to trade
|
// Allow the master and group members to trade
|
||||||
if (trader != master && !traderBotAI && (!bot->GetGroup() || !bot->GetGroup()->IsMember(trader->GetGUID())))
|
if (trader != master && !traderBotAI && (!bot->GetGroup() || !bot->GetGroup()->IsMember(trader->GetGUID())))
|
||||||
{
|
{
|
||||||
bot->Whisper("I'm kind of busy now", LANG_UNIVERSAL, trader);
|
bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_busy_now", "I'm kind of busy now", {}),
|
||||||
|
LANG_UNIVERSAL, trader);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sPlayerbotAIConfig.enableRandomBotTrading == 0 && (sRandomPlayerbotMgr.IsRandomBot(bot)|| sRandomPlayerbotMgr.IsAddclassBot(bot)))
|
if (sPlayerbotAIConfig.enableRandomBotTrading == 0 && (sRandomPlayerbotMgr.IsRandomBot(bot)|| sRandomPlayerbotMgr.IsAddclassBot(bot)))
|
||||||
{
|
{
|
||||||
bot->Whisper("Trading is disabled", LANG_UNIVERSAL, trader);
|
bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_disabled", "Trading is disabled", {}),
|
||||||
|
LANG_UNIVERSAL, trader);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,9 +185,15 @@ bool TradeStatusAction::CheckTrade()
|
|||||||
{
|
{
|
||||||
if (bot->GetGroup() && bot->GetGroup()->IsMember(bot->GetTrader()->GetGUID()) &&
|
if (bot->GetGroup() && bot->GetGroup()->IsMember(bot->GetTrader()->GetGUID()) &&
|
||||||
botAI->HasRealPlayerMaster())
|
botAI->HasRealPlayerMaster())
|
||||||
botAI->TellMasterNoFacing("Thank you " + chat->FormatWorldobject(bot->GetTrader()));
|
botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_thank_you_player",
|
||||||
|
"Thank you %player",
|
||||||
|
{{"%player", chat->FormatWorldobject(bot->GetTrader())}}));
|
||||||
else
|
else
|
||||||
bot->Say("Thank you " + chat->FormatWorldobject(bot->GetTrader()),
|
bot->Say(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_thank_you_player",
|
||||||
|
"Thank you %player",
|
||||||
|
{{"%player", chat->FormatWorldobject(bot->GetTrader())}}),
|
||||||
(bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH));
|
(bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH));
|
||||||
}
|
}
|
||||||
return isGettingItem;
|
return isGettingItem;
|
||||||
@ -210,12 +221,16 @@ bool TradeStatusAction::CheckTrade()
|
|||||||
int32 playerMoney = trader->GetTradeData()->GetMoney() + playerItemsMoney;
|
int32 playerMoney = trader->GetTradeData()->GetMoney() + playerItemsMoney;
|
||||||
if (botItemsMoney > 0 && sPlayerbotAIConfig.enableRandomBotTrading == 2 && (sRandomPlayerbotMgr.IsRandomBot(bot)|| sRandomPlayerbotMgr.IsAddclassBot(bot)))
|
if (botItemsMoney > 0 && sPlayerbotAIConfig.enableRandomBotTrading == 2 && (sRandomPlayerbotMgr.IsRandomBot(bot)|| sRandomPlayerbotMgr.IsAddclassBot(bot)))
|
||||||
{
|
{
|
||||||
bot->Whisper("Selling is disabled.", LANG_UNIVERSAL, trader);
|
bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_selling_disabled", "Selling is disabled.", {}),
|
||||||
|
LANG_UNIVERSAL, trader);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (playerItemsMoney && sPlayerbotAIConfig.enableRandomBotTrading == 3 && (sRandomPlayerbotMgr.IsRandomBot(bot)|| sRandomPlayerbotMgr.IsAddclassBot(bot)))
|
if (playerItemsMoney && sPlayerbotAIConfig.enableRandomBotTrading == 3 && (sRandomPlayerbotMgr.IsRandomBot(bot)|| sRandomPlayerbotMgr.IsAddclassBot(bot)))
|
||||||
{
|
{
|
||||||
bot->Whisper("Buying is disabled.", LANG_UNIVERSAL, trader);
|
bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_buying_disabled", "Buying is disabled.", {}),
|
||||||
|
LANG_UNIVERSAL, trader);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (uint32 slot = 0; slot < TRADE_SLOT_TRADED_COUNT; ++slot)
|
for (uint32 slot = 0; slot < TRADE_SLOT_TRADED_COUNT; ++slot)
|
||||||
@ -224,8 +239,10 @@ bool TradeStatusAction::CheckTrade()
|
|||||||
if (item && !item->GetTemplate()->SellPrice && !item->GetTemplate()->IsConjuredConsumable())
|
if (item && !item->GetTemplate()->SellPrice && !item->GetTemplate()->IsConjuredConsumable())
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << chat->FormatItem(item->GetTemplate()) << " - This is not for sale";
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
botAI->TellMaster(out);
|
"trade_item_not_for_sale",
|
||||||
|
"%item - This is not for sale",
|
||||||
|
{{"%item", chat->FormatItem(item->GetTemplate())}}));
|
||||||
botAI->PlaySound(TEXT_EMOTE_NO);
|
botAI->PlaySound(TEXT_EMOTE_NO);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -239,8 +256,10 @@ bool TradeStatusAction::CheckTrade()
|
|||||||
if ((botMoney && !item->GetTemplate()->BuyPrice) || usage == ITEM_USAGE_NONE)
|
if ((botMoney && !item->GetTemplate()->BuyPrice) || usage == ITEM_USAGE_NONE)
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << chat->FormatItem(item->GetTemplate()) << " - I don't need this";
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
botAI->TellMaster(out);
|
"trade_item_not_needed",
|
||||||
|
"%item - I don't need this",
|
||||||
|
{{"%item", chat->FormatItem(item->GetTemplate())}}));
|
||||||
botAI->PlaySound(TEXT_EMOTE_NO);
|
botAI->PlaySound(TEXT_EMOTE_NO);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -252,7 +271,8 @@ bool TradeStatusAction::CheckTrade()
|
|||||||
|
|
||||||
if (!botItemsMoney && !playerItemsMoney)
|
if (!botItemsMoney && !playerItemsMoney)
|
||||||
{
|
{
|
||||||
botAI->TellError("There are no items to trade");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_no_items_error", "There are no items to trade", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,7 +286,8 @@ bool TradeStatusAction::CheckTrade()
|
|||||||
{
|
{
|
||||||
if (moneyDelta < 0)
|
if (moneyDelta < 0)
|
||||||
{
|
{
|
||||||
botAI->TellError("You can use discount to buy items only");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_discount_buy_only", "You can use discount to buy items only", {}));
|
||||||
botAI->PlaySound(TEXT_EMOTE_NO);
|
botAI->PlaySound(TEXT_EMOTE_NO);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -282,16 +303,20 @@ bool TradeStatusAction::CheckTrade()
|
|||||||
switch (urand(0, 4))
|
switch (urand(0, 4))
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
botAI->TellMaster("A pleasure doing business with you");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_success_pleasure", "A pleasure doing business with you", {}));
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
botAI->TellMaster("Fair trade");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_success_fair_trade", "Fair trade", {}));
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
botAI->TellMaster("Thanks");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_success_thanks", "Thanks", {}));
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
botAI->TellMaster("Off with you");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_success_off_with_you", "Off with you", {}));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,8 +325,10 @@ bool TradeStatusAction::CheckTrade()
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << "I want " << chat->formatMoney(-(delta + discount)) << " for this";
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
botAI->TellMaster(out);
|
"trade_want_money_for_this",
|
||||||
|
"I want %money for this",
|
||||||
|
{{"%money", chat->formatMoney(-(delta + discount))}}));
|
||||||
botAI->PlaySound(TEXT_EMOTE_NO);
|
botAI->PlaySound(TEXT_EMOTE_NO);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,10 +5,14 @@
|
|||||||
|
|
||||||
#include "TrainerAction.h"
|
#include "TrainerAction.h"
|
||||||
|
|
||||||
|
#include "AiFactory.h"
|
||||||
|
#include "BisListMgr.h"
|
||||||
#include "BudgetValues.h"
|
#include "BudgetValues.h"
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "PlayerbotFactory.h"
|
#include "PlayerbotFactory.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
#include "ReputationMgr.h"
|
||||||
#include "Trainer.h"
|
#include "Trainer.h"
|
||||||
|
|
||||||
bool TrainerAction::Execute(Event event)
|
bool TrainerAction::Execute(Event event)
|
||||||
@ -269,6 +273,282 @@ bool MaintenanceAction::Execute(Event /*event*/)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BisGearAction::RunAutogearFallback(uint16 effectiveIlvl)
|
||||||
|
{
|
||||||
|
if (!sPlayerbotAIConfig.autoGearCommand)
|
||||||
|
{
|
||||||
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_autogear_unavailable_error",
|
||||||
|
"autogear command is not allowed, please check the configuration.", {}));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_no_rows_fallback",
|
||||||
|
"No BiS for your tier/spec/level, check cfg, running autogear instead", {}));
|
||||||
|
|
||||||
|
// Wipe all equipped slots so autogear gears from scratch at the requested ilvl
|
||||||
|
// (avoids old high-tier items surviving the incremental 1.2x threshold).
|
||||||
|
for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot)
|
||||||
|
{
|
||||||
|
if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY)
|
||||||
|
continue;
|
||||||
|
if (bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
|
||||||
|
bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 gs = effectiveIlvl == 0
|
||||||
|
? 0
|
||||||
|
: PlayerbotFactory::CalcMixedGearScore(effectiveIlvl, sPlayerbotAIConfig.autoGearQualityLimit);
|
||||||
|
PlayerbotFactory factory(bot, bot->GetLevel(), sPlayerbotAIConfig.autoGearQualityLimit, gs);
|
||||||
|
factory.InitEquipment(false, sPlayerbotAIConfig.twoRoundsGearInit);
|
||||||
|
factory.InitAmmo();
|
||||||
|
if (bot->GetLevel() >= sPlayerbotAIConfig.minEnchantingBotLevel)
|
||||||
|
factory.ApplyEnchantAndGemsNew();
|
||||||
|
bot->DurabilityRepairAll(false, 1.0f, false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BisGearAction::Execute(Event event)
|
||||||
|
{
|
||||||
|
if (!sPlayerbotAIConfig.autoGearBisCommand)
|
||||||
|
{
|
||||||
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_command_unavailable_error",
|
||||||
|
"bis command is not allowed, please check the configuration.", {}));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sPlayerbotAIConfig.autoGearCommandAltBots &&
|
||||||
|
!sPlayerbotAIConfig.IsInRandomAccountList(bot->GetSession()->GetAccountId()))
|
||||||
|
{
|
||||||
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_altbot_refused_error", "You cannot use bis on alt bots.", {}));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sPlayerbotAIConfig.autoGearQualityLimit < 4)
|
||||||
|
{
|
||||||
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_quality_floor_error", "AutoGearQualityLimit must be 4 for BiS.", {}));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sRandomPlayerbotMgr.IsSpecPvp(bot->GetGUID().GetCounter(), bot->getClass()))
|
||||||
|
{
|
||||||
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_pvp_refused_error", "bis is PvE only, bot is configured as PvP.", {}));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16 ilvl = static_cast<uint16>(sPlayerbotAIConfig.autoGearScoreLimit);
|
||||||
|
|
||||||
|
// Optional explicit ilvl override: `/p autogear bis 55`.
|
||||||
|
// Garbage or out-of-range args are hard-rejected: no autogear fallback, no gear change.
|
||||||
|
std::string const param = event.getParam();
|
||||||
|
if (!param.empty())
|
||||||
|
{
|
||||||
|
unsigned long parsed = 0;
|
||||||
|
size_t pos = 0;
|
||||||
|
bool valid = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
parsed = std::stoul(param, &pos);
|
||||||
|
valid = (parsed > 0 && pos == param.size() && parsed <= 0xFFFFu);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!valid)
|
||||||
|
{
|
||||||
|
std::map<std::string, std::string> phs;
|
||||||
|
phs["%param"] = param;
|
||||||
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_invalid_arg_error",
|
||||||
|
"Invalid BiS ilvl argument '%param'. Use a positive integer.", phs));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (parsed > static_cast<unsigned long>(sPlayerbotAIConfig.autoGearScoreLimit))
|
||||||
|
{
|
||||||
|
std::map<std::string, std::string> phs;
|
||||||
|
phs["%requested"] = std::to_string(parsed);
|
||||||
|
phs["%limit"] = std::to_string(sPlayerbotAIConfig.autoGearScoreLimit);
|
||||||
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_arg_above_limit_error",
|
||||||
|
"BiS ilvl %requested exceeds AutoGearScoreLimit %limit, refusing", phs));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ilvl = static_cast<uint16>(parsed);
|
||||||
|
}
|
||||||
|
uint8 cls = bot->getClass();
|
||||||
|
uint8 tab = AiFactory::GetPlayerSpecTab(bot);
|
||||||
|
uint8 faction = bot->GetTeamId() == TEAM_ALLIANCE ? 1 : 2;
|
||||||
|
|
||||||
|
// Druid Bear (Feral Tank) shares tab 1 with Cat. Use sentinel tab 10 when tank strategy active.
|
||||||
|
constexpr uint8 BIS_TAB_DRUID_BEAR = 10;
|
||||||
|
constexpr uint16 BIS_ILVL_FALLBACK_WINDOW = 20;
|
||||||
|
uint16 resolvedIlvl = 0;
|
||||||
|
std::map<uint8, uint32> bisMap;
|
||||||
|
if (cls == CLASS_DRUID && tab == DRUID_TAB_FERAL && PlayerbotAI::IsTank(bot))
|
||||||
|
bisMap = sBisListMgr->GetBisForNearest(ilvl, BIS_ILVL_FALLBACK_WINDOW, cls, BIS_TAB_DRUID_BEAR, faction,
|
||||||
|
&resolvedIlvl);
|
||||||
|
if (bisMap.empty())
|
||||||
|
bisMap = sBisListMgr->GetBisForNearest(ilvl, BIS_ILVL_FALLBACK_WINDOW, cls, tab, faction, &resolvedIlvl);
|
||||||
|
|
||||||
|
// No rows within fallback window -> full autogear fallback at the effective ilvl.
|
||||||
|
if (bisMap.empty())
|
||||||
|
{
|
||||||
|
std::map<std::string, std::string> phs;
|
||||||
|
phs["%ilvl"] = std::to_string(ilvl);
|
||||||
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_no_rows_autogear_msg",
|
||||||
|
"No BiS at ilvl %ilvl, using Autogear %ilvl instead", phs));
|
||||||
|
return RunAutogearFallback(ilvl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolvedIlvl != ilvl)
|
||||||
|
{
|
||||||
|
std::map<std::string, std::string> phs;
|
||||||
|
phs["%requested"] = std::to_string(ilvl);
|
||||||
|
phs["%resolved"] = std::to_string(resolvedIlvl);
|
||||||
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_closest_match_msg",
|
||||||
|
"No BiS at ilvl %requested, using closest match at ilvl %resolved", phs));
|
||||||
|
}
|
||||||
|
|
||||||
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_applying_msg", "Applying BiS gear", {}));
|
||||||
|
|
||||||
|
// 1. Wipe everything currently equipped so autogear starts from a clean slate.
|
||||||
|
// Old items linger in inventory otherwise and autogear leaves slots empty on bag conflicts.
|
||||||
|
for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot)
|
||||||
|
{
|
||||||
|
if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY)
|
||||||
|
continue;
|
||||||
|
if (bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
|
||||||
|
bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wipe equippable items from bags too. Autogear can shove old equipped items into bags
|
||||||
|
// (HandleAutoStoreBagItemOpcode), and a unique-equipped duplicate stuck in a bag blocks
|
||||||
|
// CanEquipNewItem on subsequent BiS runs. Spare consumables/reagents.
|
||||||
|
auto destroyIfEquippable = [&](uint8 bag, uint8 slot)
|
||||||
|
{
|
||||||
|
Item* item = bot->GetItemByPos(bag, slot);
|
||||||
|
if (!item)
|
||||||
|
return;
|
||||||
|
ItemTemplate const* tmpl = item->GetTemplate();
|
||||||
|
if (!tmpl)
|
||||||
|
return;
|
||||||
|
if (tmpl->Class == ITEM_CLASS_WEAPON || tmpl->Class == ITEM_CLASS_ARMOR)
|
||||||
|
bot->DestroyItem(bag, slot, true);
|
||||||
|
};
|
||||||
|
for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; ++slot)
|
||||||
|
destroyIfEquippable(INVENTORY_SLOT_BAG_0, slot);
|
||||||
|
for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag)
|
||||||
|
{
|
||||||
|
if (Bag* container = bot->GetBagByPos(bag))
|
||||||
|
for (uint32 slot = 0; slot < container->GetBagSize(); ++slot)
|
||||||
|
destroyIfEquippable(bag, slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Run full autogear on the empty bot so every slot gets a best-available pick.
|
||||||
|
// Uncovered slots will keep the autogear pick; BiS overwrites the rest below.
|
||||||
|
if (sPlayerbotAIConfig.autoGearCommand)
|
||||||
|
{
|
||||||
|
uint32 fillGs = ilvl == 0
|
||||||
|
? 0
|
||||||
|
: PlayerbotFactory::CalcMixedGearScore(ilvl, sPlayerbotAIConfig.autoGearQualityLimit);
|
||||||
|
PlayerbotFactory fillFactory(bot, bot->GetLevel(), sPlayerbotAIConfig.autoGearQualityLimit, fillGs);
|
||||||
|
fillFactory.InitEquipment(false, sPlayerbotAIConfig.twoRoundsGearInit);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2b. Pre-destroy autogear picks that would conflict with any BiS item by entry.
|
||||||
|
// Autogear may have placed the exact item BiS wants into trinket2/finger2 (or vice versa);
|
||||||
|
// unique-equipped enforcement would then make BiS's equip silently drop one copy.
|
||||||
|
std::set<uint32> bisEntries;
|
||||||
|
for (auto const& kv : bisMap)
|
||||||
|
bisEntries.insert(kv.second);
|
||||||
|
for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot)
|
||||||
|
{
|
||||||
|
if (Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
|
||||||
|
if (bisEntries.count(item->GetEntry()))
|
||||||
|
bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Apply BiS: only touch slots where the bot can actually equip the BiS item.
|
||||||
|
// If item requires reputation, grant the required rank first. If CanUseItem still
|
||||||
|
// fails (class/race/skill/level), keep autogear's pick for that slot.
|
||||||
|
for (auto const& kv : bisMap)
|
||||||
|
{
|
||||||
|
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(kv.second);
|
||||||
|
if (!proto)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Grant required reputation rank if the item gates on it.
|
||||||
|
if (proto->RequiredReputationFaction && proto->RequiredReputationRank > 0)
|
||||||
|
{
|
||||||
|
if (FactionEntry const* fac = sFactionStore.LookupEntry(proto->RequiredReputationFaction))
|
||||||
|
{
|
||||||
|
ReputationRank requiredRank = static_cast<ReputationRank>(proto->RequiredReputationRank);
|
||||||
|
if (bot->GetReputationRank(proto->RequiredReputationFaction) < requiredRank)
|
||||||
|
{
|
||||||
|
int32 standing = ReputationMgr::ReputationRankToStanding(
|
||||||
|
static_cast<ReputationRank>(requiredRank - 1)) + 1;
|
||||||
|
bot->GetReputationMgr().SetReputation(fac, standing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bot->CanUseItem(proto) != EQUIP_ERR_OK)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
uint8 slot = kv.first;
|
||||||
|
if (bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
|
||||||
|
bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true);
|
||||||
|
|
||||||
|
uint16 dest = 0;
|
||||||
|
InventoryResult eqResult = bot->CanEquipNewItem(slot, dest, kv.second, false);
|
||||||
|
|
||||||
|
// Paired slots (finger 10<->11, trinket 12<->13): destroy paired slot and retry once
|
||||||
|
// when unique-equipped or autogear residue blocks the first attempt.
|
||||||
|
if (eqResult != EQUIP_ERR_OK)
|
||||||
|
{
|
||||||
|
uint8 pairedSlot = 0xFF;
|
||||||
|
if (slot == EQUIPMENT_SLOT_FINGER1) pairedSlot = EQUIPMENT_SLOT_FINGER2;
|
||||||
|
else if (slot == EQUIPMENT_SLOT_FINGER2) pairedSlot = EQUIPMENT_SLOT_FINGER1;
|
||||||
|
else if (slot == EQUIPMENT_SLOT_TRINKET1) pairedSlot = EQUIPMENT_SLOT_TRINKET2;
|
||||||
|
else if (slot == EQUIPMENT_SLOT_TRINKET2) pairedSlot = EQUIPMENT_SLOT_TRINKET1;
|
||||||
|
|
||||||
|
if (pairedSlot != 0xFF)
|
||||||
|
{
|
||||||
|
if (bot->GetItemByPos(INVENTORY_SLOT_BAG_0, pairedSlot))
|
||||||
|
bot->DestroyItem(INVENTORY_SLOT_BAG_0, pairedSlot, true);
|
||||||
|
eqResult = bot->CanEquipNewItem(slot, dest, kv.second, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eqResult == EQUIP_ERR_OK)
|
||||||
|
{
|
||||||
|
bot->EquipNewItem(dest, kv.second, true);
|
||||||
|
bot->AutoUnequipOffhandIfNeed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerbotFactory factory(bot, bot->GetLevel(), ITEM_QUALITY_EPIC, 0);
|
||||||
|
factory.InitAmmo();
|
||||||
|
if (bot->GetLevel() >= sPlayerbotAIConfig.minEnchantingBotLevel)
|
||||||
|
factory.ApplyEnchantAndGemsNew();
|
||||||
|
|
||||||
|
bot->DurabilityRepairAll(false, 1.0f, false);
|
||||||
|
|
||||||
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_applied_msg", "BiS applied", {}));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool RemoveGlyphAction::Execute(Event /*event*/)
|
bool RemoveGlyphAction::Execute(Event /*event*/)
|
||||||
{
|
{
|
||||||
for (uint32 slotIndex = 0; slotIndex < MAX_GLYPH_SLOT_INDEX; ++slotIndex)
|
for (uint32 slotIndex = 0; slotIndex < MAX_GLYPH_SLOT_INDEX; ++slotIndex)
|
||||||
|
|||||||
@ -53,4 +53,14 @@ public:
|
|||||||
bool Execute(Event event) override;
|
bool Execute(Event event) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class BisGearAction : public Action
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BisGearAction(PlayerbotAI* botAI) : Action(botAI, "autogear bis") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool RunAutogearFallback(uint16 effectiveIlvl);
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "ItemPackets.h"
|
#include "ItemPackets.h"
|
||||||
#include "ItemUsageValue.h"
|
#include "ItemUsageValue.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
bool UseItemAction::Execute(Event event)
|
bool UseItemAction::Execute(Event event)
|
||||||
@ -35,7 +36,8 @@ bool UseItemAction::Execute(Event event)
|
|||||||
return UseItemOnGameObject(*items.begin(), *gos.begin());
|
return UseItemOnGameObject(*items.begin(), *gos.begin());
|
||||||
}
|
}
|
||||||
|
|
||||||
botAI->TellError("No items (or game objects) available");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"use_item_none_available", "No items (or game objects) available", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,8 +50,10 @@ bool UseItemAction::UseGameObject(ObjectGuid guid)
|
|||||||
go->Use(bot);
|
go->Use(bot);
|
||||||
|
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << "Using " << chat->FormatGameobject(go);
|
botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
botAI->TellMasterNoFacing(out.str());
|
"use_gameobject",
|
||||||
|
"Using %gameobject",
|
||||||
|
{{"%gameobject", chat->FormatGameobject(go)}}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,16 +96,16 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
|
|||||||
|
|
||||||
bool targetSelected = false;
|
bool targetSelected = false;
|
||||||
|
|
||||||
std::ostringstream out;
|
std::string itemText = chat->FormatItem(item->GetTemplate());
|
||||||
out << "Using " << chat->FormatItem(item->GetTemplate());
|
std::string targetText;
|
||||||
|
|
||||||
if (item->GetTemplate()->Stackable > 1)
|
if (item->GetTemplate()->Stackable > 1)
|
||||||
{
|
{
|
||||||
uint32 count = item->GetCount();
|
uint32 count = item->GetCount();
|
||||||
if (count > 1)
|
if (count > 1)
|
||||||
out << " (" << count << " available) ";
|
itemText += " (" + std::to_string(count) + " available)";
|
||||||
else
|
else
|
||||||
out << " (the last one!)";
|
itemText += " (the last one!)";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (goGuid)
|
if (goGuid)
|
||||||
@ -114,7 +118,7 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
|
|||||||
|
|
||||||
packet << targetFlag;
|
packet << targetFlag;
|
||||||
packet << goGuid.WriteAsPacked();
|
packet << goGuid.WriteAsPacked();
|
||||||
out << " on " << chat->FormatGameobject(go);
|
targetText = chat->FormatGameobject(go);
|
||||||
targetSelected = true;
|
targetSelected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +128,8 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
|
|||||||
{
|
{
|
||||||
bool fit = SocketItem(itemTarget, item) || SocketItem(itemTarget, item, true);
|
bool fit = SocketItem(itemTarget, item) || SocketItem(itemTarget, item, true);
|
||||||
if (!fit)
|
if (!fit)
|
||||||
botAI->TellMaster("Socket does not fit");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"socket_does_not_fit", "Socket does not fit", {}));
|
||||||
|
|
||||||
return fit;
|
return fit;
|
||||||
}
|
}
|
||||||
@ -133,7 +138,7 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
|
|||||||
targetFlag = TARGET_FLAG_ITEM;
|
targetFlag = TARGET_FLAG_ITEM;
|
||||||
packet << targetFlag;
|
packet << targetFlag;
|
||||||
packet << itemTarget->GetGUID().WriteAsPacked();
|
packet << itemTarget->GetGUID().WriteAsPacked();
|
||||||
out << " on " << chat->FormatItem(itemTarget->GetTemplate());
|
targetText = chat->FormatItem(itemTarget->GetTemplate());
|
||||||
targetSelected = true;
|
targetSelected = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,7 +154,7 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
|
|||||||
{
|
{
|
||||||
targetFlag = TARGET_FLAG_UNIT;
|
targetFlag = TARGET_FLAG_UNIT;
|
||||||
packet << targetFlag << masterSelection.WriteAsPacked();
|
packet << targetFlag << masterSelection.WriteAsPacked();
|
||||||
out << " on " << unit->GetName();
|
targetText = unit->GetName();
|
||||||
targetSelected = true;
|
targetSelected = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,7 +164,7 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
|
|||||||
{
|
{
|
||||||
targetFlag = TARGET_FLAG_UNIT;
|
targetFlag = TARGET_FLAG_UNIT;
|
||||||
packet << targetFlag << unitTarget->GetGUID().WriteAsPacked();
|
packet << targetFlag << unitTarget->GetGUID().WriteAsPacked();
|
||||||
out << " on " << unitTarget->GetName();
|
targetText = unitTarget->GetName();
|
||||||
targetSelected = true;
|
targetSelected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,9 +178,7 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
|
|||||||
packet << uint32(0);
|
packet << uint32(0);
|
||||||
bot->GetSession()->HandleQuestgiverAcceptQuestOpcode(packet);
|
bot->GetSession()->HandleQuestgiverAcceptQuestOpcode(packet);
|
||||||
|
|
||||||
std::ostringstream out;
|
botAI->TellMasterNoFacing("Got quest " + chat->FormatQuest(qInfo));
|
||||||
out << "Got quest " << chat->FormatQuest(qInfo);
|
|
||||||
botAI->TellMasterNoFacing(out.str());
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -217,7 +220,7 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
|
|||||||
targetFlag = TARGET_FLAG_TRADE_ITEM;
|
targetFlag = TARGET_FLAG_TRADE_ITEM;
|
||||||
packet << targetFlag << (uint8)1 << ObjectGuid((uint64)TRADE_SLOT_NONTRADED).WriteAsPacked();
|
packet << targetFlag << (uint8)1 << ObjectGuid((uint64)TRADE_SLOT_NONTRADED).WriteAsPacked();
|
||||||
targetSelected = true;
|
targetSelected = true;
|
||||||
out << " on traded item";
|
targetText = "traded item";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -225,7 +228,7 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
|
|||||||
packet << targetFlag;
|
packet << targetFlag;
|
||||||
packet << itemForSpell->GetGUID().WriteAsPacked();
|
packet << itemForSpell->GetGUID().WriteAsPacked();
|
||||||
targetSelected = true;
|
targetSelected = true;
|
||||||
out << " on " << chat->FormatItem(itemForSpell->GetTemplate());
|
targetText = chat->FormatItem(itemForSpell->GetTemplate());
|
||||||
}
|
}
|
||||||
uint32 castTime = spellInfo->CalcCastTime();
|
uint32 castTime = spellInfo->CalcCastTime();
|
||||||
botAI->SetNextCheckDelay(castTime + sPlayerbotAIConfig.reactDelay);
|
botAI->SetNextCheckDelay(castTime + sPlayerbotAIConfig.reactDelay);
|
||||||
@ -246,17 +249,17 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
|
|||||||
targetSelected = true;
|
targetSelected = true;
|
||||||
|
|
||||||
if (unitTarget == bot || !unitTarget->IsInWorld() || unitTarget->IsDuringRemoveFromWorld())
|
if (unitTarget == bot || !unitTarget->IsInWorld() || unitTarget->IsDuringRemoveFromWorld())
|
||||||
out << " on self";
|
targetText = "self";
|
||||||
else if (unitTarget->IsHostileTo(bot))
|
else if (unitTarget->IsHostileTo(bot))
|
||||||
out << " on self";
|
targetText = "self";
|
||||||
else
|
else
|
||||||
out << " on " << unitTarget->GetName();
|
targetText = unitTarget->GetName();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
packet << bot->GetPackGUID();
|
packet << bot->GetPackGUID();
|
||||||
targetSelected = true;
|
targetSelected = true;
|
||||||
out << " on self";
|
targetText = "self";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,7 +310,12 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
// botAI->SetNextCheckDelay(sPlayerbotAIConfig.globalCoolDown);
|
// botAI->SetNextCheckDelay(sPlayerbotAIConfig.globalCoolDown);
|
||||||
botAI->TellMasterNoFacing(out.str());
|
std::string useText = targetSelected
|
||||||
|
? PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"use_item_on_target", "Using %item on %target", {{"%item", itemText}, {"%target", targetText}})
|
||||||
|
: PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"use_item", "Using %item", {{"%item", itemText}});
|
||||||
|
botAI->TellMasterNoFacing(useText);
|
||||||
bot->GetSession()->HandleUseItemOpcode(packet);
|
bot->GetSession()->HandleUseItemOpcode(packet);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -372,10 +380,10 @@ bool UseItemAction::SocketItem(Item* item, Item* gem, bool replace)
|
|||||||
|
|
||||||
if (fits)
|
if (fits)
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
out << "Socketing " << chat->FormatItem(item->GetTemplate());
|
"socketing_item_with_gem",
|
||||||
out << " with " << chat->FormatItem(gem->GetTemplate());
|
"Socketing %item with %gem",
|
||||||
botAI->TellMaster(out);
|
{{"%item", chat->FormatItem(item->GetTemplate())}, {"%gem", chat->FormatItem(gem->GetTemplate())}}));
|
||||||
|
|
||||||
WorldPackets::Item::SocketGems nicePacket(std::move(packet));
|
WorldPackets::Item::SocketGems nicePacket(std::move(packet));
|
||||||
nicePacket.Read();
|
nicePacket.Read();
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
#include "GridNotifiersImpl.h"
|
#include "GridNotifiersImpl.h"
|
||||||
#include "NearestGameObjects.h"
|
#include "NearestGameObjects.h"
|
||||||
#include "PlayerbotAIConfig.h"
|
#include "PlayerbotAIConfig.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "PositionValue.h"
|
#include "PositionValue.h"
|
||||||
|
|
||||||
@ -36,7 +37,8 @@ bool UseMeetingStoneAction::Execute(Event event)
|
|||||||
|
|
||||||
if (bot->IsInCombat())
|
if (bot->IsInCombat())
|
||||||
{
|
{
|
||||||
botAI->TellError("I am in combat");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"meeting_stone_in_combat", "I am in combat", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,13 +75,15 @@ bool SummonAction::Execute(Event /*event*/)
|
|||||||
|
|
||||||
if (SummonUsingGos(master, bot, true) || SummonUsingNpcs(master, bot, true))
|
if (SummonUsingGos(master, bot, true) || SummonUsingNpcs(master, bot, true))
|
||||||
{
|
{
|
||||||
botAI->TellMasterNoFacing("Hello!");
|
botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"hello", "Hello!", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SummonUsingGos(bot, master, true) || SummonUsingNpcs(bot, master, true))
|
if (SummonUsingGos(bot, master, true) || SummonUsingNpcs(bot, master, true))
|
||||||
{
|
{
|
||||||
botAI->TellMasterNoFacing("Welcome!");
|
botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"meeting_stone_welcome", "Welcome!", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +103,10 @@ bool SummonAction::SummonUsingGos(Player* summoner, Player* player, bool preserv
|
|||||||
return Teleport(summoner, player, preserveAuras);
|
return Teleport(summoner, player, preserveAuras);
|
||||||
}
|
}
|
||||||
|
|
||||||
botAI->TellError(summoner == bot ? "There is no meeting stone nearby" : "There is no meeting stone near you");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
summoner == bot ? "meeting_stone_none_nearby" : "meeting_stone_none_near_you",
|
||||||
|
summoner == bot ? "There is no meeting stone nearby" : "There is no meeting stone near you",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,13 +126,19 @@ bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player, bool preser
|
|||||||
{
|
{
|
||||||
if (!player->HasItemCount(6948, 1, false))
|
if (!player->HasItemCount(6948, 1, false))
|
||||||
{
|
{
|
||||||
botAI->TellError(player == bot ? "I have no hearthstone" : "You have no hearthstone");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
player == bot ? "meeting_stone_no_hearthstone_self" : "meeting_stone_no_hearthstone_you",
|
||||||
|
player == bot ? "I have no hearthstone" : "You have no hearthstone",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player->HasSpellCooldown(8690))
|
if (player->HasSpellCooldown(8690))
|
||||||
{
|
{
|
||||||
botAI->TellError(player == bot ? "My hearthstone is not ready" : "Your hearthstone is not ready");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
player == bot ? "meeting_stone_hearthstone_not_ready_self" : "meeting_stone_hearthstone_not_ready_you",
|
||||||
|
player == bot ? "My hearthstone is not ready" : "Your hearthstone is not ready",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +154,10 @@ bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player, bool preser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
botAI->TellError(summoner == bot ? "There are no innkeepers nearby" : "There are no innkeepers near you");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
summoner == bot ? "meeting_stone_no_innkeepers_nearby" : "meeting_stone_no_innkeepers_near_you",
|
||||||
|
summoner == bot ? "There are no innkeepers nearby" : "There are no innkeepers near you",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +169,8 @@ bool SummonAction::Teleport(Player* summoner, Player* player, bool preserveAuras
|
|||||||
|
|
||||||
if (player->GetVehicle())
|
if (player->GetVehicle())
|
||||||
{
|
{
|
||||||
botAI->TellError("You cannot summon me while I'm on a vehicle");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"meeting_stone_cannot_summon_vehicle", "You cannot summon me while I'm on a vehicle", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,20 +191,29 @@ bool SummonAction::Teleport(Player* summoner, Player* player, bool preserveAuras
|
|||||||
|
|
||||||
if (summoner->IsInCombat() && !sPlayerbotAIConfig.allowSummonInCombat)
|
if (summoner->IsInCombat() && !sPlayerbotAIConfig.allowSummonInCombat)
|
||||||
{
|
{
|
||||||
botAI->TellError("You cannot summon me while you're in combat");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"meeting_stone_cannot_summon_master_in_combat",
|
||||||
|
"You cannot summon me while you're in combat",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!summoner->IsAlive() && !sPlayerbotAIConfig.allowSummonWhenMasterIsDead)
|
if (!summoner->IsAlive() && !sPlayerbotAIConfig.allowSummonWhenMasterIsDead)
|
||||||
{
|
{
|
||||||
botAI->TellError("You cannot summon me while you're dead");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"meeting_stone_cannot_summon_master_dead",
|
||||||
|
"You cannot summon me while you're dead",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST) &&
|
if (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST) &&
|
||||||
!sPlayerbotAIConfig.allowSummonWhenBotIsDead)
|
!sPlayerbotAIConfig.allowSummonWhenBotIsDead)
|
||||||
{
|
{
|
||||||
botAI->TellError("You cannot summon me while I'm dead, you need to release my spirit first");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"meeting_stone_cannot_summon_bot_dead",
|
||||||
|
"You cannot summon me while I'm dead, you need to release my spirit first",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,7 +225,8 @@ bool SummonAction::Teleport(Player* summoner, Player* player, bool preserveAuras
|
|||||||
{
|
{
|
||||||
bot->ResurrectPlayer(1.0f, false);
|
bot->ResurrectPlayer(1.0f, false);
|
||||||
bot->SpawnCorpseBones();
|
bot->SpawnCorpseBones();
|
||||||
botAI->TellMasterNoFacing("I live, again!");
|
botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"meeting_stone_revived", "I live, again!", {}));
|
||||||
botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Reset();
|
botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,6 +256,7 @@ bool SummonAction::Teleport(Player* summoner, Player* player, bool preserveAuras
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (summoner != player)
|
if (summoner != player)
|
||||||
botAI->TellError("Not enough place to summon");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"meeting_stone_not_enough_space", "Not enough place to summon", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -139,6 +139,7 @@ public:
|
|||||||
creators["maintenance"] = &ChatActionContext::maintenance;
|
creators["maintenance"] = &ChatActionContext::maintenance;
|
||||||
creators["remove glyph"] = &ChatActionContext::remove_glyph;
|
creators["remove glyph"] = &ChatActionContext::remove_glyph;
|
||||||
creators["autogear"] = &ChatActionContext::autogear;
|
creators["autogear"] = &ChatActionContext::autogear;
|
||||||
|
creators["autogear bis"] = &ChatActionContext::autogear_bis;
|
||||||
creators["equip upgrade"] = &ChatActionContext::equip_upgrade;
|
creators["equip upgrade"] = &ChatActionContext::equip_upgrade;
|
||||||
creators["attack my target"] = &ChatActionContext::attack_my_target;
|
creators["attack my target"] = &ChatActionContext::attack_my_target;
|
||||||
creators["pull my target"] = &ChatActionContext::pull_my_target;
|
creators["pull my target"] = &ChatActionContext::pull_my_target;
|
||||||
@ -261,6 +262,7 @@ private:
|
|||||||
static Action* maintenance(PlayerbotAI* botAI) { return new MaintenanceAction(botAI); }
|
static Action* maintenance(PlayerbotAI* botAI) { return new MaintenanceAction(botAI); }
|
||||||
static Action* remove_glyph(PlayerbotAI* botAI) { return new RemoveGlyphAction(botAI); }
|
static Action* remove_glyph(PlayerbotAI* botAI) { return new RemoveGlyphAction(botAI); }
|
||||||
static Action* autogear(PlayerbotAI* botAI) { return new AutoGearAction(botAI); }
|
static Action* autogear(PlayerbotAI* botAI) { return new AutoGearAction(botAI); }
|
||||||
|
static Action* autogear_bis(PlayerbotAI* botAI) { return new BisGearAction(botAI); }
|
||||||
static Action* equip_upgrade(PlayerbotAI* botAI) { return new EquipUpgradeAction(botAI); }
|
static Action* equip_upgrade(PlayerbotAI* botAI) { return new EquipUpgradeAction(botAI); }
|
||||||
static Action* co(PlayerbotAI* botAI) { return new ChangeCombatStrategyAction(botAI); }
|
static Action* co(PlayerbotAI* botAI) { return new ChangeCombatStrategyAction(botAI); }
|
||||||
static Action* nc(PlayerbotAI* botAI) { return new ChangeNonCombatStrategyAction(botAI); }
|
static Action* nc(PlayerbotAI* botAI) { return new ChangeNonCombatStrategyAction(botAI); }
|
||||||
|
|||||||
@ -65,6 +65,7 @@ public:
|
|||||||
creators["maintenance"] = &ChatTriggerContext::maintenance;
|
creators["maintenance"] = &ChatTriggerContext::maintenance;
|
||||||
creators["remove glyph"] = &ChatTriggerContext::remove_glyph;
|
creators["remove glyph"] = &ChatTriggerContext::remove_glyph;
|
||||||
creators["autogear"] = &ChatTriggerContext::autogear;
|
creators["autogear"] = &ChatTriggerContext::autogear;
|
||||||
|
creators["autogear bis"] = &ChatTriggerContext::autogear_bis;
|
||||||
creators["equip upgrade"] = &ChatTriggerContext::equip_upgrade;
|
creators["equip upgrade"] = &ChatTriggerContext::equip_upgrade;
|
||||||
creators["attack"] = &ChatTriggerContext::attack;
|
creators["attack"] = &ChatTriggerContext::attack;
|
||||||
creators["pull"] = &ChatTriggerContext::pull;
|
creators["pull"] = &ChatTriggerContext::pull;
|
||||||
@ -220,6 +221,7 @@ private:
|
|||||||
static Trigger* maintenance(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "maintenance"); }
|
static Trigger* maintenance(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "maintenance"); }
|
||||||
static Trigger* remove_glyph(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "remove glyph"); }
|
static Trigger* remove_glyph(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "remove glyph"); }
|
||||||
static Trigger* autogear(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "autogear"); }
|
static Trigger* autogear(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "autogear"); }
|
||||||
|
static Trigger* autogear_bis(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "autogear bis"); }
|
||||||
static Trigger* equip_upgrade(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "equip upgrade"); }
|
static Trigger* equip_upgrade(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "equip upgrade"); }
|
||||||
static Trigger* co(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "co"); }
|
static Trigger* co(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "co"); }
|
||||||
static Trigger* nc(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "nc"); }
|
static Trigger* nc(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "nc"); }
|
||||||
|
|||||||
@ -20,20 +20,23 @@ private:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Commands where trigger name =/= action name.
|
||||||
void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||||
{
|
{
|
||||||
PassTroughStrategy::InitTriggers(triggers);
|
PassTroughStrategy::InitTriggers(triggers);
|
||||||
|
|
||||||
|
// Keep single action triggers on one line, and multi-action triggers on multiple lines.
|
||||||
triggers.push_back(new TriggerNode("rep", { NextAction("reputation", relevance) }));
|
triggers.push_back(new TriggerNode("rep", { NextAction("reputation", relevance) }));
|
||||||
triggers.push_back(new TriggerNode("pvp stats", { NextAction("tell pvp stats", relevance) }));
|
triggers.push_back(new TriggerNode("pvp stats", { NextAction("tell pvp stats", relevance) }));
|
||||||
triggers.push_back(new TriggerNode("q", { NextAction("query quest", relevance),
|
triggers.push_back(new TriggerNode("q",
|
||||||
|
{ NextAction("query quest", relevance),
|
||||||
NextAction("query item usage", relevance) }));
|
NextAction("query item usage", relevance) }));
|
||||||
triggers.push_back(new TriggerNode("add all loot", { NextAction("add all loot", relevance),
|
triggers.push_back(new TriggerNode("add all loot",
|
||||||
|
{ NextAction("add all loot", relevance),
|
||||||
NextAction("loot", relevance) }));
|
NextAction("loot", relevance) }));
|
||||||
triggers.push_back(new TriggerNode("u", { NextAction("use", relevance) }));
|
triggers.push_back(new TriggerNode("u", { NextAction("use", relevance) }));
|
||||||
triggers.push_back(new TriggerNode("c", { NextAction("item count", relevance) }));
|
triggers.push_back(new TriggerNode("c", { NextAction("item count", relevance) }));
|
||||||
triggers.push_back(
|
triggers.push_back(new TriggerNode("items", { NextAction("item count", relevance) }));
|
||||||
new TriggerNode("items", { NextAction("item count", relevance) }));
|
|
||||||
triggers.push_back(new TriggerNode("inv", { NextAction("item count", relevance) }));
|
triggers.push_back(new TriggerNode("inv", { NextAction("item count", relevance) }));
|
||||||
triggers.push_back(new TriggerNode("e", { NextAction("equip", relevance) }));
|
triggers.push_back(new TriggerNode("e", { NextAction("equip", relevance) }));
|
||||||
triggers.push_back(new TriggerNode("ue", { NextAction("unequip", relevance) }));
|
triggers.push_back(new TriggerNode("ue", { NextAction("unequip", relevance) }));
|
||||||
@ -42,81 +45,40 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
|
|||||||
triggers.push_back(new TriggerNode("s", { NextAction("sell", relevance) }));
|
triggers.push_back(new TriggerNode("s", { NextAction("sell", relevance) }));
|
||||||
triggers.push_back(new TriggerNode("b", { NextAction("buy", relevance) }));
|
triggers.push_back(new TriggerNode("b", { NextAction("buy", relevance) }));
|
||||||
triggers.push_back(new TriggerNode("r", { NextAction("reward", relevance) }));
|
triggers.push_back(new TriggerNode("r", { NextAction("reward", relevance) }));
|
||||||
triggers.push_back(
|
triggers.push_back(new TriggerNode("attack", { NextAction("attack my target", relevance) }));
|
||||||
new TriggerNode("attack", { NextAction("attack my target", relevance) }));
|
triggers.push_back(new TriggerNode("accept", { NextAction("accept quest", relevance) }));
|
||||||
triggers.push_back(
|
triggers.push_back(new TriggerNode("follow", { NextAction("follow chat shortcut", relevance) }));
|
||||||
new TriggerNode("accept", { NextAction("accept quest", relevance) }));
|
triggers.push_back(new TriggerNode("stay", { NextAction("stay chat shortcut", relevance) }));
|
||||||
triggers.push_back(
|
triggers.push_back(new TriggerNode("move from group", { NextAction("move from group chat shortcut", relevance) }));
|
||||||
new TriggerNode("follow", { NextAction("follow chat shortcut", relevance) }));
|
triggers.push_back(new TriggerNode("flee", { NextAction("flee chat shortcut", relevance) }));
|
||||||
triggers.push_back(
|
triggers.push_back(new TriggerNode("tank attack", { NextAction("tank attack chat shortcut", relevance) }));
|
||||||
new TriggerNode("stay", { NextAction("stay chat shortcut", relevance) }));
|
triggers.push_back(new TriggerNode("grind", { NextAction("grind chat shortcut", relevance) }));
|
||||||
triggers.push_back(
|
triggers.push_back(new TriggerNode("talk",
|
||||||
new TriggerNode("move from group", { NextAction("move from group chat shortcut", relevance) }));
|
{ NextAction("gossip hello", relevance),
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode("flee", { NextAction("flee chat shortcut", relevance) }));
|
|
||||||
triggers.push_back(new TriggerNode(
|
|
||||||
"tank attack", { NextAction("tank attack chat shortcut", relevance) }));
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode("grind", { NextAction("grind chat shortcut", relevance) }));
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode("talk", { NextAction("gossip hello", relevance),
|
|
||||||
NextAction("talk to quest giver", relevance) }));
|
NextAction("talk to quest giver", relevance) }));
|
||||||
triggers.push_back(
|
triggers.push_back(new TriggerNode("enter vehicle", { NextAction("enter vehicle", relevance) }));
|
||||||
new TriggerNode("enter vehicle", { NextAction("enter vehicle", relevance) }));
|
triggers.push_back(new TriggerNode("leave vehicle", { NextAction("leave vehicle", relevance) }));
|
||||||
triggers.push_back(
|
triggers.push_back(new TriggerNode("cast", { NextAction("cast custom spell", relevance) }));
|
||||||
new TriggerNode("leave vehicle", { NextAction("leave vehicle", relevance) }));
|
triggers.push_back(new TriggerNode("castnc", { NextAction("cast custom nc spell", relevance) }));
|
||||||
triggers.push_back(
|
triggers.push_back(new TriggerNode("revive", { NextAction("spirit healer", relevance) }));
|
||||||
new TriggerNode("cast", { NextAction("cast custom spell", relevance) }));
|
triggers.push_back(new TriggerNode("runaway", { NextAction("runaway chat shortcut", relevance) }));
|
||||||
triggers.push_back(
|
triggers.push_back(new TriggerNode("warning", { NextAction("runaway chat shortcut", relevance) }));
|
||||||
new TriggerNode("castnc", { NextAction("cast custom nc spell", relevance) }));
|
triggers.push_back(new TriggerNode("max dps", { NextAction("max dps chat shortcut", relevance) }));
|
||||||
triggers.push_back(
|
triggers.push_back(new TriggerNode("attackers", { NextAction("tell attackers", relevance) }));
|
||||||
new TriggerNode("revive", { NextAction("spirit healer", relevance) }));
|
triggers.push_back(new TriggerNode("target", { NextAction("tell target", relevance) }));
|
||||||
triggers.push_back(
|
triggers.push_back(new TriggerNode("pull", { NextAction("pull my target", relevance) }));
|
||||||
new TriggerNode("runaway", { NextAction("runaway chat shortcut", relevance) }));
|
triggers.push_back(new TriggerNode("pull back", { NextAction("pull my target", relevance) }));
|
||||||
triggers.push_back(
|
triggers.push_back(new TriggerNode("pull rti", { NextAction("pull rti target", relevance) }));
|
||||||
new TriggerNode("warning", { NextAction("runaway chat shortcut", relevance) }));
|
triggers.push_back(new TriggerNode("ready", { NextAction("ready check", relevance) }));
|
||||||
triggers.push_back(
|
triggers.push_back(new TriggerNode("naxx", {NextAction("naxx chat shortcut", relevance) }));
|
||||||
new TriggerNode("max dps", { NextAction("max dps chat shortcut", relevance) }));
|
triggers.push_back(new TriggerNode("bwl", { NextAction("bwl chat shortcut", relevance) }));
|
||||||
triggers.push_back(
|
triggers.push_back(new TriggerNode("dps", { NextAction("tell estimated dps", relevance) }));
|
||||||
new TriggerNode("attackers", { NextAction("tell attackers", relevance) }));
|
triggers.push_back(new TriggerNode("disperse", { NextAction("disperse set", relevance) }));
|
||||||
triggers.push_back(
|
triggers.push_back(new TriggerNode("qi", { NextAction("query item usage", relevance) }));
|
||||||
new TriggerNode("target", { NextAction("tell target", relevance) }));
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode("pull", { NextAction("pull my target", relevance) }));
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode("pull back", { NextAction("pull my target", relevance) }));
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode("pull rti", { NextAction("pull rti target", relevance) }));
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode("ready", { NextAction("ready check", relevance) }));
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode("naxx", {NextAction("naxx chat shortcut", relevance)}));
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode("bwl", { NextAction("bwl chat shortcut", relevance) }));
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode("dps", { NextAction("tell estimated dps", relevance) }));
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode("disperse", { NextAction("disperse set", relevance) }));
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode("open items", { NextAction("open items", relevance) }));
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode("qi", { NextAction("query item usage", relevance) }));
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode("unlock items", { NextAction("unlock items", relevance) }));
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode("unlock traded item", { NextAction("unlock traded item", relevance) }));
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode("wipe", { NextAction("wipe", relevance) }));
|
|
||||||
triggers.push_back(new TriggerNode("tame", { NextAction("tame", relevance) }));
|
|
||||||
triggers.push_back(new TriggerNode("glyphs", { NextAction("glyphs", relevance) })); // Added for custom Glyphs
|
|
||||||
triggers.push_back(new TriggerNode("glyph equip", { NextAction("glyph equip", relevance) })); // Added for custom Glyphs
|
|
||||||
triggers.push_back(new TriggerNode("pet", { NextAction("pet", relevance) }));
|
|
||||||
triggers.push_back(new TriggerNode("pet attack", { NextAction("pet attack", relevance) }));
|
|
||||||
triggers.push_back(new TriggerNode("roll", { NextAction("roll", relevance) }));
|
|
||||||
triggers.push_back(new TriggerNode("focus heal", { NextAction("focus heal targets", relevance) }));
|
triggers.push_back(new TriggerNode("focus heal", { NextAction("focus heal targets", relevance) }));
|
||||||
triggers.push_back(new TriggerNode("emblems", { NextAction("emblems", relevance) }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Commands where trigger name == action name.
|
||||||
ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI)
|
ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI)
|
||||||
{
|
{
|
||||||
actionNodeFactories.Add(new ChatCommandActionNodeFactoryInternal());
|
actionNodeFactories.Add(new ChatCommandActionNodeFactoryInternal());
|
||||||
@ -149,6 +111,7 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
|
|||||||
supported.push_back("maintenance");
|
supported.push_back("maintenance");
|
||||||
supported.push_back("remove glyph");
|
supported.push_back("remove glyph");
|
||||||
supported.push_back("autogear");
|
supported.push_back("autogear");
|
||||||
|
supported.push_back("autogear bis");
|
||||||
supported.push_back("equip upgrade");
|
supported.push_back("equip upgrade");
|
||||||
supported.push_back("chat");
|
supported.push_back("chat");
|
||||||
supported.push_back("home");
|
supported.push_back("home");
|
||||||
@ -199,15 +162,15 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
|
|||||||
supported.push_back("rtsc");
|
supported.push_back("rtsc");
|
||||||
supported.push_back("drink");
|
supported.push_back("drink");
|
||||||
supported.push_back("calc");
|
supported.push_back("calc");
|
||||||
|
supported.push_back("roll");
|
||||||
supported.push_back("open items");
|
supported.push_back("open items");
|
||||||
supported.push_back("qi");
|
|
||||||
supported.push_back("unlock items");
|
supported.push_back("unlock items");
|
||||||
supported.push_back("unlock traded item");
|
supported.push_back("unlock traded item");
|
||||||
|
supported.push_back("wipe");
|
||||||
supported.push_back("tame");
|
supported.push_back("tame");
|
||||||
supported.push_back("glyphs");
|
supported.push_back("glyphs");
|
||||||
supported.push_back("glyph equip");
|
supported.push_back("glyph equip");
|
||||||
supported.push_back("pet");
|
supported.push_back("pet");
|
||||||
supported.push_back("pet attack");
|
supported.push_back("pet attack");
|
||||||
supported.push_back("wait for attack time");
|
supported.push_back("wait for attack time");
|
||||||
supported.push_back("focus heal");
|
|
||||||
}
|
}
|
||||||
@ -16,7 +16,7 @@ void WorldPacketHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
|
|||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
new TriggerNode("uninvite guid", { NextAction("uninvite", relevance) }));
|
new TriggerNode("uninvite guid", { NextAction("uninvite", relevance) }));
|
||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
new TriggerNode("group set leader", { /*NextAction("leader", relevance),*/ }));
|
new TriggerNode("group set leader", { NextAction("reset botAI", relevance) }));
|
||||||
triggers.push_back(new TriggerNode(
|
triggers.push_back(new TriggerNode(
|
||||||
"not enough money", { NextAction("tell not enough money", relevance) }));
|
"not enough money", { NextAction("tell not enough money", relevance) }));
|
||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "GenericBuffUtils.h"
|
||||||
#include "CreatureAI.h"
|
#include "CreatureAI.h"
|
||||||
#include "ItemVisitors.h"
|
#include "ItemVisitors.h"
|
||||||
#include "LastSpellCastValue.h"
|
#include "LastSpellCastValue.h"
|
||||||
@ -34,54 +35,57 @@ bool MediumManaTrigger::IsActive()
|
|||||||
AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig.mediumMana;
|
AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig.mediumMana;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool LowEnergyTrigger::IsActive()
|
||||||
|
{
|
||||||
|
return AI_VALUE2(uint8, "energy", "self target") < threshold;
|
||||||
|
}
|
||||||
|
|
||||||
bool NoPetTrigger::IsActive()
|
bool NoPetTrigger::IsActive()
|
||||||
{
|
{
|
||||||
return (bot->GetMinionGUID().IsEmpty()) && (!AI_VALUE(Unit*, "pet target")) && (!bot->GetGuardianPet()) &&
|
return bot->GetMinionGUID().IsEmpty() && !AI_VALUE(Unit*, "pet target") && !bot->GetGuardianPet() &&
|
||||||
(!bot->GetFirstControlled()) && (!AI_VALUE2(bool, "mounted", "self target"));
|
!bot->GetFirstControlled() && !AI_VALUE2(bool, "mounted", "self target");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HasPetTrigger::IsActive()
|
bool HasPetTrigger::IsActive()
|
||||||
{
|
{
|
||||||
return (AI_VALUE(Unit*, "pet target")) && !AI_VALUE2(bool, "mounted", "self target");
|
return AI_VALUE(Unit*, "pet target") && !AI_VALUE2(bool, "mounted", "self target");
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PetAttackTrigger::IsActive()
|
bool PetAttackTrigger::IsActive()
|
||||||
{
|
{
|
||||||
Guardian* pet = bot->GetGuardianPet();
|
Guardian* pet = bot->GetGuardianPet();
|
||||||
if (!pet)
|
if (!pet)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
Unit* target = AI_VALUE(Unit*, "current target");
|
Unit* target = AI_VALUE(Unit*, "current target");
|
||||||
if (!target)
|
if (!target)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
if (pet->GetVictim() == target && pet->GetCharmInfo()->IsCommandAttack())
|
if (pet->GetVictim() == target && pet->GetCharmInfo()->IsCommandAttack())
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
if (bot->GetMap()->IsDungeon() && bot->GetGroup() && !target->IsInCombat())
|
if (bot->GetMap()->IsDungeon() && bot->GetGroup() && !target->IsInCombat())
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HighManaTrigger::IsActive()
|
bool HighManaTrigger::IsActive()
|
||||||
{
|
{
|
||||||
return AI_VALUE2(bool, "has mana", "self target") && AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig.highMana;
|
return AI_VALUE2(bool, "has mana", "self target") &&
|
||||||
|
AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig.highMana;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AlmostFullManaTrigger::IsActive()
|
bool AlmostFullManaTrigger::IsActive()
|
||||||
{
|
{
|
||||||
return AI_VALUE2(bool, "has mana", "self target") && AI_VALUE2(uint8, "mana", "self target") > 85;
|
return AI_VALUE2(bool, "has mana", "self target") &&
|
||||||
|
AI_VALUE2(uint8, "mana", "self target") > 85;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EnoughManaTrigger::IsActive()
|
bool EnoughManaTrigger::IsActive()
|
||||||
{
|
{
|
||||||
return AI_VALUE2(bool, "has mana", "self target") && AI_VALUE2(uint8, "mana", "self target") > sPlayerbotAIConfig.highMana;
|
return AI_VALUE2(bool, "has mana", "self target") &&
|
||||||
|
AI_VALUE2(uint8, "mana", "self target") > sPlayerbotAIConfig.highMana;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RageAvailable::IsActive() { return AI_VALUE2(uint8, "rage", "self target") >= amount; }
|
bool RageAvailable::IsActive() { return AI_VALUE2(uint8, "rage", "self target") >= amount; }
|
||||||
@ -96,9 +100,8 @@ bool TargetWithComboPointsLowerHealTrigger::IsActive()
|
|||||||
{
|
{
|
||||||
Unit* target = AI_VALUE(Unit*, "current target");
|
Unit* target = AI_VALUE(Unit*, "current target");
|
||||||
if (!target || !target->IsAlive() || !target->IsInWorld())
|
if (!target || !target->IsAlive() || !target->IsInWorld())
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
return ComboPointsAvailableTrigger::IsActive() &&
|
return ComboPointsAvailableTrigger::IsActive() &&
|
||||||
(target->GetHealth() / AI_VALUE(float, "estimated group dps")) <= lifeTime;
|
(target->GetHealth() / AI_VALUE(float, "estimated group dps")) <= lifeTime;
|
||||||
}
|
}
|
||||||
@ -159,19 +162,27 @@ bool BuffTrigger::IsActive()
|
|||||||
Unit* target = GetTarget();
|
Unit* target = GetTarget();
|
||||||
if (!target)
|
if (!target)
|
||||||
return false;
|
return false;
|
||||||
if (!SpellTrigger::IsActive())
|
|
||||||
return false;
|
|
||||||
Aura* aura = botAI->GetAura(spell, target, checkIsOwner, checkDuration);
|
Aura* aura = botAI->GetAura(spell, target, checkIsOwner, checkDuration);
|
||||||
if (!aura)
|
if (!aura || (beforeDuration && aura->GetDuration() < beforeDuration))
|
||||||
return true;
|
|
||||||
if (beforeDuration && aura->GetDuration() < beforeDuration)
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Value<Unit*>* BuffOnPartyTrigger::GetTargetValue()
|
Value<Unit*>* BuffOnPartyTrigger::GetTargetValue()
|
||||||
{
|
{
|
||||||
return context->GetValue<Unit*>("party member without aura", spell);
|
return context->GetValue<Unit*>(
|
||||||
|
"party member without aura", ai::buff::MakeAuraQualifierForBuff(spell));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BuffOnPartyTrigger::IsActive()
|
||||||
|
{
|
||||||
|
Unit* target = GetTarget();
|
||||||
|
if (ai::buff::ShouldDeferPartyBuffEvaluationForRecentLogin(bot, target, spell))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return BuffTrigger::IsActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ProtectPartyMemberTrigger::IsActive() { return AI_VALUE(Unit*, "party member to protect"); }
|
bool ProtectPartyMemberTrigger::IsActive() { return AI_VALUE(Unit*, "party member to protect"); }
|
||||||
@ -204,13 +215,14 @@ bool MediumThreatTrigger::IsActive()
|
|||||||
{
|
{
|
||||||
if (!AI_VALUE(Unit*, "main tank"))
|
if (!AI_VALUE(Unit*, "main tank"))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return MyAttackerCountTrigger::IsActive();
|
return MyAttackerCountTrigger::IsActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LowTankThreatTrigger::IsActive()
|
bool LowTankThreatTrigger::IsActive()
|
||||||
{
|
{
|
||||||
Unit* mt = AI_VALUE(Unit*, "main tank");
|
Unit* mainTank = AI_VALUE(Unit*, "main tank");
|
||||||
if (!mt)
|
if (!mainTank)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Unit* current_target = AI_VALUE(Unit*, "current target");
|
Unit* current_target = AI_VALUE(Unit*, "current target");
|
||||||
@ -219,7 +231,7 @@ bool LowTankThreatTrigger::IsActive()
|
|||||||
|
|
||||||
ThreatManager& mgr = current_target->GetThreatMgr();
|
ThreatManager& mgr = current_target->GetThreatMgr();
|
||||||
float threat = mgr.GetThreat(bot);
|
float threat = mgr.GetThreat(bot);
|
||||||
float tankThreat = mgr.GetThreat(mt);
|
float tankThreat = mgr.GetThreat(mainTank);
|
||||||
return tankThreat == 0.0f || threat > tankThreat * 0.5f;
|
return tankThreat == 0.0f || threat > tankThreat * 0.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,9 +239,8 @@ bool AoeTrigger::IsActive()
|
|||||||
{
|
{
|
||||||
Unit* current_target = AI_VALUE(Unit*, "current target");
|
Unit* current_target = AI_VALUE(Unit*, "current target");
|
||||||
if (!current_target)
|
if (!current_target)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
GuidVector attackers = context->GetValue<GuidVector>("attackers")->Get();
|
GuidVector attackers = context->GetValue<GuidVector>("attackers")->Get();
|
||||||
int attackers_count = 0;
|
int attackers_count = 0;
|
||||||
for (ObjectGuid const guid : attackers)
|
for (ObjectGuid const guid : attackers)
|
||||||
@ -237,11 +248,10 @@ bool AoeTrigger::IsActive()
|
|||||||
Unit* unit = botAI->GetUnit(guid);
|
Unit* unit = botAI->GetUnit(guid);
|
||||||
if (!unit || !unit->IsAlive())
|
if (!unit || !unit->IsAlive())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (unit->GetDistance(current_target->GetPosition()) <= range)
|
if (unit->GetDistance(current_target->GetPosition()) <= range)
|
||||||
{
|
|
||||||
attackers_count++;
|
attackers_count++;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return attackers_count >= amount;
|
return attackers_count >= amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,20 +279,19 @@ bool DebuffTrigger::IsActive()
|
|||||||
{
|
{
|
||||||
Unit* target = GetTarget();
|
Unit* target = GetTarget();
|
||||||
if (!target || !target->IsAlive() || !target->IsInWorld())
|
if (!target || !target->IsAlive() || !target->IsInWorld())
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
return BuffTrigger::IsActive() && (target->GetHealth() / AI_VALUE(float, "estimated group dps")) >= needLifeTime;
|
return BuffTrigger::IsActive() &&
|
||||||
|
(target->GetHealth() / AI_VALUE(float, "estimated group dps")) >= needLifeTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DebuffOnBossTrigger::IsActive()
|
bool DebuffOnBossTrigger::IsActive()
|
||||||
{
|
{
|
||||||
if (!DebuffTrigger::IsActive())
|
if (!DebuffTrigger::IsActive())
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
Creature* c = GetTarget()->ToCreature();
|
Creature* creature = GetTarget()->ToCreature();
|
||||||
return c && ((c->IsDungeonBoss()) || (c->isWorldBoss()));
|
return creature && (creature->IsDungeonBoss() || creature->isWorldBoss());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SpellTrigger::IsActive() { return GetTarget(); }
|
bool SpellTrigger::IsActive() { return GetTarget(); }
|
||||||
@ -312,9 +321,7 @@ bool SpellCooldownTrigger::IsActive()
|
|||||||
}
|
}
|
||||||
|
|
||||||
RandomTrigger::RandomTrigger(PlayerbotAI* botAI, std::string const name, int32 probability)
|
RandomTrigger::RandomTrigger(PlayerbotAI* botAI, std::string const name, int32 probability)
|
||||||
: Trigger(botAI, name), probability(probability), lastCheck(getMSTime())
|
: Trigger(botAI, name), probability(probability), lastCheck(getMSTime()) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RandomTrigger::IsActive()
|
bool RandomTrigger::IsActive()
|
||||||
{
|
{
|
||||||
@ -325,6 +332,7 @@ bool RandomTrigger::IsActive()
|
|||||||
int32 k = (int32)(probability / sPlayerbotAIConfig.randomChangeMultiplier);
|
int32 k = (int32)(probability / sPlayerbotAIConfig.randomChangeMultiplier);
|
||||||
if (k < 1)
|
if (k < 1)
|
||||||
k = 1;
|
k = 1;
|
||||||
|
|
||||||
return (rand() % k) == 0;
|
return (rand() % k) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -363,9 +371,11 @@ bool BoostTrigger::IsActive()
|
|||||||
{
|
{
|
||||||
if (!BuffTrigger::IsActive())
|
if (!BuffTrigger::IsActive())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Unit* target = AI_VALUE(Unit*, "current target");
|
Unit* target = AI_VALUE(Unit*, "current target");
|
||||||
if (target && target->ToPlayer())
|
if (target && target->ToPlayer())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return AI_VALUE(uint8, "balance") <= balance;
|
return AI_VALUE(uint8, "balance") <= balance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,20 +384,19 @@ bool GenericBoostTrigger::IsActive()
|
|||||||
Unit* target = AI_VALUE(Unit*, "current target");
|
Unit* target = AI_VALUE(Unit*, "current target");
|
||||||
if (target && target->ToPlayer())
|
if (target && target->ToPlayer())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return AI_VALUE(uint8, "balance") <= balance;
|
return AI_VALUE(uint8, "balance") <= balance;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HealerShouldAttackTrigger::IsActive()
|
bool HealerShouldAttackTrigger::IsActive()
|
||||||
{
|
{
|
||||||
// nobody can help me
|
|
||||||
if (botAI->GetNearGroupMemberCount(sPlayerbotAIConfig.sightDistance) <= 1)
|
if (botAI->GetNearGroupMemberCount(sPlayerbotAIConfig.sightDistance) <= 1)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (AI_VALUE2(uint8, "health", "party member to heal") < sPlayerbotAIConfig.almostFullHealth)
|
if (AI_VALUE2(uint8, "health", "party member to heal") < sPlayerbotAIConfig.almostFullHealth)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// special check for resto druid (dont remove tree of life frequently)
|
if (bot->GetAura(33891)) // Tree of Life
|
||||||
if (bot->GetAura(33891))
|
|
||||||
{
|
{
|
||||||
LastSpellCast& lastSpell = botAI->GetAiObjectContext()->GetValue<LastSpellCast&>("last spell cast")->Get();
|
LastSpellCast& lastSpell = botAI->GetAiObjectContext()->GetValue<LastSpellCast&>("last spell cast")->Get();
|
||||||
if (lastSpell.timer + 5 > time(nullptr))
|
if (lastSpell.timer + 5 > time(nullptr))
|
||||||
@ -396,7 +405,6 @@ bool HealerShouldAttackTrigger::IsActive()
|
|||||||
|
|
||||||
int manaThreshold;
|
int manaThreshold;
|
||||||
int balance = AI_VALUE(uint8, "balance");
|
int balance = AI_VALUE(uint8, "balance");
|
||||||
// higher threshold in higher pressure
|
|
||||||
if (balance <= 50)
|
if (balance <= 50)
|
||||||
manaThreshold = 85;
|
manaThreshold = 85;
|
||||||
else if (balance <= 100)
|
else if (balance <= 100)
|
||||||
@ -420,13 +428,7 @@ bool InterruptSpellTrigger::IsActive()
|
|||||||
bool DeflectSpellTrigger::IsActive()
|
bool DeflectSpellTrigger::IsActive()
|
||||||
{
|
{
|
||||||
Unit* target = GetTarget();
|
Unit* target = GetTarget();
|
||||||
if (!target)
|
if (!target || !target->IsNonMeleeSpellCast(true) || target->GetTarget() != bot->GetGUID())
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!target->IsNonMeleeSpellCast(true))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (target->GetTarget() != bot->GetGUID())
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
uint32 spellid = context->GetValue<uint32>("spell id", spell)->Get();
|
uint32 spellid = context->GetValue<uint32>("spell id", spell)->Get();
|
||||||
@ -457,6 +459,7 @@ bool DeflectSpellTrigger::IsActive()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,17 +493,16 @@ bool FearSleepSapTrigger::IsActive()
|
|||||||
|
|
||||||
bool HasAuraStackTrigger::IsActive()
|
bool HasAuraStackTrigger::IsActive()
|
||||||
{
|
{
|
||||||
Aura* aura = botAI->GetAura(getName(), GetTarget(), false, true, stack);
|
return botAI->GetAura(getName(), GetTarget(), false, true, stack);
|
||||||
// sLog->outMessage("playerbot", LOG_LEVEL_DEBUG, "HasAuraStackTrigger::IsActive %s %d", getName(), aura ?
|
|
||||||
// aura->GetStackAmount() : -1);
|
|
||||||
return aura;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TimerTrigger::IsActive()
|
bool TimerTrigger::IsActive()
|
||||||
{
|
{
|
||||||
if (time(nullptr) != lastCheck)
|
time_t now = time(nullptr);
|
||||||
|
|
||||||
|
if (now != lastCheck)
|
||||||
{
|
{
|
||||||
lastCheck = time(nullptr);
|
lastCheck = now;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -547,9 +549,8 @@ bool IsBehindTargetTrigger::IsActive()
|
|||||||
bool IsNotBehindTargetTrigger::IsActive()
|
bool IsNotBehindTargetTrigger::IsActive()
|
||||||
{
|
{
|
||||||
if (botAI->HasStrategy("stay", botAI->GetState()))
|
if (botAI->HasStrategy("stay", botAI->GetState()))
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
Unit* target = AI_VALUE(Unit*, "current target");
|
Unit* target = AI_VALUE(Unit*, "current target");
|
||||||
return target && !AI_VALUE2(bool, "behind", "current target");
|
return target && !AI_VALUE2(bool, "behind", "current target");
|
||||||
}
|
}
|
||||||
@ -557,9 +558,8 @@ bool IsNotBehindTargetTrigger::IsActive()
|
|||||||
bool IsNotFacingTargetTrigger::IsActive()
|
bool IsNotFacingTargetTrigger::IsActive()
|
||||||
{
|
{
|
||||||
if (botAI->HasStrategy("stay", botAI->GetState()))
|
if (botAI->HasStrategy("stay", botAI->GetState()))
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
return !AI_VALUE2(bool, "facing", "current target");
|
return !AI_VALUE2(bool, "facing", "current target");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -576,12 +576,14 @@ bool NoPossibleTargetsTrigger::IsActive()
|
|||||||
return !targets.size();
|
return !targets.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PossibleAddsTrigger::IsActive() { return AI_VALUE(bool, "possible adds") && !AI_VALUE(ObjectGuid, "pull target"); }
|
bool PossibleAddsTrigger::IsActive()
|
||||||
|
{
|
||||||
|
return AI_VALUE(bool, "possible adds") && !AI_VALUE(ObjectGuid, "pull target");
|
||||||
|
}
|
||||||
|
|
||||||
bool NotDpsTargetActiveTrigger::IsActive()
|
bool NotDpsTargetActiveTrigger::IsActive()
|
||||||
{
|
{
|
||||||
Unit* target = AI_VALUE(Unit*, "current target");
|
Unit* target = AI_VALUE(Unit*, "current target");
|
||||||
// do not switch if enemy target
|
|
||||||
if (target && target->IsAlive())
|
if (target && target->IsAlive())
|
||||||
{
|
{
|
||||||
Unit* enemy = AI_VALUE(Unit*, "enemy player target");
|
Unit* enemy = AI_VALUE(Unit*, "enemy player target");
|
||||||
@ -599,7 +601,6 @@ bool NotDpsAoeTargetActiveTrigger::IsActive()
|
|||||||
Unit* target = AI_VALUE(Unit*, "current target");
|
Unit* target = AI_VALUE(Unit*, "current target");
|
||||||
Unit* enemy = AI_VALUE(Unit*, "enemy player target");
|
Unit* enemy = AI_VALUE(Unit*, "enemy player target");
|
||||||
|
|
||||||
// do not switch if enemy target
|
|
||||||
if (target && target == enemy && target->IsAlive())
|
if (target && target == enemy && target->IsAlive())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -633,7 +634,10 @@ Value<Unit*>* InterruptEnemyHealerTrigger::GetTargetValue()
|
|||||||
return context->GetValue<Unit*>("enemy healer target", spell);
|
return context->GetValue<Unit*>("enemy healer target", spell);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RandomBotUpdateTrigger::IsActive() { return RandomTrigger::IsActive() && AI_VALUE(bool, "random bot update"); }
|
bool RandomBotUpdateTrigger::IsActive()
|
||||||
|
{
|
||||||
|
return RandomTrigger::IsActive() && AI_VALUE(bool, "random bot update");
|
||||||
|
}
|
||||||
|
|
||||||
bool NoNonBotPlayersAroundTrigger::IsActive()
|
bool NoNonBotPlayersAroundTrigger::IsActive()
|
||||||
{
|
{
|
||||||
@ -713,43 +717,24 @@ bool AmmoCountTrigger::IsActive()
|
|||||||
|
|
||||||
bool NewPetTrigger::IsActive()
|
bool NewPetTrigger::IsActive()
|
||||||
{
|
{
|
||||||
// Get the bot player object from the AI
|
|
||||||
Player* bot = botAI->GetBot();
|
|
||||||
if (!bot)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Try to get the current pet; initialize guardian and GUID to null/empty
|
|
||||||
Pet* pet = bot->GetPet();
|
|
||||||
Guardian* guardian = nullptr;
|
|
||||||
ObjectGuid currentPetGuid = ObjectGuid::Empty;
|
ObjectGuid currentPetGuid = ObjectGuid::Empty;
|
||||||
|
|
||||||
// If bot has a pet, get its GUID
|
if (Pet* pet = bot->GetPet())
|
||||||
if (pet)
|
|
||||||
{
|
|
||||||
currentPetGuid = pet->GetGUID();
|
currentPetGuid = pet->GetGUID();
|
||||||
}
|
else if (Guardian* guardian = bot->GetGuardianPet())
|
||||||
else
|
|
||||||
{
|
|
||||||
// If no pet, try to get a guardian pet and its GUID
|
|
||||||
guardian = bot->GetGuardianPet();
|
|
||||||
if (guardian)
|
|
||||||
currentPetGuid = guardian->GetGUID();
|
currentPetGuid = guardian->GetGUID();
|
||||||
}
|
|
||||||
|
|
||||||
// If the current pet or guardian GUID has changed (including becoming empty), reset the trigger state
|
|
||||||
if (currentPetGuid != lastPetGuid)
|
if (currentPetGuid != lastPetGuid)
|
||||||
{
|
{
|
||||||
triggered = false;
|
triggered = false;
|
||||||
lastPetGuid = currentPetGuid;
|
lastPetGuid = currentPetGuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's a valid current pet/guardian (non-empty GUID) and we haven't triggered yet, activate trigger
|
|
||||||
if (currentPetGuid != ObjectGuid::Empty && !triggered)
|
if (currentPetGuid != ObjectGuid::Empty && !triggered)
|
||||||
{
|
{
|
||||||
triggered = true;
|
triggered = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, do not activate
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,9 +20,7 @@ class StatAvailable : public Trigger
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
StatAvailable(PlayerbotAI* botAI, int32 amount, std::string const name = "stat available")
|
StatAvailable(PlayerbotAI* botAI, int32 amount, std::string const name = "stat available")
|
||||||
: Trigger(botAI, name), amount(amount)
|
: Trigger(botAI, name), amount(amount) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int32 amount;
|
int32 amount;
|
||||||
@ -118,8 +116,8 @@ public:
|
|||||||
class TargetWithComboPointsLowerHealTrigger : public ComboPointsAvailableTrigger
|
class TargetWithComboPointsLowerHealTrigger : public ComboPointsAvailableTrigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TargetWithComboPointsLowerHealTrigger(PlayerbotAI* ai, int32 combo_point = 5, float lifeTime = 8.0f)
|
TargetWithComboPointsLowerHealTrigger(PlayerbotAI* botAI, int32 combo_point = 5, float lifeTime = 8.0f)
|
||||||
: ComboPointsAvailableTrigger(ai, combo_point), lifeTime(lifeTime)
|
: ComboPointsAvailableTrigger(botAI, combo_point), lifeTime(lifeTime)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
@ -196,7 +194,6 @@ public:
|
|||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: check other targets
|
|
||||||
class InterruptSpellTrigger : public SpellTrigger
|
class InterruptSpellTrigger : public SpellTrigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -217,9 +214,7 @@ class AttackerCountTrigger : public Trigger
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
AttackerCountTrigger(PlayerbotAI* botAI, int32 amount, float distance = sPlayerbotAIConfig.sightDistance)
|
AttackerCountTrigger(PlayerbotAI* botAI, int32 amount, float distance = sPlayerbotAIConfig.sightDistance)
|
||||||
: Trigger(botAI), amount(amount), distance(distance)
|
: Trigger(botAI), amount(amount), distance(distance) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
std::string const getName() override { return "attacker count"; }
|
std::string const getName() override { return "attacker count"; }
|
||||||
@ -269,9 +264,7 @@ class AoeTrigger : public AttackerCountTrigger
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
AoeTrigger(PlayerbotAI* botAI, int32 amount = 3, float range = 15.0f)
|
AoeTrigger(PlayerbotAI* botAI, int32 amount = 3, float range = 15.0f)
|
||||||
: AttackerCountTrigger(botAI, amount), range(range)
|
: AttackerCountTrigger(botAI, amount), range(range) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
std::string const getName() override { return "aoe"; }
|
std::string const getName() override { return "aoe"; }
|
||||||
@ -317,7 +310,8 @@ public:
|
|||||||
class BuffTrigger : public SpellTrigger
|
class BuffTrigger : public SpellTrigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
BuffTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1, bool checkIsOwner = false, bool checkDuration = false, uint32 beforeDuration = 0)
|
BuffTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1,
|
||||||
|
bool checkIsOwner = false, bool checkDuration = false, uint32 beforeDuration = 0)
|
||||||
: SpellTrigger(botAI, spell, checkInterval)
|
: SpellTrigger(botAI, spell, checkInterval)
|
||||||
{
|
{
|
||||||
this->checkIsOwner = checkIsOwner;
|
this->checkIsOwner = checkIsOwner;
|
||||||
@ -339,11 +333,10 @@ class BuffOnPartyTrigger : public BuffTrigger
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
BuffOnPartyTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1)
|
BuffOnPartyTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1)
|
||||||
: BuffTrigger(botAI, spell, checkInterval)
|
: BuffTrigger(botAI, spell, checkInterval) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Value<Unit*>* GetTargetValue() override;
|
Value<Unit*>* GetTargetValue() override;
|
||||||
|
bool IsActive() override;
|
||||||
std::string const getName() override { return spell + " on party"; }
|
std::string const getName() override { return spell + " on party"; }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -393,9 +386,7 @@ class DebuffTrigger : public BuffTrigger
|
|||||||
public:
|
public:
|
||||||
DebuffTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1, bool checkIsOwner = false,
|
DebuffTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1, bool checkIsOwner = false,
|
||||||
float needLifeTime = 8.0f, uint32 beforeDuration = 0)
|
float needLifeTime = 8.0f, uint32 beforeDuration = 0)
|
||||||
: BuffTrigger(botAI, spell, checkInterval, checkIsOwner, false, beforeDuration), needLifeTime(needLifeTime)
|
: BuffTrigger(botAI, spell, checkInterval, checkIsOwner, false, beforeDuration), needLifeTime(needLifeTime) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string const GetTargetName() override { return "current target"; }
|
std::string const GetTargetName() override { return "current target"; }
|
||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
@ -408,9 +399,7 @@ class DebuffOnBossTrigger : public DebuffTrigger
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
DebuffOnBossTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1, bool checkIsOwner = false)
|
DebuffOnBossTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1, bool checkIsOwner = false)
|
||||||
: DebuffTrigger(botAI, spell, checkInterval, checkIsOwner)
|
: DebuffTrigger(botAI, spell, checkInterval, checkIsOwner) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -419,9 +408,7 @@ class DebuffOnAttackerTrigger : public DebuffTrigger
|
|||||||
public:
|
public:
|
||||||
DebuffOnAttackerTrigger(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner = true,
|
DebuffOnAttackerTrigger(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner = true,
|
||||||
float needLifeTime = 8.0f)
|
float needLifeTime = 8.0f)
|
||||||
: DebuffTrigger(botAI, spell, 1, checkIsOwner, needLifeTime)
|
: DebuffTrigger(botAI, spell, 1, checkIsOwner, needLifeTime) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Value<Unit*>* GetTargetValue() override;
|
Value<Unit*>* GetTargetValue() override;
|
||||||
std::string const getName() override { return spell + " on attacker"; }
|
std::string const getName() override { return spell + " on attacker"; }
|
||||||
@ -432,9 +419,7 @@ class DebuffOnMeleeAttackerTrigger : public DebuffTrigger
|
|||||||
public:
|
public:
|
||||||
DebuffOnMeleeAttackerTrigger(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner = true,
|
DebuffOnMeleeAttackerTrigger(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner = true,
|
||||||
float needLifeTime = 8.0f)
|
float needLifeTime = 8.0f)
|
||||||
: DebuffTrigger(botAI, spell, 1, checkIsOwner, needLifeTime)
|
: DebuffTrigger(botAI, spell, 1, checkIsOwner, needLifeTime) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Value<Unit*>* GetTargetValue() override;
|
Value<Unit*>* GetTargetValue() override;
|
||||||
std::string const getName() override { return spell + " on attacker"; }
|
std::string const getName() override { return spell + " on attacker"; }
|
||||||
@ -444,9 +429,7 @@ class BoostTrigger : public BuffTrigger
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
BoostTrigger(PlayerbotAI* botAI, std::string const spell, float balance = 50.f)
|
BoostTrigger(PlayerbotAI* botAI, std::string const spell, float balance = 50.f)
|
||||||
: BuffTrigger(botAI, spell, 1), balance(balance)
|
: BuffTrigger(botAI, spell, 1), balance(balance) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
|
|
||||||
@ -458,9 +441,7 @@ class GenericBoostTrigger : public Trigger
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
GenericBoostTrigger(PlayerbotAI* botAI, float balance = 50.f)
|
GenericBoostTrigger(PlayerbotAI* botAI, float balance = 50.f)
|
||||||
: Trigger(botAI, "generic boost", 1), balance(balance)
|
: Trigger(botAI, "generic boost", 1), balance(balance) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
|
|
||||||
@ -472,9 +453,7 @@ class HealerShouldAttackTrigger : public Trigger
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
HealerShouldAttackTrigger(PlayerbotAI* botAI)
|
HealerShouldAttackTrigger(PlayerbotAI* botAI)
|
||||||
: Trigger(botAI, "healer should attack", 1)
|
: Trigger(botAI, "healer should attack", 1) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
@ -550,6 +529,17 @@ public:
|
|||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class LowEnergyTrigger : public Trigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LowEnergyTrigger(PlayerbotAI* botAI, uint8 threshold = 30) : Trigger(botAI, "low energy"), threshold(threshold) {}
|
||||||
|
|
||||||
|
bool IsActive() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8 threshold;
|
||||||
|
};
|
||||||
|
|
||||||
BEGIN_TRIGGER(PanicTrigger, Trigger) // cppcheck-suppress unknownMacro
|
BEGIN_TRIGGER(PanicTrigger, Trigger) // cppcheck-suppress unknownMacro
|
||||||
std::string const getName() override { return "panic"; }
|
std::string const getName() override { return "panic"; }
|
||||||
END_TRIGGER()
|
END_TRIGGER()
|
||||||
@ -569,7 +559,7 @@ public:
|
|||||||
class HasPetTrigger : public Trigger
|
class HasPetTrigger : public Trigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
HasPetTrigger(PlayerbotAI* ai) : Trigger(ai, "has pet", 5 * 1000) {}
|
HasPetTrigger(PlayerbotAI* botAI) : Trigger(botAI, "has pet", 5 * 1000) {}
|
||||||
|
|
||||||
virtual bool IsActive() override;
|
virtual bool IsActive() override;
|
||||||
};
|
};
|
||||||
@ -577,7 +567,7 @@ public:
|
|||||||
class PetAttackTrigger : public Trigger
|
class PetAttackTrigger : public Trigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PetAttackTrigger(PlayerbotAI* ai) : Trigger(ai, "pet attack") {}
|
PetAttackTrigger(PlayerbotAI* botAI) : Trigger(botAI, "pet attack") {}
|
||||||
|
|
||||||
virtual bool IsActive() override;
|
virtual bool IsActive() override;
|
||||||
};
|
};
|
||||||
@ -586,9 +576,7 @@ class ItemCountTrigger : public Trigger
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ItemCountTrigger(PlayerbotAI* botAI, std::string const item, int32 count, int32 interval = 30 * 1000)
|
ItemCountTrigger(PlayerbotAI* botAI, std::string const item, int32 count, int32 interval = 30 * 1000)
|
||||||
: Trigger(botAI, item, interval), item(item), count(count)
|
: Trigger(botAI, item, interval), item(item), count(count) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
std::string const getName() override { return "item count"; }
|
std::string const getName() override { return "item count"; }
|
||||||
@ -602,9 +590,7 @@ class AmmoCountTrigger : public ItemCountTrigger
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
AmmoCountTrigger(PlayerbotAI* botAI, std::string const item, uint32 count = 1, int32 interval = 30 * 1000)
|
AmmoCountTrigger(PlayerbotAI* botAI, std::string const item, uint32 count = 1, int32 interval = 30 * 1000)
|
||||||
: ItemCountTrigger(botAI, item, count, interval)
|
: ItemCountTrigger(botAI, item, count, interval) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -612,9 +598,7 @@ class HasAuraTrigger : public Trigger
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
HasAuraTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1)
|
HasAuraTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1)
|
||||||
: Trigger(botAI, spell, checkInterval)
|
: Trigger(botAI, spell, checkInterval) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string const GetTargetName() override { return "self target"; }
|
std::string const GetTargetName() override { return "self target"; }
|
||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
@ -623,10 +607,8 @@ public:
|
|||||||
class HasAuraStackTrigger : public Trigger
|
class HasAuraStackTrigger : public Trigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
HasAuraStackTrigger(PlayerbotAI* ai, std::string spell, int stack, int checkInterval = 1)
|
HasAuraStackTrigger(PlayerbotAI* botAI, std::string spell, int stack, int checkInterval = 1)
|
||||||
: Trigger(ai, spell, checkInterval), stack(stack)
|
: Trigger(botAI, spell, checkInterval), stack(stack) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string const GetTargetName() override { return "self target"; }
|
std::string const GetTargetName() override { return "self target"; }
|
||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
@ -847,9 +829,7 @@ class StayTimeTrigger : public Trigger
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
StayTimeTrigger(PlayerbotAI* botAI, uint32 delay, std::string const name)
|
StayTimeTrigger(PlayerbotAI* botAI, uint32 delay, std::string const name)
|
||||||
: Trigger(botAI, name, 5 * 1000), delay(delay)
|
: Trigger(botAI, name, 5 * 1000), delay(delay) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
|
|
||||||
@ -866,7 +846,7 @@ public:
|
|||||||
class ReturnToStayPositionTrigger : public Trigger
|
class ReturnToStayPositionTrigger : public Trigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ReturnToStayPositionTrigger(PlayerbotAI* ai) : Trigger(ai, "return to stay position", 2) {}
|
ReturnToStayPositionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "return to stay position", 2) {}
|
||||||
|
|
||||||
virtual bool IsActive() override;
|
virtual bool IsActive() override;
|
||||||
};
|
};
|
||||||
@ -881,9 +861,7 @@ class GiveItemTrigger : public Trigger
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
GiveItemTrigger(PlayerbotAI* botAI, std::string const name, std::string const item)
|
GiveItemTrigger(PlayerbotAI* botAI, std::string const name, std::string const item)
|
||||||
: Trigger(botAI, name, 2 * 1000), item(item)
|
: Trigger(botAI, name, 2 * 1000), item(item) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
|
|
||||||
@ -951,9 +929,7 @@ class BuffOnMainTankTrigger : public BuffTrigger
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
BuffOnMainTankTrigger(PlayerbotAI* botAI, std::string spell, bool checkIsOwner = false, int checkInterval = 1)
|
BuffOnMainTankTrigger(PlayerbotAI* botAI, std::string spell, bool checkIsOwner = false, int checkInterval = 1)
|
||||||
: BuffTrigger(botAI, spell, checkInterval, checkIsOwner)
|
: BuffTrigger(botAI, spell, checkInterval, checkIsOwner) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual Value<Unit*>* GetTargetValue();
|
virtual Value<Unit*>* GetTargetValue();
|
||||||
@ -962,7 +938,7 @@ public:
|
|||||||
class SelfResurrectTrigger : public Trigger
|
class SelfResurrectTrigger : public Trigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SelfResurrectTrigger(PlayerbotAI* ai) : Trigger(ai, "can self resurrect") {}
|
SelfResurrectTrigger(PlayerbotAI* botAI) : Trigger(botAI, "can self resurrect") {}
|
||||||
|
|
||||||
bool IsActive() override { return !bot->IsAlive() && bot->GetUInt32Value(PLAYER_SELF_RES_SPELL); }
|
bool IsActive() override { return !bot->IsAlive() && bot->GetUInt32Value(PLAYER_SELF_RES_SPELL); }
|
||||||
};
|
};
|
||||||
@ -970,7 +946,7 @@ public:
|
|||||||
class NewPetTrigger : public Trigger
|
class NewPetTrigger : public Trigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
NewPetTrigger(PlayerbotAI* ai) : Trigger(ai, "new pet"), lastPetGuid(ObjectGuid::Empty), triggered(false) {}
|
NewPetTrigger(PlayerbotAI* botAI) : Trigger(botAI, "new pet"), lastPetGuid(ObjectGuid::Empty), triggered(false) {}
|
||||||
|
|
||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
|
|
||||||
|
|||||||
@ -22,6 +22,15 @@ bool DeadTrigger::IsActive() { return AI_VALUE2(bool, "dead", GetTargetName());
|
|||||||
|
|
||||||
bool AoeHealTrigger::IsActive() { return AI_VALUE2(uint8, "aoe heal", type) >= count; }
|
bool AoeHealTrigger::IsActive() { return AI_VALUE2(uint8, "aoe heal", type) >= count; }
|
||||||
|
|
||||||
|
bool HealerLowManaTrigger::IsActive()
|
||||||
|
{
|
||||||
|
Unit* target = GetTarget();
|
||||||
|
if (!target)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return target->GetPowerPct(POWER_MANA) < sPlayerbotAIConfig.lowMana;
|
||||||
|
}
|
||||||
|
|
||||||
bool AoeInGroupTrigger::IsActive()
|
bool AoeInGroupTrigger::IsActive()
|
||||||
{
|
{
|
||||||
int32 member = botAI->GetNearGroupMemberCount();
|
int32 member = botAI->GetNearGroupMemberCount();
|
||||||
|
|||||||
@ -143,6 +143,15 @@ public:
|
|||||||
TargetCriticalHealthTrigger(PlayerbotAI* botAI) : TargetLowHealthTrigger(botAI, 20) {}
|
TargetCriticalHealthTrigger(PlayerbotAI* botAI) : TargetLowHealthTrigger(botAI, 20) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class HealerLowManaTrigger : public Trigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
HealerLowManaTrigger(PlayerbotAI* botAI) : Trigger(botAI, "healer low mana") {}
|
||||||
|
|
||||||
|
std::string const GetTargetName() override { return "healer low mana"; }
|
||||||
|
bool IsActive() override;
|
||||||
|
};
|
||||||
|
|
||||||
class PartyMemberDeadTrigger : public Trigger
|
class PartyMemberDeadTrigger : public Trigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
@ -18,3 +18,19 @@ bool NoRtiTrigger::IsActive()
|
|||||||
Unit* target = AI_VALUE(Unit*, "rti target");
|
Unit* target = AI_VALUE(Unit*, "rti target");
|
||||||
return target == nullptr;
|
return target == nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fires when the RTI CC target should be crowd controlled by this spell.
|
||||||
|
// Standard path: the target is already in the attackers list and "cc target" matches the RTI
|
||||||
|
// mark — delegates to HasCcTargetTrigger to confirm no one else is already CCing it.
|
||||||
|
bool RtiCcTrigger::IsActive()
|
||||||
|
{
|
||||||
|
Unit* rtiCcTarget = AI_VALUE(Unit*, "rti cc target");
|
||||||
|
if (!rtiCcTarget)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Unit* ccTarget = AI_VALUE2(Unit*, "cc target", getName());
|
||||||
|
if (ccTarget && ccTarget == rtiCcTarget)
|
||||||
|
return HasCcTargetTrigger::IsActive();
|
||||||
|
|
||||||
|
return botAI->CanCastSpell(getName(), rtiCcTarget);
|
||||||
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
#ifndef _PLAYERBOT_RTITRIGGERS_H
|
#ifndef _PLAYERBOT_RTITRIGGERS_H
|
||||||
#define _PLAYERBOT_RTITRIGGERS_H
|
#define _PLAYERBOT_RTITRIGGERS_H
|
||||||
|
|
||||||
|
#include "GenericTriggers.h"
|
||||||
#include "Trigger.h"
|
#include "Trigger.h"
|
||||||
|
|
||||||
class PlayerbotAI;
|
class PlayerbotAI;
|
||||||
@ -18,4 +19,12 @@ public:
|
|||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class RtiCcTrigger : public HasCcTargetTrigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RtiCcTrigger(PlayerbotAI* botAI, std::string const name) : HasCcTargetTrigger(botAI, name) {}
|
||||||
|
|
||||||
|
bool IsActive() override;
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -51,6 +51,7 @@ public:
|
|||||||
|
|
||||||
creators["low mana"] = &TriggerContext::LowMana;
|
creators["low mana"] = &TriggerContext::LowMana;
|
||||||
creators["medium mana"] = &TriggerContext::MediumMana;
|
creators["medium mana"] = &TriggerContext::MediumMana;
|
||||||
|
creators["low energy"] = &TriggerContext::LowEnergy;
|
||||||
creators["high mana"] = &TriggerContext::HighMana;
|
creators["high mana"] = &TriggerContext::HighMana;
|
||||||
creators["almost full mana"] = &TriggerContext::AlmostFullMana;
|
creators["almost full mana"] = &TriggerContext::AlmostFullMana;
|
||||||
creators["enough mana"] = &TriggerContext::EnoughMana;
|
creators["enough mana"] = &TriggerContext::EnoughMana;
|
||||||
@ -59,6 +60,7 @@ public:
|
|||||||
creators["party member low health"] = &TriggerContext::PartyMemberLowHealth;
|
creators["party member low health"] = &TriggerContext::PartyMemberLowHealth;
|
||||||
creators["party member medium health"] = &TriggerContext::PartyMemberMediumHealth;
|
creators["party member medium health"] = &TriggerContext::PartyMemberMediumHealth;
|
||||||
creators["party member almost full health"] = &TriggerContext::PartyMemberAlmostFullHealth;
|
creators["party member almost full health"] = &TriggerContext::PartyMemberAlmostFullHealth;
|
||||||
|
creators["healer low mana"] = &TriggerContext::HealerLowMana;
|
||||||
|
|
||||||
creators["generic boost"] = &TriggerContext::generic_boost;
|
creators["generic boost"] = &TriggerContext::generic_boost;
|
||||||
creators["loss of control"] = &TriggerContext::loss_of_control;
|
creators["loss of control"] = &TriggerContext::loss_of_control;
|
||||||
@ -312,6 +314,7 @@ private:
|
|||||||
static Trigger* TargetCriticalHealth(PlayerbotAI* botAI) { return new TargetCriticalHealthTrigger(botAI); }
|
static Trigger* TargetCriticalHealth(PlayerbotAI* botAI) { return new TargetCriticalHealthTrigger(botAI); }
|
||||||
static Trigger* LowMana(PlayerbotAI* botAI) { return new LowManaTrigger(botAI); }
|
static Trigger* LowMana(PlayerbotAI* botAI) { return new LowManaTrigger(botAI); }
|
||||||
static Trigger* MediumMana(PlayerbotAI* botAI) { return new MediumManaTrigger(botAI); }
|
static Trigger* MediumMana(PlayerbotAI* botAI) { return new MediumManaTrigger(botAI); }
|
||||||
|
static Trigger* LowEnergy(PlayerbotAI* botAI) { return new LowEnergyTrigger(botAI); }
|
||||||
static Trigger* HighMana(PlayerbotAI* botAI) { return new HighManaTrigger(botAI); }
|
static Trigger* HighMana(PlayerbotAI* botAI) { return new HighManaTrigger(botAI); }
|
||||||
static Trigger* AlmostFullMana(PlayerbotAI* botAI) { return new AlmostFullManaTrigger(botAI); }
|
static Trigger* AlmostFullMana(PlayerbotAI* botAI) { return new AlmostFullManaTrigger(botAI); }
|
||||||
static Trigger* EnoughMana(PlayerbotAI* botAI) { return new EnoughManaTrigger(botAI); }
|
static Trigger* EnoughMana(PlayerbotAI* botAI) { return new EnoughManaTrigger(botAI); }
|
||||||
@ -383,6 +386,7 @@ private:
|
|||||||
{
|
{
|
||||||
return new PartyMemberCriticalHealthTrigger(botAI);
|
return new PartyMemberCriticalHealthTrigger(botAI);
|
||||||
}
|
}
|
||||||
|
static Trigger* HealerLowMana(PlayerbotAI* botAI) { return new HealerLowManaTrigger(botAI); }
|
||||||
static Trigger* protect_party_member(PlayerbotAI* botAI) { return new ProtectPartyMemberTrigger(botAI); }
|
static Trigger* protect_party_member(PlayerbotAI* botAI) { return new ProtectPartyMemberTrigger(botAI); }
|
||||||
static Trigger* no_pet(PlayerbotAI* botAI) { return new NoPetTrigger(botAI); }
|
static Trigger* no_pet(PlayerbotAI* botAI) { return new NoPetTrigger(botAI); }
|
||||||
static Trigger* has_pet(PlayerbotAI* botAI) { return new HasPetTrigger(botAI); }
|
static Trigger* has_pet(PlayerbotAI* botAI) { return new HasPetTrigger(botAI); }
|
||||||
|
|||||||
@ -4,23 +4,89 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "GenericBuffUtils.h"
|
#include "GenericBuffUtils.h"
|
||||||
#include "PlayerbotAIConfig.h"
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
#include "Player.h"
|
|
||||||
#include "Group.h"
|
|
||||||
#include "SpellMgr.h"
|
|
||||||
#include "Chat.h"
|
|
||||||
#include "PlayerbotAI.h"
|
|
||||||
#include "ServerFacade.h"
|
|
||||||
#include "AiObjectContext.h"
|
#include "AiObjectContext.h"
|
||||||
|
|
||||||
|
#include "GameTime.h"
|
||||||
|
#include "Group.h"
|
||||||
|
#include "Player.h"
|
||||||
|
#include "PlayerbotAI.h"
|
||||||
|
#include "PlayerbotAIConfig.h"
|
||||||
|
#include "SpellMgr.h"
|
||||||
|
#include "Unit.h"
|
||||||
#include "Value.h"
|
#include "Value.h"
|
||||||
#include "Config.h"
|
|
||||||
#include "PlayerbotTextMgr.h"
|
|
||||||
|
|
||||||
namespace ai::buff
|
namespace ai::buff
|
||||||
{
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// Prevents bots from immediately casting already-present buffs upon logging in
|
||||||
|
constexpr uint32 POST_LOGIN_BUFF_GRACE_MS = 5 * IN_MILLISECONDS;
|
||||||
|
|
||||||
|
bool IsWithinPostLoginBuffGrace(Player* player)
|
||||||
|
{
|
||||||
|
if (!player)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return getMSTimeDiff(
|
||||||
|
player->GetInGameTime(), GameTime::GetGameTimeMS().count()) < POST_LOGIN_BUFF_GRACE_MS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool HasEnoughSameMapMissingPlayersForGroupVariant(
|
||||||
|
Player* bot, PlayerbotAI* botAI, std::string const& baseName,
|
||||||
|
std::string const& groupName, uint32 requiredCount = 3)
|
||||||
|
{
|
||||||
|
Group* group = bot->GetGroup();
|
||||||
|
if (!group)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
uint32 missingCount = 0;
|
||||||
|
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||||||
|
{
|
||||||
|
Player* member = gref->GetSource();
|
||||||
|
if (!member || !member->IsInWorld() || !member->IsAlive() ||
|
||||||
|
member->GetMap() != bot->GetMap())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (botAI->HasAura(baseName, member) || botAI->HasAura(groupName, member))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (++missingCount >= requiredCount)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsEligibleGroupForPartyBuffs(Group const* group)
|
||||||
|
{
|
||||||
|
if (!group)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (sPlayerbotAIConfig.autoPartyBuffs)
|
||||||
|
{
|
||||||
|
case AutoPartyBuffMode::RAID_ONLY:
|
||||||
|
return group->isRaidGroup();
|
||||||
|
case AutoPartyBuffMode::GROUP_OR_RAID:
|
||||||
|
return true;
|
||||||
|
case AutoPartyBuffMode::DISABLED:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsGroupVariantEnabled(Player* bot, std::string const& name)
|
||||||
|
{
|
||||||
|
if (!IsEligibleGroupForPartyBuffs(bot->GetGroup()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return !GroupVariantFor(name).empty();
|
||||||
|
}
|
||||||
|
|
||||||
std::string MakeAuraQualifierForBuff(std::string const& name)
|
std::string MakeAuraQualifierForBuff(std::string const& name)
|
||||||
{
|
{
|
||||||
// Paladin
|
// Paladin
|
||||||
@ -34,27 +100,89 @@ namespace ai::buff
|
|||||||
if (name == "arcane intellect") return "arcane intellect,arcane brilliance";
|
if (name == "arcane intellect") return "arcane intellect,arcane brilliance";
|
||||||
// Priest
|
// Priest
|
||||||
if (name == "power word: fortitude") return "power word: fortitude,prayer of fortitude";
|
if (name == "power word: fortitude") return "power word: fortitude,prayer of fortitude";
|
||||||
|
if (name == "divine spirit") return "divine spirit,prayer of spirit";
|
||||||
|
if (name == "shadow protection") return "shadow protection,prayer of shadow protection";
|
||||||
|
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string GroupVariantFor(std::string const& name)
|
std::string GroupVariantFor(std::string const& name)
|
||||||
{
|
{
|
||||||
// Paladin
|
|
||||||
if (name == "blessing of kings") return "greater blessing of kings";
|
|
||||||
if (name == "blessing of might") return "greater blessing of might";
|
|
||||||
if (name == "blessing of wisdom") return "greater blessing of wisdom";
|
|
||||||
if (name == "blessing of sanctuary") return "greater blessing of sanctuary";
|
|
||||||
// Druid
|
// Druid
|
||||||
if (name == "mark of the wild") return "gift of the wild";
|
if (name == "mark of the wild") return "gift of the wild";
|
||||||
// Mage
|
// Mage
|
||||||
if (name == "arcane intellect") return "arcane brilliance";
|
if (name == "arcane intellect") return "arcane brilliance";
|
||||||
// Priest
|
// Priest
|
||||||
if (name == "power word: fortitude") return "prayer of fortitude";
|
if (name == "power word: fortitude") return "prayer of fortitude";
|
||||||
|
if (name == "divine spirit") return "prayer of spirit";
|
||||||
|
if (name == "shadow protection") return "prayer of shadow protection";
|
||||||
|
|
||||||
|
// Paladin blessings are intentionally not included here because they are
|
||||||
|
// coordinated by the auto greater blessing system instead.
|
||||||
return std::string();
|
return std::string();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool NeedsPostLoginBuffGrace(std::string const& name)
|
||||||
|
{
|
||||||
|
static char const* const trackedBuffs[] = {
|
||||||
|
"mark of the wild",
|
||||||
|
"arcane intellect",
|
||||||
|
"power word: fortitude",
|
||||||
|
"prayer of fortitude",
|
||||||
|
"divine spirit",
|
||||||
|
"prayer of spirit",
|
||||||
|
"shadow protection",
|
||||||
|
"prayer of shadow protection",
|
||||||
|
"blessing of kings",
|
||||||
|
"blessing of might",
|
||||||
|
"blessing of wisdom",
|
||||||
|
"blessing of sanctuary"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (char const* trackedBuff : trackedBuffs)
|
||||||
|
{
|
||||||
|
if (name.find(trackedBuff) != std::string::npos)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShouldDeferPartyBuffEvaluationForRecentLogin(
|
||||||
|
Player* bot, Unit* target, std::string const& spell)
|
||||||
|
{
|
||||||
|
if (!NeedsPostLoginBuffGrace(spell))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (IsWithinPostLoginBuffGrace(bot))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
Player* playerTarget = target ? target->ToPlayer() : nullptr;
|
||||||
|
return IsWithinPostLoginBuffGrace(playerTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShouldDeferGreaterBlessingAssignmentForRecentLogin(Player* bot)
|
||||||
|
{
|
||||||
|
if (IsWithinPostLoginBuffGrace(bot))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
Group* group = bot->GetGroup();
|
||||||
|
if (!group)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||||||
|
{
|
||||||
|
Player* member = gref->GetSource();
|
||||||
|
if (!member || !member->IsInWorld())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (IsWithinPostLoginBuffGrace(member))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool HasRequiredReagents(Player* bot, uint32 spellId)
|
bool HasRequiredReagents(Player* bot, uint32 spellId)
|
||||||
{
|
{
|
||||||
if (!spellId)
|
if (!spellId)
|
||||||
@ -72,75 +200,33 @@ namespace ai::buff
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// No reagent required
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string UpgradeToGroupIfAppropriate(
|
std::string UpgradeToGroupIfAppropriate(
|
||||||
Player* bot,
|
Player* bot, PlayerbotAI* botAI, std::string const& baseName)
|
||||||
PlayerbotAI* botAI,
|
|
||||||
std::string const& baseName,
|
|
||||||
bool announceOnMissing,
|
|
||||||
std::function<void(std::string const&)> announce)
|
|
||||||
{
|
{
|
||||||
std::string castName = baseName;
|
if (!IsGroupVariantEnabled(bot, baseName))
|
||||||
Group* g = bot->GetGroup();
|
return baseName;
|
||||||
if (!g || g->GetMembersCount() < static_cast<uint32>(sPlayerbotAIConfig.minBotsForGreaterBuff))
|
|
||||||
return castName; // Group too small: stay in solo mode
|
|
||||||
|
|
||||||
if (std::string const groupName = GroupVariantFor(baseName); !groupName.empty())
|
std::string const groupName = GroupVariantFor(baseName);
|
||||||
{
|
if (groupName.empty())
|
||||||
uint32 const groupVariantSpellId = botAI->GetAiObjectContext()
|
return baseName;
|
||||||
|
|
||||||
|
// Prefer singles until at least three living, in-world group members on the bot's map
|
||||||
|
// are missing both the single-target buff and its group variant.
|
||||||
|
if (!HasEnoughSameMapMissingPlayersForGroupVariant(bot, botAI, baseName, groupName))
|
||||||
|
return baseName;
|
||||||
|
|
||||||
|
uint32 const groupSpellId = botAI->GetAiObjectContext()
|
||||||
->GetValue<uint32>("spell id", groupName)->Get();
|
->GetValue<uint32>("spell id", groupName)->Get();
|
||||||
|
|
||||||
// We check usefulness on the **basic** buff (not the greater version),
|
if (groupSpellId && HasRequiredReagents(bot, groupSpellId))
|
||||||
// because "spell cast useful" may return false for the greater variant.
|
|
||||||
bool const usefulBase = botAI->GetAiObjectContext()
|
|
||||||
->GetValue<bool>("spell cast useful", baseName)->Get();
|
|
||||||
|
|
||||||
if (groupVariantSpellId && HasRequiredReagents(bot, groupVariantSpellId))
|
|
||||||
{
|
|
||||||
// Learned + reagents OK -> switch to greater
|
|
||||||
return groupName;
|
return groupName;
|
||||||
}
|
|
||||||
|
|
||||||
// Missing reagents -> announce if (a) greater is known, (b) base buff is useful,
|
return baseName;
|
||||||
// (c) announce was requested, (d) a callback is provided.
|
|
||||||
if (announceOnMissing && groupVariantSpellId && usefulBase && announce)
|
|
||||||
{
|
|
||||||
static std::map<std::pair<uint32, std::string>, time_t> s_lastWarn; // par bot & par buff
|
|
||||||
time_t now = std::time(nullptr);
|
|
||||||
uint32 botLow = static_cast<uint32>(bot->GetGUID().GetCounter());
|
|
||||||
time_t& last = s_lastWarn[ std::make_pair(botLow, groupName) ];
|
|
||||||
if (!last || now - last >= sPlayerbotAIConfig.rpWarningCooldown) // Configurable anti-spam
|
|
||||||
{
|
|
||||||
// DB Key choice in regard of the buff
|
|
||||||
std::string key;
|
|
||||||
if (groupName.find("greater blessing") != std::string::npos)
|
|
||||||
key = "rp_missing_reagent_greater_blessing";
|
|
||||||
else if (groupName == "gift of the wild")
|
|
||||||
key = "rp_missing_reagent_gift_of_the_wild";
|
|
||||||
else if (groupName == "arcane brilliance")
|
|
||||||
key = "rp_missing_reagent_arcane_brilliance";
|
|
||||||
else
|
|
||||||
key = "rp_missing_reagent_generic";
|
|
||||||
|
|
||||||
// Placeholders
|
|
||||||
std::map<std::string, std::string> placeholders;
|
|
||||||
placeholders["%group_spell"] = groupName;
|
|
||||||
placeholders["%base_spell"] = baseName;
|
|
||||||
|
|
||||||
std::string announceText = PlayerbotTextMgr::instance().GetBotTextOrDefault(key,
|
|
||||||
"Out of components for %group_spell. Using %base_spell!", placeholders);
|
|
||||||
|
|
||||||
announce(announceText);
|
|
||||||
last = now;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return castName;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,63 +6,40 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <functional>
|
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
#include "Group.h"
|
|
||||||
#include "Chat.h"
|
|
||||||
#include "Language.h"
|
|
||||||
|
|
||||||
class Player;
|
class Player;
|
||||||
class PlayerbotAI;
|
class PlayerbotAI;
|
||||||
|
class Unit;
|
||||||
|
|
||||||
namespace ai::buff
|
namespace ai::buff
|
||||||
{
|
{
|
||||||
|
|
||||||
// Build an aura qualifier "single + greater" to avoid double-buffing
|
bool IsGroupVariantEnabled(Player* bot, std::string const& name);
|
||||||
|
|
||||||
std::string MakeAuraQualifierForBuff(std::string const& name);
|
std::string MakeAuraQualifierForBuff(std::string const& name);
|
||||||
|
|
||||||
// Returns the group spell name for a given single-target buff.
|
|
||||||
// If no group equivalent exists, returns "".
|
|
||||||
std::string GroupVariantFor(std::string const& name);
|
std::string GroupVariantFor(std::string const& name);
|
||||||
|
|
||||||
// Checks if the bot has the required reagents to cast a spell (by its spellId).
|
bool NeedsPostLoginBuffGrace(std::string const& name);
|
||||||
// Returns false if the spellId is invalid.
|
|
||||||
|
bool ShouldDeferPartyBuffEvaluationForRecentLogin(
|
||||||
|
Player* bot,
|
||||||
|
Unit* target,
|
||||||
|
std::string const& spell);
|
||||||
|
|
||||||
|
bool ShouldDeferGreaterBlessingAssignmentForRecentLogin(Player* bot);
|
||||||
|
|
||||||
bool HasRequiredReagents(Player* bot, uint32 spellId);
|
bool HasRequiredReagents(Player* bot, uint32 spellId);
|
||||||
|
|
||||||
// Applies the "switch to group buff" policy if: the bot is in a group of size x+,
|
|
||||||
// the group variant is known/useful, and reagents are available. Otherwise, returns baseName.
|
|
||||||
// If announceOnMissing == true and reagents are missing, calls the 'announce' callback
|
|
||||||
// (if provided) to notify the party/raid.
|
|
||||||
std::string UpgradeToGroupIfAppropriate(
|
std::string UpgradeToGroupIfAppropriate(
|
||||||
Player* bot,
|
Player* bot,
|
||||||
PlayerbotAI* botAI,
|
PlayerbotAI* botAI,
|
||||||
std::string const& baseName,
|
std::string const& baseName);
|
||||||
bool announceOnMissing = false,
|
|
||||||
std::function<void(std::string const&)> announce = {}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace ai::spell
|
namespace ai::spell
|
||||||
{
|
{
|
||||||
bool HasSpellOrCategoryCooldown(Player* bot, uint32 spellId);
|
bool HasSpellOrCategoryCooldown(Player* bot, uint32 spellId);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace ai::chat {
|
|
||||||
inline std::function<void(std::string const&)> MakeGroupAnnouncer(Player* me)
|
|
||||||
{
|
|
||||||
return [me](std::string const& msg)
|
|
||||||
{
|
|
||||||
if (Group* g = me->GetGroup())
|
|
||||||
{
|
|
||||||
WorldPacket data;
|
|
||||||
ChatMsg type = g->isRaidGroup() ? CHAT_MSG_RAID : CHAT_MSG_PARTY;
|
|
||||||
ChatHandler::BuildChatPacket(data, type, LANG_UNIVERSAL, me, /*receiver=*/nullptr, msg.c_str());
|
|
||||||
g->BroadcastPacket(&data, true, -1, me->GetGUID());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
me->Say(msg, LANG_UNIVERSAL);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -29,7 +29,7 @@ public:
|
|||||||
if (!botAI->GetBot()->IsWithinLOSInMap(unit))
|
if (!botAI->GetBot()->IsWithinLOSInMap(unit))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return botAI->IsMovementImpaired(unit);
|
return botAI->IsMovementImpaired(unit) && !botAI->HasAnyAuraOf(unit, "stealth", "prowl", nullptr);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -135,6 +135,32 @@ bool PartyMemberToHeal::Check(Unit* player)
|
|||||||
bot->GetDistance2d(player) < sPlayerbotAIConfig.healDistance * 2 && bot->IsWithinLOSInMap(player);
|
bot->GetDistance2d(player) < sPlayerbotAIConfig.healDistance * 2 && bot->IsWithinLOSInMap(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Unit* HealerLowMana::Calculate()
|
||||||
|
{
|
||||||
|
Group* group = bot->GetGroup();
|
||||||
|
if (!group)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
MinValueCalculator calc(100);
|
||||||
|
|
||||||
|
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||||||
|
{
|
||||||
|
Player* player = gref->GetSource();
|
||||||
|
if (!player || player == bot)
|
||||||
|
continue;
|
||||||
|
if (player->IsGameMaster() || !player->IsAlive())
|
||||||
|
continue;
|
||||||
|
if (!botAI->IsHeal(player))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float mana = player->GetPowerPct(POWER_MANA);
|
||||||
|
if (mana < calc.minValue)
|
||||||
|
calc.probe(mana, player);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Unit*)calc.param;
|
||||||
|
}
|
||||||
|
|
||||||
Unit* PartyMemberToProtect::Calculate()
|
Unit* PartyMemberToProtect::Calculate()
|
||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|||||||
@ -37,4 +37,13 @@ protected:
|
|||||||
Unit* Calculate() override;
|
Unit* Calculate() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class HealerLowMana : public PartyMemberValue
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
HealerLowMana(PlayerbotAI* botAI) : PartyMemberValue(botAI, "healer low mana") {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Unit* Calculate() override;
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -47,7 +47,13 @@ public:
|
|||||||
{
|
{
|
||||||
if (allowedGOFlags.empty())
|
if (allowedGOFlags.empty())
|
||||||
{
|
{
|
||||||
|
// questgivers for accept/turn-in; rest for quest progression
|
||||||
|
// (chests, runes, altars, moonwells, lily piles, …)
|
||||||
allowedGOFlags.push_back(GAMEOBJECT_TYPE_QUESTGIVER);
|
allowedGOFlags.push_back(GAMEOBJECT_TYPE_QUESTGIVER);
|
||||||
|
allowedGOFlags.push_back(GAMEOBJECT_TYPE_CHEST);
|
||||||
|
allowedGOFlags.push_back(GAMEOBJECT_TYPE_GOOBER);
|
||||||
|
allowedGOFlags.push_back(GAMEOBJECT_TYPE_SPELL_FOCUS);
|
||||||
|
allowedGOFlags.push_back(GAMEOBJECT_TYPE_GENERIC);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -132,6 +132,7 @@ public:
|
|||||||
creators["attacker without aura"] = &ValueContext::attacker_without_aura;
|
creators["attacker without aura"] = &ValueContext::attacker_without_aura;
|
||||||
creators["melee attacker without aura"] = &ValueContext::melee_attacker_without_aura;
|
creators["melee attacker without aura"] = &ValueContext::melee_attacker_without_aura;
|
||||||
creators["party member to heal"] = &ValueContext::party_member_to_heal;
|
creators["party member to heal"] = &ValueContext::party_member_to_heal;
|
||||||
|
creators["healer low mana"] = &ValueContext::healer_low_mana;
|
||||||
creators["party member to resurrect"] = &ValueContext::party_member_to_resurrect;
|
creators["party member to resurrect"] = &ValueContext::party_member_to_resurrect;
|
||||||
creators["current target"] = &ValueContext::current_target;
|
creators["current target"] = &ValueContext::current_target;
|
||||||
creators["self target"] = &ValueContext::self_target;
|
creators["self target"] = &ValueContext::self_target;
|
||||||
@ -451,6 +452,7 @@ private:
|
|||||||
return new MeleeAttackerWithoutAuraTargetValue(botAI);
|
return new MeleeAttackerWithoutAuraTargetValue(botAI);
|
||||||
}
|
}
|
||||||
static UntypedValue* party_member_to_heal(PlayerbotAI* botAI) { return new PartyMemberToHeal(botAI); }
|
static UntypedValue* party_member_to_heal(PlayerbotAI* botAI) { return new PartyMemberToHeal(botAI); }
|
||||||
|
static UntypedValue* healer_low_mana(PlayerbotAI* botAI) { return new HealerLowMana(botAI); }
|
||||||
static UntypedValue* party_member_to_resurrect(PlayerbotAI* botAI) { return new PartyMemberToResurrect(botAI); }
|
static UntypedValue* party_member_to_resurrect(PlayerbotAI* botAI) { return new PartyMemberToResurrect(botAI); }
|
||||||
static UntypedValue* party_member_to_dispel(PlayerbotAI* botAI) { return new PartyMemberToDispel(botAI); }
|
static UntypedValue* party_member_to_dispel(PlayerbotAI* botAI) { return new PartyMemberToDispel(botAI); }
|
||||||
static UntypedValue* party_member_to_protect(PlayerbotAI* botAI) { return new PartyMemberToProtect(botAI); }
|
static UntypedValue* party_member_to_protect(PlayerbotAI* botAI) { return new PartyMemberToProtect(botAI); }
|
||||||
|
|||||||
@ -41,6 +41,7 @@ void GenericDKNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
|
|||||||
{
|
{
|
||||||
NonCombatStrategy::InitTriggers(triggers);
|
NonCombatStrategy::InitTriggers(triggers);
|
||||||
|
|
||||||
|
triggers.push_back(new TriggerNode("often", { NextAction("apply stone", 1.0f) }));
|
||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
new TriggerNode("horn of winter", { NextAction("horn of winter", 21.0f) }));
|
new TriggerNode("horn of winter", { NextAction("horn of winter", 21.0f) }));
|
||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
|
|||||||
@ -11,6 +11,9 @@
|
|||||||
#include "AoeValues.h"
|
#include "AoeValues.h"
|
||||||
#include "TargetValue.h"
|
#include "TargetValue.h"
|
||||||
|
|
||||||
|
constexpr uint32 SPELL_ECLIPSE_SOLAR = 48517;
|
||||||
|
constexpr uint32 SPELL_ECLIPSE_LUNAR = 48518;
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
bool PrepareThornsTarget(PlayerbotAI* botAI, Unit* target)
|
bool PrepareThornsTarget(PlayerbotAI* botAI, Unit* target)
|
||||||
@ -64,16 +67,89 @@ bool CastThornsOnMainTankAction::Execute(Event event)
|
|||||||
return PrepareThornsTarget(botAI, GetTarget()) && BuffOnMainTankAction::Execute(event);
|
return PrepareThornsTarget(botAI, GetTarget()) && BuffOnMainTankAction::Execute(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
Value<Unit*>* CastEntanglingRootsCcAction::GetTargetValue()
|
bool CastWrathAction::isUseful()
|
||||||
{
|
{
|
||||||
return context->GetValue<Unit*>("cc target", "entangling roots");
|
time_t now = time(nullptr);
|
||||||
|
time_t solarTime = context->GetValue<time_t>("eclipse solar proc time")->Get();
|
||||||
|
time_t lunarTime = context->GetValue<time_t>("eclipse lunar proc time")->Get();
|
||||||
|
|
||||||
|
// --- Update Solar Eclipse tracking ---
|
||||||
|
// Wrath is selected during Solar Eclipse (eclipse trigger at 20.0f), so we reliably see it here.
|
||||||
|
if (bot->HasAura(SPELL_ECLIPSE_SOLAR) && !solarTime)
|
||||||
|
context->GetValue<time_t>("eclipse solar proc time")->Set(now);
|
||||||
|
// Lunar procced — Solar fishing window is over, new cycle begins
|
||||||
|
if (bot->HasAura(SPELL_ECLIPSE_LUNAR) && solarTime)
|
||||||
|
context->GetValue<time_t>("eclipse solar proc time")->Set(0);
|
||||||
|
// 30 s cooldown window expired
|
||||||
|
if (solarTime && (now - solarTime) >= 30)
|
||||||
|
context->GetValue<time_t>("eclipse solar proc time")->Set(0);
|
||||||
|
|
||||||
|
// --- Update Lunar Eclipse tracking (belt-and-suspenders in case Starfire isn't evaluated) ---
|
||||||
|
if (bot->HasAura(SPELL_ECLIPSE_LUNAR) && !lunarTime)
|
||||||
|
context->GetValue<time_t>("eclipse lunar proc time")->Set(now);
|
||||||
|
// Solar procced — Lunar fishing window is over
|
||||||
|
if (bot->HasAura(SPELL_ECLIPSE_SOLAR) && lunarTime)
|
||||||
|
context->GetValue<time_t>("eclipse lunar proc time")->Set(0);
|
||||||
|
if (lunarTime && (now - lunarTime) >= 30)
|
||||||
|
context->GetValue<time_t>("eclipse lunar proc time")->Set(0);
|
||||||
|
|
||||||
|
// Block Wrath while in Lunar Eclipse / post-Lunar fishing window
|
||||||
|
if (context->GetValue<time_t>("eclipse lunar proc time")->Get())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return CastSpellAction::isUseful();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CastEntanglingRootsCcAction::Execute(Event /*event*/) { return botAI->CastSpell("entangling roots", GetTarget()); }
|
bool CastStarfireAction::isUseful()
|
||||||
|
{
|
||||||
|
time_t now = time(nullptr);
|
||||||
|
time_t solarTime = context->GetValue<time_t>("eclipse solar proc time")->Get();
|
||||||
|
time_t lunarTime = context->GetValue<time_t>("eclipse lunar proc time")->Get();
|
||||||
|
|
||||||
Value<Unit*>* CastHibernateCcAction::GetTargetValue() { return context->GetValue<Unit*>("cc target", "hibernate"); }
|
// --- Update Lunar Eclipse tracking ---
|
||||||
|
// Starfire is selected during Lunar Eclipse (eclipse trigger at 20.0f), so we reliably see it here.
|
||||||
|
if (bot->HasAura(SPELL_ECLIPSE_LUNAR) && !lunarTime)
|
||||||
|
context->GetValue<time_t>("eclipse lunar proc time")->Set(now);
|
||||||
|
// Solar procced — Lunar fishing window is over, new cycle begins
|
||||||
|
if (bot->HasAura(SPELL_ECLIPSE_SOLAR) && lunarTime)
|
||||||
|
context->GetValue<time_t>("eclipse lunar proc time")->Set(0);
|
||||||
|
// 30 s cooldown window expired
|
||||||
|
if (lunarTime && (now - lunarTime) >= 30)
|
||||||
|
context->GetValue<time_t>("eclipse lunar proc time")->Set(0);
|
||||||
|
|
||||||
|
// --- Update Solar Eclipse tracking (belt-and-suspenders in case Wrath isn't evaluated) ---
|
||||||
|
if (bot->HasAura(SPELL_ECLIPSE_SOLAR) && !solarTime)
|
||||||
|
context->GetValue<time_t>("eclipse solar proc time")->Set(now);
|
||||||
|
// Lunar procced — Solar fishing window is over
|
||||||
|
if (bot->HasAura(SPELL_ECLIPSE_LUNAR) && solarTime)
|
||||||
|
context->GetValue<time_t>("eclipse solar proc time")->Set(0);
|
||||||
|
if (solarTime && (now - solarTime) >= 30)
|
||||||
|
context->GetValue<time_t>("eclipse solar proc time")->Set(0);
|
||||||
|
|
||||||
|
// Block Starfire while in Solar Eclipse / post-Solar fishing window
|
||||||
|
if (context->GetValue<time_t>("eclipse solar proc time")->Get())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return CastSpellAction::isUseful();
|
||||||
|
}
|
||||||
|
|
||||||
|
Value<Unit*>* CastEntanglingRootsCcAction::GetTargetValue()
|
||||||
|
{
|
||||||
|
return context->GetValue<Unit*>("rti cc target");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value<Unit*>* CastHibernateCcAction::GetTargetValue() { return context->GetValue<Unit*>("rti cc target"); }
|
||||||
|
|
||||||
|
Value<Unit*>* CastCycloneCcAction::GetTargetValue() { return context->GetValue<Unit*>("rti cc target"); }
|
||||||
|
|
||||||
|
bool CastTyphoonAction::isUseful()
|
||||||
|
{
|
||||||
|
bool facingTarget = AI_VALUE2(bool, "facing", "current target");
|
||||||
|
bool targetClose = ServerFacade::instance().IsDistanceLessOrEqualThan(
|
||||||
|
AI_VALUE2(float, "distance", GetTargetName()), 15.f);
|
||||||
|
return facingTarget && targetClose;
|
||||||
|
}
|
||||||
|
|
||||||
bool CastHibernateCcAction::Execute(Event /*event*/) { return botAI->CastSpell("hibernate", GetTarget()); }
|
|
||||||
bool CastStarfallAction::isUseful()
|
bool CastStarfallAction::isUseful()
|
||||||
{
|
{
|
||||||
if (!CastSpellAction::isUseful())
|
if (!CastSpellAction::isUseful())
|
||||||
@ -89,12 +165,26 @@ bool CastStarfallAction::isUseful()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid single-target usage on initial pull
|
// Suppress if any unengaged hostile unit is within 40 yards — Starfall's 36-yard radius would pull them.
|
||||||
uint8 aoeCount = *context->GetValue<uint8>("aoe count");
|
Unit* currentTarget = AI_VALUE(Unit*, "current target");
|
||||||
if (aoeCount < 2)
|
GuidVector const& nearbyNpcs = AI_VALUE(GuidVector, "possible targets");
|
||||||
|
for (ObjectGuid const& guid : nearbyNpcs)
|
||||||
{
|
{
|
||||||
Unit* target = context->GetValue<Unit*>("current target")->Get();
|
Unit* unit = botAI->GetUnit(guid);
|
||||||
if (!target || (!botAI->HasAura("moonfire", target) && !botAI->HasAura("insect swarm", target)))
|
// Standard null/world-state guard before touching the unit.
|
||||||
|
if (!unit || !unit->IsAlive() || !unit->IsInWorld())
|
||||||
|
continue;
|
||||||
|
// Already our target — its in-combat flag covers it.
|
||||||
|
if (unit == currentTarget)
|
||||||
|
continue;
|
||||||
|
// Safety net for any hostile-faction trigger creature that carries NON_ATTACKABLE flags.
|
||||||
|
if (!bot->IsValidAttackTarget(unit))
|
||||||
|
continue;
|
||||||
|
// Outside Starfall's actual radius; no pull risk.
|
||||||
|
if (ServerFacade::instance().GetDistance2d(bot, unit) > 40.0f)
|
||||||
|
continue;
|
||||||
|
// Unengaged mob within range — casting would pull it.
|
||||||
|
if (!unit->IsInCombat())
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,6 +209,24 @@ bool CastRebirthAction::isUseful()
|
|||||||
AI_VALUE2(float, "distance", GetTargetName()) <= sPlayerbotAIConfig.spellDistance;
|
AI_VALUE2(float, "distance", GetTargetName()) <= sPlayerbotAIConfig.spellDistance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CastInnervateOnHealerAction::isPossible()
|
||||||
|
{
|
||||||
|
Unit* target = GetTarget();
|
||||||
|
if (!target || !target->IsInWorld())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (botAI->HasAura("innervate", target))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
uint32 spellId = AI_VALUE2(uint32, "spell id", "innervate");
|
||||||
|
return spellId && !bot->HasSpellCooldown(spellId);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<NextAction> CastInnervateOnHealerAction::getPrerequisites()
|
||||||
|
{
|
||||||
|
return { NextAction("caster form") };
|
||||||
|
}
|
||||||
|
|
||||||
Unit* CastRejuvenationOnNotFullAction::GetTarget()
|
Unit* CastRejuvenationOnNotFullAction::GetTarget()
|
||||||
{
|
{
|
||||||
Group* group = bot->GetGroup();
|
Group* group = bot->GetGroup();
|
||||||
@ -149,3 +257,63 @@ bool CastRejuvenationOnNotFullAction::isUseful()
|
|||||||
{
|
{
|
||||||
return GetTarget();
|
return GetTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Blanket HoT actions ---
|
||||||
|
|
||||||
|
Unit* CastBlanketHotAction::GetBlanketTarget(std::string const& auraName)
|
||||||
|
{
|
||||||
|
Group* group = bot->GetGroup();
|
||||||
|
if (!group)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
auto eligible = [&](Player* member) -> bool
|
||||||
|
{
|
||||||
|
return member && member->IsAlive() &&
|
||||||
|
!member->IsGameMaster() &&
|
||||||
|
bot->GetDistance2d(member) <= sPlayerbotAIConfig.spellDistance &&
|
||||||
|
!botAI->HasAura(auraName, member, false, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
Player* firstMelee = nullptr;
|
||||||
|
Player* firstRanged = nullptr;
|
||||||
|
|
||||||
|
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||||
|
{
|
||||||
|
Player* member = ref->GetSource();
|
||||||
|
if (!eligible(member))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (PlayerbotAI::IsTank(member))
|
||||||
|
return member;
|
||||||
|
else if (!firstMelee && PlayerbotAI::IsMelee(member) && !PlayerbotAI::IsTank(member))
|
||||||
|
firstMelee = member;
|
||||||
|
else if (!firstRanged && PlayerbotAI::IsRanged(member))
|
||||||
|
firstRanged = member;
|
||||||
|
|
||||||
|
if (firstMelee && firstRanged)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstMelee) return firstMelee;
|
||||||
|
return firstRanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
Unit* CastRejuvenationBlanketAction::GetTarget()
|
||||||
|
{
|
||||||
|
return GetBlanketTarget("rejuvenation");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CastRejuvenationBlanketAction::isUseful()
|
||||||
|
{
|
||||||
|
return GetTarget() != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Unit* CastWildGrowthBlanketAction::GetTarget()
|
||||||
|
{
|
||||||
|
return GetBlanketTarget("wild growth");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CastWildGrowthBlanketAction::isUseful()
|
||||||
|
{
|
||||||
|
return GetTarget() != nullptr;
|
||||||
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include "GenericSpellActions.h"
|
#include "GenericSpellActions.h"
|
||||||
#include "SharedDefines.h"
|
#include "SharedDefines.h"
|
||||||
|
#include "Value.h"
|
||||||
|
|
||||||
class PlayerbotAI;
|
class PlayerbotAI;
|
||||||
class Unit;
|
class Unit;
|
||||||
@ -64,7 +65,7 @@ class CastHealingTouchOnPartyAction : public HealPartyMemberAction
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CastHealingTouchOnPartyAction(PlayerbotAI* botAI)
|
CastHealingTouchOnPartyAction(PlayerbotAI* botAI)
|
||||||
: HealPartyMemberAction(botAI, "healing touch", 50.0f, HealingManaEfficiency::LOW)
|
: HealPartyMemberAction(botAI, "healing touch", 50.0f, HealingManaEfficiency::MEDIUM)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -86,16 +87,16 @@ public:
|
|||||||
bool isUseful() override;
|
bool isUseful() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastMarkOfTheWildAction : public CastBuffSpellAction
|
class CastMarkOfTheWildAction : public GroupBuffSpellAction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CastMarkOfTheWildAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "mark of the wild") {}
|
CastMarkOfTheWildAction(PlayerbotAI* botAI) : GroupBuffSpellAction(botAI, "mark of the wild") {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastMarkOfTheWildOnPartyAction : public BuffOnPartyAction
|
class CastMarkOfTheWildOnPartyAction : public GroupBuffOnPartyAction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CastMarkOfTheWildOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "mark of the wild") {}
|
CastMarkOfTheWildOnPartyAction(PlayerbotAI* botAI) : GroupBuffOnPartyAction(botAI, "mark of the wild") {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastSurvivalInstinctsAction : public CastBuffSpellAction
|
class CastSurvivalInstinctsAction : public CastBuffSpellAction
|
||||||
@ -142,16 +143,11 @@ public:
|
|||||||
bool isUseful() override;
|
bool isUseful() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastOmenOfClarityAction : public CastBuffSpellAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CastOmenOfClarityAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "omen of clarity") {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class CastWrathAction : public CastSpellAction
|
class CastWrathAction : public CastSpellAction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CastWrathAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "wrath") {}
|
CastWrathAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "wrath") {}
|
||||||
|
bool isUseful() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastStarfallAction : public CastSpellAction
|
class CastStarfallAction : public CastSpellAction
|
||||||
@ -169,6 +165,14 @@ public:
|
|||||||
ActionThreatType getThreatType() override { return ActionThreatType::Aoe; }
|
ActionThreatType getThreatType() override { return ActionThreatType::Aoe; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class CastTyphoonAction : public CastSpellAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CastTyphoonAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "typhoon") {}
|
||||||
|
ActionThreatType getThreatType() override { return ActionThreatType::Aoe; }
|
||||||
|
bool isUseful() override;
|
||||||
|
};
|
||||||
|
|
||||||
class CastMoonfireAction : public CastDebuffSpellAction
|
class CastMoonfireAction : public CastDebuffSpellAction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -185,6 +189,7 @@ class CastStarfireAction : public CastSpellAction
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CastStarfireAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "starfire") {}
|
CastStarfireAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "starfire") {}
|
||||||
|
bool isUseful() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastEntanglingRootsAction : public CastSpellAction
|
class CastEntanglingRootsAction : public CastSpellAction
|
||||||
@ -193,12 +198,11 @@ public:
|
|||||||
CastEntanglingRootsAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "entangling roots") {}
|
CastEntanglingRootsAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "entangling roots") {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastEntanglingRootsCcAction : public CastSpellAction
|
class CastEntanglingRootsCcAction : public CastCrowdControlSpellAction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CastEntanglingRootsCcAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "entangling roots on cc") {}
|
CastEntanglingRootsCcAction(PlayerbotAI* botAI) : CastCrowdControlSpellAction(botAI, "entangling roots") {}
|
||||||
Value<Unit*>* GetTargetValue() override;
|
Value<Unit*>* GetTargetValue() override;
|
||||||
bool Execute(Event event) override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastHibernateAction : public CastSpellAction
|
class CastHibernateAction : public CastSpellAction
|
||||||
@ -207,12 +211,18 @@ public:
|
|||||||
CastHibernateAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "hibernate") {}
|
CastHibernateAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "hibernate") {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastHibernateCcAction : public CastSpellAction
|
class CastHibernateCcAction : public CastCrowdControlSpellAction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CastHibernateCcAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "hibernate on cc") {}
|
CastHibernateCcAction(PlayerbotAI* botAI) : CastCrowdControlSpellAction(botAI, "hibernate") {}
|
||||||
|
Value<Unit*>* GetTargetValue() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CastCycloneCcAction : public CastCrowdControlSpellAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CastCycloneCcAction(PlayerbotAI* botAI) : CastCrowdControlSpellAction(botAI, "cyclone") {}
|
||||||
Value<Unit*>* GetTargetValue() override;
|
Value<Unit*>* GetTargetValue() override;
|
||||||
bool Execute(Event event) override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastNaturesGraspAction : public CastBuffSpellAction
|
class CastNaturesGraspAction : public CastBuffSpellAction
|
||||||
@ -264,6 +274,16 @@ public:
|
|||||||
std::string const GetTargetName() override { return "self target"; }
|
std::string const GetTargetName() override { return "self target"; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class CastInnervateOnHealerAction : public CastSpellAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CastInnervateOnHealerAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "innervate") {}
|
||||||
|
|
||||||
|
std::string const GetTargetName() override { return "healer low mana"; }
|
||||||
|
bool isPossible() override;
|
||||||
|
std::vector<NextAction> getPrerequisites() override;
|
||||||
|
};
|
||||||
|
|
||||||
class CastTranquilityAction : public CastAoeHealSpellAction
|
class CastTranquilityAction : public CastAoeHealSpellAction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -312,13 +332,15 @@ public:
|
|||||||
class CastInsectSwarmOnAttackerAction : public CastDebuffSpellOnAttackerAction
|
class CastInsectSwarmOnAttackerAction : public CastDebuffSpellOnAttackerAction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CastInsectSwarmOnAttackerAction(PlayerbotAI* ai) : CastDebuffSpellOnAttackerAction(ai, "insect swarm") {}
|
CastInsectSwarmOnAttackerAction(PlayerbotAI* ai) : CastDebuffSpellOnAttackerAction(ai, "insect swarm", true, 0.0f) {}
|
||||||
|
bool isUseful() override { return CastAuraSpellAction::isUseful(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastMoonfireOnAttackerAction : public CastDebuffSpellOnAttackerAction
|
class CastMoonfireOnAttackerAction : public CastDebuffSpellOnAttackerAction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CastMoonfireOnAttackerAction(PlayerbotAI* ai) : CastDebuffSpellOnAttackerAction(ai, "moonfire") {}
|
CastMoonfireOnAttackerAction(PlayerbotAI* ai) : CastDebuffSpellOnAttackerAction(ai, "moonfire", true, 0.0f) {}
|
||||||
|
bool isUseful() override { return CastAuraSpellAction::isUseful(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastEnrageAction : public CastBuffSpellAction
|
class CastEnrageAction : public CastBuffSpellAction
|
||||||
@ -344,4 +366,48 @@ public:
|
|||||||
CastForceOfNatureAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "force of nature") {}
|
CastForceOfNatureAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "force of nature") {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Base for blanket HoT actions. Provides GetBlanketTarget() as a member so
|
||||||
|
// subclasses can use AI_VALUE and the standard context machinery.
|
||||||
|
class CastBlanketHotAction : public CastSpellAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CastBlanketHotAction(PlayerbotAI* ai, std::string const& spell) : CastSpellAction(ai, spell)
|
||||||
|
{
|
||||||
|
range = botAI->GetRange("heal");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Unit* GetBlanketTarget(std::string const& auraName);
|
||||||
|
};
|
||||||
|
|
||||||
|
class CastRejuvenationBlanketAction : public CastBlanketHotAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CastRejuvenationBlanketAction(PlayerbotAI* ai) : CastBlanketHotAction(ai, "rejuvenation") {}
|
||||||
|
bool isUseful() override;
|
||||||
|
Unit* GetTarget() override;
|
||||||
|
std::string const getName() override { return "rejuvenation blanket"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class CastWildGrowthBlanketAction : public CastBlanketHotAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CastWildGrowthBlanketAction(PlayerbotAI* ai) : CastBlanketHotAction(ai, "wild growth") {}
|
||||||
|
bool isUseful() override;
|
||||||
|
Unit* GetTarget() override;
|
||||||
|
std::string const getName() override { return "wild growth blanket"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class EclipseSolarProcTimeValue : public ManualSetValue<time_t>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EclipseSolarProcTimeValue(PlayerbotAI* botAI) : ManualSetValue<time_t>(botAI, 0) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class EclipseLunarProcTimeValue : public ManualSetValue<time_t>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EclipseLunarProcTimeValue(PlayerbotAI* botAI) : ManualSetValue<time_t>(botAI, 0) {}
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -9,12 +9,23 @@
|
|||||||
#include "GenericSpellActions.h"
|
#include "GenericSpellActions.h"
|
||||||
#include "ReachTargetActions.h"
|
#include "ReachTargetActions.h"
|
||||||
|
|
||||||
|
constexpr uint32 SPELL_POUNCE_RANK_1 = 9005;
|
||||||
|
constexpr uint32 SPELL_RAVAGE_RANK_1 = 6785;
|
||||||
|
|
||||||
class PlayerbotAI;
|
class PlayerbotAI;
|
||||||
|
|
||||||
class CastFeralChargeCatAction : public CastReachTargetSpellAction
|
class CastFeralChargeCatAction : public CastReachTargetSpellAction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CastFeralChargeCatAction(PlayerbotAI* botAI) : CastReachTargetSpellAction(botAI, "feral charge - cat", 1.5f) {}
|
CastFeralChargeCatAction(PlayerbotAI* botAI) : CastReachTargetSpellAction(botAI, "feral charge - cat", 1.5f) {}
|
||||||
|
|
||||||
|
bool isUseful() override
|
||||||
|
{
|
||||||
|
if (botAI->HasAura("prowl", bot))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return CastReachTargetSpellAction::isUseful();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastCowerAction : public CastBuffSpellAction
|
class CastCowerAction : public CastBuffSpellAction
|
||||||
@ -48,28 +59,47 @@ public:
|
|||||||
CastRakeAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "rake", true, 6.0f) {}
|
CastRakeAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "rake", true, 6.0f) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastRakeOnMeleeAttackersAction : public CastDebuffSpellOnMeleeAttackerAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CastRakeOnMeleeAttackersAction(PlayerbotAI* botAI) : CastDebuffSpellOnMeleeAttackerAction(botAI, "rake", true, 6.0f) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class CastClawAction : public CastMeleeSpellAction
|
class CastClawAction : public CastMeleeSpellAction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CastClawAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "claw") {}
|
CastClawAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "claw") {}
|
||||||
|
|
||||||
|
bool isUseful() override
|
||||||
|
{
|
||||||
|
// Block Claw once Pounce is learned; Claw remains available as the stealth opener before then.
|
||||||
|
if (botAI->HasAura("prowl", bot) && bot->HasSpell(SPELL_POUNCE_RANK_1))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return CastMeleeSpellAction::isUseful();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastMangleCatAction : public CastMeleeSpellAction
|
class CastMangleCatAction : public CastMeleeSpellAction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CastMangleCatAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "mangle (cat)") {}
|
CastMangleCatAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "mangle (cat)") {}
|
||||||
|
|
||||||
|
bool isUseful() override
|
||||||
|
{
|
||||||
|
if (botAI->HasAura("prowl", bot))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return CastMeleeSpellAction::isUseful();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastSwipeCatAction : public CastMeleeSpellAction
|
class CastSwipeCatAction : public CastMeleeSpellAction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CastSwipeCatAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "swipe (cat)") {}
|
CastSwipeCatAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "swipe (cat)") {}
|
||||||
|
|
||||||
|
bool isUseful() override
|
||||||
|
{
|
||||||
|
if (botAI->HasAura("prowl", bot))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return CastMeleeSpellAction::isUseful();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastFerociousBiteAction : public CastMeleeSpellAction
|
class CastFerociousBiteAction : public CastMeleeSpellAction
|
||||||
@ -78,6 +108,21 @@ public:
|
|||||||
CastFerociousBiteAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "ferocious bite") {}
|
CastFerociousBiteAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "ferocious bite") {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class CastMaimAction : public CastMeleeSpellAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CastMaimAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "maim") {}
|
||||||
|
|
||||||
|
bool isUseful() override
|
||||||
|
{
|
||||||
|
Unit* target = GetTarget();
|
||||||
|
if (!target || !target->ToPlayer())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return CastMeleeSpellAction::isUseful();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class CastRipAction : public CastMeleeDebuffSpellAction
|
class CastRipAction : public CastMeleeDebuffSpellAction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -88,6 +133,14 @@ class CastShredAction : public CastMeleeSpellAction
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CastShredAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "shred") {}
|
CastShredAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "shred") {}
|
||||||
|
|
||||||
|
bool isUseful() override
|
||||||
|
{
|
||||||
|
if (botAI->HasAura("prowl", bot) && bot->HasSpell(SPELL_RAVAGE_RANK_1))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return CastMeleeSpellAction::isUseful();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastProwlAction : public CastBuffSpellAction
|
class CastProwlAction : public CastBuffSpellAction
|
||||||
@ -106,12 +159,28 @@ class CastRavageAction : public CastMeleeSpellAction
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CastRavageAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "ravage") {}
|
CastRavageAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "ravage") {}
|
||||||
|
|
||||||
|
bool isUseful() override
|
||||||
|
{
|
||||||
|
if (!botAI->HasAura("prowl", bot))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return CastMeleeSpellAction::isUseful();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastPounceAction : public CastMeleeSpellAction
|
class CastPounceAction : public CastMeleeSpellAction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CastPounceAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "pounce") {}
|
CastPounceAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "pounce") {}
|
||||||
|
|
||||||
|
bool isUseful() override
|
||||||
|
{
|
||||||
|
if (!botAI->HasAura("prowl", bot))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return CastMeleeSpellAction::isUseful();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -5,9 +5,9 @@
|
|||||||
|
|
||||||
#include "DruidAiObjectContext.h"
|
#include "DruidAiObjectContext.h"
|
||||||
|
|
||||||
#include "BearTankDruidStrategy.h"
|
#include "BalanceDruidStrategy.h"
|
||||||
#include "CasterDruidStrategy.h"
|
#include "BearDruidStrategy.h"
|
||||||
#include "CatDpsDruidStrategy.h"
|
#include "CatDruidStrategy.h"
|
||||||
#include "DruidActions.h"
|
#include "DruidActions.h"
|
||||||
#include "DruidBearActions.h"
|
#include "DruidBearActions.h"
|
||||||
#include "DruidCatActions.h"
|
#include "DruidCatActions.h"
|
||||||
@ -15,9 +15,7 @@
|
|||||||
#include "DruidTriggers.h"
|
#include "DruidTriggers.h"
|
||||||
#include "GenericDruidNonCombatStrategy.h"
|
#include "GenericDruidNonCombatStrategy.h"
|
||||||
#include "GenericDruidStrategy.h"
|
#include "GenericDruidStrategy.h"
|
||||||
#include "HealDruidStrategy.h"
|
#include "RestoDruidStrategy.h"
|
||||||
#include "MeleeDruidStrategy.h"
|
|
||||||
#include "OffhealDruidCatStrategy.h"
|
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "DruidPullStrategy.h"
|
#include "DruidPullStrategy.h"
|
||||||
|
|
||||||
@ -28,30 +26,31 @@ public:
|
|||||||
{
|
{
|
||||||
creators["nc"] = &DruidStrategyFactoryInternal::nc;
|
creators["nc"] = &DruidStrategyFactoryInternal::nc;
|
||||||
creators["pull"] = &DruidStrategyFactoryInternal::pull;
|
creators["pull"] = &DruidStrategyFactoryInternal::pull;
|
||||||
creators["cat aoe"] = &DruidStrategyFactoryInternal::cat_aoe;
|
creators["aoe"] = &DruidStrategyFactoryInternal::aoe;
|
||||||
creators["caster aoe"] = &DruidStrategyFactoryInternal::caster_aoe;
|
|
||||||
creators["caster debuff"] = &DruidStrategyFactoryInternal::caster_debuff;
|
|
||||||
creators["dps debuff"] = &DruidStrategyFactoryInternal::caster_debuff;
|
|
||||||
creators["cure"] = &DruidStrategyFactoryInternal::cure;
|
creators["cure"] = &DruidStrategyFactoryInternal::cure;
|
||||||
creators["melee"] = &DruidStrategyFactoryInternal::melee;
|
|
||||||
creators["buff"] = &DruidStrategyFactoryInternal::buff;
|
creators["buff"] = &DruidStrategyFactoryInternal::buff;
|
||||||
creators["boost"] = &DruidStrategyFactoryInternal::boost;
|
creators["boost"] = &DruidStrategyFactoryInternal::boost;
|
||||||
creators["cc"] = &DruidStrategyFactoryInternal::cc;
|
creators["cc"] = &DruidStrategyFactoryInternal::cc;
|
||||||
creators["healer dps"] = &DruidStrategyFactoryInternal::healer_dps;
|
creators["healer dps"] = &DruidStrategyFactoryInternal::healer_dps;
|
||||||
|
creators["offheal"] = &DruidStrategyFactoryInternal::offheal;
|
||||||
|
creators["blanketing"] = &DruidStrategyFactoryInternal::blanketing;
|
||||||
|
creators["tranquility"] = &DruidStrategyFactoryInternal::tranquility;
|
||||||
|
creators["feral charge"] = &DruidStrategyFactoryInternal::feral_charge;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static Strategy* nc(PlayerbotAI* botAI) { return new GenericDruidNonCombatStrategy(botAI); }
|
static Strategy* nc(PlayerbotAI* botAI) { return new GenericDruidNonCombatStrategy(botAI); }
|
||||||
static Strategy* pull(PlayerbotAI* botAI) { return new DruidPullStrategy(botAI); }
|
static Strategy* pull(PlayerbotAI* botAI) { return new DruidPullStrategy(botAI); }
|
||||||
static Strategy* cat_aoe(PlayerbotAI* botAI) { return new CatAoeDruidStrategy(botAI); }
|
static Strategy* aoe(PlayerbotAI* botAI) { return new DruidAoeStrategy(botAI); }
|
||||||
static Strategy* caster_aoe(PlayerbotAI* botAI) { return new CasterDruidAoeStrategy(botAI); }
|
|
||||||
static Strategy* caster_debuff(PlayerbotAI* botAI) { return new CasterDruidDebuffStrategy(botAI); }
|
|
||||||
static Strategy* cure(PlayerbotAI* botAI) { return new DruidCureStrategy(botAI); }
|
static Strategy* cure(PlayerbotAI* botAI) { return new DruidCureStrategy(botAI); }
|
||||||
static Strategy* melee(PlayerbotAI* botAI) { return new MeleeDruidStrategy(botAI); }
|
|
||||||
static Strategy* buff(PlayerbotAI* botAI) { return new GenericDruidBuffStrategy(botAI); }
|
static Strategy* buff(PlayerbotAI* botAI) { return new GenericDruidBuffStrategy(botAI); }
|
||||||
static Strategy* boost(PlayerbotAI* botAI) { return new DruidBoostStrategy(botAI); }
|
static Strategy* boost(PlayerbotAI* botAI) { return new DruidBoostStrategy(botAI); }
|
||||||
static Strategy* cc(PlayerbotAI* botAI) { return new DruidCcStrategy(botAI); }
|
static Strategy* cc(PlayerbotAI* botAI) { return new DruidCcStrategy(botAI); }
|
||||||
static Strategy* healer_dps(PlayerbotAI* botAI) { return new DruidHealerDpsStrategy(botAI); }
|
static Strategy* healer_dps(PlayerbotAI* botAI) { return new DruidHealerDpsStrategy(botAI); }
|
||||||
|
static Strategy* offheal(PlayerbotAI* botAI) { return new CatOffhealStrategy(botAI); }
|
||||||
|
static Strategy* blanketing(PlayerbotAI* botAI) { return new DruidBlanketStrategy(botAI); }
|
||||||
|
static Strategy* tranquility(PlayerbotAI* botAI) { return new DruidTranquilityStrategy(botAI); }
|
||||||
|
static Strategy* feral_charge(PlayerbotAI* botAI) { return new FeralChargeDruidStrategy(botAI); }
|
||||||
};
|
};
|
||||||
|
|
||||||
class DruidDruidStrategyFactoryInternal : public NamedObjectContext<Strategy>
|
class DruidDruidStrategyFactoryInternal : public NamedObjectContext<Strategy>
|
||||||
@ -62,18 +61,16 @@ public:
|
|||||||
creators["bear"] = &DruidDruidStrategyFactoryInternal::bear;
|
creators["bear"] = &DruidDruidStrategyFactoryInternal::bear;
|
||||||
creators["tank"] = &DruidDruidStrategyFactoryInternal::bear;
|
creators["tank"] = &DruidDruidStrategyFactoryInternal::bear;
|
||||||
creators["cat"] = &DruidDruidStrategyFactoryInternal::cat;
|
creators["cat"] = &DruidDruidStrategyFactoryInternal::cat;
|
||||||
creators["caster"] = &DruidDruidStrategyFactoryInternal::caster;
|
creators["balance"] = &DruidDruidStrategyFactoryInternal::balance;
|
||||||
creators["dps"] = &DruidDruidStrategyFactoryInternal::cat;
|
creators["dps"] = &DruidDruidStrategyFactoryInternal::cat;
|
||||||
creators["heal"] = &DruidDruidStrategyFactoryInternal::heal;
|
creators["resto"] = &DruidDruidStrategyFactoryInternal::heal;
|
||||||
creators["offheal"] = &DruidDruidStrategyFactoryInternal::offheal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static Strategy* bear(PlayerbotAI* botAI) { return new BearTankDruidStrategy(botAI); }
|
static Strategy* bear(PlayerbotAI* botAI) { return new BearDruidStrategy(botAI); }
|
||||||
static Strategy* cat(PlayerbotAI* botAI) { return new CatDpsDruidStrategy(botAI); }
|
static Strategy* cat(PlayerbotAI* botAI) { return new CatDruidStrategy(botAI); }
|
||||||
static Strategy* caster(PlayerbotAI* botAI) { return new CasterDruidStrategy(botAI); }
|
static Strategy* balance(PlayerbotAI* botAI) { return new BalanceDruidStrategy(botAI); }
|
||||||
static Strategy* heal(PlayerbotAI* botAI) { return new HealDruidStrategy(botAI); }
|
static Strategy* heal(PlayerbotAI* botAI) { return new RestoDruidStrategy(botAI); }
|
||||||
static Strategy* offheal(PlayerbotAI* botAI) { return new OffhealDruidCatStrategy(botAI); }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class DruidTriggerFactoryInternal : public NamedObjectContext<Trigger>
|
class DruidTriggerFactoryInternal : public NamedObjectContext<Trigger>
|
||||||
@ -81,7 +78,6 @@ class DruidTriggerFactoryInternal : public NamedObjectContext<Trigger>
|
|||||||
public:
|
public:
|
||||||
DruidTriggerFactoryInternal()
|
DruidTriggerFactoryInternal()
|
||||||
{
|
{
|
||||||
creators["omen of clarity"] = &DruidTriggerFactoryInternal::omen_of_clarity;
|
|
||||||
creators["clearcasting"] = &DruidTriggerFactoryInternal::clearcasting;
|
creators["clearcasting"] = &DruidTriggerFactoryInternal::clearcasting;
|
||||||
creators["thorns"] = &DruidTriggerFactoryInternal::thorns;
|
creators["thorns"] = &DruidTriggerFactoryInternal::thorns;
|
||||||
creators["thorns on party"] = &DruidTriggerFactoryInternal::thorns_on_party;
|
creators["thorns on party"] = &DruidTriggerFactoryInternal::thorns_on_party;
|
||||||
@ -90,10 +86,12 @@ public:
|
|||||||
creators["faerie fire (feral)"] = &DruidTriggerFactoryInternal::faerie_fire_feral;
|
creators["faerie fire (feral)"] = &DruidTriggerFactoryInternal::faerie_fire_feral;
|
||||||
creators["faerie fire"] = &DruidTriggerFactoryInternal::faerie_fire;
|
creators["faerie fire"] = &DruidTriggerFactoryInternal::faerie_fire;
|
||||||
creators["insect swarm"] = &DruidTriggerFactoryInternal::insect_swarm;
|
creators["insect swarm"] = &DruidTriggerFactoryInternal::insect_swarm;
|
||||||
|
creators["insect swarm on attacker"] = &DruidTriggerFactoryInternal::insect_swarm_on_attacker;
|
||||||
creators["moonfire"] = &DruidTriggerFactoryInternal::moonfire;
|
creators["moonfire"] = &DruidTriggerFactoryInternal::moonfire;
|
||||||
|
creators["moonfire on attacker"] = &DruidTriggerFactoryInternal::moonfire_on_attacker;
|
||||||
creators["nature's grasp"] = &DruidTriggerFactoryInternal::natures_grasp;
|
creators["nature's grasp"] = &DruidTriggerFactoryInternal::natures_grasp;
|
||||||
creators["tiger's fury"] = &DruidTriggerFactoryInternal::tigers_fury;
|
|
||||||
creators["berserk"] = &DruidTriggerFactoryInternal::berserk;
|
creators["berserk"] = &DruidTriggerFactoryInternal::berserk;
|
||||||
|
creators["berserk active"] = &DruidTriggerFactoryInternal::berserk_active;
|
||||||
creators["savage roar"] = &DruidTriggerFactoryInternal::savage_roar;
|
creators["savage roar"] = &DruidTriggerFactoryInternal::savage_roar;
|
||||||
creators["rake"] = &DruidTriggerFactoryInternal::rake;
|
creators["rake"] = &DruidTriggerFactoryInternal::rake;
|
||||||
creators["mark of the wild"] = &DruidTriggerFactoryInternal::mark_of_the_wild;
|
creators["mark of the wild"] = &DruidTriggerFactoryInternal::mark_of_the_wild;
|
||||||
@ -110,17 +108,34 @@ public:
|
|||||||
creators["eclipse (lunar)"] = &DruidTriggerFactoryInternal::eclipse_lunar;
|
creators["eclipse (lunar)"] = &DruidTriggerFactoryInternal::eclipse_lunar;
|
||||||
creators["bash on enemy healer"] = &DruidTriggerFactoryInternal::bash_on_enemy_healer;
|
creators["bash on enemy healer"] = &DruidTriggerFactoryInternal::bash_on_enemy_healer;
|
||||||
creators["nature's swiftness"] = &DruidTriggerFactoryInternal::natures_swiftness;
|
creators["nature's swiftness"] = &DruidTriggerFactoryInternal::natures_swiftness;
|
||||||
|
creators["nature's swiftness active"] = &DruidTriggerFactoryInternal::natures_swiftness_active;
|
||||||
creators["party member remove curse"] = &DruidTriggerFactoryInternal::party_member_remove_curse;
|
creators["party member remove curse"] = &DruidTriggerFactoryInternal::party_member_remove_curse;
|
||||||
creators["eclipse (solar) cooldown"] = &DruidTriggerFactoryInternal::eclipse_solar_cooldown;
|
creators["mangle (bear)"] = &DruidTriggerFactoryInternal::mangle_bear_trigger;
|
||||||
creators["eclipse (lunar) cooldown"] = &DruidTriggerFactoryInternal::eclipse_lunar_cooldown;
|
creators["lacerate"] = &DruidTriggerFactoryInternal::lacerate_trigger;
|
||||||
|
creators["demoralizing roar"] = &DruidTriggerFactoryInternal::demoralize_roar;
|
||||||
creators["mangle (cat)"] = &DruidTriggerFactoryInternal::mangle_cat;
|
creators["mangle (cat)"] = &DruidTriggerFactoryInternal::mangle_cat;
|
||||||
creators["ferocious bite time"] = &DruidTriggerFactoryInternal::ferocious_bite_time;
|
creators["ferocious bite time"] = &DruidTriggerFactoryInternal::ferocious_bite_time;
|
||||||
|
creators["ferocious bite execute"] = &DruidTriggerFactoryInternal::ferocious_bite_execute;
|
||||||
creators["hurricane channel check"] = &DruidTriggerFactoryInternal::hurricane_channel_check;
|
creators["hurricane channel check"] = &DruidTriggerFactoryInternal::hurricane_channel_check;
|
||||||
creators["no healer dps strategy"] = &DruidTriggerFactoryInternal::no_healer_dps_strategy;
|
creators["no healer dps strategy"] = &DruidTriggerFactoryInternal::no_healer_dps_strategy;
|
||||||
|
creators["starfall"] = &DruidTriggerFactoryInternal::starfall;
|
||||||
|
creators["force of nature"] = &DruidTriggerFactoryInternal::force_of_nature;
|
||||||
|
creators["cyclone"] = &DruidTriggerFactoryInternal::cyclone;
|
||||||
|
creators["predator's swiftness"] = &DruidTriggerFactoryInternal::predators_swiftness;
|
||||||
|
creators["predator's swiftness and cyclone"] = &DruidTriggerFactoryInternal::predators_swiftness_and_cyclone;
|
||||||
|
creators["predator's swiftness and hibernate"] = &DruidTriggerFactoryInternal::predators_swiftness_and_hibernate;
|
||||||
|
creators["predator's swiftness and entangling roots"] = &DruidTriggerFactoryInternal::predators_swiftness_and_entangling_roots;
|
||||||
|
creators["predator's swiftness and combat party member dead"] = &DruidTriggerFactoryInternal::predators_swiftness_and_combat_party_member_dead;
|
||||||
|
creators["clearcasting and medium aoe"] = &DruidTriggerFactoryInternal::clearcasting_and_medium_aoe;
|
||||||
|
creators["prowl"] = &DruidTriggerFactoryInternal::prowl_trigger;
|
||||||
|
creators["rejuvenation blanket"] = &DruidTriggerFactoryInternal::rejuvenation_blanket;
|
||||||
|
creators["wild growth blanket"] = &DruidTriggerFactoryInternal::wild_growth_blanket;
|
||||||
|
creators["aquatic form"] = &DruidTriggerFactoryInternal::aquatic_form;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static Trigger* natures_swiftness(PlayerbotAI* botAI) { return new NaturesSwiftnessTrigger(botAI); }
|
static Trigger* natures_swiftness(PlayerbotAI* botAI) { return new NaturesSwiftnessTrigger(botAI); }
|
||||||
|
static Trigger* natures_swiftness_active(PlayerbotAI* botAI) { return new NaturesSwiftnessActiveTrigger(botAI); }
|
||||||
static Trigger* clearcasting(PlayerbotAI* botAI) { return new ClearcastingTrigger(botAI); }
|
static Trigger* clearcasting(PlayerbotAI* botAI) { return new ClearcastingTrigger(botAI); }
|
||||||
static Trigger* eclipse_solar(PlayerbotAI* botAI) { return new EclipseSolarTrigger(botAI); }
|
static Trigger* eclipse_solar(PlayerbotAI* botAI) { return new EclipseSolarTrigger(botAI); }
|
||||||
static Trigger* eclipse_lunar(PlayerbotAI* botAI) { return new EclipseLunarTrigger(botAI); }
|
static Trigger* eclipse_lunar(PlayerbotAI* botAI) { return new EclipseLunarTrigger(botAI); }
|
||||||
@ -130,11 +145,13 @@ private:
|
|||||||
static Trigger* bash(PlayerbotAI* botAI) { return new BashInterruptSpellTrigger(botAI); }
|
static Trigger* bash(PlayerbotAI* botAI) { return new BashInterruptSpellTrigger(botAI); }
|
||||||
static Trigger* faerie_fire_feral(PlayerbotAI* botAI) { return new FaerieFireFeralTrigger(botAI); }
|
static Trigger* faerie_fire_feral(PlayerbotAI* botAI) { return new FaerieFireFeralTrigger(botAI); }
|
||||||
static Trigger* insect_swarm(PlayerbotAI* botAI) { return new InsectSwarmTrigger(botAI); }
|
static Trigger* insect_swarm(PlayerbotAI* botAI) { return new InsectSwarmTrigger(botAI); }
|
||||||
|
static Trigger* insect_swarm_on_attacker(PlayerbotAI* botAI) { return new InsectSwarmOnAttackerTrigger(botAI); }
|
||||||
static Trigger* moonfire(PlayerbotAI* botAI) { return new MoonfireTrigger(botAI); }
|
static Trigger* moonfire(PlayerbotAI* botAI) { return new MoonfireTrigger(botAI); }
|
||||||
|
static Trigger* moonfire_on_attacker(PlayerbotAI* botAI) { return new MoonfireOnAttackerTrigger(botAI); }
|
||||||
static Trigger* faerie_fire(PlayerbotAI* botAI) { return new FaerieFireTrigger(botAI); }
|
static Trigger* faerie_fire(PlayerbotAI* botAI) { return new FaerieFireTrigger(botAI); }
|
||||||
static Trigger* natures_grasp(PlayerbotAI* botAI) { return new NaturesGraspTrigger(botAI); }
|
static Trigger* natures_grasp(PlayerbotAI* botAI) { return new NaturesGraspTrigger(botAI); }
|
||||||
static Trigger* tigers_fury(PlayerbotAI* botAI) { return new TigersFuryTrigger(botAI); }
|
|
||||||
static Trigger* berserk(PlayerbotAI* botAI) { return new BerserkTrigger(botAI); }
|
static Trigger* berserk(PlayerbotAI* botAI) { return new BerserkTrigger(botAI); }
|
||||||
|
static Trigger* berserk_active(PlayerbotAI* botAI) { return new BerserkActiveTrigger(botAI); }
|
||||||
static Trigger* savage_roar(PlayerbotAI* botAI) { return new SavageRoarTrigger(botAI); }
|
static Trigger* savage_roar(PlayerbotAI* botAI) { return new SavageRoarTrigger(botAI); }
|
||||||
static Trigger* rake(PlayerbotAI* botAI) { return new RakeTrigger(botAI); }
|
static Trigger* rake(PlayerbotAI* botAI) { return new RakeTrigger(botAI); }
|
||||||
static Trigger* mark_of_the_wild(PlayerbotAI* botAI) { return new MarkOfTheWildTrigger(botAI); }
|
static Trigger* mark_of_the_wild(PlayerbotAI* botAI) { return new MarkOfTheWildTrigger(botAI); }
|
||||||
@ -148,14 +165,28 @@ private:
|
|||||||
static Trigger* cat_form(PlayerbotAI* botAI) { return new CatFormTrigger(botAI); }
|
static Trigger* cat_form(PlayerbotAI* botAI) { return new CatFormTrigger(botAI); }
|
||||||
static Trigger* tree_form(PlayerbotAI* botAI) { return new TreeFormTrigger(botAI); }
|
static Trigger* tree_form(PlayerbotAI* botAI) { return new TreeFormTrigger(botAI); }
|
||||||
static Trigger* bash_on_enemy_healer(PlayerbotAI* botAI) { return new BashInterruptEnemyHealerSpellTrigger(botAI); }
|
static Trigger* bash_on_enemy_healer(PlayerbotAI* botAI) { return new BashInterruptEnemyHealerSpellTrigger(botAI); }
|
||||||
static Trigger* omen_of_clarity(PlayerbotAI* botAI) { return new OmenOfClarityTrigger(botAI); }
|
|
||||||
static Trigger* party_member_remove_curse(PlayerbotAI* ai) { return new DruidPartyMemberRemoveCurseTrigger(ai); }
|
static Trigger* party_member_remove_curse(PlayerbotAI* ai) { return new DruidPartyMemberRemoveCurseTrigger(ai); }
|
||||||
static Trigger* eclipse_solar_cooldown(PlayerbotAI* ai) { return new EclipseSolarCooldownTrigger(ai); }
|
static Trigger* mangle_bear_trigger(PlayerbotAI* botAI) { return new MangleBearTrigger(botAI); }
|
||||||
static Trigger* eclipse_lunar_cooldown(PlayerbotAI* ai) { return new EclipseLunarCooldownTrigger(ai); }
|
static Trigger* lacerate_trigger(PlayerbotAI* botAI) { return new LacerateTrigger(botAI); }
|
||||||
|
static Trigger* demoralize_roar(PlayerbotAI* botAI) { return new DemoralizeRoarTrigger(botAI); }
|
||||||
static Trigger* mangle_cat(PlayerbotAI* ai) { return new MangleCatTrigger(ai); }
|
static Trigger* mangle_cat(PlayerbotAI* ai) { return new MangleCatTrigger(ai); }
|
||||||
static Trigger* ferocious_bite_time(PlayerbotAI* ai) { return new FerociousBiteTimeTrigger(ai); }
|
static Trigger* ferocious_bite_time(PlayerbotAI* ai) { return new FerociousBiteTimeTrigger(ai); }
|
||||||
|
static Trigger* ferocious_bite_execute(PlayerbotAI* ai) { return new FerociousBiteExecuteTrigger(ai); }
|
||||||
static Trigger* hurricane_channel_check(PlayerbotAI* ai) { return new HurricaneChannelCheckTrigger(ai); }
|
static Trigger* hurricane_channel_check(PlayerbotAI* ai) { return new HurricaneChannelCheckTrigger(ai); }
|
||||||
static Trigger* no_healer_dps_strategy(PlayerbotAI* ai) { return new NoHealerDpsStrategyTrigger(ai); }
|
static Trigger* no_healer_dps_strategy(PlayerbotAI* ai) { return new NoHealerDpsStrategyTrigger(ai); }
|
||||||
|
static Trigger* starfall(PlayerbotAI* ai) { return new StarfallTrigger(ai); }
|
||||||
|
static Trigger* force_of_nature(PlayerbotAI* ai) { return new ForceOfNatureTrigger(ai); }
|
||||||
|
static Trigger* cyclone(PlayerbotAI* ai) { return new CycloneTrigger(ai); }
|
||||||
|
static Trigger* predators_swiftness(PlayerbotAI* ai) { return new PredatorsSwiftnessTrigger(ai); }
|
||||||
|
static Trigger* predators_swiftness_and_cyclone(PlayerbotAI* ai) { return new TwoTriggers(ai, "predator's swiftness", "cyclone"); }
|
||||||
|
static Trigger* predators_swiftness_and_hibernate(PlayerbotAI* ai) { return new TwoTriggers(ai, "predator's swiftness", "hibernate"); }
|
||||||
|
static Trigger* predators_swiftness_and_entangling_roots(PlayerbotAI* ai) { return new TwoTriggers(ai, "predator's swiftness", "entangling roots"); }
|
||||||
|
static Trigger* predators_swiftness_and_combat_party_member_dead(PlayerbotAI* ai) { return new TwoTriggers(ai, "predator's swiftness", "combat party member dead"); }
|
||||||
|
static Trigger* clearcasting_and_medium_aoe(PlayerbotAI* ai) { return new TwoTriggers(ai, "clearcasting", "medium aoe"); }
|
||||||
|
static Trigger* prowl_trigger(PlayerbotAI* ai) { return new ProwlTrigger(ai); }
|
||||||
|
static Trigger* rejuvenation_blanket(PlayerbotAI* ai) { return new BuffOnPartyTrigger(ai, "rejuvenation"); }
|
||||||
|
static Trigger* wild_growth_blanket(PlayerbotAI* ai) { return new BuffOnPartyTrigger(ai, "wild growth"); }
|
||||||
|
static Trigger* aquatic_form(PlayerbotAI* ai) { return new AquaticFormTrigger(ai); }
|
||||||
};
|
};
|
||||||
|
|
||||||
class DruidAiObjectContextInternal : public NamedObjectContext<Action>
|
class DruidAiObjectContextInternal : public NamedObjectContext<Action>
|
||||||
@ -193,8 +224,8 @@ public:
|
|||||||
creators["hibernate"] = &DruidAiObjectContextInternal::hibernate;
|
creators["hibernate"] = &DruidAiObjectContextInternal::hibernate;
|
||||||
creators["entangling roots"] = &DruidAiObjectContextInternal::entangling_roots;
|
creators["entangling roots"] = &DruidAiObjectContextInternal::entangling_roots;
|
||||||
creators["entangling roots on cc"] = &DruidAiObjectContextInternal::entangling_roots_on_cc;
|
creators["entangling roots on cc"] = &DruidAiObjectContextInternal::entangling_roots_on_cc;
|
||||||
creators["hibernate"] = &DruidAiObjectContextInternal::hibernate;
|
|
||||||
creators["hibernate on cc"] = &DruidAiObjectContextInternal::hibernate_on_cc;
|
creators["hibernate on cc"] = &DruidAiObjectContextInternal::hibernate_on_cc;
|
||||||
|
creators["cyclone on cc"] = &DruidAiObjectContextInternal::cyclone_on_cc;
|
||||||
creators["wrath"] = &DruidAiObjectContextInternal::wrath;
|
creators["wrath"] = &DruidAiObjectContextInternal::wrath;
|
||||||
creators["starfall"] = &DruidAiObjectContextInternal::starfall;
|
creators["starfall"] = &DruidAiObjectContextInternal::starfall;
|
||||||
creators["insect swarm"] = &DruidAiObjectContextInternal::insect_swarm;
|
creators["insect swarm"] = &DruidAiObjectContextInternal::insect_swarm;
|
||||||
@ -205,9 +236,9 @@ public:
|
|||||||
creators["mangle (cat)"] = &DruidAiObjectContextInternal::mangle_cat;
|
creators["mangle (cat)"] = &DruidAiObjectContextInternal::mangle_cat;
|
||||||
creators["swipe (cat)"] = &DruidAiObjectContextInternal::swipe_cat;
|
creators["swipe (cat)"] = &DruidAiObjectContextInternal::swipe_cat;
|
||||||
creators["rake"] = &DruidAiObjectContextInternal::rake;
|
creators["rake"] = &DruidAiObjectContextInternal::rake;
|
||||||
creators["rake on attacker"] = &DruidAiObjectContextInternal::rake_on_attacker;
|
|
||||||
creators["ferocious bite"] = &DruidAiObjectContextInternal::ferocious_bite;
|
creators["ferocious bite"] = &DruidAiObjectContextInternal::ferocious_bite;
|
||||||
creators["rip"] = &DruidAiObjectContextInternal::rip;
|
creators["rip"] = &DruidAiObjectContextInternal::rip;
|
||||||
|
creators["maim"] = &DruidAiObjectContextInternal::maim;
|
||||||
creators["cower"] = &DruidAiObjectContextInternal::cower;
|
creators["cower"] = &DruidAiObjectContextInternal::cower;
|
||||||
creators["survival instincts"] = &DruidAiObjectContextInternal::survival_instincts;
|
creators["survival instincts"] = &DruidAiObjectContextInternal::survival_instincts;
|
||||||
creators["frenzied regeneration"] = &DruidAiObjectContextInternal::frenzied_regeneration;
|
creators["frenzied regeneration"] = &DruidAiObjectContextInternal::frenzied_regeneration;
|
||||||
@ -237,9 +268,9 @@ public:
|
|||||||
creators["lacerate"] = &DruidAiObjectContextInternal::lacerate;
|
creators["lacerate"] = &DruidAiObjectContextInternal::lacerate;
|
||||||
creators["hurricane"] = &DruidAiObjectContextInternal::hurricane;
|
creators["hurricane"] = &DruidAiObjectContextInternal::hurricane;
|
||||||
creators["innervate"] = &DruidAiObjectContextInternal::innervate;
|
creators["innervate"] = &DruidAiObjectContextInternal::innervate;
|
||||||
|
creators["innervate on healer"] = &DruidAiObjectContextInternal::innervate_on_healer;
|
||||||
creators["tranquility"] = &DruidAiObjectContextInternal::tranquility;
|
creators["tranquility"] = &DruidAiObjectContextInternal::tranquility;
|
||||||
creators["bash on enemy healer"] = &DruidAiObjectContextInternal::bash_on_enemy_healer;
|
creators["bash on enemy healer"] = &DruidAiObjectContextInternal::bash_on_enemy_healer;
|
||||||
creators["omen of clarity"] = &DruidAiObjectContextInternal::omen_of_clarity;
|
|
||||||
creators["nature's swiftness"] = &DruidAiObjectContextInternal::natures_swiftness;
|
creators["nature's swiftness"] = &DruidAiObjectContextInternal::natures_swiftness;
|
||||||
creators["prowl"] = &DruidAiObjectContextInternal::prowl;
|
creators["prowl"] = &DruidAiObjectContextInternal::prowl;
|
||||||
creators["dash"] = &DruidAiObjectContextInternal::dash;
|
creators["dash"] = &DruidAiObjectContextInternal::dash;
|
||||||
@ -254,11 +285,13 @@ public:
|
|||||||
creators["moonfire on attacker"] = &DruidAiObjectContextInternal::moonfire_on_attacker;
|
creators["moonfire on attacker"] = &DruidAiObjectContextInternal::moonfire_on_attacker;
|
||||||
creators["enrage"] = &DruidAiObjectContextInternal::enrage;
|
creators["enrage"] = &DruidAiObjectContextInternal::enrage;
|
||||||
creators["force of nature"] = &DruidAiObjectContextInternal::force_of_nature;
|
creators["force of nature"] = &DruidAiObjectContextInternal::force_of_nature;
|
||||||
|
creators["typhoon"] = &DruidAiObjectContextInternal::typhoon;
|
||||||
|
creators["rejuvenation blanket"] = &DruidAiObjectContextInternal::rejuvenation_blanket;
|
||||||
|
creators["wild growth blanket"] = &DruidAiObjectContextInternal::wild_growth_blanket;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static Action* natures_swiftness(PlayerbotAI* botAI) { return new CastNaturesSwiftnessAction(botAI); }
|
static Action* natures_swiftness(PlayerbotAI* botAI) { return new CastNaturesSwiftnessAction(botAI); }
|
||||||
static Action* omen_of_clarity(PlayerbotAI* botAI) { return new CastOmenOfClarityAction(botAI); }
|
|
||||||
static Action* tranquility(PlayerbotAI* botAI) { return new CastTranquilityAction(botAI); }
|
static Action* tranquility(PlayerbotAI* botAI) { return new CastTranquilityAction(botAI); }
|
||||||
static Action* feral_charge_bear(PlayerbotAI* botAI) { return new CastFeralChargeBearAction(botAI); }
|
static Action* feral_charge_bear(PlayerbotAI* botAI) { return new CastFeralChargeBearAction(botAI); }
|
||||||
static Action* feral_charge_cat(PlayerbotAI* botAI) { return new CastFeralChargeCatAction(botAI); }
|
static Action* feral_charge_cat(PlayerbotAI* botAI) { return new CastFeralChargeCatAction(botAI); }
|
||||||
@ -291,6 +324,7 @@ private:
|
|||||||
static Action* entangling_roots(PlayerbotAI* botAI) { return new CastEntanglingRootsAction(botAI); }
|
static Action* entangling_roots(PlayerbotAI* botAI) { return new CastEntanglingRootsAction(botAI); }
|
||||||
static Action* hibernate_on_cc(PlayerbotAI* botAI) { return new CastHibernateCcAction(botAI); }
|
static Action* hibernate_on_cc(PlayerbotAI* botAI) { return new CastHibernateCcAction(botAI); }
|
||||||
static Action* entangling_roots_on_cc(PlayerbotAI* botAI) { return new CastEntanglingRootsCcAction(botAI); }
|
static Action* entangling_roots_on_cc(PlayerbotAI* botAI) { return new CastEntanglingRootsCcAction(botAI); }
|
||||||
|
static Action* cyclone_on_cc(PlayerbotAI* botAI) { return new CastCycloneCcAction(botAI); }
|
||||||
static Action* wrath(PlayerbotAI* botAI) { return new CastWrathAction(botAI); }
|
static Action* wrath(PlayerbotAI* botAI) { return new CastWrathAction(botAI); }
|
||||||
static Action* starfall(PlayerbotAI* botAI) { return new CastStarfallAction(botAI); }
|
static Action* starfall(PlayerbotAI* botAI) { return new CastStarfallAction(botAI); }
|
||||||
static Action* insect_swarm(PlayerbotAI* botAI) { return new CastInsectSwarmAction(botAI); }
|
static Action* insect_swarm(PlayerbotAI* botAI) { return new CastInsectSwarmAction(botAI); }
|
||||||
@ -301,9 +335,9 @@ private:
|
|||||||
static Action* mangle_cat(PlayerbotAI* botAI) { return new CastMangleCatAction(botAI); }
|
static Action* mangle_cat(PlayerbotAI* botAI) { return new CastMangleCatAction(botAI); }
|
||||||
static Action* swipe_cat(PlayerbotAI* botAI) { return new CastSwipeCatAction(botAI); }
|
static Action* swipe_cat(PlayerbotAI* botAI) { return new CastSwipeCatAction(botAI); }
|
||||||
static Action* rake(PlayerbotAI* botAI) { return new CastRakeAction(botAI); }
|
static Action* rake(PlayerbotAI* botAI) { return new CastRakeAction(botAI); }
|
||||||
static Action* rake_on_attacker(PlayerbotAI* botAI) { return new CastRakeOnMeleeAttackersAction(botAI); }
|
|
||||||
static Action* ferocious_bite(PlayerbotAI* botAI) { return new CastFerociousBiteAction(botAI); }
|
static Action* ferocious_bite(PlayerbotAI* botAI) { return new CastFerociousBiteAction(botAI); }
|
||||||
static Action* rip(PlayerbotAI* botAI) { return new CastRipAction(botAI); }
|
static Action* rip(PlayerbotAI* botAI) { return new CastRipAction(botAI); }
|
||||||
|
static Action* maim(PlayerbotAI* botAI) { return new CastMaimAction(botAI); }
|
||||||
static Action* cower(PlayerbotAI* botAI) { return new CastCowerAction(botAI); }
|
static Action* cower(PlayerbotAI* botAI) { return new CastCowerAction(botAI); }
|
||||||
static Action* survival_instincts(PlayerbotAI* botAI) { return new CastSurvivalInstinctsAction(botAI); }
|
static Action* survival_instincts(PlayerbotAI* botAI) { return new CastSurvivalInstinctsAction(botAI); }
|
||||||
static Action* frenzied_regeneration(PlayerbotAI* botAI) { return new CastFrenziedRegenerationAction(botAI); }
|
static Action* frenzied_regeneration(PlayerbotAI* botAI) { return new CastFrenziedRegenerationAction(botAI); }
|
||||||
@ -333,6 +367,7 @@ private:
|
|||||||
static Action* lacerate(PlayerbotAI* botAI) { return new CastLacerateAction(botAI); }
|
static Action* lacerate(PlayerbotAI* botAI) { return new CastLacerateAction(botAI); }
|
||||||
static Action* hurricane(PlayerbotAI* botAI) { return new CastHurricaneAction(botAI); }
|
static Action* hurricane(PlayerbotAI* botAI) { return new CastHurricaneAction(botAI); }
|
||||||
static Action* innervate(PlayerbotAI* botAI) { return new CastInnervateAction(botAI); }
|
static Action* innervate(PlayerbotAI* botAI) { return new CastInnervateAction(botAI); }
|
||||||
|
static Action* innervate_on_healer(PlayerbotAI* botAI) { return new CastInnervateOnHealerAction(botAI); }
|
||||||
static Action* bash_on_enemy_healer(PlayerbotAI* botAI) { return new CastBashOnEnemyHealerAction(botAI); }
|
static Action* bash_on_enemy_healer(PlayerbotAI* botAI) { return new CastBashOnEnemyHealerAction(botAI); }
|
||||||
static Action* ravage(PlayerbotAI* botAI) { return new CastRavageAction(botAI); }
|
static Action* ravage(PlayerbotAI* botAI) { return new CastRavageAction(botAI); }
|
||||||
static Action* pounce(PlayerbotAI* botAI) { return new CastPounceAction(botAI); }
|
static Action* pounce(PlayerbotAI* botAI) { return new CastPounceAction(botAI); }
|
||||||
@ -347,6 +382,9 @@ private:
|
|||||||
static Action* moonfire_on_attacker(PlayerbotAI* ai) { return new CastMoonfireOnAttackerAction(ai); }
|
static Action* moonfire_on_attacker(PlayerbotAI* ai) { return new CastMoonfireOnAttackerAction(ai); }
|
||||||
static Action* enrage(PlayerbotAI* ai) { return new CastEnrageAction(ai); }
|
static Action* enrage(PlayerbotAI* ai) { return new CastEnrageAction(ai); }
|
||||||
static Action* force_of_nature(PlayerbotAI* ai) { return new CastForceOfNatureAction(ai); }
|
static Action* force_of_nature(PlayerbotAI* ai) { return new CastForceOfNatureAction(ai); }
|
||||||
|
static Action* typhoon(PlayerbotAI* ai) { return new CastTyphoonAction(ai); }
|
||||||
|
static Action* rejuvenation_blanket(PlayerbotAI* ai) { return new CastRejuvenationBlanketAction(ai); }
|
||||||
|
static Action* wild_growth_blanket(PlayerbotAI* ai) { return new CastWildGrowthBlanketAction(ai); }
|
||||||
};
|
};
|
||||||
|
|
||||||
SharedNamedObjectContextList<Strategy> DruidAiObjectContext::sharedStrategyContexts;
|
SharedNamedObjectContextList<Strategy> DruidAiObjectContext::sharedStrategyContexts;
|
||||||
@ -386,7 +424,22 @@ void DruidAiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContextLi
|
|||||||
triggerContexts.Add(new DruidTriggerFactoryInternal());
|
triggerContexts.Add(new DruidTriggerFactoryInternal());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DruidValueContextInternal : public NamedObjectContext<UntypedValue>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DruidValueContextInternal()
|
||||||
|
{
|
||||||
|
creators["eclipse solar proc time"] = &DruidValueContextInternal::eclipse_solar_proc_time;
|
||||||
|
creators["eclipse lunar proc time"] = &DruidValueContextInternal::eclipse_lunar_proc_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static UntypedValue* eclipse_solar_proc_time(PlayerbotAI* botAI) { return new EclipseSolarProcTimeValue(botAI); }
|
||||||
|
static UntypedValue* eclipse_lunar_proc_time(PlayerbotAI* botAI) { return new EclipseLunarProcTimeValue(botAI); }
|
||||||
|
};
|
||||||
|
|
||||||
void DruidAiObjectContext::BuildSharedValueContexts(SharedNamedObjectContextList<UntypedValue>& valueContexts)
|
void DruidAiObjectContext::BuildSharedValueContexts(SharedNamedObjectContextList<UntypedValue>& valueContexts)
|
||||||
{
|
{
|
||||||
AiObjectContext::BuildSharedValueContexts(valueContexts);
|
AiObjectContext::BuildSharedValueContexts(valueContexts);
|
||||||
|
valueContexts.Add(new DruidValueContextInternal());
|
||||||
}
|
}
|
||||||
|
|||||||
118
src/Ai/Class/Druid/DruidTriggers.cpp
Normal file
118
src/Ai/Class/Druid/DruidTriggers.cpp
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||||
|
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "DruidTriggers.h"
|
||||||
|
#include "DynamicObject.h"
|
||||||
|
#include "Player.h"
|
||||||
|
#include "Playerbots.h"
|
||||||
|
#include "ServerFacade.h"
|
||||||
|
|
||||||
|
bool MarkOfTheWildTrigger::IsActive()
|
||||||
|
{
|
||||||
|
return BuffTrigger::IsActive() && !botAI->HasAura("gift of the wild", GetTarget());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ThornsOnPartyTrigger::IsActive()
|
||||||
|
{
|
||||||
|
return BuffOnPartyTrigger::IsActive() && !botAI->HasAura("thorns", GetTarget());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EntanglingRootsKiteTrigger::IsActive()
|
||||||
|
{
|
||||||
|
return DebuffTrigger::IsActive() && AI_VALUE(uint8, "attacker count") < 3 && !GetTarget()->GetPower(POWER_MANA);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ThornsTrigger::IsActive() { return BuffTrigger::IsActive() && !botAI->HasAura("thorns", GetTarget()); }
|
||||||
|
|
||||||
|
bool BearFormTrigger::IsActive() { return !botAI->HasAnyAuraOf(bot, "bear form", "dire bear form", nullptr); }
|
||||||
|
|
||||||
|
bool TreeFormTrigger::IsActive() { return !botAI->HasAura(33891, bot); }
|
||||||
|
|
||||||
|
bool CatFormTrigger::IsActive() { return !botAI->HasAura("cat form", bot); }
|
||||||
|
|
||||||
|
bool AquaticFormTrigger::IsActive()
|
||||||
|
{
|
||||||
|
return !bot->IsInCombat() && !botAI->HasAura("aquatic form", bot) &&
|
||||||
|
bot->GetLiquidData().Status == LIQUID_MAP_UNDER_WATER;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProwlTrigger::IsActive()
|
||||||
|
{
|
||||||
|
if (botAI->HasAura("prowl", bot) || bot->IsInCombat())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
uint32 prowlId = botAI->GetAiObjectContext()->GetValue<uint32>("spell id", "prowl")->Get();
|
||||||
|
if (!prowlId || !bot->HasSpell(prowlId) || bot->HasSpellCooldown(prowlId))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
float distance = 30.f;
|
||||||
|
|
||||||
|
Unit* target = AI_VALUE(Unit*, "enemy player target");
|
||||||
|
if (target && !target->IsInWorld())
|
||||||
|
return false;
|
||||||
|
if (!target)
|
||||||
|
target = AI_VALUE(Unit*, "grind target");
|
||||||
|
if (!target)
|
||||||
|
target = AI_VALUE(Unit*, "dps target");
|
||||||
|
if (!target)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (target && target->GetVictim())
|
||||||
|
distance -= 10;
|
||||||
|
if (target->isMoving() && target->GetVictim())
|
||||||
|
distance -= 10;
|
||||||
|
if (bot->InBattleground())
|
||||||
|
distance += 15;
|
||||||
|
if (bot->InArena())
|
||||||
|
distance += 15;
|
||||||
|
|
||||||
|
return target && ServerFacade::instance().GetDistance2d(bot, target) < distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::set<uint32> HurricaneChannelCheckTrigger::HURRICANE_SPELL_IDS = {
|
||||||
|
16914, // Hurricane Rank 1
|
||||||
|
17401, // Hurricane Rank 2
|
||||||
|
17402, // Hurricane Rank 3
|
||||||
|
27012, // Hurricane Rank 4
|
||||||
|
48467 // Hurricane Rank 5
|
||||||
|
};
|
||||||
|
|
||||||
|
bool HurricaneChannelCheckTrigger::IsActive()
|
||||||
|
{
|
||||||
|
if (Spell* spell = bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
|
||||||
|
{
|
||||||
|
if (!HURRICANE_SPELL_IDS.count(spell->m_spellInfo->Id))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Find this bot's own Hurricane DynamicObject
|
||||||
|
DynamicObject* dynObj = nullptr;
|
||||||
|
for (uint32 spellId : HURRICANE_SPELL_IDS)
|
||||||
|
{
|
||||||
|
dynObj = bot->GetDynObject(spellId);
|
||||||
|
if (dynObj)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dynObj)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Count attackers actually inside the Hurricane AoE
|
||||||
|
float radius = dynObj->GetRadius();
|
||||||
|
GuidVector attackers = AI_VALUE(GuidVector, "attackers");
|
||||||
|
uint32 count = 0;
|
||||||
|
for (ObjectGuid const& guid : attackers)
|
||||||
|
{
|
||||||
|
Unit* unit = botAI->GetUnit(guid);
|
||||||
|
if (!unit || !unit->IsAlive())
|
||||||
|
continue;
|
||||||
|
if (unit->GetDistance(dynObj->GetPosition()) <= radius)
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count < minEnemies;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include "CureTriggers.h"
|
#include "CureTriggers.h"
|
||||||
#include "GenericTriggers.h"
|
#include "GenericTriggers.h"
|
||||||
|
#include "RtiTriggers.h"
|
||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
#include "PlayerbotAI.h"
|
#include "PlayerbotAI.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
@ -15,20 +16,20 @@
|
|||||||
#include "Trigger.h"
|
#include "Trigger.h"
|
||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
|
constexpr uint32 AURA_OMEN_OF_CLARITY = 16864;
|
||||||
|
|
||||||
class PlayerbotAI;
|
class PlayerbotAI;
|
||||||
|
|
||||||
class MarkOfTheWildOnPartyTrigger : public BuffOnPartyTrigger
|
class MarkOfTheWildOnPartyTrigger : public BuffOnPartyTrigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MarkOfTheWildOnPartyTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "mark of the wild", 2 * 2000) {}
|
MarkOfTheWildOnPartyTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "mark of the wild", 4 * 2000) {}
|
||||||
|
|
||||||
bool IsActive() override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class MarkOfTheWildTrigger : public BuffTrigger
|
class MarkOfTheWildTrigger : public BuffTrigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MarkOfTheWildTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "mark of the wild", 2 * 2000) {}
|
MarkOfTheWildTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "mark of the wild", 4 * 2000) {}
|
||||||
|
|
||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
@ -55,22 +56,30 @@ public:
|
|||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class OmenOfClarityTrigger : public BuffTrigger
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
OmenOfClarityTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "omen of clarity") {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class ClearcastingTrigger : public HasAuraTrigger
|
class ClearcastingTrigger : public HasAuraTrigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ClearcastingTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "clearcasting") {}
|
ClearcastingTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "clearcasting") {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class PredatorsSwiftnessTrigger : public HasAuraTrigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PredatorsSwiftnessTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "predator's swiftness") {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class NaturesSwiftnessActiveTrigger : public HasAuraTrigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
NaturesSwiftnessActiveTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "nature's swiftness") {}
|
||||||
|
bool IsActive() override { return botAI->HasAura("nature's swiftness", bot); }
|
||||||
|
};
|
||||||
|
|
||||||
class RakeTrigger : public DebuffTrigger
|
class RakeTrigger : public DebuffTrigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
RakeTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "rake", 1, true) {}
|
RakeTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "rake", 1, true) {}
|
||||||
|
bool IsActive() override { return !botAI->HasAura("prowl", bot) && DebuffTrigger::IsActive(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
class InsectSwarmTrigger : public DebuffTrigger
|
class InsectSwarmTrigger : public DebuffTrigger
|
||||||
@ -79,12 +88,26 @@ public:
|
|||||||
InsectSwarmTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "insect swarm", 1, true) {}
|
InsectSwarmTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "insect swarm", 1, true) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class InsectSwarmOnAttackerTrigger : public DebuffOnAttackerTrigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
InsectSwarmOnAttackerTrigger(PlayerbotAI* botAI) : DebuffOnAttackerTrigger(botAI, "insect swarm", true) {}
|
||||||
|
bool IsActive() override { return BuffTrigger::IsActive(); }
|
||||||
|
};
|
||||||
|
|
||||||
class MoonfireTrigger : public DebuffTrigger
|
class MoonfireTrigger : public DebuffTrigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MoonfireTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "moonfire", 1, true) {}
|
MoonfireTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "moonfire", 1, true) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class MoonfireOnAttackerTrigger : public DebuffOnAttackerTrigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MoonfireOnAttackerTrigger(PlayerbotAI* botAI) : DebuffOnAttackerTrigger(botAI, "moonfire", true) {}
|
||||||
|
bool IsActive() override { return BuffTrigger::IsActive(); }
|
||||||
|
};
|
||||||
|
|
||||||
class FaerieFireTrigger : public DebuffTrigger
|
class FaerieFireTrigger : public DebuffTrigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -95,6 +118,35 @@ class FaerieFireFeralTrigger : public DebuffTrigger
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
FaerieFireFeralTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "faerie fire (feral)") {}
|
FaerieFireFeralTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "faerie fire (feral)") {}
|
||||||
|
|
||||||
|
bool IsActive() override
|
||||||
|
{
|
||||||
|
if (!bot->IsInCombat())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Bear: every cast generates immediate threat/damage for free — spam it
|
||||||
|
if (botAI->HasAnyAuraOf(bot, "bear form", "dire bear form", nullptr))
|
||||||
|
{
|
||||||
|
Unit* target = GetTarget();
|
||||||
|
return target && target->IsAlive() && target->IsInWorld();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!botAI->HasAura("cat form", bot))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (botAI->HasAura("prowl", bot))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Cat with Omen of Clarity: spam to fish for Clearcasting procs
|
||||||
|
if (bot->HasAura(AURA_OMEN_OF_CLARITY))
|
||||||
|
{
|
||||||
|
Unit* target = GetTarget();
|
||||||
|
return target && target->IsAlive() && target->IsInWorld();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cat without Omen of Clarity: apply as a normal debuff, don't reapply
|
||||||
|
return DebuffTrigger::IsActive();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class BashInterruptSpellTrigger : public InterruptSpellTrigger
|
class BashInterruptSpellTrigger : public InterruptSpellTrigger
|
||||||
@ -103,18 +155,18 @@ public:
|
|||||||
BashInterruptSpellTrigger(PlayerbotAI* botAI) : InterruptSpellTrigger(botAI, "bash") {}
|
BashInterruptSpellTrigger(PlayerbotAI* botAI) : InterruptSpellTrigger(botAI, "bash") {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class TigersFuryTrigger : public BuffTrigger
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
TigersFuryTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "tiger's fury") {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class BerserkTrigger : public BoostTrigger
|
class BerserkTrigger : public BoostTrigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
BerserkTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "berserk") {}
|
BerserkTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "berserk") {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class BerserkActiveTrigger : public HasAuraTrigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BerserkActiveTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "berserk") {}
|
||||||
|
};
|
||||||
|
|
||||||
class SavageRoarTrigger : public BuffTrigger
|
class SavageRoarTrigger : public BuffTrigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -127,10 +179,10 @@ public:
|
|||||||
NaturesGraspTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "nature's grasp") {}
|
NaturesGraspTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "nature's grasp") {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class EntanglingRootsTrigger : public HasCcTargetTrigger
|
class EntanglingRootsTrigger : public RtiCcTrigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
EntanglingRootsTrigger(PlayerbotAI* botAI) : HasCcTargetTrigger(botAI, "entangling roots") {}
|
EntanglingRootsTrigger(PlayerbotAI* botAI) : RtiCcTrigger(botAI, "entangling roots") {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class EntanglingRootsKiteTrigger : public DebuffTrigger
|
class EntanglingRootsKiteTrigger : public DebuffTrigger
|
||||||
@ -141,10 +193,16 @@ public:
|
|||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class HibernateTrigger : public HasCcTargetTrigger
|
class HibernateTrigger : public RtiCcTrigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
HibernateTrigger(PlayerbotAI* botAI) : HasCcTargetTrigger(botAI, "hibernate") {}
|
HibernateTrigger(PlayerbotAI* botAI) : RtiCcTrigger(botAI, "hibernate") {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class CycloneTrigger : public RtiCcTrigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CycloneTrigger(PlayerbotAI* botAI) : RtiCcTrigger(botAI, "cyclone") {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class CurePoisonTrigger : public NeedCureTrigger
|
class CurePoisonTrigger : public NeedCureTrigger
|
||||||
@ -185,6 +243,14 @@ public:
|
|||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class AquaticFormTrigger : public Trigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AquaticFormTrigger(PlayerbotAI* botAI) : Trigger(botAI, "aquatic form") {}
|
||||||
|
|
||||||
|
bool IsActive() override;
|
||||||
|
};
|
||||||
|
|
||||||
class EclipseSolarTrigger : public HasAuraTrigger
|
class EclipseSolarTrigger : public HasAuraTrigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -218,18 +284,69 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class EclipseSolarCooldownTrigger : public SpellCooldownTrigger
|
class StarfallTrigger : public SpellNoCooldownTrigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
EclipseSolarCooldownTrigger(PlayerbotAI* ai) : SpellCooldownTrigger(ai, "eclipse (solar)") {}
|
StarfallTrigger(PlayerbotAI* botAI) : SpellNoCooldownTrigger(botAI, "starfall") {}
|
||||||
bool IsActive() override { return bot->HasSpellCooldown(48517); }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class EclipseLunarCooldownTrigger : public SpellCooldownTrigger
|
class ForceOfNatureTrigger : public BoostTrigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
EclipseLunarCooldownTrigger(PlayerbotAI* ai) : SpellCooldownTrigger(ai, "eclipse (lunar)") {}
|
ForceOfNatureTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "force of nature") {}
|
||||||
bool IsActive() override { return bot->HasSpellCooldown(48518); }
|
};
|
||||||
|
|
||||||
|
class MangleBearTrigger : public DebuffTrigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MangleBearTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "mangle (bear)") {}
|
||||||
|
|
||||||
|
bool IsActive() override
|
||||||
|
{
|
||||||
|
if (!bot->IsInCombat() || !botAI->HasAnyAuraOf(bot, "bear form", "dire bear form", nullptr))
|
||||||
|
return false;
|
||||||
|
Unit* target = GetTarget();
|
||||||
|
return target && target->IsAlive() && target->IsInWorld();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class LacerateTrigger : public DebuffTrigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LacerateTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "lacerate") {}
|
||||||
|
|
||||||
|
bool IsActive() override
|
||||||
|
{
|
||||||
|
if (!bot->IsInCombat() || !botAI->HasAnyAuraOf(bot, "bear form", "dire bear form", nullptr))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Unit* target = GetTarget();
|
||||||
|
if (!target || !target->IsAlive() || !target->IsInWorld())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Aura* lacerate = botAI->GetAura("lacerate", target, false, false);
|
||||||
|
if (!lacerate)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (lacerate->GetStackAmount() < 5)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return lacerate->GetDuration() <= 6000;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class DemoralizeRoarTrigger : public DebuffTrigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DemoralizeRoarTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "demoralizing roar") {}
|
||||||
|
|
||||||
|
bool IsActive() override
|
||||||
|
{
|
||||||
|
return DebuffTrigger::IsActive()
|
||||||
|
&& !botAI->HasAura("curse of weakness", GetTarget(), false, false)
|
||||||
|
&& !botAI->HasAura("demoralizing shout", GetTarget(), false, false)
|
||||||
|
&& !botAI->HasAura("vindication", GetTarget(), false, false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class MangleCatTrigger : public DebuffTrigger
|
class MangleCatTrigger : public DebuffTrigger
|
||||||
@ -238,6 +355,8 @@ public:
|
|||||||
MangleCatTrigger(PlayerbotAI* ai) : DebuffTrigger(ai, "mangle (cat)", 1, false, 0.0f) {}
|
MangleCatTrigger(PlayerbotAI* ai) : DebuffTrigger(ai, "mangle (cat)", 1, false, 0.0f) {}
|
||||||
bool IsActive() override
|
bool IsActive() override
|
||||||
{
|
{
|
||||||
|
if (botAI->HasAura("prowl", bot))
|
||||||
|
return false;
|
||||||
return DebuffTrigger::IsActive() && !botAI->HasAura("mangle (bear)", GetTarget(), false, false, -1, true)
|
return DebuffTrigger::IsActive() && !botAI->HasAura("mangle (bear)", GetTarget(), false, false, -1, true)
|
||||||
&& !botAI->HasAura("trauma", GetTarget(), false, false, -1, true);
|
&& !botAI->HasAura("trauma", GetTarget(), false, false, -1, true);
|
||||||
}
|
}
|
||||||
@ -271,10 +390,36 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class FerociousBiteExecuteTrigger : public Trigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FerociousBiteExecuteTrigger(PlayerbotAI* ai) : Trigger(ai, "ferocious bite execute") {}
|
||||||
|
bool IsActive() override
|
||||||
|
{
|
||||||
|
Unit* target = AI_VALUE(Unit*, "current target");
|
||||||
|
if (!target || !target->IsAlive())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!AI_VALUE2(uint32, "spell id", "ferocious bite"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (AI_VALUE2(uint8, "combo", "current target") < 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (target->GetHealthPct() >= 25.0f)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (target->GetHealth() >= 20000)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class HurricaneChannelCheckTrigger : public Trigger
|
class HurricaneChannelCheckTrigger : public Trigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
HurricaneChannelCheckTrigger(PlayerbotAI* botAI, uint32 minEnemies = 2)
|
HurricaneChannelCheckTrigger(PlayerbotAI* botAI, uint32 minEnemies = 3)
|
||||||
: Trigger(botAI, "hurricane channel check"), minEnemies(minEnemies)
|
: Trigger(botAI, "hurricane channel check"), minEnemies(minEnemies)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -297,4 +442,12 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ProwlTrigger : public Trigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ProwlTrigger(PlayerbotAI* botAI) : Trigger(botAI, "prowl") {}
|
||||||
|
|
||||||
|
bool IsActive() override;
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@ -3,15 +3,15 @@
|
|||||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "CasterDruidStrategy.h"
|
#include "BalanceDruidStrategy.h"
|
||||||
|
|
||||||
#include "AiObjectContext.h"
|
#include "AiObjectContext.h"
|
||||||
#include "FeralDruidStrategy.h"
|
#include "FeralDruidStrategy.h"
|
||||||
|
|
||||||
class CasterDruidStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
|
class BalanceDruidStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CasterDruidStrategyActionNodeFactory()
|
BalanceDruidStrategyActionNodeFactory()
|
||||||
{
|
{
|
||||||
creators["faerie fire"] = &faerie_fire;
|
creators["faerie fire"] = &faerie_fire;
|
||||||
creators["hibernate"] = &hibernate;
|
creators["hibernate"] = &hibernate;
|
||||||
@ -23,6 +23,10 @@ public:
|
|||||||
creators["moonfire"] = &moonfire;
|
creators["moonfire"] = &moonfire;
|
||||||
creators["starfire"] = &starfire;
|
creators["starfire"] = &starfire;
|
||||||
creators["moonkin form"] = &moonkin_form;
|
creators["moonkin form"] = &moonkin_form;
|
||||||
|
creators["typhoon"] = &typhoon;
|
||||||
|
creators["hurricane"] = &hurricane;
|
||||||
|
creators["force of nature"] = &force_of_nature;
|
||||||
|
creators["cyclone on cc"] = &cyclone_on_cc;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -125,130 +129,76 @@ private:
|
|||||||
/*C*/ {}
|
/*C*/ {}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ActionNode* typhoon([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
return new ActionNode(
|
||||||
|
"typhoon",
|
||||||
|
/*P*/ { NextAction("moonkin form") },
|
||||||
|
/*A*/ {},
|
||||||
|
/*C*/ {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ActionNode* hurricane([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
return new ActionNode(
|
||||||
|
"hurricane",
|
||||||
|
/*P*/ { NextAction("moonkin form") },
|
||||||
|
/*A*/ {},
|
||||||
|
/*C*/ {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ActionNode* force_of_nature([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
return new ActionNode(
|
||||||
|
"force of nature",
|
||||||
|
/*P*/ { NextAction("moonkin form") },
|
||||||
|
/*A*/ {},
|
||||||
|
/*C*/ {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ActionNode* cyclone_on_cc([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
return new ActionNode(
|
||||||
|
"cyclone on cc",
|
||||||
|
/*P*/ { NextAction("moonkin form") },
|
||||||
|
/*A*/ {},
|
||||||
|
/*C*/ {}
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
CasterDruidStrategy::CasterDruidStrategy(PlayerbotAI* botAI) : GenericDruidStrategy(botAI)
|
BalanceDruidStrategy::BalanceDruidStrategy(PlayerbotAI* botAI) : GenericDruidStrategy(botAI)
|
||||||
{
|
{
|
||||||
actionNodeFactories.Add(new CasterDruidStrategyActionNodeFactory());
|
actionNodeFactories.Add(new BalanceDruidStrategyActionNodeFactory());
|
||||||
actionNodeFactories.Add(new ShapeshiftDruidStrategyActionNodeFactory());
|
actionNodeFactories.Add(new ShapeshiftDruidStrategyActionNodeFactory());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<NextAction> CasterDruidStrategy::getDefaultActions()
|
std::vector<NextAction> BalanceDruidStrategy::getDefaultActions()
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
NextAction("starfall", ACTION_HIGH + 1.0f),
|
NextAction("starfire", 5.4f),
|
||||||
NextAction("force of nature", ACTION_DEFAULT + 1.0f),
|
NextAction("wrath", 5.3f),
|
||||||
NextAction("wrath", ACTION_DEFAULT + 0.1f),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void CasterDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
void BalanceDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||||
{
|
{
|
||||||
GenericDruidStrategy::InitTriggers(triggers);
|
GenericDruidStrategy::InitTriggers(triggers);
|
||||||
|
|
||||||
triggers.push_back(
|
// Debuffs and DoTs
|
||||||
new TriggerNode(
|
triggers.push_back(new TriggerNode("faerie fire", { NextAction("faerie fire", 29.5f) }));
|
||||||
"eclipse (lunar) cooldown",
|
triggers.push_back(new TriggerNode("insect swarm", { NextAction("insect swarm", 18.0f) }));
|
||||||
{
|
triggers.push_back(new TriggerNode("moonfire", { NextAction("moonfire", 17.5f) }));
|
||||||
NextAction("starfire", ACTION_DEFAULT + 0.2f)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"eclipse (solar) cooldown",
|
|
||||||
{
|
|
||||||
NextAction("wrath", ACTION_DEFAULT + 0.2f)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"insect swarm",
|
|
||||||
{
|
|
||||||
NextAction("insect swarm", ACTION_NORMAL + 5)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"moonfire",
|
|
||||||
{
|
|
||||||
NextAction("moonfire", ACTION_NORMAL + 4)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"eclipse (solar)",
|
|
||||||
{
|
|
||||||
NextAction("wrath", ACTION_NORMAL + 6)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"eclipse (lunar)",
|
|
||||||
{
|
|
||||||
NextAction("starfire", ACTION_NORMAL + 6)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"medium mana",
|
|
||||||
{
|
|
||||||
NextAction("innervate", ACTION_HIGH + 9)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"enemy too close for spell",
|
|
||||||
{
|
|
||||||
NextAction("flee", ACTION_MOVE + 9)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CasterDruidAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
// Eclipse procs
|
||||||
{
|
triggers.push_back(new TriggerNode("eclipse (solar)", { NextAction("wrath", 20.0f) }));
|
||||||
triggers.push_back(
|
triggers.push_back(new TriggerNode("eclipse (lunar)", { NextAction("starfire", 20.0f) }));
|
||||||
new TriggerNode(
|
|
||||||
"hurricane channel check",
|
|
||||||
{
|
|
||||||
NextAction("cancel channel", ACTION_HIGH + 2)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"medium aoe",
|
|
||||||
{
|
|
||||||
NextAction("hurricane", ACTION_HIGH + 1)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"light aoe",
|
|
||||||
{
|
|
||||||
NextAction("insect swarm on attacker", ACTION_NORMAL + 3),
|
|
||||||
NextAction("moonfire on attacker", ACTION_NORMAL + 3)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CasterDruidDebuffStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
// Utility/Defensive
|
||||||
{
|
triggers.push_back(new TriggerNode("medium mana", { NextAction("innervate", 29.0f) }));
|
||||||
triggers.push_back(
|
triggers.push_back(new TriggerNode("enemy too close for spell", { NextAction("flee", 39.0f) }));
|
||||||
new TriggerNode(
|
|
||||||
"faerie fire",
|
|
||||||
{
|
|
||||||
NextAction("faerie fire", ACTION_HIGH)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
25
src/Ai/Class/Druid/Strategy/BalanceDruidStrategy.h
Normal file
25
src/Ai/Class/Druid/Strategy/BalanceDruidStrategy.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||||
|
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PLAYERBOT_BALANCEDRUIDSTRATEGY_H
|
||||||
|
#define _PLAYERBOT_BALANCEDRUIDSTRATEGY_H
|
||||||
|
|
||||||
|
#include "GenericDruidStrategy.h"
|
||||||
|
|
||||||
|
class PlayerbotAI;
|
||||||
|
|
||||||
|
class BalanceDruidStrategy : public GenericDruidStrategy
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BalanceDruidStrategy(PlayerbotAI* botAI);
|
||||||
|
|
||||||
|
public:
|
||||||
|
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
||||||
|
std::string const getName() override { return "balance"; }
|
||||||
|
std::vector<NextAction> getDefaultActions() override;
|
||||||
|
uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_RANGED; }
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -3,19 +3,17 @@
|
|||||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "BearTankDruidStrategy.h"
|
#include "BearDruidStrategy.h"
|
||||||
|
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
class BearTankDruidStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
|
class BearDruidStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
BearTankDruidStrategyActionNodeFactory()
|
BearDruidStrategyActionNodeFactory()
|
||||||
{
|
{
|
||||||
creators["melee"] = &melee;
|
|
||||||
creators["feral charge - bear"] = &feral_charge_bear;
|
creators["feral charge - bear"] = &feral_charge_bear;
|
||||||
creators["swipe (bear)"] = &swipe_bear;
|
creators["swipe (bear)"] = &swipe_bear;
|
||||||
creators["faerie fire (feral)"] = &faerie_fire_feral;
|
|
||||||
creators["bear form"] = &bear_form;
|
creators["bear form"] = &bear_form;
|
||||||
creators["dire bear form"] = &dire_bear_form;
|
creators["dire bear form"] = &dire_bear_form;
|
||||||
creators["mangle (bear)"] = &mangle_bear;
|
creators["mangle (bear)"] = &mangle_bear;
|
||||||
@ -28,16 +26,6 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static ActionNode* melee([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"melee",
|
|
||||||
/*P*/ { NextAction("feral charge - bear") },
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* feral_charge_bear([[maybe_unused]] PlayerbotAI* botAI)
|
static ActionNode* feral_charge_bear([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
{
|
{
|
||||||
return new ActionNode(
|
return new ActionNode(
|
||||||
@ -58,16 +46,6 @@ private:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ActionNode* faerie_fire_feral([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"faerie fire (feral)",
|
|
||||||
/*P*/ { NextAction("feral charge - bear") },
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* bear_form([[maybe_unused]] PlayerbotAI* botAI)
|
static ActionNode* bear_form([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
{
|
{
|
||||||
return new ActionNode(
|
return new ActionNode(
|
||||||
@ -159,99 +137,81 @@ private:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
BearTankDruidStrategy::BearTankDruidStrategy(PlayerbotAI* botAI) : FeralDruidStrategy(botAI)
|
BearDruidStrategy::BearDruidStrategy(PlayerbotAI* botAI) : FeralDruidStrategy(botAI)
|
||||||
{
|
{
|
||||||
actionNodeFactories.Add(new BearTankDruidStrategyActionNodeFactory());
|
actionNodeFactories.Add(new BearDruidStrategyActionNodeFactory());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<NextAction> BearTankDruidStrategy::getDefaultActions()
|
std::vector<NextAction> BearDruidStrategy::getDefaultActions()
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
NextAction("mangle (bear)", ACTION_DEFAULT + 0.5f),
|
NextAction("maul", 5.2f),
|
||||||
NextAction("faerie fire (feral)", ACTION_DEFAULT + 0.4f),
|
NextAction("enrage", 5.1f),
|
||||||
NextAction("lacerate", ACTION_DEFAULT + 0.3f),
|
NextAction("melee", 5.0f)
|
||||||
NextAction("maul", ACTION_DEFAULT + 0.2f),
|
|
||||||
NextAction("enrage", ACTION_DEFAULT + 0.1f),
|
|
||||||
NextAction("melee", ACTION_DEFAULT)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void BearTankDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
void BearDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||||
{
|
{
|
||||||
FeralDruidStrategy::InitTriggers(triggers);
|
FeralDruidStrategy::InitTriggers(triggers);
|
||||||
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"enemy out of melee",
|
|
||||||
{
|
|
||||||
NextAction("feral charge - bear", ACTION_NORMAL + 8)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
new TriggerNode(
|
new TriggerNode(
|
||||||
"bear form",
|
"bear form",
|
||||||
{
|
{ NextAction("dire bear form", 28.0f) }
|
||||||
NextAction("dire bear form", ACTION_HIGH + 8)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
new TriggerNode(
|
new TriggerNode(
|
||||||
"low health",
|
"medium health",
|
||||||
{
|
{ NextAction("frenzied regeneration", 27.0f) }
|
||||||
NextAction("frenzied regeneration", ACTION_HIGH + 7)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
triggers.push_back(
|
triggers.push_back(new TriggerNode(
|
||||||
new TriggerNode(
|
"mangle (bear)", { NextAction("mangle (bear)", 17.5f) }
|
||||||
"faerie fire (feral)",
|
));
|
||||||
|
triggers.push_back(new TriggerNode(
|
||||||
|
"faerie fire (feral)", { NextAction("faerie fire (feral)", 17.0f) }
|
||||||
|
));
|
||||||
|
triggers.push_back(new TriggerNode(
|
||||||
|
"lacerate", { NextAction("lacerate", 16.0f) }
|
||||||
|
));
|
||||||
|
triggers.push_back(new TriggerNode(
|
||||||
|
"demoralizing roar", { NextAction("demoralizing roar", 15.5f) }
|
||||||
|
));
|
||||||
|
triggers.push_back(new TriggerNode("high aoe", { NextAction("challenging roar", 26.5f) }));
|
||||||
|
triggers.push_back(new TriggerNode("lose aggro",
|
||||||
{
|
{
|
||||||
NextAction("faerie fire (feral)", ACTION_HIGH + 7)
|
NextAction("growl", 26.0f),
|
||||||
|
NextAction("faerie fire (feral)", 25.5f)
|
||||||
}
|
}
|
||||||
)
|
));
|
||||||
);
|
triggers.push_back(new TriggerNode("berserk active", { NextAction("mangle (bear)", 25.0f) }));
|
||||||
triggers.push_back(new TriggerNode("high aoe", {NextAction("challenging roar", ACTION_HIGH + 8)}));
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"lose aggro",
|
|
||||||
{
|
|
||||||
NextAction("growl", ACTION_HIGH + 8)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
new TriggerNode(
|
new TriggerNode(
|
||||||
"medium aoe",
|
"medium aoe",
|
||||||
{
|
{
|
||||||
NextAction("demoralizing roar", ACTION_HIGH + 6),
|
NextAction("demoralizing roar", 24.5f),
|
||||||
NextAction("swipe (bear)", ACTION_HIGH + 6)
|
NextAction("swipe (bear)", 24.0f)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
new TriggerNode(
|
new TriggerNode(
|
||||||
"light aoe",
|
"light aoe",
|
||||||
{
|
{ NextAction("swipe (bear)", 24.0f) }
|
||||||
NextAction("swipe (bear)", ACTION_HIGH + 5)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
new TriggerNode(
|
new TriggerNode(
|
||||||
"bash",
|
"bash",
|
||||||
{
|
{ NextAction("bash", 42.0f) }
|
||||||
NextAction("bash", ACTION_INTERRUPT + 2)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
new TriggerNode(
|
new TriggerNode(
|
||||||
"bash on enemy healer",
|
"bash on enemy healer",
|
||||||
{
|
{ NextAction("bash on enemy healer", 41.0f) }
|
||||||
NextAction("bash on enemy healer", ACTION_INTERRUPT + 1)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -3,17 +3,17 @@
|
|||||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _PLAYERBOT_BEARTANKDRUIDSTRATEGY_H
|
#ifndef _PLAYERBOT_BEARDRUIDSTRATEGY_H
|
||||||
#define _PLAYERBOT_BEARTANKDRUIDSTRATEGY_H
|
#define _PLAYERBOT_BEARDRUIDSTRATEGY_H
|
||||||
|
|
||||||
#include "FeralDruidStrategy.h"
|
#include "FeralDruidStrategy.h"
|
||||||
|
|
||||||
class PlayerbotAI;
|
class PlayerbotAI;
|
||||||
|
|
||||||
class BearTankDruidStrategy : public FeralDruidStrategy
|
class BearDruidStrategy : public FeralDruidStrategy
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
BearTankDruidStrategy(PlayerbotAI* botAI);
|
BearDruidStrategy(PlayerbotAI* botAI);
|
||||||
|
|
||||||
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
||||||
std::string const getName() override { return "bear"; }
|
std::string const getName() override { return "bear"; }
|
||||||
@ -1,45 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
|
||||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _PLAYERBOT_CASTERDRUIDSTRATEGY_H
|
|
||||||
#define _PLAYERBOT_CASTERDRUIDSTRATEGY_H
|
|
||||||
|
|
||||||
#include "GenericDruidStrategy.h"
|
|
||||||
|
|
||||||
class PlayerbotAI;
|
|
||||||
|
|
||||||
class CasterDruidStrategy : public GenericDruidStrategy
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CasterDruidStrategy(PlayerbotAI* botAI);
|
|
||||||
|
|
||||||
public:
|
|
||||||
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
|
||||||
std::string const getName() override { return "caster"; }
|
|
||||||
std::vector<NextAction> getDefaultActions() override;
|
|
||||||
uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_RANGED; }
|
|
||||||
};
|
|
||||||
|
|
||||||
class CasterDruidAoeStrategy : public CombatStrategy
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CasterDruidAoeStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {}
|
|
||||||
|
|
||||||
public:
|
|
||||||
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
|
||||||
std::string const getName() override { return "caster aoe"; }
|
|
||||||
};
|
|
||||||
|
|
||||||
class CasterDruidDebuffStrategy : public CombatStrategy
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CasterDruidDebuffStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {}
|
|
||||||
|
|
||||||
public:
|
|
||||||
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
|
||||||
std::string const getName() override { return "caster debuff"; }
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,314 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
|
||||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "CatDpsDruidStrategy.h"
|
|
||||||
|
|
||||||
#include "AiObjectContext.h"
|
|
||||||
|
|
||||||
class CatDpsDruidStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CatDpsDruidStrategyActionNodeFactory()
|
|
||||||
{
|
|
||||||
creators["faerie fire (feral)"] = &faerie_fire_feral;
|
|
||||||
creators["melee"] = &melee;
|
|
||||||
creators["feral charge - cat"] = &feral_charge_cat;
|
|
||||||
creators["cat form"] = &cat_form;
|
|
||||||
creators["claw"] = &claw;
|
|
||||||
creators["mangle (cat)"] = &mangle_cat;
|
|
||||||
creators["rake"] = &rake;
|
|
||||||
creators["ferocious bite"] = &ferocious_bite;
|
|
||||||
creators["rip"] = &rip;
|
|
||||||
creators["pounce"] = &pounce;
|
|
||||||
creators["ravage"] = &ravage;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static ActionNode* faerie_fire_feral([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"faerie fire (feral)",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* melee([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"melee",
|
|
||||||
/*P*/ { NextAction("feral charge - cat") },
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* feral_charge_cat([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"feral charge - cat",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ { NextAction("reach melee") },
|
|
||||||
/*C*/ {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* cat_form([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"cat form",
|
|
||||||
/*P*/ { NextAction("caster form") },
|
|
||||||
/*A*/ { NextAction("bear form") },
|
|
||||||
/*C*/ {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* claw([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"claw",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ { NextAction("melee") },
|
|
||||||
/*C*/ {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* mangle_cat([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"mangle (cat)",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* rake([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"rake",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* ferocious_bite([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"ferocious bite",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ { NextAction("rip") },
|
|
||||||
/*C*/ {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* rip([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"rip",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* pounce([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"pounce",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ { NextAction("ravage") },
|
|
||||||
/*C*/ {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* ravage([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"ravage",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ { NextAction("shred") },
|
|
||||||
/*C*/ {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
CatDpsDruidStrategy::CatDpsDruidStrategy(PlayerbotAI* botAI) : FeralDruidStrategy(botAI)
|
|
||||||
{
|
|
||||||
actionNodeFactories.Add(new CatDpsDruidStrategyActionNodeFactory());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<NextAction> CatDpsDruidStrategy::getDefaultActions()
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
NextAction("tiger's fury", ACTION_DEFAULT + 0.1f)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void CatDpsDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|
||||||
{
|
|
||||||
FeralDruidStrategy::InitTriggers(triggers);
|
|
||||||
|
|
||||||
// Default priority
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"almost full energy available",
|
|
||||||
{
|
|
||||||
NextAction("shred", ACTION_DEFAULT + 0.4f)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"combo points not full",
|
|
||||||
{
|
|
||||||
NextAction("shred", ACTION_DEFAULT + 0.4f)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"almost full energy available",
|
|
||||||
{
|
|
||||||
NextAction("mangle (cat)", ACTION_DEFAULT + 0.3f)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"combo points not full and high energy",
|
|
||||||
{
|
|
||||||
NextAction("mangle (cat)", ACTION_DEFAULT + 0.3f)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"almost full energy available",
|
|
||||||
{
|
|
||||||
NextAction("claw", ACTION_DEFAULT + 0.2f)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"combo points not full and high energy",
|
|
||||||
{
|
|
||||||
NextAction("claw", ACTION_DEFAULT + 0.2f)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"faerie fire (feral)",
|
|
||||||
{
|
|
||||||
NextAction("faerie fire (feral)", ACTION_DEFAULT + 0.0f)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Main spell
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"cat form", {
|
|
||||||
NextAction("cat form", ACTION_HIGH + 8)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"savage roar", {
|
|
||||||
NextAction("savage roar", ACTION_HIGH + 7)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"combo points 5 available",
|
|
||||||
{
|
|
||||||
NextAction("rip", ACTION_HIGH + 6)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"ferocious bite time",
|
|
||||||
{
|
|
||||||
NextAction("ferocious bite", ACTION_HIGH + 5)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"target with combo points almost dead",
|
|
||||||
{
|
|
||||||
NextAction("ferocious bite", ACTION_HIGH + 4)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"mangle (cat)",
|
|
||||||
{
|
|
||||||
NextAction("mangle (cat)", ACTION_HIGH + 3)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"rake",
|
|
||||||
{
|
|
||||||
NextAction("rake", ACTION_HIGH + 2)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"medium threat",
|
|
||||||
{
|
|
||||||
NextAction("cower", ACTION_HIGH + 1)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// AOE
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"medium aoe",
|
|
||||||
{
|
|
||||||
NextAction("swipe (cat)", ACTION_HIGH + 3)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"light aoe",
|
|
||||||
{
|
|
||||||
NextAction("rake on attacker", ACTION_HIGH + 2)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
// Reach target
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"enemy out of melee",
|
|
||||||
{
|
|
||||||
NextAction("feral charge - cat", ACTION_HIGH + 9)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"enemy out of melee",
|
|
||||||
{
|
|
||||||
NextAction("dash", ACTION_HIGH + 8)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CatAoeDruidStrategy::InitTriggers(std::vector<TriggerNode*>& /*triggers*/) {}
|
|
||||||
446
src/Ai/Class/Druid/Strategy/CatDruidStrategy.cpp
Normal file
446
src/Ai/Class/Druid/Strategy/CatDruidStrategy.cpp
Normal file
@ -0,0 +1,446 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||||
|
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "CatDruidStrategy.h"
|
||||||
|
|
||||||
|
#include "AiObjectContext.h"
|
||||||
|
|
||||||
|
class CatDruidStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CatDruidStrategyActionNodeFactory()
|
||||||
|
{
|
||||||
|
creators["faerie fire (feral)"] = &faerie_fire_feral;
|
||||||
|
creators["melee"] = &melee;
|
||||||
|
creators["feral charge - cat"] = &feral_charge_cat;
|
||||||
|
creators["cat form"] = &cat_form;
|
||||||
|
creators["claw"] = &claw;
|
||||||
|
creators["mangle (cat)"] = &mangle_cat;
|
||||||
|
creators["rake"] = &rake;
|
||||||
|
creators["ferocious bite"] = &ferocious_bite;
|
||||||
|
creators["rip"] = &rip;
|
||||||
|
creators["pounce"] = &pounce;
|
||||||
|
creators["ravage"] = &ravage;
|
||||||
|
creators["prowl"] = &prowl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static ActionNode* faerie_fire_feral([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
return new ActionNode(
|
||||||
|
"faerie fire (feral)",
|
||||||
|
/*P*/ {},
|
||||||
|
/*A*/ {},
|
||||||
|
/*C*/ {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ActionNode* melee([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
return new ActionNode(
|
||||||
|
"melee",
|
||||||
|
/*P*/ {},
|
||||||
|
/*A*/ {},
|
||||||
|
/*C*/ {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ActionNode* feral_charge_cat([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
return new ActionNode(
|
||||||
|
"feral charge - cat",
|
||||||
|
/*P*/ {},
|
||||||
|
/*A*/ { NextAction("reach melee") },
|
||||||
|
/*C*/ {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ActionNode* cat_form([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
return new ActionNode(
|
||||||
|
"cat form",
|
||||||
|
/*P*/ { NextAction("caster form") },
|
||||||
|
/*A*/ { NextAction("bear form") },
|
||||||
|
/*C*/ {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ActionNode* claw([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
return new ActionNode(
|
||||||
|
"claw",
|
||||||
|
/*P*/ {},
|
||||||
|
/*A*/ { NextAction("melee") },
|
||||||
|
/*C*/ {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ActionNode* mangle_cat([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
return new ActionNode(
|
||||||
|
"mangle (cat)",
|
||||||
|
/*P*/ {},
|
||||||
|
/*A*/ {},
|
||||||
|
/*C*/ {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ActionNode* rake([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
return new ActionNode(
|
||||||
|
"rake",
|
||||||
|
/*P*/ {},
|
||||||
|
/*A*/ {},
|
||||||
|
/*C*/ {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ActionNode* ferocious_bite([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
return new ActionNode(
|
||||||
|
"ferocious bite",
|
||||||
|
/*P*/ {},
|
||||||
|
/*A*/ {},
|
||||||
|
/*C*/ {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ActionNode* rip([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
return new ActionNode(
|
||||||
|
"rip",
|
||||||
|
/*P*/ {},
|
||||||
|
/*A*/ {},
|
||||||
|
/*C*/ {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ActionNode* ravage([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
return new ActionNode(
|
||||||
|
"ravage",
|
||||||
|
/*P*/ {},
|
||||||
|
/*A*/ { NextAction("pounce") },
|
||||||
|
/*C*/ {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ActionNode* pounce([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
return new ActionNode(
|
||||||
|
"pounce",
|
||||||
|
/*P*/ {},
|
||||||
|
/*A*/ { NextAction("shred") },
|
||||||
|
/*C*/ {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ActionNode* prowl([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
return new ActionNode(
|
||||||
|
"prowl",
|
||||||
|
/*P*/ { NextAction("cat form") },
|
||||||
|
/*A*/ {},
|
||||||
|
/*C*/ {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
CatDruidStrategy::CatDruidStrategy(PlayerbotAI* botAI) : FeralDruidStrategy(botAI)
|
||||||
|
{
|
||||||
|
actionNodeFactories.Add(new CatDruidStrategyActionNodeFactory());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<NextAction> CatDruidStrategy::getDefaultActions()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
NextAction("melee", ACTION_DEFAULT)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void CatDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||||
|
{
|
||||||
|
FeralDruidStrategy::InitTriggers(triggers);
|
||||||
|
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"healer low mana", {
|
||||||
|
NextAction("innervate on healer", 35.0f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"prowl", {
|
||||||
|
NextAction("prowl", 29.5f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"enemy out of melee", {
|
||||||
|
NextAction("dash", 28.0f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"cat form", {
|
||||||
|
NextAction("cat form", 28.0f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"low energy", {
|
||||||
|
NextAction("tiger's fury", 27.0f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"savage roar", {
|
||||||
|
NextAction("savage roar", 26.0f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"combo points 5 available", {
|
||||||
|
NextAction("rip", 23.5f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"combo points 5 available", {
|
||||||
|
NextAction("maim", 23.0f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"ferocious bite execute", {
|
||||||
|
NextAction("ferocious bite", 24.0f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"clearcasting", {
|
||||||
|
NextAction("shred", 24.5f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"ferocious bite time", {
|
||||||
|
NextAction("ferocious bite", 22.5f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"mangle (cat)", {
|
||||||
|
NextAction("mangle (cat)", 22.0f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"rake", {
|
||||||
|
NextAction("rake", 21.5f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"medium threat", {
|
||||||
|
NextAction("cower", 21.0f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"almost full energy available", {
|
||||||
|
NextAction("ravage", 5.6f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"combo points not full", {
|
||||||
|
NextAction("ravage", 5.6f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"almost full energy available", {
|
||||||
|
NextAction("pounce", 5.5f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"combo points not full", {
|
||||||
|
NextAction("pounce", 5.5f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"almost full energy available", {
|
||||||
|
NextAction("shred", 5.4f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"combo points not full", {
|
||||||
|
NextAction("shred", 5.4f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"almost full energy available", {
|
||||||
|
NextAction("mangle (cat)", 5.3f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"combo points not full and high energy", {
|
||||||
|
NextAction("mangle (cat)", 5.3f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"almost full energy available", {
|
||||||
|
NextAction("claw", 5.2f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"combo points not full and high energy", {
|
||||||
|
NextAction("claw", 5.2f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"faerie fire (feral)", {
|
||||||
|
NextAction("faerie fire (feral)", 5.0f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// CatOffhealStrategy
|
||||||
|
// Additive overlay — only the healing triggers. Designed to be
|
||||||
|
// stacked on top of "cat" so the bot stays in cat form for DPS
|
||||||
|
// but shifts out to heal when the party needs it.
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
class CatOffhealStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CatOffhealStrategyActionNodeFactory()
|
||||||
|
{
|
||||||
|
creators["healing touch on party"] = &healing_touch_on_party;
|
||||||
|
creators["regrowth on party"] = ®rowth_on_party;
|
||||||
|
creators["rejuvenation on party"] = &rejuvenation_on_party;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// P: shift to caster form before casting C: shift back to cat form afterwards
|
||||||
|
static ActionNode* healing_touch_on_party([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
return new ActionNode(
|
||||||
|
"healing touch on party",
|
||||||
|
/*P*/ { NextAction("caster form") },
|
||||||
|
/*A*/ {},
|
||||||
|
/*C*/ { NextAction("cat form") }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ActionNode* regrowth_on_party([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
return new ActionNode(
|
||||||
|
"regrowth on party",
|
||||||
|
/*P*/ { NextAction("caster form") },
|
||||||
|
/*A*/ {},
|
||||||
|
/*C*/ { NextAction("cat form") }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ActionNode* rejuvenation_on_party([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
return new ActionNode(
|
||||||
|
"rejuvenation on party",
|
||||||
|
/*P*/ { NextAction("caster form") },
|
||||||
|
/*A*/ {},
|
||||||
|
/*C*/ { NextAction("cat form") }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CatOffhealStrategy::CatOffhealStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI)
|
||||||
|
{
|
||||||
|
actionNodeFactories.Add(new CatOffhealStrategyActionNodeFactory());
|
||||||
|
}
|
||||||
|
|
||||||
|
void CatOffhealStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||||
|
{
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"party member critical health",
|
||||||
|
{
|
||||||
|
NextAction("regrowth on party", 36.0f),
|
||||||
|
NextAction("healing touch on party", 35.0f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"party member low health",
|
||||||
|
{
|
||||||
|
NextAction("healing touch on party", 25.0f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"party member medium health",
|
||||||
|
{
|
||||||
|
NextAction("rejuvenation on party", 18.0f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"party member to heal out of spell range",
|
||||||
|
{
|
||||||
|
NextAction("reach party member to heal", 93.0f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode(
|
||||||
|
"low mana",
|
||||||
|
{
|
||||||
|
NextAction("innervate", 24.0f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -3,17 +3,17 @@
|
|||||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _PLAYERBOT_CATDPSDRUIDSTRATEGY_H
|
#ifndef _PLAYERBOT_CATDRUIDSTRATEGY_H
|
||||||
#define _PLAYERBOT_CATDPSDRUIDSTRATEGY_H
|
#define _PLAYERBOT_CATDRUIDSTRATEGY_H
|
||||||
|
|
||||||
#include "FeralDruidStrategy.h"
|
#include "FeralDruidStrategy.h"
|
||||||
|
|
||||||
class PlayerbotAI;
|
class PlayerbotAI;
|
||||||
|
|
||||||
class CatDpsDruidStrategy : public FeralDruidStrategy
|
class CatDruidStrategy : public FeralDruidStrategy
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CatDpsDruidStrategy(PlayerbotAI* botAI);
|
CatDruidStrategy(PlayerbotAI* botAI);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
||||||
@ -22,14 +22,16 @@ public:
|
|||||||
uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_MELEE; }
|
uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_MELEE; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class CatAoeDruidStrategy : public CombatStrategy
|
// Optional additive strategy. Layers emergency heals on top of the "cat" strategy.
|
||||||
|
// Enable : co +offheal
|
||||||
|
// Disable: co -offheal
|
||||||
|
class CatOffhealStrategy : public CombatStrategy
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CatAoeDruidStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {}
|
CatOffhealStrategy(PlayerbotAI* botAI);
|
||||||
|
|
||||||
public:
|
|
||||||
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
||||||
std::string const getName() override { return "cat aoe"; }
|
std::string const getName() override { return "offheal"; }
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@ -14,12 +14,10 @@ public:
|
|||||||
{
|
{
|
||||||
creators["survival instincts"] = &survival_instincts;
|
creators["survival instincts"] = &survival_instincts;
|
||||||
creators["thorns"] = þs;
|
creators["thorns"] = þs;
|
||||||
creators["omen of clarity"] = &omen_of_clarity;
|
|
||||||
creators["cure poison"] = &cure_poison;
|
creators["cure poison"] = &cure_poison;
|
||||||
creators["cure poison on party"] = &cure_poison_on_party;
|
creators["cure poison on party"] = &cure_poison_on_party;
|
||||||
creators["abolish poison"] = &abolish_poison;
|
creators["abolish poison"] = &abolish_poison;
|
||||||
creators["abolish poison on party"] = &abolish_poison_on_party;
|
creators["abolish poison on party"] = &abolish_poison_on_party;
|
||||||
creators["prowl"] = &prowl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -39,14 +37,6 @@ private:
|
|||||||
/*C*/ {});
|
/*C*/ {});
|
||||||
}
|
}
|
||||||
|
|
||||||
static ActionNode* omen_of_clarity([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode("omen of clarity",
|
|
||||||
/*P*/ { NextAction("caster form") },
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ {});
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* cure_poison([[maybe_unused]] PlayerbotAI* botAI)
|
static ActionNode* cure_poison([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
{
|
{
|
||||||
return new ActionNode("cure poison",
|
return new ActionNode("cure poison",
|
||||||
@ -79,13 +69,6 @@ private:
|
|||||||
/*C*/ {});
|
/*C*/ {});
|
||||||
}
|
}
|
||||||
|
|
||||||
static ActionNode* prowl([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode("prowl",
|
|
||||||
/*P*/ { NextAction("cat form") },
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ {});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
FeralDruidStrategy::FeralDruidStrategy(PlayerbotAI* botAI) : GenericDruidStrategy(botAI)
|
FeralDruidStrategy::FeralDruidStrategy(PlayerbotAI* botAI) : GenericDruidStrategy(botAI)
|
||||||
@ -99,15 +82,23 @@ void FeralDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
GenericDruidStrategy::InitTriggers(triggers);
|
GenericDruidStrategy::InitTriggers(triggers);
|
||||||
|
|
||||||
triggers.push_back(new TriggerNode(
|
triggers.push_back(new TriggerNode(
|
||||||
"enemy out of melee", { NextAction("reach melee", ACTION_HIGH + 1) }));
|
"enemy out of melee", { NextAction("reach melee", 21.0f) }));
|
||||||
triggers.push_back(new TriggerNode(
|
triggers.push_back(new TriggerNode(
|
||||||
"critical health", { NextAction("survival instincts", ACTION_EMERGENCY + 1) }));
|
"low health", { NextAction("survival instincts", 91.0f) }));
|
||||||
triggers.push_back(new TriggerNode(
|
|
||||||
"omen of clarity", { NextAction("omen of clarity", ACTION_HIGH + 9) }));
|
|
||||||
triggers.push_back(new TriggerNode("player has flag",
|
triggers.push_back(new TriggerNode("player has flag",
|
||||||
{ NextAction("dash", ACTION_EMERGENCY + 2) }));
|
{ NextAction("dash", 92.0f) }));
|
||||||
triggers.push_back(new TriggerNode("enemy flagcarrier near",
|
triggers.push_back(new TriggerNode("enemy flagcarrier near",
|
||||||
{ NextAction("dash", ACTION_EMERGENCY + 2) }));
|
{ NextAction("dash", 92.0f) }));
|
||||||
triggers.push_back(
|
}
|
||||||
new TriggerNode("berserk", { NextAction("berserk", ACTION_HIGH + 6) }));
|
|
||||||
|
void FeralChargeDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||||
|
{
|
||||||
|
Player* bot = botAI->GetBot();
|
||||||
|
|
||||||
|
if (bot->HasSpell(SPELL_CAT_FORM) && !bot->HasAura(AURA_THICK_HIDE))
|
||||||
|
triggers.push_back(new TriggerNode(
|
||||||
|
"enemy out of melee", { NextAction("feral charge - cat", 29.0f) }));
|
||||||
|
else
|
||||||
|
triggers.push_back(new TriggerNode(
|
||||||
|
"enemy out of melee", { NextAction("feral charge - bear", 18.0f) }));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,11 +3,14 @@
|
|||||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _PLAYERBOT_FERALRUIDSTRATEGY_H
|
#ifndef _PLAYERBOT_FERALDRUIDSTRATEGY_H
|
||||||
#define _PLAYERBOT_FERALRUIDSTRATEGY_H
|
#define _PLAYERBOT_FERALDRUIDSTRATEGY_H
|
||||||
|
|
||||||
#include "GenericDruidStrategy.h"
|
#include "GenericDruidStrategy.h"
|
||||||
|
|
||||||
|
constexpr uint32 SPELL_CAT_FORM = 768;
|
||||||
|
constexpr uint32 AURA_THICK_HIDE = 16931;
|
||||||
|
|
||||||
class PlayerbotAI;
|
class PlayerbotAI;
|
||||||
|
|
||||||
class ShapeshiftDruidStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
|
class ShapeshiftDruidStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
|
||||||
@ -83,4 +86,18 @@ public:
|
|||||||
uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_MELEE; }
|
uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_MELEE; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Optional strategy — enabled by default for cat and bear.
|
||||||
|
// Registers the "enemy out of melee" → Feral Charge trigger, spec-gated at
|
||||||
|
// init time so cats get Feral Charge (Cat) and bears get Feral Charge (Bear).
|
||||||
|
// Disable with: co -feral charge
|
||||||
|
// Re-enable with: co +feral charge
|
||||||
|
class FeralChargeDruidStrategy : public CombatStrategy
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FeralChargeDruidStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {}
|
||||||
|
|
||||||
|
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
||||||
|
std::string const getName() override { return "feral charge"; }
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -17,12 +17,13 @@ public:
|
|||||||
creators["thorns on party"] = þs_on_party;
|
creators["thorns on party"] = þs_on_party;
|
||||||
creators["mark of the wild"] = &mark_of_the_wild;
|
creators["mark of the wild"] = &mark_of_the_wild;
|
||||||
creators["mark of the wild on party"] = &mark_of_the_wild_on_party;
|
creators["mark of the wild on party"] = &mark_of_the_wild_on_party;
|
||||||
// creators["innervate"] = &innervate;
|
|
||||||
creators["regrowth_on_party"] = ®rowth_on_party;
|
creators["regrowth_on_party"] = ®rowth_on_party;
|
||||||
creators["rejuvenation on party"] = &rejuvenation_on_party;
|
creators["rejuvenation on party"] = &rejuvenation_on_party;
|
||||||
creators["remove curse on party"] = &remove_curse_on_party;
|
creators["remove curse on party"] = &remove_curse_on_party;
|
||||||
creators["abolish poison on party"] = &abolish_poison_on_party;
|
creators["abolish poison on party"] = &abolish_poison_on_party;
|
||||||
creators["revive"] = &revive;
|
creators["revive"] = &revive;
|
||||||
|
creators["prowl"] = &prowl;
|
||||||
|
creators["aquatic form"] = &aquatic_form;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -92,6 +93,23 @@ private:
|
|||||||
/*A*/ {},
|
/*A*/ {},
|
||||||
/*C*/ {});
|
/*C*/ {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ActionNode* prowl([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
return new ActionNode("prowl",
|
||||||
|
/*P*/ { NextAction("cat form") },
|
||||||
|
/*A*/ {},
|
||||||
|
/*C*/ {});
|
||||||
|
}
|
||||||
|
|
||||||
|
static ActionNode* aquatic_form([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
return new ActionNode("aquatic form",
|
||||||
|
/*P*/ { NextAction("caster form") },
|
||||||
|
/*A*/ {},
|
||||||
|
/*C*/ {});
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
GenericDruidNonCombatStrategy::GenericDruidNonCombatStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI)
|
GenericDruidNonCombatStrategy::GenericDruidNonCombatStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI)
|
||||||
@ -165,11 +183,16 @@ void GenericDruidNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trig
|
|||||||
NextAction("remove curse on party", ACTION_DISPEL + 7),
|
NextAction("remove curse on party", ACTION_DISPEL + 7),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
triggers.push_back(new TriggerNode("aquatic form", { NextAction("aquatic form", 10.0f) }));
|
||||||
|
|
||||||
int specTab = AiFactory::GetPlayerSpecTab(botAI->GetBot());
|
int specTab = AiFactory::GetPlayerSpecTab(botAI->GetBot());
|
||||||
if (specTab == 0 || specTab == 2) // Balance or Restoration
|
if (specTab == DRUID_TAB_BALANCE || specTab == DRUID_TAB_RESTORATION)
|
||||||
triggers.push_back(new TriggerNode("often", { NextAction("apply oil", 1.0f) }));
|
triggers.push_back(new TriggerNode("often", { NextAction("apply oil", 1.0f) }));
|
||||||
if (specTab == 1) // Feral
|
if (specTab == DRUID_TAB_FERAL)
|
||||||
|
{
|
||||||
triggers.push_back(new TriggerNode("often", { NextAction("apply stone", 1.0f) }));
|
triggers.push_back(new TriggerNode("often", { NextAction("apply stone", 1.0f) }));
|
||||||
|
triggers.push_back(new TriggerNode("prowl", { NextAction("prowl", ACTION_INTERRUPT) }));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
#include "GenericDruidStrategy.h"
|
#include "GenericDruidStrategy.h"
|
||||||
|
|
||||||
|
#include "AiFactory.h"
|
||||||
|
#include "FeralDruidStrategy.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
class GenericDruidStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
|
class GenericDruidStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
|
||||||
@ -20,6 +22,8 @@ public:
|
|||||||
creators["abolish poison on party"] = &abolish_poison_on_party;
|
creators["abolish poison on party"] = &abolish_poison_on_party;
|
||||||
creators["rebirth"] = &rebirth;
|
creators["rebirth"] = &rebirth;
|
||||||
creators["entangling roots on cc"] = &entangling_roots_on_cc;
|
creators["entangling roots on cc"] = &entangling_roots_on_cc;
|
||||||
|
creators["cyclone on cc"] = &cyclone_on_cc;
|
||||||
|
creators["hibernate on cc"] = &hibernate_on_cc;
|
||||||
creators["innervate"] = &innervate;
|
creators["innervate"] = &innervate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,6 +92,22 @@ private:
|
|||||||
/*C*/ {});
|
/*C*/ {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ActionNode* cyclone_on_cc([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
return new ActionNode("cyclone on cc",
|
||||||
|
/*P*/ { NextAction("caster form") },
|
||||||
|
/*A*/ {},
|
||||||
|
/*C*/ {});
|
||||||
|
}
|
||||||
|
|
||||||
|
static ActionNode* hibernate_on_cc([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
return new ActionNode("hibernate on cc",
|
||||||
|
/*P*/ { NextAction("caster form") },
|
||||||
|
/*A*/ {},
|
||||||
|
/*C*/ {});
|
||||||
|
}
|
||||||
|
|
||||||
static ActionNode* innervate([[maybe_unused]] PlayerbotAI* botAI)
|
static ActionNode* innervate([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
{
|
{
|
||||||
return new ActionNode("innervate",
|
return new ActionNode("innervate",
|
||||||
@ -107,41 +127,95 @@ void GenericDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
CombatStrategy::InitTriggers(triggers);
|
CombatStrategy::InitTriggers(triggers);
|
||||||
|
|
||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
new TriggerNode("low health", { NextAction("barkskin", ACTION_HIGH + 7) }));
|
new TriggerNode("almost full health", { NextAction("barkskin", 40.0f) }));
|
||||||
|
|
||||||
|
Player* bot = botAI->GetBot();
|
||||||
|
int tab = AiFactory::GetPlayerSpecTab(bot);
|
||||||
|
|
||||||
|
if (tab == DRUID_TAB_FERAL)
|
||||||
|
{
|
||||||
|
if (!bot->HasAura(16931) /*thick hide — bear spec*/)
|
||||||
|
{
|
||||||
|
triggers.push_back(new TriggerNode("predator's swiftness and combat party member dead",
|
||||||
|
{ NextAction("rebirth", 29.0f) }));
|
||||||
triggers.push_back(new TriggerNode("combat party member dead",
|
triggers.push_back(new TriggerNode("combat party member dead",
|
||||||
{ NextAction("rebirth", ACTION_HIGH + 9) }));
|
{ NextAction("rebirth", 28.5f) }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
triggers.push_back(new TriggerNode("combat party member dead",
|
||||||
|
{ NextAction("rebirth", 29.0f) }));
|
||||||
|
}
|
||||||
|
|
||||||
triggers.push_back(new TriggerNode("being attacked",
|
triggers.push_back(new TriggerNode("being attacked",
|
||||||
{ NextAction("nature's grasp", ACTION_HIGH + 1) }));
|
{ NextAction("nature's grasp", 39.0f) }));
|
||||||
triggers.push_back(new TriggerNode("new pet", { NextAction("set pet stance", 60.0f) }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DruidCureStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
void DruidCureStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||||
{
|
{
|
||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
new TriggerNode("party member cure poison",
|
new TriggerNode("party member cure poison",
|
||||||
{ NextAction("abolish poison on party", ACTION_DISPEL + 1) }));
|
{ NextAction("abolish poison on party", 51.0f) }));
|
||||||
|
|
||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
new TriggerNode("party member remove curse",
|
new TriggerNode("party member remove curse",
|
||||||
{ NextAction("remove curse on party", ACTION_DISPEL + 7) }));
|
{ NextAction("remove curse on party", 57.0f) }));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DruidBoostStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
void DruidBoostStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||||
{
|
{
|
||||||
triggers.push_back(new TriggerNode(
|
Player* bot = botAI->GetBot();
|
||||||
"nature's swiftness", { NextAction("nature's swiftness", ACTION_HIGH + 9) }));
|
int tab = AiFactory::GetPlayerSpecTab(bot);
|
||||||
|
|
||||||
|
if (tab == DRUID_TAB_BALANCE)
|
||||||
|
{
|
||||||
|
triggers.push_back(new TriggerNode("force of nature", { NextAction("force of nature", 29.0f) }));
|
||||||
|
triggers.push_back(new TriggerNode("new pet", { NextAction("set pet stance", 60.0f) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tab == DRUID_TAB_FERAL)
|
||||||
|
{
|
||||||
|
triggers.push_back(new TriggerNode("berserk", { NextAction("berserk", 27.5f) }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DruidCcStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
void DruidCcStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||||
{
|
{
|
||||||
|
Player* bot = botAI->GetBot();
|
||||||
|
int tab = AiFactory::GetPlayerSpecTab(bot);
|
||||||
|
|
||||||
|
if (tab == DRUID_TAB_BALANCE || tab == DRUID_TAB_RESTORATION)
|
||||||
|
{
|
||||||
triggers.push_back(new TriggerNode(
|
triggers.push_back(new TriggerNode(
|
||||||
"entangling roots", { NextAction("entangling roots on cc", ACTION_HIGH + 2) }));
|
"cyclone", { NextAction("cyclone on cc", 42.0f) }));
|
||||||
triggers.push_back(new TriggerNode(
|
triggers.push_back(new TriggerNode(
|
||||||
"entangling roots kite", { NextAction("entangling roots", ACTION_HIGH + 2) }));
|
"hibernate", { NextAction("hibernate on cc", 41.0f) }));
|
||||||
triggers.push_back(new TriggerNode(
|
triggers.push_back(new TriggerNode(
|
||||||
"hibernate", { NextAction("hibernate on cc", ACTION_HIGH + 3) }));
|
"entangling roots", { NextAction("entangling roots on cc", 40.0f) }));
|
||||||
|
}
|
||||||
|
if (tab == DRUID_TAB_FERAL)
|
||||||
|
{
|
||||||
|
if (bot->HasSpell(SPELL_CAT_FORM) && !bot->HasAura(AURA_THICK_HIDE))
|
||||||
|
{
|
||||||
|
triggers.push_back(new TriggerNode(
|
||||||
|
"predator's swiftness and cyclone", { NextAction("cyclone on cc", 42.0f) }));
|
||||||
|
triggers.push_back(new TriggerNode(
|
||||||
|
"predator's swiftness and hibernate", { NextAction("hibernate on cc", 41.0f) }));
|
||||||
|
triggers.push_back(new TriggerNode(
|
||||||
|
"predator's swiftness and entangling roots", { NextAction("entangling roots on cc", 40.0f) }));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
triggers.push_back(new TriggerNode(
|
||||||
|
"cyclone", { NextAction("cyclone on cc", 42.0f) }));
|
||||||
|
triggers.push_back(new TriggerNode(
|
||||||
|
"hibernate", { NextAction("hibernate on cc", 41.0f) }));
|
||||||
|
triggers.push_back(new TriggerNode(
|
||||||
|
"entangling roots", { NextAction("entangling roots on cc", 40.0f) }));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DruidHealerDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
void DruidHealerDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||||
@ -149,10 +223,39 @@ void DruidHealerDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
new TriggerNode("healer should attack",
|
new TriggerNode("healer should attack",
|
||||||
{
|
{
|
||||||
NextAction("cancel tree form", ACTION_DEFAULT + 0.4f),
|
NextAction("cancel tree form", 5.4f),
|
||||||
NextAction("moonfire", ACTION_DEFAULT + 0.3f),
|
NextAction("moonfire", 5.3f),
|
||||||
NextAction("wrath", ACTION_DEFAULT + 0.2f),
|
NextAction("wrath", 5.2f),
|
||||||
NextAction("starfire", ACTION_DEFAULT + 0.1f),
|
NextAction("starfire", 5.1f),
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DruidAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||||
|
{
|
||||||
|
Player* bot = botAI->GetBot();
|
||||||
|
int tab = AiFactory::GetPlayerSpecTab(bot);
|
||||||
|
|
||||||
|
if (tab == DRUID_TAB_BALANCE)
|
||||||
|
{
|
||||||
|
triggers.push_back(new TriggerNode("hurricane channel check", { NextAction("cancel channel", 22.0f) }));
|
||||||
|
triggers.push_back(new TriggerNode("starfall", { NextAction("starfall", 28.5f) }));
|
||||||
|
triggers.push_back(new TriggerNode("medium aoe", { NextAction("hurricane", 23.0f) }));
|
||||||
|
triggers.push_back(new TriggerNode("enemy within melee", { NextAction("typhoon", 40.0f) }));
|
||||||
|
triggers.push_back(new TriggerNode("insect swarm on attacker", { NextAction("insect swarm on attacker", 5.2f) }));
|
||||||
|
triggers.push_back(new TriggerNode("moonfire on attacker", { NextAction("moonfire on attacker", 5.1f) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tab == DRUID_TAB_RESTORATION)
|
||||||
|
{
|
||||||
|
triggers.push_back(new TriggerNode("hurricane channel check", { NextAction("cancel channel", 22.0f) }));
|
||||||
|
triggers.push_back(new TriggerNode("medium aoe", { NextAction("hurricane", 23.0f) }));
|
||||||
|
triggers.push_back(new TriggerNode("insect swarm on attacker", { NextAction("insect swarm on attacker", 5.2f) }));
|
||||||
|
triggers.push_back(new TriggerNode("moonfire on attacker", { NextAction("moonfire on attacker", 5.1f) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tab == DRUID_TAB_FERAL && bot->HasSpell(SPELL_CAT_FORM) && !bot->HasAura(AURA_THICK_HIDE))
|
||||||
|
{
|
||||||
|
triggers.push_back(new TriggerNode("clearcasting and medium aoe", { NextAction("swipe (cat)", 25.5f) }));
|
||||||
|
triggers.push_back(new TriggerNode("medium aoe", { NextAction("swipe (cat)", 25.0f) }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,4 +55,13 @@ public:
|
|||||||
std::string const getName() override { return "healer dps"; }
|
std::string const getName() override { return "healer dps"; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class DruidAoeStrategy : public Strategy
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DruidAoeStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
|
||||||
|
|
||||||
|
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
||||||
|
std::string const getName() override { return "aoe"; }
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -1,108 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
|
||||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "HealDruidStrategy.h"
|
|
||||||
|
|
||||||
#include "Playerbots.h"
|
|
||||||
|
|
||||||
class HealDruidStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
HealDruidStrategyActionNodeFactory() {
|
|
||||||
creators["nourish on party"] = &nourtish_on_party;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static ActionNode* nourtish_on_party([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode("nourish on party",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ { NextAction("healing touch on party") },
|
|
||||||
/*C*/ {});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
HealDruidStrategy::HealDruidStrategy(PlayerbotAI* botAI) : GenericDruidStrategy(botAI)
|
|
||||||
{
|
|
||||||
actionNodeFactories.Add(new HealDruidStrategyActionNodeFactory());
|
|
||||||
}
|
|
||||||
|
|
||||||
void HealDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|
||||||
{
|
|
||||||
GenericDruidStrategy::InitTriggers(triggers);
|
|
||||||
|
|
||||||
// no healer dps strategy
|
|
||||||
triggers.push_back(new TriggerNode("no healer dps strategy",
|
|
||||||
{ NextAction("tree form", ACTION_DEFAULT) }));
|
|
||||||
|
|
||||||
triggers.push_back(new TriggerNode(
|
|
||||||
"party member to heal out of spell range",
|
|
||||||
{ NextAction("reach party member to heal", ACTION_CRITICAL_HEAL + 9) }));
|
|
||||||
|
|
||||||
// CRITICAL
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode("party member critical health",
|
|
||||||
{
|
|
||||||
NextAction("tree form", ACTION_CRITICAL_HEAL + 4.1f),
|
|
||||||
NextAction("swiftmend on party", ACTION_CRITICAL_HEAL + 4),
|
|
||||||
NextAction("regrowth on party", ACTION_CRITICAL_HEAL + 3),
|
|
||||||
NextAction("wild growth on party", ACTION_CRITICAL_HEAL + 2),
|
|
||||||
NextAction("nourish on party", ACTION_CRITICAL_HEAL + 1),
|
|
||||||
}));
|
|
||||||
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode("party member critical health",
|
|
||||||
{ NextAction("nature's swiftness", ACTION_CRITICAL_HEAL + 4) }));
|
|
||||||
|
|
||||||
triggers.push_back(new TriggerNode("clearcasting",
|
|
||||||
{ NextAction("lifebloom on main tank", ACTION_CRITICAL_HEAL - 1) }));
|
|
||||||
|
|
||||||
triggers.push_back(new TriggerNode(
|
|
||||||
"group heal setting",
|
|
||||||
{
|
|
||||||
NextAction("tree form", ACTION_MEDIUM_HEAL + 2.3f),
|
|
||||||
NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 2.2f),
|
|
||||||
NextAction("rejuvenation on not full", ACTION_MEDIUM_HEAL + 2.1f),
|
|
||||||
}));
|
|
||||||
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode("medium group heal setting",
|
|
||||||
{
|
|
||||||
NextAction("tree form", ACTION_CRITICAL_HEAL + 0.6f),
|
|
||||||
NextAction("tranquility", ACTION_CRITICAL_HEAL + 0.5f) }));
|
|
||||||
|
|
||||||
// LOW
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode("party member low health",
|
|
||||||
{ NextAction("tree form", ACTION_MEDIUM_HEAL + 1.5f),
|
|
||||||
NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 1.4f),
|
|
||||||
NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 1.3f),
|
|
||||||
NextAction("swiftmend on party", ACTION_MEDIUM_HEAL + 1.2),
|
|
||||||
NextAction("nourish on party", ACTION_MEDIUM_HEAL + 1.1f),
|
|
||||||
}));
|
|
||||||
|
|
||||||
// MEDIUM
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode("party member medium health",
|
|
||||||
{
|
|
||||||
NextAction("tree form", ACTION_MEDIUM_HEAL + 0.5f),
|
|
||||||
NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 0.4f),
|
|
||||||
NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 0.3f),
|
|
||||||
NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 0.2f),
|
|
||||||
NextAction("nourish on party", ACTION_MEDIUM_HEAL + 0.1f) }));
|
|
||||||
|
|
||||||
// almost full
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode("party member almost full health",
|
|
||||||
{ NextAction("wild growth on party", ACTION_LIGHT_HEAL + 0.3f),
|
|
||||||
NextAction("rejuvenation on party", ACTION_LIGHT_HEAL + 0.2f),
|
|
||||||
NextAction("regrowth on party", ACTION_LIGHT_HEAL + 0.1f) }));
|
|
||||||
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode("medium mana", { NextAction("innervate", ACTION_HIGH + 5) }));
|
|
||||||
|
|
||||||
triggers.push_back(new TriggerNode("enemy too close for spell",
|
|
||||||
{ NextAction("flee", ACTION_MOVE + 9) }));
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
|
||||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _PLAYERBOT_HEALDRUIDSTRATEGY_H
|
|
||||||
#define _PLAYERBOT_HEALDRUIDSTRATEGY_H
|
|
||||||
|
|
||||||
#include "GenericDruidStrategy.h"
|
|
||||||
#include "Strategy.h"
|
|
||||||
|
|
||||||
class PlayerbotAI;
|
|
||||||
|
|
||||||
class HealDruidStrategy : public GenericDruidStrategy
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
HealDruidStrategy(PlayerbotAI* botAI);
|
|
||||||
|
|
||||||
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
|
||||||
std::string const getName() override { return "heal"; }
|
|
||||||
uint32 GetType() const override { return STRATEGY_TYPE_RANGED | STRATEGY_TYPE_HEAL; }
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
|
||||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "MeleeDruidStrategy.h"
|
|
||||||
|
|
||||||
#include "Playerbots.h"
|
|
||||||
|
|
||||||
MeleeDruidStrategy::MeleeDruidStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {}
|
|
||||||
|
|
||||||
std::vector<NextAction> MeleeDruidStrategy::getDefaultActions()
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
NextAction("faerie fire", ACTION_DEFAULT + 0.1f),
|
|
||||||
NextAction("melee", ACTION_DEFAULT)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void MeleeDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|
||||||
{
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"omen of clarity",
|
|
||||||
{
|
|
||||||
NextAction("omen of clarity", ACTION_HIGH + 9)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
CombatStrategy::InitTriggers(triggers);
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
|
||||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _PLAYERBOT_MELEEDRUIDSTRATEGY_H
|
|
||||||
#define _PLAYERBOT_MELEEDRUIDSTRATEGY_H
|
|
||||||
|
|
||||||
#include "CombatStrategy.h"
|
|
||||||
|
|
||||||
class MeleeDruidStrategy : public CombatStrategy
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
MeleeDruidStrategy(PlayerbotAI* botAI);
|
|
||||||
|
|
||||||
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
|
||||||
std::string const getName() override { return "melee"; }
|
|
||||||
std::vector<NextAction> getDefaultActions() override;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,307 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
|
||||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "OffhealDruidCatStrategy.h"
|
|
||||||
|
|
||||||
#include "Playerbots.h"
|
|
||||||
#include "Strategy.h"
|
|
||||||
|
|
||||||
class OffhealDruidCatStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
OffhealDruidCatStrategyActionNodeFactory()
|
|
||||||
{
|
|
||||||
creators["cat form"] = &cat_form;
|
|
||||||
creators["mangle (cat)"] = &mangle_cat;
|
|
||||||
creators["shred"] = &shred;
|
|
||||||
creators["rake"] = &rake;
|
|
||||||
creators["rip"] = &rip;
|
|
||||||
creators["ferocious bite"] = &ferocious_bite;
|
|
||||||
creators["savage roar"] = &savage_roar;
|
|
||||||
creators["faerie fire (feral)"] = &faerie_fire_feral;
|
|
||||||
creators["healing touch on party"] = &healing_touch_on_party;
|
|
||||||
creators["regrowth on party"] = ®rowth_on_party;
|
|
||||||
creators["rejuvenation on party"] = &rejuvenation_on_party;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static ActionNode* cat_form([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"cat form",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* mangle_cat([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"mangle (cat)",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* shred([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"shred",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ { NextAction("claw") },
|
|
||||||
/*C*/ {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* rake([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"rake",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* rip([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"rip",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* ferocious_bite([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"ferocious bite",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ { NextAction("rip") },
|
|
||||||
/*C*/ {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* savage_roar([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"savage roar",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* faerie_fire_feral([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"faerie fire (feral)",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* healing_touch_on_party([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"healing touch on party",
|
|
||||||
/*P*/ { NextAction("caster form") },
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ { NextAction("cat form") }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* regrowth_on_party([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"regrowth on party",
|
|
||||||
/*P*/ { NextAction("caster form") },
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ { NextAction("cat form") }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* rejuvenation_on_party([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode(
|
|
||||||
"rejuvenation on party",
|
|
||||||
/*P*/ { NextAction("caster form") },
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ { NextAction("cat form") }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
OffhealDruidCatStrategy::OffhealDruidCatStrategy(PlayerbotAI* botAI) : FeralDruidStrategy(botAI)
|
|
||||||
{
|
|
||||||
actionNodeFactories.Add(new OffhealDruidCatStrategyActionNodeFactory());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<NextAction> OffhealDruidCatStrategy::getDefaultActions()
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
NextAction("mangle (cat)", ACTION_DEFAULT + 0.5f),
|
|
||||||
NextAction("shred", ACTION_DEFAULT + 0.4f),
|
|
||||||
NextAction("rake", ACTION_DEFAULT + 0.3f),
|
|
||||||
NextAction("melee", ACTION_DEFAULT),
|
|
||||||
NextAction("cat form", ACTION_DEFAULT - 0.1f)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void OffhealDruidCatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|
||||||
{
|
|
||||||
FeralDruidStrategy::InitTriggers(triggers);
|
|
||||||
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"cat form",
|
|
||||||
{
|
|
||||||
NextAction("cat form", ACTION_HIGH + 8)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"savage roar",
|
|
||||||
{
|
|
||||||
NextAction("savage roar", ACTION_HIGH + 7)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"combo points 5 available",
|
|
||||||
{
|
|
||||||
NextAction("rip", ACTION_HIGH + 6)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"ferocious bite time",
|
|
||||||
{
|
|
||||||
NextAction("ferocious bite", ACTION_HIGH + 5)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"target with combo points almost dead",
|
|
||||||
{
|
|
||||||
NextAction("ferocious bite", ACTION_HIGH + 4)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"mangle (cat)",
|
|
||||||
{
|
|
||||||
NextAction("mangle (cat)", ACTION_HIGH + 3)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"rake",
|
|
||||||
{
|
|
||||||
NextAction("rake", ACTION_HIGH + 2)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"almost full energy available",
|
|
||||||
{
|
|
||||||
NextAction("shred", ACTION_DEFAULT + 0.4f)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"combo points not full",
|
|
||||||
{
|
|
||||||
NextAction("shred", ACTION_DEFAULT + 0.4f)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"faerie fire (feral)",
|
|
||||||
{
|
|
||||||
NextAction("faerie fire (feral)", ACTION_NORMAL)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"enemy out of melee",
|
|
||||||
{
|
|
||||||
NextAction("feral charge - cat", ACTION_HIGH + 9),
|
|
||||||
NextAction("dash", ACTION_HIGH + 8)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"medium aoe",
|
|
||||||
{
|
|
||||||
NextAction("swipe (cat)", ACTION_HIGH + 3)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"tiger's fury",
|
|
||||||
{
|
|
||||||
NextAction("tiger's fury", ACTION_NORMAL + 1)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"party member critical health",
|
|
||||||
{
|
|
||||||
NextAction("regrowth on party", ACTION_CRITICAL_HEAL + 6),
|
|
||||||
NextAction("healing touch on party", ACTION_CRITICAL_HEAL + 5)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"party member low health",
|
|
||||||
{
|
|
||||||
NextAction("healing touch on party", ACTION_MEDIUM_HEAL + 5)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"party member medium health",
|
|
||||||
{
|
|
||||||
NextAction("rejuvenation on party", ACTION_LIGHT_HEAL + 8)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"party member to heal out of spell range",
|
|
||||||
{
|
|
||||||
NextAction("reach party member to heal", ACTION_EMERGENCY + 3)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
triggers.push_back(
|
|
||||||
new TriggerNode(
|
|
||||||
"low mana",
|
|
||||||
{
|
|
||||||
NextAction("innervate", ACTION_HIGH + 4)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user