mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-01-13 09:07:19 +00:00
Compare commits
56 Commits
ce2a990495
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
965d300203 | ||
|
|
dc55ecfd9c | ||
|
|
59d6eb139e | ||
|
|
00171a8c82 | ||
|
|
02e8465a3b | ||
|
|
f53a8704eb | ||
|
|
e5525958c8 | ||
|
|
9ae457d069 | ||
|
|
c9e98a6b4e | ||
|
|
3d9623f119 | ||
|
|
c9cc4324d3 | ||
|
|
b13fb7d12a | ||
|
|
962fdeb3d1 | ||
|
|
83c6977de5 | ||
|
|
686fe513b2 | ||
|
|
61402e83a1 | ||
|
|
b16789fa54 | ||
|
|
8f638b6a66 | ||
|
|
33f5e733dc | ||
|
|
9917863ca1 | ||
|
|
2317652d72 | ||
|
|
1fcd6c5cda | ||
|
|
88016789ba | ||
|
|
6be860c967 | ||
|
|
9971622093 | ||
|
|
895df9b197 | ||
|
|
467b63b840 | ||
|
|
66f5f597bb | ||
|
|
cafbd4681e | ||
|
|
f5c84ee7ff | ||
|
|
b6f882886d | ||
|
|
c1222da8b0 | ||
|
|
00cb177c86 | ||
|
|
5f697e806e | ||
|
|
934e73ae20 | ||
|
|
f4b4d8967f | ||
|
|
910b8a9c53 | ||
|
|
bb569b4d39 | ||
|
|
dde16674c3 | ||
|
|
e5b2791053 | ||
|
|
353c29dfc4 | ||
|
|
52c3e96641 | ||
|
|
38e2d8584b | ||
|
|
d5dbc4ddd7 | ||
|
|
2424f73bc4 | ||
|
|
cf743a186a | ||
|
|
10213d8381 | ||
|
|
d97870facd | ||
|
|
0c1700c117 | ||
|
|
0b1b0eaecc | ||
|
|
8e03371147 | ||
|
|
27311b734d | ||
|
|
bb5ed37cd3 | ||
|
|
e88c1b779b | ||
|
|
05057ae9b5 | ||
|
|
610a032379 |
5
.github/workflows/codestyle_cpp.yml
vendored
5
.github/workflows/codestyle_cpp.yml
vendored
@@ -5,11 +5,16 @@ on:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
paths:
|
||||
- src/**
|
||||
- "!README.md"
|
||||
- "!docs/**"
|
||||
|
||||
concurrency:
|
||||
group: "codestyle-cppcheck-${{ github.event.pull_request.number }}"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -34,7 +34,7 @@ We also have a **[Discord server](https://discord.gg/NQm5QShwf9)** where you can
|
||||
|
||||
Supported platforms are Ubuntu, Windows, and macOS. Other Linux distributions may work, but may not receive support.
|
||||
|
||||
**All `mod-playerbots` installations require a custom branch of AzerothCore: [mod-playerbots/azerothcore-wotlk/tree/Playerbot](https://github.com/mod-playerbots/azerothcore-wotlk/tree/Playerbot).** This branch allows the playerbots module to build and function. Updates from the upstream are implemneted regularly to this branch. Instructions for installing this required branch and this module are provided below.
|
||||
**All `mod-playerbots` installations require a custom branch of AzerothCore: [mod-playerbots/azerothcore-wotlk/tree/Playerbot](https://github.com/mod-playerbots/azerothcore-wotlk/tree/Playerbot).** This branch allows the `mod-playerbots` module to build and function. Updates from the upstream are implemented regularly to this branch. Instructions for installing this required branch and this module are provided below.
|
||||
|
||||
### Cloning the Repositories
|
||||
|
||||
@@ -50,7 +50,7 @@ For more information, refer to the [AzerothCore Installation Guide](https://www.
|
||||
|
||||
### Docker Installation
|
||||
|
||||
Docker installations are considered experimental (unofficial with limited support), and previous Docker experience is recommended. To install the `mod-playerbots` on Docker, first clone the required branch of AzerothCore and this module:
|
||||
Docker installations are considered experimental (unofficial with limited support), and previous Docker experience is recommended. To install `mod-playerbots` on Docker, first clone the required branch of AzerothCore and this module:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/mod-playerbots/azerothcore-wotlk.git --branch=Playerbot
|
||||
@@ -103,7 +103,7 @@ Please click on the "⭐" button to stay up to date and help us gain more visibi
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
`mod-playerbots` is is based off [ZhengPeiRu21/mod-playerbots](https://github.com/ZhengPeiRu21/mod-playerbots) and [celguar/mangosbot-bots](https://github.com/celguar/mangosbot-bots). We extend our gratitude to [@ZhengPeiRu21](https://github.com/ZhengPeiRu21) and [@celguar](https://github.com/celguar) for the continued efforts in maintaining the module.
|
||||
`mod-playerbots` is based on [ZhengPeiRu21/mod-playerbots](https://github.com/ZhengPeiRu21/mod-playerbots) and [celguar/mangosbot-bots](https://github.com/celguar/mangosbot-bots). We extend our gratitude to [@ZhengPeiRu21](https://github.com/ZhengPeiRu21) and [@celguar](https://github.com/celguar) for their continued efforts in maintaining the module.
|
||||
|
||||
Also, a thank you to the many contributors who've helped build this project:
|
||||
|
||||
|
||||
@@ -21,10 +21,11 @@
|
||||
# THRESHOLDS
|
||||
# QUESTS
|
||||
# COMBAT
|
||||
# PALADIN BUFFS STRATEGIES
|
||||
# GREATER BUFFS STRATEGIES
|
||||
# CHEATS
|
||||
# SPELLS
|
||||
# FLIGHTPATH
|
||||
# PROFESSIONS
|
||||
# RANDOMBOT-SPECIFIC SETTINGS
|
||||
# GENERAL
|
||||
# LEVELS
|
||||
@@ -44,7 +45,7 @@
|
||||
# HUNTER
|
||||
# ROGUE
|
||||
# PRIEST
|
||||
# DEATHKNIGHT
|
||||
# DEATH KNIGHT
|
||||
# SHAMAN
|
||||
# MAGE
|
||||
# WARLOCK
|
||||
@@ -55,7 +56,7 @@
|
||||
# HUNTER
|
||||
# ROGUE
|
||||
# PRIEST
|
||||
# DEATHKNIGHT
|
||||
# DEATH KNIGHT
|
||||
# SHAMAN
|
||||
# MAGE
|
||||
# WARLOCK
|
||||
@@ -90,17 +91,20 @@ AiPlayerbot.MinRandomBots = 500
|
||||
AiPlayerbot.MaxRandomBots = 500
|
||||
|
||||
# Randombot accounts
|
||||
# If you are not using any expansion at all, you may have to set this manually, in which case please ensure that RandomBotAccountCount is at least greater than (MaxRandomBots / 10 + AddClassAccountPoolSize)
|
||||
# If you are not using any expansion at all, you may have to set this manually, in which case please
|
||||
# ensure that RandomBotAccountCount is at least greater than (MaxRandomBots / 10 + AddClassAccountPoolSize)
|
||||
# Default: 0 (automatic)
|
||||
AiPlayerbot.RandomBotAccountCount = 0
|
||||
|
||||
# Delete all randombot accounts
|
||||
# To apply this, set the number to 1 and run the Worldserver. Once deletion is complete, if you would like to recreate randombots, set the number back to 0 and rerun the Worldserver.
|
||||
# To apply this, set the number to 1 and run the Worldserver. Once deletion is complete, if you would
|
||||
# like to recreate randombots, set the number back to 0 and rerun the Worldserver.
|
||||
AiPlayerbot.DeleteRandomBotAccounts = 0
|
||||
|
||||
# Disable randombots when no real players are logged in
|
||||
# Default: 0 (randombots will login when server starts)
|
||||
# If enabled, randombots will only log in 30 seconds (default) after a real player logs in, and will log out 300 seconds (default) after all real players log out
|
||||
# If enabled, randombots will only log in 30 seconds (default) after a real player logs in, and will
|
||||
# log out 300 seconds (default) after all real players log out
|
||||
AiPlayerbot.DisabledWithoutRealPlayer = 0
|
||||
AiPlayerbot.DisabledWithoutRealPlayerLoginDelay = 30
|
||||
AiPlayerbot.DisabledWithoutRealPlayerLogoutDelay = 300
|
||||
@@ -152,7 +156,8 @@ AiPlayerbot.AllowGuildBots = 1
|
||||
AiPlayerbot.AllowTrustedAccountBots = 1
|
||||
|
||||
# Randombots will create guilds with nearby randombots
|
||||
# Note: currently, randombots will not invite more bots after a guild is created (i.e., randombot guilds will have only the 10 initial randombots needed to sign the charter)
|
||||
# Note: currently, randombots will not invite more bots after a guild is created,
|
||||
# meaning randombot guilds will have only the 10 initial randombots needed to sign the charter
|
||||
# Default: 0 (disabled)
|
||||
AiPlayerbot.RandomBotGuildNearby = 0
|
||||
|
||||
@@ -186,7 +191,8 @@ AiPlayerbot.AutoInitOnly = 0
|
||||
# Default: 1.0 (same with the player)
|
||||
AiPlayerbot.AutoInitEquipLevelLimitRatio = 1.0
|
||||
|
||||
# Bot automatically trains spells when talking to trainer (yes = train all available spells as long as the bot has the money, free = auto trains with no money cost, no = only list spells)
|
||||
# Bot automatically trains spells when talking to trainer
|
||||
# yes = train all available spells as long as the bot has the money, free = auto trains with no money cost, no = only list spells
|
||||
AiPlayerbot.AutoTrainSpells = yes
|
||||
|
||||
#
|
||||
@@ -263,7 +269,7 @@ AiPlayerbot.UseFastFlyMountAtMinLevel = 70
|
||||
AiPlayerbot.RandomBotShowHelmet = 1
|
||||
AiPlayerbot.RandomBotShowCloak = 1
|
||||
|
||||
# Randombots and altbots automatically equip upgrades (bots will equip any item obtained from looting or a quest if they are sufficient upgrades)
|
||||
# Randombots and altbots automatically equip any items in their inventory that are sufficient upgrades
|
||||
# Default: 1 (enabled)
|
||||
AiPlayerbot.AutoEquipUpgradeLoot = 1
|
||||
|
||||
@@ -311,7 +317,8 @@ AiPlayerbot.GlobalCooldown = 500
|
||||
# Max wait time when moving
|
||||
AiPlayerbot.MaxWaitForMove = 5000
|
||||
|
||||
# Disable use of MoveSplinePath for bot movement, will result in more erratic bot movement but means stun/snare/root/etc will work on bots (they wont reliably work when MoveSplinePath is enabled, though slowing effects still work ok)
|
||||
# Enable/disable use of MoveSplinePath for bot movement
|
||||
# Disabling will result in more erratic movement but is required for stuns, snares, and roots to work on bots
|
||||
# Default: 0 - MoveSplinePath enabled
|
||||
# 1 - MoveSplinePath disabled in BG/Arena only
|
||||
# 2 - MoveSplinePath disabled everywhere
|
||||
@@ -405,10 +412,11 @@ AiPlayerbot.HighMana = 65
|
||||
#
|
||||
#
|
||||
|
||||
# Bots pick their quest rewards (yes = picks the most useful item, no = list all rewards, ask = pick useful item and lists if multiple)
|
||||
# Bots pick their quest rewards
|
||||
# yes = picks the most useful item, no = list all rewards, ask = pick useful item and lists if multiple
|
||||
AiPlayerbot.AutoPickReward = yes
|
||||
|
||||
# Sync quests with player (Bots will complete quests the moment you hand them in and will not loot quest items.)
|
||||
# Sync quests with player (bots will complete quests the moment you hand them in and will not loot quest items.)
|
||||
# Default: 1 (enabled)
|
||||
AiPlayerbot.SyncQuestWithPlayer = 1
|
||||
|
||||
@@ -433,7 +441,7 @@ AiPlayerbot.DropObsoleteQuests = 1
|
||||
# Auto add dungeon/raid strategies when entering the instance if implemented
|
||||
AiPlayerbot.ApplyInstanceStrategies = 1
|
||||
|
||||
# Enable auto avoid aoe strategy (experimental)
|
||||
# Enable auto avoid aoe strategy
|
||||
# Default: 1 (enabled)
|
||||
AiPlayerbot.AutoAvoidAoe = 1
|
||||
|
||||
@@ -460,7 +468,7 @@ AiPlayerbot.FleeingEnabled = 1
|
||||
####################################################################################################
|
||||
|
||||
####################################################################################################
|
||||
# PALADIN BUFFS STRATEGIES
|
||||
# GREATER BUFFS STRATEGIES
|
||||
#
|
||||
#
|
||||
|
||||
@@ -483,12 +491,13 @@ AiPlayerbot.RPWarningCooldown = 30
|
||||
#
|
||||
|
||||
# Enable/Disable maintenance command
|
||||
# Learn all available spells and skills, refresh consumables, repair, enchant equipment and socket gems if bot's level is above AiPlayerbot.MinEnchantingBotLevel
|
||||
# Learn all available spells and skills, assign talent points, refresh consumables, repair, enchant equipment, socket gems, etc.
|
||||
# Applies if bot's level is above AiPlayerbot.MinEnchantingBotLevel
|
||||
# Default: 1 (enabled)
|
||||
AiPlayerbot.MaintenanceCommand = 1
|
||||
|
||||
# Enable/Disable specific maintenance command functionality for alt bots
|
||||
# Disable to prevent players from giving free bags, spells, skill levels etc to their alt bots
|
||||
# Disable to prevent players from giving free bags, spells, skill levels, etc. to their alt bots
|
||||
# Default: 1 (enabled)
|
||||
AiPlayerbot.AltMaintenanceAmmo = 1
|
||||
AiPlayerbot.AltMaintenanceFood = 1
|
||||
@@ -500,6 +509,7 @@ AiPlayerbot.AltMaintenanceBags = 1
|
||||
AiPlayerbot.AltMaintenanceMounts = 1
|
||||
AiPlayerbot.AltMaintenanceSkills = 1
|
||||
|
||||
# "Special Spells" consist of any spells listed in AiPlayerbot.RandomBotSpellIds and Death Gate for Death Knights
|
||||
AiPlayerbot.AltMaintenanceClassSpells = 1
|
||||
AiPlayerbot.AltMaintenanceAvailableSpells = 1
|
||||
AiPlayerbot.AltMaintenanceSpecialSpells = 1
|
||||
@@ -514,8 +524,8 @@ AiPlayerbot.AltMaintenanceReputation = 1
|
||||
AiPlayerbot.AltMaintenanceAttunementQuests = 1
|
||||
AiPlayerbot.AltMaintenanceKeyring = 1
|
||||
|
||||
|
||||
# Enable/Disable autogear command, which automatically upgrades bots' gear; the quality is limited by AutoGearQualityLimit and AutoGearScoreLimit
|
||||
# Enable/Disable autogear command, which automatically upgrades bots' gear
|
||||
# The quality is limited by AutoGearQualityLimit and AutoGearScoreLimit
|
||||
# Default: 1 (enabled)
|
||||
AiPlayerbot.AutoGearCommand = 1
|
||||
|
||||
@@ -579,6 +589,30 @@ AiPlayerbot.BotTaxiGapJitterMs = 100
|
||||
#
|
||||
####################################################################################################
|
||||
|
||||
####################################################################################################
|
||||
# PROFESSIONS
|
||||
# Note: Random bots currently do not get professions
|
||||
#
|
||||
|
||||
# Automatically adds the 'master fishing' strategy to bots that have the fishing skill when the bots master fishes.
|
||||
# Default: 1 (Enabled)
|
||||
AiPlayerbot.EnableFishingWithMaster = 1
|
||||
|
||||
# Distance from itself (in yards) that a bot with a master will search for water to fish
|
||||
AiPlayerbot.FishingDistanceFromMaster = 10.0
|
||||
|
||||
# Distance from itself (in yards) that a bot without a master will search for water to fish
|
||||
# Currently not relevant since masterless bots will not fish
|
||||
AiPlayerbot.FishingDistance = 40.0
|
||||
|
||||
# Distance from water (in yards) beyond which a bot will remove the 'master fishing' strategy
|
||||
AiPlayerbot.EndFishingWithMaster = 30.0
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
####################################################################################################
|
||||
|
||||
#######################################
|
||||
# #
|
||||
# RANDOMBOT-SPECIFIC SETTINGS #
|
||||
@@ -630,7 +664,7 @@ AiPlayerbot.RandomBotHordeRatio = 50
|
||||
AiPlayerbot.DisableDeathKnightLogin = 0
|
||||
|
||||
# Enable simulated expansion limitation for talents and glyphs
|
||||
# If enabled, limits talent trees to 5 rows plus the middle talent of the 6th row for bots until level 61
|
||||
# If enabled, limits talent trees to 5 rows plus the middle talent of the 6th row for bots until level 61
|
||||
# and 7 rows plus the middle talent of the 8th row for bots from level 61 until level 71
|
||||
# Default: 0 (disabled)
|
||||
AiPlayerbot.LimitTalentsExpansion = 0
|
||||
@@ -700,7 +734,7 @@ AiPlayerbot.RandomGearQualityLimit = 3
|
||||
# Max iLVL Phase 1(MC, Ony, ZG) = 78 | Phase 2(BWL) = 83 | Phase 2.5(AQ40) = 88 | Phase 3(Naxx40) = 92
|
||||
# TBC
|
||||
# Max iLVL Tier 4 = 120 | Tier 5 = 133 | Tier 6 = 164
|
||||
# Max iLVL Phase 1(Kara, Gruul, Mag) = 125 | Phase 1.5(ZA) = 138 | Phase 2(SC, TK) = 141 | Phase 3(Hyjal, BT) = 156 | Phase 4(Sunwell) = 164
|
||||
# Max iLVL Phase 1(Kara, Gruul, Mag) = 125 | Phase 2(SSC, TK, ZA) = 141 | Phase 3(Hyjal, BT) = 156 | Phase 4(Sunwell) = 164
|
||||
# Wotlk
|
||||
# Max iLVL Tier 7(10/25) = 200/213 | Tier 8(10/25) = 225/232 | Tier 9(10/25) = 232/245 | Tier 10(10/25/HC) = 251/264/290
|
||||
# Max iLVL Phase 1(Naxx) = 224 | Phase 2(Ulduar) = 245 | Phase 3(ToC) = 258 | Phase 4(ICC) = 290
|
||||
@@ -711,7 +745,8 @@ AiPlayerbot.RandomGearScoreLimit = 0
|
||||
# Default: 1 (enabled)
|
||||
AiPlayerbot.IncrementalGearInit = 1
|
||||
|
||||
# Set minimum level of bots that will enchant their equipment (if greater than RandomBotMaxlevel, bots will not enchant equipment)
|
||||
# Set minimum level of bots that will enchant and socket gems into their equipment with maintenance
|
||||
# If greater than RandomBotMaxlevel, bots will not automatically enchant equipment or socket gems
|
||||
# Default: 60
|
||||
AiPlayerbot.MinEnchantingBotLevel = 60
|
||||
|
||||
@@ -863,13 +898,15 @@ AiPlayerbot.OpenGoSpell = 6477
|
||||
#
|
||||
|
||||
# Additional randombot strategies
|
||||
# Strategies added here are applied to all randombots, in addition (or subtraction) to spec/role-based default strategies. These rules are processed after the defaults.
|
||||
# Strategies added here are applied to all randombots, in addition (or subtraction) to spec/role-based default strategies.
|
||||
# These rules are processed after the defaults.
|
||||
# Example: "+threat,-potions"
|
||||
AiPlayerbot.RandomBotCombatStrategies = ""
|
||||
AiPlayerbot.RandomBotNonCombatStrategies = ""
|
||||
|
||||
# Additional altbot strategies
|
||||
# Strategies added here are applied to all altbots, in addition (or subtraction) to spec/role-based default strategies. These rules are processed after the defaults.
|
||||
# Strategies added here are applied to all altbots, in addition (or subtraction) to spec/role-based default strategies.
|
||||
# These rules are processed after the defaults.
|
||||
AiPlayerbot.CombatStrategies = ""
|
||||
AiPlayerbot.NonCombatStrategies = ""
|
||||
|
||||
@@ -1185,7 +1222,7 @@ AiPlayerbot.DeleteRandomBotArenaTeams = 0
|
||||
AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,3951"
|
||||
|
||||
# PvP Restricted Areas (bots don't pvp)
|
||||
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754"
|
||||
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754,3786,3973"
|
||||
|
||||
# Improve reaction speeds in battlegrounds and arenas (may cause lag)
|
||||
AiPlayerbot.FastReactInBG = 1
|
||||
@@ -1455,7 +1492,7 @@ AiPlayerbot.PremadeSpecLink.5.5.80 = 50332031003--005323241223112003102311351
|
||||
####################################################################################################
|
||||
|
||||
####################################################################################################
|
||||
# DEATHKNIGHT
|
||||
# DEATH KNIGHT
|
||||
#
|
||||
#
|
||||
|
||||
@@ -1778,7 +1815,7 @@ AiPlayerbot.RandomClassSpecIndex.5.2 = 2
|
||||
####################################################################################################
|
||||
|
||||
####################################################################################################
|
||||
# DEATHKNIGHT
|
||||
# DEATH KNIGHT
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
DELETE FROM ai_playerbot_texts WHERE name IN (
|
||||
'pet_usage_error',
|
||||
'pet_no_pet_error',
|
||||
'pet_stance_report',
|
||||
'pet_no_target_error',
|
||||
'pet_target_dead_error',
|
||||
'pet_invalid_target_error',
|
||||
'pet_pvp_prohibited_error',
|
||||
'pet_attack_success',
|
||||
'pet_attack_failed',
|
||||
'pet_follow_success',
|
||||
'pet_stay_success',
|
||||
'pet_unknown_command_error',
|
||||
'pet_stance_set_success',
|
||||
'pet_type_pet',
|
||||
'pet_type_guardian',
|
||||
'pet_stance_aggressive',
|
||||
'pet_stance_defensive',
|
||||
'pet_stance_passive',
|
||||
'pet_stance_unknown'
|
||||
);
|
||||
|
||||
DELETE FROM ai_playerbot_texts_chance WHERE name IN (
|
||||
'pet_usage_error',
|
||||
'pet_no_pet_error',
|
||||
'pet_stance_report',
|
||||
'pet_no_target_error',
|
||||
'pet_target_dead_error',
|
||||
'pet_invalid_target_error',
|
||||
'pet_pvp_prohibited_error',
|
||||
'pet_attack_success',
|
||||
'pet_attack_failed',
|
||||
'pet_follow_success',
|
||||
'pet_stay_success',
|
||||
'pet_unknown_command_error',
|
||||
'pet_stance_set_success',
|
||||
'pet_type_pet',
|
||||
'pet_type_guardian',
|
||||
'pet_stance_aggressive',
|
||||
'pet_stance_defensive',
|
||||
'pet_stance_passive',
|
||||
'pet_stance_unknown'
|
||||
);
|
||||
|
||||
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
|
||||
(1717, 'pet_usage_error', "Usage: pet <aggressive|defensive|passive|stance|attack|follow|stay>", 0, 0,
|
||||
"사용법: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Utilisation: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Verwendung: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"用法: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"用法: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Uso: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Uso: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Использование: pet <aggressive|defensive|passive|stance|attack|follow|stay>"),
|
||||
|
||||
(1718, 'pet_no_pet_error', "You have no pet or guardian pet.", 0, 0,
|
||||
"펫이나 수호자 펫이 없습니다.",
|
||||
"Vous n'avez pas de familier ou gardien.",
|
||||
"Du hast kein Tier oder Wächter.",
|
||||
"你没有宠物或守护者宠物。",
|
||||
"你沒有寵物或守護者寵物。",
|
||||
"No tienes mascota o mascota guardián.",
|
||||
"No tienes mascota o mascota guardián.",
|
||||
"У вас нет питомца или защитника."),
|
||||
|
||||
(1719, 'pet_stance_report', "Current stance of %type \"%name\": %stance.", 0, 0,
|
||||
"%type \"%name\"의 현재 태세: %stance.",
|
||||
"Position actuelle du %type \"%name\": %stance.",
|
||||
"Aktuelle Haltung des %type \"%name\": %stance.",
|
||||
"%type \"%name\" 的当前姿态: %stance。",
|
||||
"%type \"%name\" 的當前姿態: %stance。",
|
||||
"Postura actual del %type \"%name\": %stance.",
|
||||
"Postura actual del %type \"%name\": %stance.",
|
||||
"Текущая позиция %type \"%name\": %stance."),
|
||||
|
||||
(1720, 'pet_no_target_error', "No valid target selected by master.", 0, 0,
|
||||
"주인이 유효한 대상을 선택하지 않았습니다.",
|
||||
"Aucune cible valide sélectionnée par le maître.",
|
||||
"Kein gültiges Ziel vom Meister ausgewählt.",
|
||||
"主人未选择有效目标。",
|
||||
"主人未選擇有效目標。",
|
||||
"No hay objetivo válido seleccionado por el maestro.",
|
||||
"No hay objetivo válido seleccionado por el maestro.",
|
||||
"Хозяин не выбрал действительную цель."),
|
||||
|
||||
(1721, 'pet_target_dead_error', "Target is not alive.", 0, 0,
|
||||
"대상이 살아있지 않습니다.",
|
||||
"La cible n'est pas vivante.",
|
||||
"Das Ziel ist nicht am Leben.",
|
||||
"目标未存活。",
|
||||
"目標未存活。",
|
||||
"El objetivo no está vivo.",
|
||||
"El objetivo no está vivo.",
|
||||
"Цель не жива."),
|
||||
|
||||
(1722, 'pet_invalid_target_error', "Target is not a valid attack target for the bot.", 0, 0,
|
||||
"대상이 봇에게 유효한 공격 대상이 아닙니다.",
|
||||
"La cible n'est pas une cible d'attaque valide pour le bot.",
|
||||
"Das Ziel ist kein gültiges Angriffsziel für den Bot.",
|
||||
"目标不是机器人的有效攻击目标。",
|
||||
"目標不是機器人的有效攻擊目標。",
|
||||
"El objetivo no es un objetivo de ataque válido para el bot.",
|
||||
"El objetivo no es un objetivo de ataque válido para el bot.",
|
||||
"Цель не является допустимой целью атаки для бота."),
|
||||
|
||||
(1723, 'pet_pvp_prohibited_error', "I cannot command my pet to attack players in PvP prohibited areas.", 0, 0,
|
||||
"PvP 금지 지역에서는 펫에게 플레이어 공격 명령을 내릴 수 없습니다.",
|
||||
"Je ne peux pas commander à mon familier d'attaquer des joueurs dans les zones où le PvP est interdit.",
|
||||
"Ich kann meinem Tier nicht befehlen, Spieler in PvP-verbotenen Gebieten anzugreifen.",
|
||||
"我不能命令我的宠物在禁止PvP的区域攻击玩家。",
|
||||
"我不能命令我的寵物在禁止PvP的區域攻擊玩家。",
|
||||
"No puedo ordenar a mi mascota atacar jugadores en áreas donde el PvP está prohibido.",
|
||||
"No puedo ordenar a mi mascota atacar jugadores en áreas donde el PvP está prohibido.",
|
||||
"Я не могу приказать своему питомцу атаковать игроков в зонах, где PvP запрещено."),
|
||||
|
||||
(1724, 'pet_attack_success', "Pet commanded to attack your target.", 0, 0,
|
||||
"펫이 당신의 대상을 공격하도록 명령했습니다.",
|
||||
"Le familier a reçu l'ordre d'attaquer votre cible.",
|
||||
"Tier wurde befohlen, dein Ziel anzugreifen.",
|
||||
"宠物已命令攻击你的目标。",
|
||||
"寵物已命令攻擊你的目標。",
|
||||
"Mascota ordenada a atacar tu objetivo.",
|
||||
"Mascota ordenada a atacar tu objetivo.",
|
||||
"Питомцу приказано атаковать вашу цель."),
|
||||
|
||||
(1725, 'pet_attack_failed', "Pet did not attack. (Already attacking or unable to attack target)", 0, 0,
|
||||
"펫이 공격하지 않았습니다. (이미 공격 중이거나 대상 공격 불가)",
|
||||
"Le familier n'a pas attaqué. (Attaque déjà en cours ou impossible d'attaquer la cible)",
|
||||
"Tier hat nicht angegriffen. (Greift bereits an oder kann Ziel nicht angreifen)",
|
||||
"宠物未攻击。(已在攻击或无法攻击目标)",
|
||||
"寵物未攻擊。(已在攻擊或無法攻擊目標)",
|
||||
"La mascota no atacó. (Ya está atacando o no puede atacar al objetivo)",
|
||||
"La mascota no atacó. (Ya está atacando o no puede atacar al objetivo)",
|
||||
"Питомец не атаковал. (Уже атакует или не может атаковать цель)"),
|
||||
|
||||
(1726, 'pet_follow_success', "Pet commanded to follow.", 0, 0,
|
||||
"펫이 따라오도록 명령했습니다.",
|
||||
"Le familier a reçu l'ordre de suivre.",
|
||||
"Tier wurde befohlen zu folgen.",
|
||||
"宠物已命令跟随。",
|
||||
"寵物已命令跟隨。",
|
||||
"Mascota ordenada a seguir.",
|
||||
"Mascota ordenada a seguir.",
|
||||
"Питомцу приказано следовать."),
|
||||
|
||||
(1727, 'pet_stay_success', "Pet commanded to stay.", 0, 0,
|
||||
"펫이 머물도록 명령했습니다.",
|
||||
"Le familier a reçu l'ordre de rester.",
|
||||
"Tier wurde befohlen zu bleiben.",
|
||||
"宠物已命令停留。",
|
||||
"寵物已命令停留。",
|
||||
"Mascota ordenada a quedarse.",
|
||||
"Mascota ordenada a quedarse.",
|
||||
"Питомцу приказано остаться."),
|
||||
|
||||
(1728, 'pet_unknown_command_error', "Unknown pet command: %param. Use: pet <aggressive|defensive|passive|stance|attack|follow|stay>", 0, 0,
|
||||
"알 수 없는 펫 명령: %param. 사용법: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Commande de familier inconnue: %param. Utilisation: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Unbekannter Tierbefehl: %param. Verwendung: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"未知宠物命令: %param。用法: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"未知寵物命令: %param。用法: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Comando de mascota desconocido: %param. Uso: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Comando de mascota desconocido: %param. Uso: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Неизвестная команда питомца: %param. Использование: pet <aggressive|defensive|passive|stance|attack|follow|stay>"),
|
||||
|
||||
(1729, 'pet_stance_set_success', "Pet stance set to %stance.", 0, 0,
|
||||
"펫 태세가 %stance(으)로 설정되었습니다.",
|
||||
"Position du familier définie sur %stance.",
|
||||
"Tierhaltung auf %stance gesetzt.",
|
||||
"宠物姿态设置为 %stance。",
|
||||
"寵物姿態設置為 %stance。",
|
||||
"Postura de mascota establecida en %stance.",
|
||||
"Postura de mascota establecida en %stance.",
|
||||
"Позиция питомца установлена на %stance."),
|
||||
|
||||
(1730, 'pet_type_pet', "pet", 0, 0,
|
||||
"펫",
|
||||
"familier",
|
||||
"Tier",
|
||||
"宠物",
|
||||
"寵物",
|
||||
"mascota",
|
||||
"mascota",
|
||||
"питомец"),
|
||||
|
||||
(1731, 'pet_type_guardian', "guardian", 0, 0,
|
||||
"수호자",
|
||||
"gardien",
|
||||
"Wächter",
|
||||
"守护者",
|
||||
"守護者",
|
||||
"guardián",
|
||||
"guardián",
|
||||
"защитник"),
|
||||
|
||||
(1732, 'pet_stance_aggressive', "aggressive", 0, 0,
|
||||
"공격적",
|
||||
"agressif",
|
||||
"aggressiv",
|
||||
"进攻",
|
||||
"進攻",
|
||||
"agresivo",
|
||||
"agresivo",
|
||||
"агрессивная"),
|
||||
|
||||
(1733, 'pet_stance_defensive', "defensive", 0, 0,
|
||||
"방어적",
|
||||
"défensif",
|
||||
"defensiv",
|
||||
"防御",
|
||||
"防禦",
|
||||
"defensivo",
|
||||
"defensivo",
|
||||
"защитная"),
|
||||
|
||||
(1734, 'pet_stance_passive', "passive", 0, 0,
|
||||
"수동적",
|
||||
"passif",
|
||||
"passiv",
|
||||
"被动",
|
||||
"被動",
|
||||
"pasivo",
|
||||
"pasivo",
|
||||
"пассивная"),
|
||||
|
||||
(1735, 'pet_stance_unknown', "unknown", 0, 0,
|
||||
"알 수 없음",
|
||||
"inconnu",
|
||||
"unbekannt",
|
||||
"未知",
|
||||
"未知",
|
||||
"desconocido",
|
||||
"desconocido",
|
||||
"неизвестная");
|
||||
|
||||
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES
|
||||
('pet_usage_error', 100),
|
||||
('pet_no_pet_error', 100),
|
||||
('pet_stance_report', 100),
|
||||
('pet_no_target_error', 100),
|
||||
('pet_target_dead_error', 100),
|
||||
('pet_invalid_target_error', 100),
|
||||
('pet_pvp_prohibited_error', 100),
|
||||
('pet_attack_success', 100),
|
||||
('pet_attack_failed', 100),
|
||||
('pet_follow_success', 100),
|
||||
('pet_stay_success', 100),
|
||||
('pet_unknown_command_error', 100),
|
||||
('pet_stance_set_success', 100),
|
||||
('pet_type_pet', 100),
|
||||
('pet_type_guardian', 100),
|
||||
('pet_stance_aggressive', 100),
|
||||
('pet_stance_defensive', 100),
|
||||
('pet_stance_passive', 100),
|
||||
('pet_stance_unknown', 100);
|
||||
@@ -0,0 +1,15 @@
|
||||
DELETE FROM ai_playerbot_texts WHERE name IN ('no_fishing_pole_error');
|
||||
DELETE FROM ai_playerbot_texts_chance WHERE name IN ('no_fishing_pole_error');
|
||||
|
||||
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
|
||||
(1736, 'no_fishing_pole_error', "I don't have a Fishing Pole", 0, 0,
|
||||
"낚싯대가 없습니다",
|
||||
"Je n’ai pas de canne à pêche",
|
||||
"Ich habe keine Angelrute",
|
||||
"我沒有釣魚竿",
|
||||
"我没有钓鱼竿",
|
||||
"No tengo una caña de pescar",
|
||||
"No tengo una caña de pescar",
|
||||
"У меня нет удочки");
|
||||
|
||||
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('no_fishing_pole_error', 100);
|
||||
@@ -140,37 +140,37 @@ BotRoles AiFactory::GetPlayerRoles(Player* player)
|
||||
switch (player->getClass())
|
||||
{
|
||||
case CLASS_PRIEST:
|
||||
if (tab == 2)
|
||||
if (tab == PRIEST_TAB_SHADOW)
|
||||
role = BOT_ROLE_DPS;
|
||||
else
|
||||
role = BOT_ROLE_HEALER;
|
||||
break;
|
||||
case CLASS_SHAMAN:
|
||||
if (tab == 2)
|
||||
if (tab == SHAMAN_TAB_RESTORATION)
|
||||
role = BOT_ROLE_HEALER;
|
||||
else
|
||||
role = BOT_ROLE_DPS;
|
||||
break;
|
||||
case CLASS_WARRIOR:
|
||||
if (tab == 2)
|
||||
if (tab == WARRIOR_TAB_PROTECTION)
|
||||
role = BOT_ROLE_TANK;
|
||||
else
|
||||
role = BOT_ROLE_DPS;
|
||||
break;
|
||||
case CLASS_PALADIN:
|
||||
if (tab == 0)
|
||||
if (tab == PALADIN_TAB_HOLY)
|
||||
role = BOT_ROLE_HEALER;
|
||||
else if (tab == 1)
|
||||
else if (tab == PALADIN_TAB_PROTECTION)
|
||||
role = BOT_ROLE_TANK;
|
||||
else if (tab == 2)
|
||||
else if (tab == PALADIN_TAB_RETRIBUTION)
|
||||
role = BOT_ROLE_DPS;
|
||||
break;
|
||||
case CLASS_DRUID:
|
||||
if (tab == 0)
|
||||
if (tab == DRUID_TAB_BALANCE)
|
||||
role = BOT_ROLE_DPS;
|
||||
else if (tab == 1)
|
||||
else if (tab == DRUID_TAB_FERAL)
|
||||
role = (BotRoles)(BOT_ROLE_TANK | BOT_ROLE_DPS);
|
||||
else if (tab == 2)
|
||||
else if (tab == DRUID_TAB_RESTORATION)
|
||||
role = BOT_ROLE_HEALER;
|
||||
break;
|
||||
default:
|
||||
@@ -188,84 +188,83 @@ std::string AiFactory::GetPlayerSpecName(Player* player)
|
||||
switch (player->getClass())
|
||||
{
|
||||
case CLASS_PRIEST:
|
||||
if (tab == 2)
|
||||
if (tab == PRIEST_TAB_SHADOW)
|
||||
specName = "shadow";
|
||||
else if (tab == 1)
|
||||
else if (tab == PRIEST_TAB_HOLY)
|
||||
specName = "holy";
|
||||
else
|
||||
specName = "disc";
|
||||
;
|
||||
break;
|
||||
case CLASS_SHAMAN:
|
||||
if (tab == 2)
|
||||
if (tab == SHAMAN_TAB_RESTORATION)
|
||||
specName = "resto";
|
||||
else if (tab == 1)
|
||||
else if (tab == SHAMAN_TAB_ENHANCEMENT)
|
||||
specName = "enhance";
|
||||
else
|
||||
specName = "elem";
|
||||
break;
|
||||
case CLASS_WARRIOR:
|
||||
if (tab == 2)
|
||||
if (tab == WARRIOR_TAB_PROTECTION)
|
||||
specName = "prot";
|
||||
else if (tab == 1)
|
||||
else if (tab == WARRIOR_TAB_FURY)
|
||||
specName = "fury";
|
||||
else
|
||||
specName = "arms";
|
||||
break;
|
||||
case CLASS_PALADIN:
|
||||
if (tab == 0)
|
||||
if (tab == PALADIN_TAB_HOLY)
|
||||
specName = "holy";
|
||||
else if (tab == 1)
|
||||
else if (tab == PALADIN_TAB_PROTECTION)
|
||||
specName = "prot";
|
||||
else if (tab == 2)
|
||||
else if (tab == PALADIN_TAB_RETRIBUTION)
|
||||
specName = "retrib";
|
||||
break;
|
||||
case CLASS_DRUID:
|
||||
if (tab == 0)
|
||||
if (tab == DRUID_TAB_BALANCE)
|
||||
specName = "balance";
|
||||
else if (tab == 1)
|
||||
else if (tab == DRUID_TAB_FERAL)
|
||||
specName = "feraldps";
|
||||
else if (tab == 2)
|
||||
else if (tab == DRUID_TAB_RESTORATION)
|
||||
specName = "resto";
|
||||
break;
|
||||
case CLASS_ROGUE:
|
||||
if (tab == 0)
|
||||
if (tab == ROGUE_TAB_ASSASSINATION)
|
||||
specName = "assas";
|
||||
else if (tab == 1)
|
||||
else if (tab == ROGUE_TAB_COMBAT)
|
||||
specName = "combat";
|
||||
else if (tab == 2)
|
||||
else if (tab == ROGUE_TAB_SUBTLETY)
|
||||
specName = "subtle";
|
||||
break;
|
||||
case CLASS_HUNTER:
|
||||
if (tab == 0)
|
||||
if (tab == HUNTER_TAB_BEAST_MASTERY)
|
||||
specName = "beast";
|
||||
else if (tab == 1)
|
||||
else if (tab == HUNTER_TAB_MARKSMANSHIP)
|
||||
specName = "marks";
|
||||
else if (tab == 2)
|
||||
else if (tab == HUNTER_TAB_SURVIVAL)
|
||||
specName = "surv";
|
||||
break;
|
||||
case CLASS_DEATH_KNIGHT:
|
||||
if (tab == 0)
|
||||
if (tab == DEATH_KNIGHT_TAB_BLOOD)
|
||||
specName = "blooddps";
|
||||
else if (tab == 1)
|
||||
else if (tab == DEATH_KNIGHT_TAB_FROST)
|
||||
specName = "frostdps";
|
||||
else if (tab == 2)
|
||||
else if (tab == DEATH_KNIGHT_TAB_UNHOLY)
|
||||
specName = "unholydps";
|
||||
break;
|
||||
case CLASS_MAGE:
|
||||
if (tab == 0)
|
||||
if (tab == MAGE_TAB_ARCANE)
|
||||
specName = "arcane";
|
||||
else if (tab == 1)
|
||||
else if (tab == MAGE_TAB_FIRE)
|
||||
specName = "fire";
|
||||
else if (tab == 2)
|
||||
else if (tab == MAGE_TAB_FROST)
|
||||
specName = "frost";
|
||||
break;
|
||||
case CLASS_WARLOCK:
|
||||
if (tab == 0)
|
||||
if (tab == WARLOCK_TAB_AFFLICTION)
|
||||
specName = "afflic";
|
||||
else if (tab == 1)
|
||||
else if (tab == WARLOCK_TAB_DEMONOLOGY)
|
||||
specName = "demo";
|
||||
else if (tab == 2)
|
||||
else if (tab == WARLOCK_TAB_DESTRUCTION)
|
||||
specName = "destro";
|
||||
break;
|
||||
default:
|
||||
@@ -280,147 +279,124 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
uint8 tab = GetPlayerSpecTab(player);
|
||||
|
||||
if (!player->InBattleground())
|
||||
{
|
||||
engine->addStrategiesNoInit("racials", "chat", "default", "cast time", "potions", "duel", "boost", nullptr);
|
||||
}
|
||||
|
||||
if (sPlayerbotAIConfig->autoAvoidAoe && facade->HasRealPlayerMaster())
|
||||
{
|
||||
engine->addStrategy("avoid aoe", false);
|
||||
}
|
||||
|
||||
engine->addStrategy("formation", false);
|
||||
|
||||
switch (player->getClass())
|
||||
{
|
||||
case CLASS_PRIEST:
|
||||
if (tab == 2)
|
||||
{
|
||||
if (tab == PRIEST_TAB_SHADOW)
|
||||
engine->addStrategiesNoInit("dps", "shadow debuff", "shadow aoe", nullptr);
|
||||
}
|
||||
else if (tab == PRIEST_TAB_DISCIPLINE)
|
||||
{
|
||||
engine->addStrategiesNoInit("heal", nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
engine->addStrategiesNoInit("holy heal", nullptr);
|
||||
}
|
||||
|
||||
engine->addStrategiesNoInit("dps assist", "cure", nullptr);
|
||||
break;
|
||||
case CLASS_MAGE:
|
||||
if (tab == 0) // Arcane
|
||||
if (tab == MAGE_TAB_ARCANE)
|
||||
engine->addStrategiesNoInit("arcane", nullptr);
|
||||
else if (tab == 1) // Fire
|
||||
else if (tab == MAGE_TAB_FIRE)
|
||||
{
|
||||
if (player->HasSpell(44614) /*Frostfire Bolt*/ && player->HasAura(15047) /*Ice Shards*/)
|
||||
{
|
||||
engine->addStrategiesNoInit("frostfire", nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
engine->addStrategiesNoInit("fire", nullptr);
|
||||
}
|
||||
}
|
||||
else // Frost
|
||||
else
|
||||
engine->addStrategiesNoInit("frost", nullptr);
|
||||
|
||||
engine->addStrategiesNoInit("dps", "dps assist", "cure", "aoe", nullptr);
|
||||
break;
|
||||
case CLASS_WARRIOR:
|
||||
if (tab == 2)
|
||||
if (tab == WARRIOR_TAB_PROTECTION)
|
||||
engine->addStrategiesNoInit("tank", "tank assist", "aoe", nullptr);
|
||||
else if (tab == 0 || !player->HasSpell(1680)) // Whirlwind
|
||||
engine->addStrategiesNoInit("arms", "aoe", "dps assist", /*"behind",*/ nullptr);
|
||||
else if (tab == WARRIOR_TAB_ARMS || !player->HasSpell(1680)) // Whirlwind
|
||||
engine->addStrategiesNoInit("arms", "aoe", "dps assist", nullptr);
|
||||
else
|
||||
engine->addStrategiesNoInit("fury", "aoe", "dps assist", /*"behind",*/ nullptr);
|
||||
engine->addStrategiesNoInit("fury", "aoe", "dps assist", nullptr);
|
||||
break;
|
||||
case CLASS_SHAMAN:
|
||||
if (tab == 0) // Elemental
|
||||
if (tab == SHAMAN_TAB_ELEMENTAL)
|
||||
engine->addStrategiesNoInit("ele", "stoneskin", "wrath", "mana spring", "wrath of air", nullptr);
|
||||
else if (tab == 2) // Restoration
|
||||
else if (tab == SHAMAN_TAB_RESTORATION)
|
||||
engine->addStrategiesNoInit("resto", "stoneskin", "flametongue", "mana spring", "wrath of air", nullptr);
|
||||
else // Enhancement
|
||||
else
|
||||
engine->addStrategiesNoInit("enh", "strength of earth", "magma", "healing stream", "windfury", nullptr);
|
||||
|
||||
engine->addStrategiesNoInit("dps assist", "cure", "aoe", nullptr);
|
||||
break;
|
||||
case CLASS_PALADIN:
|
||||
if (tab == 1)
|
||||
if (tab == PALADIN_TAB_PROTECTION)
|
||||
engine->addStrategiesNoInit("tank", "tank assist", "bthreat", "barmor", "cure", nullptr);
|
||||
else if (tab == 0)
|
||||
else if (tab == PALADIN_TAB_HOLY)
|
||||
engine->addStrategiesNoInit("heal", "dps assist", "cure", "bcast", nullptr);
|
||||
else
|
||||
engine->addStrategiesNoInit("dps", "dps assist", "cure", "baoe", nullptr);
|
||||
|
||||
break;
|
||||
case CLASS_DRUID:
|
||||
if (tab == 0)
|
||||
if (tab == DRUID_TAB_BALANCE)
|
||||
{
|
||||
engine->addStrategiesNoInit("caster", "cure", "caster aoe", "dps assist", nullptr);
|
||||
engine->addStrategy("caster debuff", false);
|
||||
}
|
||||
else if (tab == 2)
|
||||
else if (tab == DRUID_TAB_RESTORATION)
|
||||
engine->addStrategiesNoInit("heal", "cure", "dps assist", nullptr);
|
||||
else
|
||||
{
|
||||
if (player->HasSpell(768) /*cat form*/&& !player->HasAura(16931) /*thick hide*/)
|
||||
{
|
||||
if (player->HasSpell(768) /*cat form*/ && !player->HasAura(16931) /*thick hide*/)
|
||||
engine->addStrategiesNoInit("cat", "dps assist", nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
engine->addStrategiesNoInit("bear", "tank assist", nullptr);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CLASS_HUNTER:
|
||||
if (tab == 0) // Beast Mastery
|
||||
if (tab == HUNTER_TAB_BEAST_MASTERY)
|
||||
engine->addStrategiesNoInit("bm", nullptr);
|
||||
else if (tab == 1) // Marksmanship
|
||||
else if (tab == HUNTER_TAB_MARKSMANSHIP)
|
||||
engine->addStrategiesNoInit("mm", nullptr);
|
||||
else if (tab == 2) // Survival
|
||||
else
|
||||
engine->addStrategiesNoInit("surv", nullptr);
|
||||
|
||||
engine->addStrategiesNoInit("cc", "dps assist", "aoe", nullptr);
|
||||
break;
|
||||
case CLASS_ROGUE:
|
||||
if (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY)
|
||||
{
|
||||
engine->addStrategiesNoInit("melee", "dps assist", "aoe", nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
engine->addStrategiesNoInit("dps", "dps assist", "aoe", nullptr);
|
||||
}
|
||||
break;
|
||||
case CLASS_WARLOCK:
|
||||
if (tab == 0) // Affliction
|
||||
if (tab == WARLOCK_TAB_AFFLICTION)
|
||||
engine->addStrategiesNoInit("affli", "curse of agony", nullptr);
|
||||
else if (tab == 1) // Demonology
|
||||
else if (tab == WARLOCK_TAB_DEMONOLOGY)
|
||||
engine->addStrategiesNoInit("demo", "curse of agony", "meta melee", nullptr);
|
||||
else if (tab == 2) // Destruction
|
||||
else
|
||||
engine->addStrategiesNoInit("destro", "curse of elements", nullptr);
|
||||
|
||||
engine->addStrategiesNoInit("cc", "dps assist", "aoe", nullptr);
|
||||
break;
|
||||
|
||||
case CLASS_DEATH_KNIGHT:
|
||||
if (tab == 0)
|
||||
if (tab == DEATH_KNIGHT_TAB_BLOOD)
|
||||
engine->addStrategiesNoInit("blood", "tank assist", nullptr);
|
||||
else if (tab == 1)
|
||||
else if (tab == DEATH_KNIGHT_TAB_FROST)
|
||||
engine->addStrategiesNoInit("frost", "frost aoe", "dps assist", nullptr);
|
||||
else
|
||||
engine->addStrategiesNoInit("unholy", "unholy aoe", "dps assist", nullptr);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (PlayerbotAI::IsTank(player, true))
|
||||
{
|
||||
engine->addStrategy("tank face", false);
|
||||
}
|
||||
|
||||
if (PlayerbotAI::IsMelee(player, true) && PlayerbotAI::IsDps(player, true))
|
||||
{
|
||||
engine->addStrategy("behind", false);
|
||||
}
|
||||
|
||||
if (PlayerbotAI::IsHeal(player, true))
|
||||
{
|
||||
if (sPlayerbotAIConfig->autoSaveMana)
|
||||
@@ -428,6 +404,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
if (!sPlayerbotAIConfig->IsRestrictedHealerDPSMap(player->GetMapId()))
|
||||
engine->addStrategy("healer dps", false);
|
||||
}
|
||||
|
||||
if (facade->IsRealPlayer() || sRandomPlayerbotMgr->IsRandomBot(player))
|
||||
{
|
||||
if (!player->GetGroup())
|
||||
@@ -436,15 +413,13 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
engine->addStrategy("boost", false);
|
||||
engine->addStrategy("dps assist", false);
|
||||
engine->removeStrategy("threat", false);
|
||||
// engine-
|
||||
|
||||
switch (player->getClass())
|
||||
{
|
||||
case CLASS_PRIEST:
|
||||
{
|
||||
if (tab != PRIEST_TAB_SHADOW)
|
||||
{
|
||||
engine->addStrategiesNoInit("holy dps", "shadow debuff", "shadow aoe", nullptr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CLASS_DRUID:
|
||||
@@ -459,17 +434,13 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
case CLASS_SHAMAN:
|
||||
{
|
||||
if (tab == SHAMAN_TAB_RESTORATION)
|
||||
{
|
||||
engine->addStrategiesNoInit("caster", "caster aoe", "bmana", nullptr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CLASS_PALADIN:
|
||||
{
|
||||
if (tab == PALADIN_TAB_HOLY)
|
||||
{
|
||||
engine->addStrategiesNoInit("dps", "dps assist", "baoe", nullptr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -478,13 +449,9 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
}
|
||||
}
|
||||
if (sRandomPlayerbotMgr->IsRandomBot(player))
|
||||
{
|
||||
engine->ChangeStrategy(sPlayerbotAIConfig->randomBotCombatStrategies);
|
||||
}
|
||||
else
|
||||
{
|
||||
engine->ChangeStrategy(sPlayerbotAIConfig->combatStrategies);
|
||||
}
|
||||
|
||||
// Battleground switch
|
||||
if (player->InBattleground() && player->GetBattleground())
|
||||
@@ -511,23 +478,15 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
if (player->InArena())
|
||||
{
|
||||
engine->addStrategy("arena", false);
|
||||
engine->addStrategiesNoInit("boost", "racials", "chat", "default", "aoe", "cast time", "dps assist", nullptr);
|
||||
}
|
||||
else
|
||||
engine->addStrategiesNoInit("boost", "racials", "chat", "default", "aoe", "potions", "cast time", "dps assist", nullptr);
|
||||
|
||||
engine->addStrategiesNoInit("boost", "racials", "chat", "default", "aoe", "potions", "cast time", "dps assist",
|
||||
nullptr);
|
||||
engine->removeStrategy("custom::say", false);
|
||||
engine->removeStrategy("flee", false);
|
||||
engine->removeStrategy("threat", false);
|
||||
engine->addStrategy("boost", false);
|
||||
|
||||
// if ((player->getClass() == CLASS_DRUID && tab == 2) || (player->getClass() == CLASS_SHAMAN && tab == 2))
|
||||
// engine->addStrategiesNoInit("caster", "caster aoe", nullptr);
|
||||
|
||||
// if (player->getClass() == CLASS_DRUID && tab == 1)
|
||||
// engine->addStrategiesNoInit(/*"behind",*/ "dps", nullptr);
|
||||
|
||||
// if (player->getClass() == CLASS_ROGUE)
|
||||
// engine->addStrategiesNoInit(/*"behind",*/ "stealth", nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,19 +508,15 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
|
||||
nonCombatEngine->addStrategiesNoInit("dps assist", "cure", nullptr);
|
||||
break;
|
||||
case CLASS_PALADIN:
|
||||
if (tab == 1)
|
||||
if (tab == PALADIN_TAB_PROTECTION)
|
||||
{
|
||||
nonCombatEngine->addStrategiesNoInit("bthreat", "tank assist", "barmor", nullptr);
|
||||
if (player->GetLevel() >= 20)
|
||||
{
|
||||
nonCombatEngine->addStrategy("bhealth", false);
|
||||
}
|
||||
else
|
||||
{
|
||||
nonCombatEngine->addStrategy("bdps", false);
|
||||
}
|
||||
}
|
||||
else if (tab == 0)
|
||||
else if (tab == PALADIN_TAB_HOLY)
|
||||
nonCombatEngine->addStrategiesNoInit("dps assist", "bmana", "bcast", nullptr);
|
||||
else
|
||||
nonCombatEngine->addStrategiesNoInit("dps assist", "bdps", "baoe", nullptr);
|
||||
@@ -572,7 +527,7 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
|
||||
nonCombatEngine->addStrategiesNoInit("bdps", "dps assist", "pet", nullptr);
|
||||
break;
|
||||
case CLASS_SHAMAN:
|
||||
if (tab == 0 || tab == 2)
|
||||
if (tab == SHAMAN_TAB_ELEMENTAL || tab == SHAMAN_TAB_RESTORATION)
|
||||
nonCombatEngine->addStrategy("bmana", false);
|
||||
else
|
||||
nonCombatEngine->addStrategy("bdps", false);
|
||||
@@ -588,43 +543,34 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
|
||||
nonCombatEngine->addStrategiesNoInit("dps assist", "cure", nullptr);
|
||||
break;
|
||||
case CLASS_DRUID:
|
||||
if (tab == 1)
|
||||
if (tab == DRUID_TAB_FERAL)
|
||||
{
|
||||
if (player->GetLevel() >= 20 && !player->HasAura(16931) /*thick hide*/)
|
||||
{
|
||||
nonCombatEngine->addStrategy("dps assist", false);
|
||||
}
|
||||
else
|
||||
{
|
||||
nonCombatEngine->addStrategy("tank assist", false);
|
||||
}
|
||||
}
|
||||
else
|
||||
nonCombatEngine->addStrategiesNoInit("dps assist", "cure", nullptr);
|
||||
break;
|
||||
case CLASS_WARRIOR:
|
||||
if (tab == 2)
|
||||
if (tab == WARRIOR_TAB_PROTECTION)
|
||||
nonCombatEngine->addStrategy("tank assist", false);
|
||||
else
|
||||
nonCombatEngine->addStrategy("dps assist", false);
|
||||
break;
|
||||
case CLASS_WARLOCK:
|
||||
if (tab == WARLOCK_TAB_AFFLICTION)
|
||||
{
|
||||
nonCombatEngine->addStrategiesNoInit("felhunter", "spellstone", nullptr);
|
||||
}
|
||||
else if (tab == WARLOCK_TAB_DEMONOLOGY)
|
||||
{
|
||||
nonCombatEngine->addStrategiesNoInit("felguard", "spellstone", nullptr);
|
||||
}
|
||||
else if (tab == WARLOCK_TAB_DESTRUCTION)
|
||||
{
|
||||
nonCombatEngine->addStrategiesNoInit("imp", "firestone", nullptr);
|
||||
}
|
||||
|
||||
nonCombatEngine->addStrategiesNoInit("dps assist", "ss self", nullptr);
|
||||
break;
|
||||
case CLASS_DEATH_KNIGHT:
|
||||
if (tab == 0)
|
||||
if (tab == DEATH_KNIGHT_TAB_BLOOD)
|
||||
nonCombatEngine->addStrategy("tank assist", false);
|
||||
else
|
||||
nonCombatEngine->addStrategy("dps assist", false);
|
||||
@@ -641,9 +587,7 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
|
||||
}
|
||||
|
||||
if (sPlayerbotAIConfig->autoSaveMana && PlayerbotAI::IsHeal(player, true))
|
||||
{
|
||||
nonCombatEngine->addStrategy("save mana", false);
|
||||
}
|
||||
|
||||
if ((sRandomPlayerbotMgr->IsRandomBot(player)) && !player->InBattleground())
|
||||
{
|
||||
@@ -669,18 +613,14 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
|
||||
nonCombatEngine->addStrategy("grind", false);
|
||||
|
||||
if (sPlayerbotAIConfig->enableNewRpgStrategy)
|
||||
{
|
||||
nonCombatEngine->addStrategy("new rpg", false);
|
||||
}
|
||||
else if (sPlayerbotAIConfig->autoDoQuests)
|
||||
{
|
||||
// nonCombatEngine->addStrategy("travel");
|
||||
nonCombatEngine->addStrategy("rpg", false);
|
||||
}
|
||||
else
|
||||
{
|
||||
nonCombatEngine->addStrategy("move random", false);
|
||||
}
|
||||
|
||||
if (sPlayerbotAIConfig->randomBotJoinBG)
|
||||
nonCombatEngine->addStrategy("bg", false);
|
||||
@@ -729,11 +669,8 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nonCombatEngine->ChangeStrategy(sPlayerbotAIConfig->nonCombatStrategies);
|
||||
}
|
||||
// nonCombatEngine->addStrategy("battleground");
|
||||
// nonCombatEngine->addStrategy("warsong");
|
||||
|
||||
// Battleground switch
|
||||
if (player->InBattleground() && player->GetBattleground())
|
||||
{
|
||||
@@ -790,9 +727,7 @@ void AiFactory::AddDefaultDeadStrategies(Player* player, PlayerbotAI* const faca
|
||||
deadEngine->addStrategiesNoInit("dead", "stay", "chat", "default", "follow", nullptr);
|
||||
|
||||
if (sRandomPlayerbotMgr->IsRandomBot(player) && !player->GetGroup())
|
||||
{
|
||||
deadEngine->removeStrategy("follow", false);
|
||||
}
|
||||
}
|
||||
|
||||
Engine* AiFactory::createDeadEngine(Player* player, PlayerbotAI* const facade, AiObjectContext* AiObjectContext)
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "PlayerbotDbStore.h"
|
||||
#include "PlayerbotMgr.h"
|
||||
#include "PlayerbotGuildMgr.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PointMovementGenerator.h"
|
||||
#include "PositionValue.h"
|
||||
@@ -242,8 +243,8 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
|
||||
nextAICheckDelay = 0;
|
||||
|
||||
// Early return if bot is in invalid state
|
||||
if (!bot || !bot->IsInWorld() || !bot->GetSession() || bot->GetSession()->isLogingOut() ||
|
||||
bot->IsDuringRemoveFromWorld())
|
||||
if (!bot || !bot->GetSession() || !bot->IsInWorld() || bot->IsBeingTeleported() ||
|
||||
bot->GetSession()->isLogingOut() || bot->IsDuringRemoveFromWorld())
|
||||
return;
|
||||
|
||||
// Handle cheat options (set bot health and power if cheats are enabled)
|
||||
@@ -365,7 +366,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
|
||||
}
|
||||
|
||||
// Update the bot's group status (moved to helper function)
|
||||
UpdateAIGroupAndMaster();
|
||||
UpdateAIGroupMaster();
|
||||
|
||||
// Update internal AI
|
||||
UpdateAIInternal(elapsed, minimal);
|
||||
@@ -373,7 +374,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
|
||||
}
|
||||
|
||||
// Helper function for UpdateAI to check group membership and handle removal if necessary
|
||||
void PlayerbotAI::UpdateAIGroupAndMaster()
|
||||
void PlayerbotAI::UpdateAIGroupMaster()
|
||||
{
|
||||
if (!bot)
|
||||
return;
|
||||
@@ -420,7 +421,7 @@ void PlayerbotAI::UpdateAIGroupAndMaster()
|
||||
{
|
||||
botAI->ChangeStrategy("+follow", BOT_STATE_NON_COMBAT);
|
||||
|
||||
if (botAI->GetMaster() == botAI->GetGroupMaster())
|
||||
if (botAI->GetMaster() == botAI->GetGroupLeader())
|
||||
botAI->TellMaster("Hello, I follow you!");
|
||||
else
|
||||
botAI->TellMaster(!urand(0, 2) ? "Hello!" : "Hi!");
|
||||
@@ -431,14 +432,12 @@ void PlayerbotAI::UpdateAIGroupAndMaster()
|
||||
botAI->ChangeStrategy("-follow", BOT_STATE_NON_COMBAT);
|
||||
}
|
||||
}
|
||||
else if (!newMaster && !bot->InBattleground())
|
||||
LeaveOrDisbandGroup();
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerbotAI::UpdateAIInternal([[maybe_unused]] uint32 elapsed, bool minimal)
|
||||
{
|
||||
if (bot->IsBeingTeleported() || !bot->IsInWorld())
|
||||
if (!bot || bot->IsBeingTeleported() || !bot->IsInWorld())
|
||||
return;
|
||||
|
||||
std::string const mapString = WorldPosition(bot).isOverworld() ? std::to_string(bot->GetMapId()) : "I";
|
||||
@@ -517,23 +516,37 @@ void PlayerbotAI::UpdateAIInternal([[maybe_unused]] uint32 elapsed, bool minimal
|
||||
void PlayerbotAI::HandleCommands()
|
||||
{
|
||||
ExternalEventHelper helper(aiObjectContext);
|
||||
|
||||
for (auto it = chatCommands.begin(); it != chatCommands.end();)
|
||||
{
|
||||
time_t& checkTime = it->GetTime();
|
||||
if (checkTime && time(0) < checkTime)
|
||||
if (checkTime && time(nullptr) < checkTime)
|
||||
{
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string& command = it->GetCommand();
|
||||
Player* owner = it->GetOwner();
|
||||
if (!owner)
|
||||
{
|
||||
it = chatCommands.erase(it);
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string& command = it->GetCommand();
|
||||
if (command.empty())
|
||||
{
|
||||
it = chatCommands.erase(it);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!helper.ParseChatCommand(command, owner) && it->GetType() == CHAT_MSG_WHISPER)
|
||||
{
|
||||
// ostringstream out; out << "Unknown command " << command;
|
||||
// TellPlayer(out);
|
||||
// helper.ParseChatCommand("help");
|
||||
}
|
||||
|
||||
it = chatCommands.erase(it);
|
||||
}
|
||||
}
|
||||
@@ -541,6 +554,9 @@ void PlayerbotAI::HandleCommands()
|
||||
std::map<std::string, ChatMsg> chatMap;
|
||||
void PlayerbotAI::HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang)
|
||||
{
|
||||
if (!bot)
|
||||
return;
|
||||
|
||||
std::string filtered = text;
|
||||
|
||||
if (!IsAllowedCommand(filtered) && !GetSecurity()->CheckLevelFor(PlayerbotSecurityLevel::PLAYERBOT_SECURITY_INVITE,
|
||||
@@ -712,45 +728,82 @@ void PlayerbotAI::HandleCommand(uint32 type, const std::string& text, Player& fr
|
||||
|
||||
void PlayerbotAI::HandleTeleportAck()
|
||||
{
|
||||
if (!bot || !bot->GetSession())
|
||||
return;
|
||||
|
||||
// only for bots
|
||||
if (IsRealPlayer())
|
||||
return;
|
||||
|
||||
bot->GetMotionMaster()->Clear(true);
|
||||
bot->StopMoving();
|
||||
if (bot->IsBeingTeleportedNear())
|
||||
{
|
||||
// Temporary fix for instance can not enter
|
||||
if (!bot->IsInWorld())
|
||||
{
|
||||
bot->GetMap()->AddPlayerToMap(bot);
|
||||
}
|
||||
while (bot->IsInWorld() && bot->IsBeingTeleportedNear())
|
||||
{
|
||||
Player* plMover = bot->m_mover->ToPlayer();
|
||||
if (!plMover)
|
||||
return;
|
||||
WorldPacket p = WorldPacket(MSG_MOVE_TELEPORT_ACK, 20);
|
||||
p << plMover->GetPackGUID();
|
||||
p << (uint32)0; // supposed to be flags? not used currently
|
||||
p << (uint32)0; // time - not currently used
|
||||
bot->GetSession()->HandleMoveTeleportAck(p);
|
||||
};
|
||||
}
|
||||
/*
|
||||
* FAR TELEPORT (worldport / map change)
|
||||
* Player may NOT be in world or grid here.
|
||||
* Handle this FIRST.
|
||||
*/
|
||||
if (bot->IsBeingTeleportedFar())
|
||||
{
|
||||
while (bot->IsBeingTeleportedFar())
|
||||
bot->GetSession()->HandleMoveWorldportAck();
|
||||
|
||||
// after worldport ACK the player should be in a valid map
|
||||
if (!bot->GetMap())
|
||||
{
|
||||
bot->GetSession()->HandleMoveWorldportAck();
|
||||
LOG_ERROR("playerbot", "Bot {} has no map after worldport ACK", bot->GetGUID().ToString());
|
||||
return;
|
||||
}
|
||||
// SetNextCheckDelay(urand(2000, 5000));
|
||||
|
||||
// apply instance-related strategies after map attach
|
||||
if (sPlayerbotAIConfig->applyInstanceStrategies)
|
||||
ApplyInstanceStrategies(bot->GetMapId(), true);
|
||||
|
||||
if (sPlayerbotAIConfig->restrictHealerDPS)
|
||||
EvaluateHealerDpsStrategy();
|
||||
|
||||
// reset AI state after teleport
|
||||
Reset(true);
|
||||
|
||||
// clear movement only AFTER teleport is finalized and bot is in world
|
||||
if (bot->IsInWorld() && bot->GetMotionMaster())
|
||||
{
|
||||
bot->GetMotionMaster()->Clear(true);
|
||||
bot->StopMoving();
|
||||
}
|
||||
|
||||
// simulate far teleport latency (cmangos-style)
|
||||
SetNextCheckDelay(urand(2000, 5000));
|
||||
return;
|
||||
}
|
||||
|
||||
SetNextCheckDelay(sPlayerbotAIConfig->globalCoolDown);
|
||||
/*
|
||||
* NEAR TELEPORT (same map / instance)
|
||||
* Player MUST be in world (and in grid).
|
||||
*/
|
||||
if (bot->IsBeingTeleportedNear())
|
||||
{
|
||||
if (!bot->IsInWorld())
|
||||
return;
|
||||
|
||||
Player* plMover = bot->m_mover ? bot->m_mover->ToPlayer() : nullptr;
|
||||
if (!plMover)
|
||||
return;
|
||||
|
||||
WorldPacket p(MSG_MOVE_TELEPORT_ACK, 20);
|
||||
p << plMover->GetPackGUID();
|
||||
p << uint32(0); // flags
|
||||
p << uint32(0); // time
|
||||
|
||||
bot->GetSession()->HandleMoveTeleportAck(p);
|
||||
|
||||
// clear movement after successful relocation
|
||||
if (bot->GetMotionMaster())
|
||||
{
|
||||
bot->GetMotionMaster()->Clear(true);
|
||||
bot->StopMoving();
|
||||
}
|
||||
|
||||
// simulate near teleport latency
|
||||
SetNextCheckDelay(urand(1000, 2000));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerbotAI::Reset(bool full)
|
||||
@@ -912,7 +965,6 @@ void PlayerbotAI::HandleCommand(uint32 type, std::string const text, Player* fro
|
||||
fromPlayer->SendDirectMessage(&data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsAllowedCommand(filtered) &&
|
||||
(!GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_ALLOW_ALL, type != CHAT_MSG_WHISPER, fromPlayer)))
|
||||
return;
|
||||
@@ -990,10 +1042,10 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
|
||||
{
|
||||
if (packet.empty())
|
||||
return;
|
||||
|
||||
if (!bot || !bot->IsInWorld() || bot->IsDuringRemoveFromWorld())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (packet.GetOpcode())
|
||||
{
|
||||
case SMSG_SPELL_FAILURE:
|
||||
@@ -1161,7 +1213,26 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
|
||||
|
||||
return;
|
||||
}
|
||||
case SMSG_MOVE_KNOCK_BACK: // handle knockbacks
|
||||
case SMSG_FORCE_MOVE_ROOT: // CMSG_FORCE_MOVE_ROOT_ACK
|
||||
case SMSG_FORCE_MOVE_UNROOT: // CMSG_FORCE_MOVE_UNROOT_ACK
|
||||
{
|
||||
// Quick fix for CMSG_FORCE_MOVE_ROOT_ACK and CMSG_FORCE_MOVE_UNROOT_ACK:
|
||||
// this should resolve issues with MOVEMENTFLAG_ROOT being permanently set
|
||||
// when rooted during lost client control (charm + root effects)
|
||||
// @see https://github.com/azerothcore/azerothcore-wotlk/pull/23147
|
||||
bool forceRoot = (packet.GetOpcode() == SMSG_FORCE_MOVE_ROOT);
|
||||
if (forceRoot)
|
||||
{
|
||||
bot->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_MASK_MOVING_FLY);
|
||||
bot->m_movementInfo.AddMovementFlag(MOVEMENTFLAG_ROOT);
|
||||
bot->StopMoving();
|
||||
}
|
||||
else
|
||||
bot->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_ROOT);
|
||||
|
||||
return;
|
||||
}
|
||||
case SMSG_MOVE_KNOCK_BACK: // CMSG_MOVE_KNOCK_BACK_ACK
|
||||
{
|
||||
WorldPacket p(packet);
|
||||
p.rpos(0);
|
||||
@@ -1249,7 +1320,12 @@ void PlayerbotAI::SpellInterrupted(uint32 spellid)
|
||||
Spell* spell = bot->GetCurrentSpell((CurrentSpellTypes)type);
|
||||
if (!spell)
|
||||
continue;
|
||||
if (spell->GetSpellInfo()->Id == spellid)
|
||||
|
||||
SpellInfo const* spellInfo = spell->GetSpellInfo();
|
||||
if (!spellInfo)
|
||||
continue;
|
||||
|
||||
if (spellInfo->Id == spellid)
|
||||
bot->InterruptSpell((CurrentSpellTypes)type);
|
||||
}
|
||||
// LastSpellCast& lastSpell = aiObjectContext->GetValue<LastSpellCast&>("last spell cast")->Get();
|
||||
@@ -1333,10 +1409,6 @@ void PlayerbotAI::DoNextAction(bool min)
|
||||
bool isBotAlive = bot->IsAlive();
|
||||
if (currentEngine != engines[BOT_STATE_DEAD] && !isBotAlive)
|
||||
{
|
||||
bot->StopMoving();
|
||||
bot->GetMotionMaster()->Clear();
|
||||
bot->GetMotionMaster()->MoveIdle();
|
||||
|
||||
// Death Count to prevent skeleton piles
|
||||
// Player* master = GetMaster(); // warning here - whipowill
|
||||
if (!HasActivePlayerMaster() && !bot->InBattleground())
|
||||
@@ -1354,9 +1426,11 @@ void PlayerbotAI::DoNextAction(bool min)
|
||||
return;
|
||||
}
|
||||
|
||||
// Change engine if just ressed
|
||||
if (currentEngine == engines[BOT_STATE_DEAD] && isBotAlive)
|
||||
// Change engine if just ressed (no movement update when rooted)
|
||||
if (currentEngine == engines[BOT_STATE_DEAD] && isBotAlive && !bot->IsRooted())
|
||||
{
|
||||
bot->SendMovementFlagUpdate();
|
||||
|
||||
ChangeEngine(BOT_STATE_NON_COMBAT);
|
||||
return;
|
||||
}
|
||||
@@ -1386,9 +1460,6 @@ void PlayerbotAI::DoNextAction(bool min)
|
||||
else if (bot->isAFK())
|
||||
bot->ToggleAFK();
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
PlayerbotAI* masterBotAI = nullptr;
|
||||
|
||||
if (master && master->IsInWorld())
|
||||
{
|
||||
float distance = sServerFacade->GetDistance2d(bot, master);
|
||||
@@ -1461,7 +1532,7 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
|
||||
strategyName = "onyxia"; // Onyxia's Lair
|
||||
break;
|
||||
case 409:
|
||||
strategyName = "mc"; // Molten Core
|
||||
strategyName = "moltencore"; // Molten Core
|
||||
break;
|
||||
case 469:
|
||||
strategyName = "bwl"; // Blackwing Lair
|
||||
@@ -1472,11 +1543,9 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
|
||||
case 532:
|
||||
strategyName = "karazhan"; // Karazhan
|
||||
break;
|
||||
case 533:
|
||||
strategyName = "naxx"; // Naxxramas
|
||||
break;
|
||||
case 544:
|
||||
strategyName = "magtheridon"; // Magtheridon's Lair
|
||||
break;
|
||||
case 565:
|
||||
strategyName = "gruulslair"; // Gruul's Lair
|
||||
break;
|
||||
@@ -1698,6 +1767,7 @@ bool PlayerbotAI::IsRanged(Player* player, bool bySpec)
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1791,10 +1861,9 @@ bool PlayerbotAI::IsRangedDpsAssistantOfIndex(Player* player, int index)
|
||||
|
||||
bool PlayerbotAI::HasAggro(Unit* unit)
|
||||
{
|
||||
if (!unit)
|
||||
{
|
||||
if (!IsValidUnit(unit))
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isMT = IsMainTank(bot);
|
||||
Unit* victim = unit->GetVictim();
|
||||
if (victim && (victim->GetGUID() == bot->GetGUID() || (!isMT && victim->ToPlayer() && IsTank(victim->ToPlayer()))))
|
||||
@@ -2002,7 +2071,7 @@ bool PlayerbotAI::IsTank(Player* player, bool bySpec)
|
||||
switch (player->getClass())
|
||||
{
|
||||
case CLASS_DEATH_KNIGHT:
|
||||
if (tab == DEATHKNIGHT_TAB_BLOOD)
|
||||
if (tab == DEATH_KNIGHT_TAB_BLOOD)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -2110,7 +2179,7 @@ bool PlayerbotAI::IsDps(Player* player, bool bySpec)
|
||||
}
|
||||
break;
|
||||
case CLASS_DEATH_KNIGHT:
|
||||
if (tab != DEATHKNIGHT_TAB_BLOOD)
|
||||
if (tab != DEATH_KNIGHT_TAB_BLOOD)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -2248,7 +2317,7 @@ uint32 PlayerbotAI::GetGroupTankNum(Player* player)
|
||||
|
||||
bool PlayerbotAI::IsAssistTank(Player* player) { return IsTank(player) && !IsMainTank(player); }
|
||||
|
||||
bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index)
|
||||
bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index, bool ignoreDeadPlayers)
|
||||
{
|
||||
Group* group = player->GetGroup();
|
||||
if (!group)
|
||||
@@ -2265,6 +2334,9 @@ bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ignoreDeadPlayers && !member->IsAlive())
|
||||
continue;
|
||||
|
||||
if (group->IsAssistant(member->GetGUID()) && IsAssistTank(member))
|
||||
{
|
||||
if (index == counter)
|
||||
@@ -2284,6 +2356,9 @@ bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ignoreDeadPlayers && !member->IsAlive())
|
||||
continue;
|
||||
|
||||
if (!group->IsAssistant(member->GetGUID()) && IsAssistTank(member))
|
||||
{
|
||||
if (index == counter)
|
||||
@@ -2764,7 +2839,12 @@ bool PlayerbotAI::TellMaster(std::ostringstream& stream, PlayerbotSecurityLevel
|
||||
|
||||
bool PlayerbotAI::TellMaster(std::string const text, PlayerbotSecurityLevel securityLevel)
|
||||
{
|
||||
if (!master || !TellMasterNoFacing(text, securityLevel))
|
||||
if (!master)
|
||||
{
|
||||
if (sPlayerbotAIConfig->randomBotSayWithoutMaster)
|
||||
return TellMasterNoFacing(text, securityLevel);
|
||||
}
|
||||
if (!TellMasterNoFacing(text, securityLevel))
|
||||
return false;
|
||||
|
||||
if (!bot->isMoving() && !bot->IsInCombat() && bot->GetMapId() == master->GetMapId() &&
|
||||
@@ -2781,6 +2861,9 @@ bool PlayerbotAI::TellMaster(std::string const text, PlayerbotSecurityLevel secu
|
||||
|
||||
bool IsRealAura(Player* bot, AuraEffect const* aurEff, Unit const* unit)
|
||||
{
|
||||
if (!unit || !unit->IsInWorld() || unit->IsDuringRemoveFromWorld())
|
||||
return false;
|
||||
|
||||
if (!aurEff)
|
||||
return false;
|
||||
|
||||
@@ -2788,6 +2871,8 @@ bool IsRealAura(Player* bot, AuraEffect const* aurEff, Unit const* unit)
|
||||
return true;
|
||||
|
||||
SpellInfo const* spellInfo = aurEff->GetSpellInfo();
|
||||
if (!spellInfo)
|
||||
return false;
|
||||
|
||||
uint32 stacks = aurEff->GetBase()->GetStackAmount();
|
||||
if (stacks >= spellInfo->StackAmount)
|
||||
@@ -2803,7 +2888,7 @@ bool IsRealAura(Player* bot, AuraEffect const* aurEff, Unit const* unit)
|
||||
bool PlayerbotAI::HasAura(std::string const name, Unit* unit, bool maxStack, bool checkIsOwner, int maxAuraAmount,
|
||||
bool checkDuration)
|
||||
{
|
||||
if (!unit)
|
||||
if (!IsValidUnit(unit))
|
||||
return false;
|
||||
|
||||
std::wstring wnamepart;
|
||||
@@ -2899,7 +2984,7 @@ bool PlayerbotAI::HasAura(uint32 spellId, Unit const* unit)
|
||||
|
||||
Aura* PlayerbotAI::GetAura(std::string const name, Unit* unit, bool checkIsOwner, bool checkDuration, int checkStack)
|
||||
{
|
||||
if (!unit)
|
||||
if (!IsValidUnit(unit))
|
||||
return nullptr;
|
||||
|
||||
std::wstring wnamepart;
|
||||
@@ -2917,6 +3002,9 @@ Aura* PlayerbotAI::GetAura(std::string const name, Unit* unit, bool checkIsOwner
|
||||
for (AuraEffect const* aurEff : auras)
|
||||
{
|
||||
SpellInfo const* spellInfo = aurEff->GetSpellInfo();
|
||||
if (!spellInfo)
|
||||
continue;
|
||||
|
||||
std::string const& auraName = spellInfo->SpellName[0];
|
||||
|
||||
// Directly skip if name mismatch (both length and content)
|
||||
@@ -2997,6 +3085,9 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell,
|
||||
if (!target)
|
||||
target = bot;
|
||||
|
||||
if (!IsValidUnit(target))
|
||||
return false;
|
||||
|
||||
if (Pet* pet = bot->GetPet())
|
||||
if (pet->HasSpell(spellid))
|
||||
return true;
|
||||
@@ -3258,6 +3349,9 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, float x, float y, float z, bool c
|
||||
|
||||
bool PlayerbotAI::CastSpell(std::string const name, Unit* target, Item* itemTarget)
|
||||
{
|
||||
if (!IsValidUnit(target))
|
||||
return false;
|
||||
|
||||
bool result = CastSpell(aiObjectContext->GetValue<uint32>("spell id", name)->Get(), target, itemTarget);
|
||||
if (result)
|
||||
{
|
||||
@@ -3270,15 +3364,19 @@ bool PlayerbotAI::CastSpell(std::string const name, Unit* target, Item* itemTarg
|
||||
bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
|
||||
{
|
||||
if (!spellId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!target)
|
||||
target = bot;
|
||||
|
||||
Pet* pet = bot->GetPet();
|
||||
if (!IsValidUnit(target))
|
||||
return false;
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
if (!spellInfo)
|
||||
return false;
|
||||
|
||||
Pet* pet = bot->GetPet();
|
||||
if (pet && pet->HasSpell(spellId))
|
||||
{
|
||||
// List of spell IDs for which we do NOT want to toggle auto-cast or send message
|
||||
@@ -3681,6 +3779,9 @@ bool PlayerbotAI::CanCastVehicleSpell(uint32 spellId, Unit* target)
|
||||
if (!spellId)
|
||||
return false;
|
||||
|
||||
if (!IsValidUnit(target))
|
||||
return false;
|
||||
|
||||
Vehicle* vehicle = bot->GetVehicle();
|
||||
if (!vehicle)
|
||||
return false;
|
||||
@@ -3691,12 +3792,12 @@ bool PlayerbotAI::CanCastVehicleSpell(uint32 spellId, Unit* target)
|
||||
return false;
|
||||
|
||||
Unit* vehicleBase = vehicle->GetBase();
|
||||
|
||||
Unit* spellTarget = target;
|
||||
|
||||
if (!spellTarget)
|
||||
spellTarget = vehicleBase;
|
||||
|
||||
if (!spellTarget)
|
||||
if (!IsValidUnit(spellTarget))
|
||||
return false;
|
||||
|
||||
if (vehicleBase->HasSpellCooldown(spellId))
|
||||
@@ -3763,6 +3864,9 @@ bool PlayerbotAI::CastVehicleSpell(uint32 spellId, Unit* target)
|
||||
if (!spellId)
|
||||
return false;
|
||||
|
||||
if (!IsValidUnit(target))
|
||||
return false;
|
||||
|
||||
Vehicle* vehicle = bot->GetVehicle();
|
||||
if (!vehicle)
|
||||
return false;
|
||||
@@ -3773,12 +3877,12 @@ bool PlayerbotAI::CastVehicleSpell(uint32 spellId, Unit* target)
|
||||
return false;
|
||||
|
||||
Unit* vehicleBase = vehicle->GetBase();
|
||||
|
||||
Unit* spellTarget = target;
|
||||
|
||||
if (!spellTarget)
|
||||
spellTarget = vehicleBase;
|
||||
|
||||
if (!spellTarget)
|
||||
if (!IsValidUnit(spellTarget))
|
||||
return false;
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
@@ -3931,9 +4035,13 @@ bool PlayerbotAI::IsInVehicle(bool canControl, bool canCast, bool canAttack, boo
|
||||
|
||||
void PlayerbotAI::WaitForSpellCast(Spell* spell)
|
||||
{
|
||||
if (!spell)
|
||||
return;
|
||||
|
||||
SpellInfo const* spellInfo = spell->GetSpellInfo();
|
||||
uint32 castTime = spell->GetCastTime();
|
||||
if (spellInfo->IsChanneled())
|
||||
|
||||
if (spellInfo && spellInfo->IsChanneled())
|
||||
{
|
||||
int32 duration = spellInfo->GetDuration();
|
||||
bot->ApplySpellMod(spellInfo->Id, SPELLMOD_DURATION, duration);
|
||||
@@ -3981,6 +4089,9 @@ void PlayerbotAI::RemoveAura(std::string const name)
|
||||
|
||||
bool PlayerbotAI::IsInterruptableSpellCasting(Unit* target, std::string const spell)
|
||||
{
|
||||
if (!IsValidUnit(target))
|
||||
return false;
|
||||
|
||||
uint32 spellid = aiObjectContext->GetValue<uint32>("spell id", spell)->Get();
|
||||
if (!spellid || !target->IsNonMeleeSpellCast(true))
|
||||
return false;
|
||||
@@ -4009,17 +4120,25 @@ bool PlayerbotAI::IsInterruptableSpellCasting(Unit* target, std::string const sp
|
||||
|
||||
bool PlayerbotAI::HasAuraToDispel(Unit* target, uint32 dispelType)
|
||||
{
|
||||
if (!target->IsInWorld())
|
||||
{
|
||||
if (!IsValidUnit(target) || !target->IsAlive())
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsValidPlayer(bot))
|
||||
return false;
|
||||
|
||||
bool isFriend = bot->IsFriendlyTo(target);
|
||||
|
||||
Unit::VisibleAuraMap const* visibleAuras = target->GetVisibleAuras();
|
||||
if (!visibleAuras)
|
||||
return false;
|
||||
|
||||
for (Unit::VisibleAuraMap::const_iterator itr = visibleAuras->begin(); itr != visibleAuras->end(); ++itr)
|
||||
{
|
||||
Aura* aura = itr->second->GetBase();
|
||||
if (!itr->second)
|
||||
continue;
|
||||
|
||||
if (aura->IsPassive())
|
||||
Aura* aura = itr->second->GetBase();
|
||||
if (!aura || aura->IsPassive() || aura->IsRemoved())
|
||||
continue;
|
||||
|
||||
if (sPlayerbotAIConfig->dispelAuraDuration && aura->GetDuration() &&
|
||||
@@ -4027,6 +4146,8 @@ bool PlayerbotAI::HasAuraToDispel(Unit* target, uint32 dispelType)
|
||||
continue;
|
||||
|
||||
SpellInfo const* spellInfo = aura->GetSpellInfo();
|
||||
if (!spellInfo)
|
||||
continue;
|
||||
|
||||
bool isPositiveSpell = spellInfo->IsPositive();
|
||||
if (isPositiveSpell && isFriend)
|
||||
@@ -4038,6 +4159,7 @@ bool PlayerbotAI::HasAuraToDispel(Unit* target, uint32 dispelType)
|
||||
if (canDispel(spellInfo, dispelType))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -4092,7 +4214,7 @@ Player* PlayerbotAI::FindNewMaster()
|
||||
if (!group)
|
||||
return nullptr;
|
||||
|
||||
Player* groupLeader = GetGroupMaster();
|
||||
Player* groupLeader = GetGroupLeader();
|
||||
PlayerbotAI* leaderBotAI = GET_PLAYERBOT_AI(groupLeader);
|
||||
if (!leaderBotAI || leaderBotAI->IsRealPlayer())
|
||||
return groupLeader;
|
||||
@@ -4101,8 +4223,7 @@ Player* PlayerbotAI::FindNewMaster()
|
||||
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||||
{
|
||||
Player* member = gref->GetSource();
|
||||
if (!member || member == bot || !member->IsInWorld() ||
|
||||
!member->IsInSameRaidWith(bot))
|
||||
if (!member || member == bot || !member->IsInWorld() || !member->IsInSameRaidWith(bot))
|
||||
continue;
|
||||
|
||||
PlayerbotAI* memberBotAI = GET_PLAYERBOT_AI(member);
|
||||
@@ -4143,7 +4264,7 @@ bool PlayerbotAI::HasActivePlayerMaster() { return master && !GET_PLAYERBOT_AI(m
|
||||
|
||||
bool PlayerbotAI::IsAlt() { return HasRealPlayerMaster() && !sRandomPlayerbotMgr->IsRandomBot(bot); }
|
||||
|
||||
Player* PlayerbotAI::GetGroupMaster()
|
||||
Player* PlayerbotAI::GetGroupLeader()
|
||||
{
|
||||
if (!bot->InBattleground())
|
||||
if (Group* group = bot->GetGroup())
|
||||
@@ -4337,6 +4458,11 @@ inline bool ZoneHasRealPlayers(Player* bot)
|
||||
|
||||
bool PlayerbotAI::AllowActive(ActivityType activityType)
|
||||
{
|
||||
// Early return if bot is in invalid state
|
||||
if (!bot || !bot->GetSession() || !bot->IsInWorld() || bot->IsBeingTeleported() ||
|
||||
bot->GetSession()->isLogingOut() || bot->IsDuringRemoveFromWorld())
|
||||
return false;
|
||||
|
||||
// when botActiveAlone is 100% and smartScale disabled
|
||||
if (sPlayerbotAIConfig->botActiveAlone >= 100 && !sPlayerbotAIConfig->botActiveAloneSmartScale)
|
||||
{
|
||||
@@ -4427,10 +4553,8 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
|
||||
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||||
{
|
||||
Player* member = gref->GetSource();
|
||||
if ((!member || !member->IsInWorld()) && member->GetMapId() != bot->GetMapId())
|
||||
{
|
||||
if (!member || !member->IsInWorld() || member->GetMapId() != bot->GetMapId())
|
||||
continue;
|
||||
}
|
||||
|
||||
if (member == bot)
|
||||
{
|
||||
@@ -4481,23 +4605,23 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
|
||||
// HasFriend
|
||||
if (sPlayerbotAIConfig->BotActiveAloneForceWhenIsFriend)
|
||||
{
|
||||
if (!bot || !bot->IsInWorld() || !bot->GetGUID())
|
||||
// shouldnt be needed analyse in future
|
||||
if (!bot->GetGUID())
|
||||
return false;
|
||||
|
||||
for (auto& player : sRandomPlayerbotMgr->GetPlayers())
|
||||
{
|
||||
if (!player || !player->IsInWorld())
|
||||
if (!player || !player->GetSession() || !player->IsInWorld() || player->IsDuringRemoveFromWorld() ||
|
||||
player->GetSession()->isLogingOut())
|
||||
continue;
|
||||
|
||||
Player* connectedPlayer = ObjectAccessor::FindPlayer(player->GetGUID());
|
||||
if (!connectedPlayer)
|
||||
PlayerbotAI* playerAI = GET_PLAYERBOT_AI(player);
|
||||
if (!playerAI || !playerAI->IsRealPlayer())
|
||||
continue;
|
||||
|
||||
// if a real player has the bot as a friend
|
||||
PlayerSocial* social = player->GetSocial();
|
||||
if (!social)
|
||||
continue;
|
||||
|
||||
if (social->HasFriend(bot->GetGUID()))
|
||||
if (social && social->HasFriend(bot->GetGUID()))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -4511,7 +4635,7 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
|
||||
}
|
||||
}
|
||||
|
||||
// Bots don't need to move using PathGenerator.
|
||||
// Bots don't need react to PathGenerator activities
|
||||
if (activityType == DETAILED_MOVE_ACTIVITY)
|
||||
{
|
||||
return false;
|
||||
@@ -4547,15 +4671,25 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
|
||||
|
||||
bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow)
|
||||
{
|
||||
if (!allowActiveCheckTimer[activityType])
|
||||
allowActiveCheckTimer[activityType] = time(nullptr);
|
||||
const int activityIndex = static_cast<int>(activityType);
|
||||
|
||||
if (!checkNow && time(nullptr) < (allowActiveCheckTimer[activityType] + 5))
|
||||
return allowActive[activityType];
|
||||
// Unknown/out-of-range avoid blocking, added logging for further analysing should not happen in the first place.
|
||||
if (activityIndex <= 0 || activityIndex >= MAX_ACTIVITY_TYPE)
|
||||
{
|
||||
LOG_ERROR("playerbots", "AllowActivity received invalid activity type value: {}", activityIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!allowActiveCheckTimer[activityIndex])
|
||||
allowActiveCheckTimer[activityIndex] = time(nullptr);
|
||||
|
||||
if (!checkNow && time(nullptr) < (allowActiveCheckTimer[activityIndex] + 5))
|
||||
return allowActive[activityIndex];
|
||||
|
||||
const bool allowed = AllowActive(activityType);
|
||||
allowActive[activityIndex] = allowed;
|
||||
allowActiveCheckTimer[activityIndex] = time(nullptr);
|
||||
|
||||
bool allowed = AllowActive(activityType);
|
||||
allowActive[activityType] = allowed;
|
||||
allowActiveCheckTimer[activityType] = time(nullptr);
|
||||
return allowed;
|
||||
}
|
||||
|
||||
@@ -5339,15 +5473,13 @@ Item* PlayerbotAI::FindStoneFor(Item* weapon) const
|
||||
if (!item_template)
|
||||
return nullptr;
|
||||
|
||||
static const std::vector<uint32_t> uPrioritizedSharpStoneIds = {
|
||||
ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, ELEMENTAL_SHARPENING_STONE, DENSE_SHARPENING_STONE,
|
||||
SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE
|
||||
};
|
||||
static const std::vector<uint32_t> uPrioritizedSharpStoneIds = {
|
||||
ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, ELEMENTAL_SHARPENING_STONE, DENSE_SHARPENING_STONE,
|
||||
SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE};
|
||||
|
||||
static const std::vector<uint32_t> uPrioritizedWeightStoneIds = {
|
||||
ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE,
|
||||
HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE
|
||||
};
|
||||
static const std::vector<uint32_t> uPrioritizedWeightStoneIds = {
|
||||
ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE,
|
||||
HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE};
|
||||
|
||||
Item* stone = nullptr;
|
||||
ItemTemplate const* pProto = weapon->GetTemplate();
|
||||
@@ -5383,7 +5515,6 @@ static const std::vector<uint32_t> uPrioritizedWeightStoneIds = {
|
||||
|
||||
Item* PlayerbotAI::FindOilFor(Item* weapon) const
|
||||
{
|
||||
|
||||
if (!weapon)
|
||||
return nullptr;
|
||||
|
||||
@@ -5392,12 +5523,12 @@ Item* PlayerbotAI::FindOilFor(Item* weapon) const
|
||||
return nullptr;
|
||||
|
||||
static const std::vector<uint32_t> uPrioritizedWizardOilIds = {
|
||||
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL,
|
||||
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
|
||||
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL,
|
||||
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
|
||||
|
||||
static const std::vector<uint32_t> uPrioritizedManaOilIds = {
|
||||
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL,
|
||||
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
|
||||
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL, BRILLIANT_WIZARD_OIL,
|
||||
SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
|
||||
|
||||
Item* oil = nullptr;
|
||||
int botClass = bot->getClass();
|
||||
@@ -5413,22 +5544,22 @@ Item* PlayerbotAI::FindOilFor(Item* weapon) const
|
||||
prioritizedOils = &uPrioritizedWizardOilIds;
|
||||
break;
|
||||
case CLASS_DRUID:
|
||||
if (specTab == 0) // Balance
|
||||
if (specTab == 0) // Balance
|
||||
prioritizedOils = &uPrioritizedWizardOilIds;
|
||||
else if (specTab == 1) // Feral
|
||||
else if (specTab == 1) // Feral
|
||||
prioritizedOils = nullptr;
|
||||
else // Restoration (specTab == 2) or any other/unspecified spec
|
||||
else // Restoration (specTab == 2) or any other/unspecified spec
|
||||
prioritizedOils = &uPrioritizedManaOilIds;
|
||||
break;
|
||||
case CLASS_HUNTER:
|
||||
prioritizedOils = &uPrioritizedManaOilIds;
|
||||
break;
|
||||
case CLASS_PALADIN:
|
||||
if (specTab == 1) // Protection
|
||||
if (specTab == 1) // Protection
|
||||
prioritizedOils = &uPrioritizedWizardOilIds;
|
||||
else if (specTab == 2) // Retribution
|
||||
else if (specTab == 2) // Retribution
|
||||
prioritizedOils = nullptr;
|
||||
else // Holy (specTab == 0) or any other/unspecified spec
|
||||
else // Holy (specTab == 0) or any other/unspecified spec
|
||||
prioritizedOils = &uPrioritizedManaOilIds;
|
||||
break;
|
||||
default:
|
||||
@@ -5659,7 +5790,7 @@ void PlayerbotAI::ImbueItem(Item* item) { ImbueItem(item, TARGET_FLAG_NONE, Obje
|
||||
// item on unit
|
||||
void PlayerbotAI::ImbueItem(Item* item, Unit* target)
|
||||
{
|
||||
if (!target)
|
||||
if (!IsValidUnit(target))
|
||||
return;
|
||||
|
||||
ImbueItem(item, TARGET_FLAG_UNIT, target->GetGUID());
|
||||
@@ -5801,30 +5932,38 @@ int32 PlayerbotAI::GetNearGroupMemberCount(float dis)
|
||||
|
||||
bool PlayerbotAI::CanMove()
|
||||
{
|
||||
// do not allow if not vehicle driver
|
||||
if (IsInVehicle() && !IsInVehicle(true))
|
||||
// Most common checks: confused, stunned, fleeing, jumping, charging. All these
|
||||
// states are set when handling certain aura effects. We don't check against
|
||||
// UNIT_STATE_ROOT here, because this state is used by vehicles.
|
||||
if (bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
||||
return false;
|
||||
|
||||
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
|
||||
bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() || bot->HasConfuseAura() ||
|
||||
bot->IsCharmed() || bot->HasStunAura() || bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
||||
// Death state (w/o spirit release) and Spirit of Redemption aura (priest)
|
||||
if ((bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) || bot->HasSpiritOfRedemptionAura())
|
||||
return false;
|
||||
|
||||
return bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FLIGHT_MOTION_TYPE;
|
||||
}
|
||||
|
||||
bool PlayerbotAI::IsRealGuild(uint32 guildId)
|
||||
{
|
||||
Guild* guild = sGuildMgr->GetGuildById(guildId);
|
||||
if (!guild)
|
||||
{
|
||||
// Common CC effects, ordered by frequency: rooted > charmed > frozen > polymorphed.
|
||||
// NOTE: Can't find proper way to check if bot is rooted or charmed w/o additional
|
||||
// vehicle check -- when a passenger is added, they become rooted and charmed.
|
||||
if (!bot->GetVehicle() && (bot->IsRooted() || bot->IsCharmed()))
|
||||
return false;
|
||||
}
|
||||
uint32 leaderAccount = sCharacterCache->GetCharacterAccountIdByGuid(guild->GetLeaderGUID());
|
||||
if (!leaderAccount)
|
||||
if (bot->isFrozen() || bot->IsPolymorphed())
|
||||
return false;
|
||||
|
||||
return !(sPlayerbotAIConfig->IsInRandomAccountList(leaderAccount));
|
||||
// Check for the MM controlled slot types: feared, confused, fleeing, etc.
|
||||
if (bot->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE)
|
||||
return false;
|
||||
|
||||
// Traveling state: taxi flight and being teleported (relatively rare)
|
||||
if (bot->IsInFlight() || bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE ||
|
||||
bot->IsBeingTeleported())
|
||||
return false;
|
||||
|
||||
// Vehicle state: is in the vehicle and can control it (rare, content-specific)
|
||||
if ((bot->GetVehicle() && !IsInVehicle(true)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlayerbotAI::IsInRealGuild()
|
||||
@@ -5832,7 +5971,7 @@ bool PlayerbotAI::IsInRealGuild()
|
||||
if (!bot->GetGuildId())
|
||||
return false;
|
||||
|
||||
return IsRealGuild(bot->GetGuildId());
|
||||
return sPlayerbotGuildMgr->IsRealGuild(bot->GetGuildId());
|
||||
}
|
||||
|
||||
void PlayerbotAI::QueueChatResponse(const ChatQueuedReply chatReply) { chatReplies.push_back(std::move(chatReply)); }
|
||||
|
||||
@@ -276,7 +276,7 @@ enum BotRoles : uint8
|
||||
|
||||
enum HUNTER_TABS
|
||||
{
|
||||
HUNTER_TAB_BEASTMASTERY,
|
||||
HUNTER_TAB_BEAST_MASTERY,
|
||||
HUNTER_TAB_MARKSMANSHIP,
|
||||
HUNTER_TAB_SURVIVAL,
|
||||
};
|
||||
@@ -295,11 +295,11 @@ enum PRIEST_TABS
|
||||
PRIEST_TAB_SHADOW,
|
||||
};
|
||||
|
||||
enum DEATHKNIGHT_TABS
|
||||
enum DEATH_KNIGHT_TABS
|
||||
{
|
||||
DEATHKNIGHT_TAB_BLOOD,
|
||||
DEATHKNIGHT_TAB_FROST,
|
||||
DEATHKNIGHT_TAB_UNHOLY,
|
||||
DEATH_KNIGHT_TAB_BLOOD,
|
||||
DEATH_KNIGHT_TAB_FROST,
|
||||
DEATH_KNIGHT_TAB_UNHOLY,
|
||||
};
|
||||
|
||||
enum DRUID_TABS
|
||||
@@ -428,7 +428,7 @@ public:
|
||||
static bool IsMainTank(Player* player);
|
||||
static uint32 GetGroupTankNum(Player* player);
|
||||
static bool IsAssistTank(Player* player);
|
||||
static bool IsAssistTankOfIndex(Player* player, int index);
|
||||
static bool IsAssistTankOfIndex(Player* player, int index, bool ignoreDeadPlayers = false);
|
||||
static bool IsHealAssistantOfIndex(Player* player, int index);
|
||||
static bool IsRangedDpsAssistantOfIndex(Player* player, int index);
|
||||
bool HasAggro(Unit* unit);
|
||||
@@ -540,7 +540,7 @@ public:
|
||||
// Get the group leader or the master of the bot.
|
||||
// Checks if the bot is summoned as alt of a player
|
||||
bool IsAlt();
|
||||
Player* GetGroupMaster();
|
||||
Player* GetGroupLeader();
|
||||
// Returns a semi-random (cycling) number that is fixed for each bot.
|
||||
uint32 GetFixedBotNumer(uint32 maxNum = 100, float cyclePerMin = 1);
|
||||
GrouperType GetGrouperType();
|
||||
@@ -579,7 +579,6 @@ public:
|
||||
void ResetJumpDestination() { jumpDestination = Position(); }
|
||||
|
||||
bool CanMove();
|
||||
static bool IsRealGuild(uint32 guildId);
|
||||
bool IsInRealGuild();
|
||||
static std::vector<std::string> dispel_whitelist;
|
||||
bool EqualLowercaseName(std::string s1, std::string s2);
|
||||
@@ -612,12 +611,20 @@ private:
|
||||
static void _fillGearScoreData(Player* player, Item* item, std::vector<uint32>* gearScore, uint32& twoHandScore,
|
||||
bool mixed = false);
|
||||
bool IsTellAllowed(PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
|
||||
void UpdateAIGroupAndMaster();
|
||||
void UpdateAIGroupMaster();
|
||||
Item* FindItemInInventory(std::function<bool(ItemTemplate const*)> checkItem) const;
|
||||
void HandleCommands();
|
||||
void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL);
|
||||
bool _isBotInitializing = false;
|
||||
|
||||
inline bool IsValidUnit(const Unit* unit) const
|
||||
{
|
||||
return unit && unit->IsInWorld() && !unit->IsDuringRemoveFromWorld();
|
||||
}
|
||||
inline bool IsValidPlayer(const Player* player) const
|
||||
{
|
||||
return player && player->GetSession() && player->IsInWorld() && !player->IsDuringRemoveFromWorld() &&
|
||||
!player->IsBeingTeleported();
|
||||
}
|
||||
protected:
|
||||
Player* bot;
|
||||
Player* master;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "PlayerbotDungeonSuggestionMgr.h"
|
||||
#include "PlayerbotFactory.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PlayerbotGuildMgr.h"
|
||||
#include "RandomItemMgr.h"
|
||||
#include "RandomPlayerbotFactory.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
@@ -165,7 +166,7 @@ bool PlayerbotAIConfig::Initialize()
|
||||
pvpProhibitedZoneIds);
|
||||
LoadList<std::vector<uint32>>(
|
||||
sConfigMgr->GetOption<std::string>("AiPlayerbot.PvpProhibitedAreaIds",
|
||||
"976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754"),
|
||||
"976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754,3786,3973"),
|
||||
pvpProhibitedAreaIds);
|
||||
fastReactInBG = sConfigMgr->GetOption<bool>("AiPlayerbot.FastReactInBG", true);
|
||||
LoadList<std::vector<uint32>>(
|
||||
@@ -222,6 +223,11 @@ bool PlayerbotAIConfig::Initialize()
|
||||
|
||||
EnableICCBuffs = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableICCBuffs", true);
|
||||
|
||||
//////////////////////////// Professions
|
||||
fishingDistanceFromMaster = sConfigMgr->GetOption<float>("AiPlayerbot.FishingDistanceFromMaster", 10.0f);
|
||||
endFishingWithMaster = sConfigMgr->GetOption<float>("AiPlayerbot.EndFishingWithMaster", 30.0f);
|
||||
fishingDistance = sConfigMgr->GetOption<float>("AiPlayerbot.FishingDistance", 40.0f);
|
||||
enableFishingWithMaster = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableFishingWithMaster", true);
|
||||
//////////////////////////// CHAT
|
||||
enableBroadcasts = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableBroadcasts", true);
|
||||
randomBotTalk = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotTalk", false);
|
||||
@@ -661,6 +667,7 @@ bool PlayerbotAIConfig::Initialize()
|
||||
sRandomPlayerbotMgr->Init();
|
||||
}
|
||||
|
||||
sPlayerbotGuildMgr->Init();
|
||||
sRandomItemMgr->Init();
|
||||
sRandomItemMgr->InitAfterAhBot();
|
||||
sPlayerbotTextMgr->LoadBotTexts();
|
||||
@@ -725,8 +732,8 @@ std::string const PlayerbotAIConfig::GetTimestampStr()
|
||||
// HH hour (2 digits 00-23)
|
||||
// MM minutes (2 digits 00-59)
|
||||
// SS seconds (2 digits 00-59)
|
||||
char buf[20];
|
||||
snprintf(buf, 20, "%04d-%02d-%02d %02d-%02d-%02d", aTm->tm_year + 1900, aTm->tm_mon + 1, aTm->tm_mday, aTm->tm_hour,
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%04d-%02d-%02d %02d-%02d-%02d", aTm->tm_year + 1900, aTm->tm_mon + 1, aTm->tm_mday, aTm->tm_hour,
|
||||
aTm->tm_min, aTm->tm_sec);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
@@ -145,6 +145,10 @@ public:
|
||||
// Cooldown (seconds) between reagent-missing RP warnings, per bot & per buff. Default: 30
|
||||
int32 rpWarningCooldown;
|
||||
|
||||
// Professions
|
||||
bool enableFishingWithMaster;
|
||||
float fishingDistanceFromMaster, fishingDistance, endFishingWithMaster;
|
||||
|
||||
// chat
|
||||
bool randomBotTalk;
|
||||
bool randomBotEmote;
|
||||
@@ -269,7 +273,6 @@ public:
|
||||
bool deleteRandomBotAccounts;
|
||||
uint32 randomBotGuildCount, randomBotGuildSizeMax;
|
||||
bool deleteRandomBotGuilds;
|
||||
std::vector<uint32> randomBotGuilds;
|
||||
std::vector<uint32> pvpProhibitedZoneIds;
|
||||
std::vector<uint32> pvpProhibitedAreaIds;
|
||||
bool fastReactInBG;
|
||||
|
||||
322
src/PlayerbotGuildMgr.cpp
Normal file
322
src/PlayerbotGuildMgr.cpp
Normal file
@@ -0,0 +1,322 @@
|
||||
#include "PlayerbotGuildMgr.h"
|
||||
#include "Player.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Guild.h"
|
||||
#include "GuildMgr.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "ScriptMgr.h"
|
||||
|
||||
PlayerbotGuildMgr::PlayerbotGuildMgr(){}
|
||||
|
||||
void PlayerbotGuildMgr::Init()
|
||||
{
|
||||
_guildCache.clear();
|
||||
if (sPlayerbotAIConfig->deleteRandomBotGuilds)
|
||||
DeleteBotGuilds();
|
||||
|
||||
LoadGuildNames();
|
||||
ValidateGuildCache();
|
||||
}
|
||||
|
||||
bool PlayerbotGuildMgr::CreateGuild(Player* player, std::string guildName)
|
||||
{
|
||||
Guild* guild = new Guild();
|
||||
if (!guild->Create(player, guildName))
|
||||
{
|
||||
LOG_ERROR("playerbots", "Error creating guild [ {} ] with leader [ {} ]", guildName,
|
||||
player->GetName());
|
||||
delete guild;
|
||||
return false;
|
||||
}
|
||||
sGuildMgr->AddGuild(guild);
|
||||
|
||||
LOG_DEBUG("playerbots", "Guild created: id={} name='{}'", guild->GetId(), guildName);
|
||||
SetGuildEmblem(guild->GetId());
|
||||
|
||||
GuildCache entry;
|
||||
entry.name = guildName;
|
||||
entry.memberCount = 1;
|
||||
entry.status = 1;
|
||||
entry.maxMembers = sPlayerbotAIConfig->randomBotGuildSizeMax;
|
||||
entry.faction = player->GetTeamId();
|
||||
|
||||
_guildCache[guild->GetId()] = entry;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlayerbotGuildMgr::SetGuildEmblem(uint32 guildId)
|
||||
{
|
||||
Guild* guild = sGuildMgr->GetGuildById(guildId);
|
||||
if (!guild)
|
||||
return false;
|
||||
|
||||
// create random emblem
|
||||
uint32 st, cl, br, bc, bg;
|
||||
bg = urand(0, 51);
|
||||
bc = urand(0, 17);
|
||||
cl = urand(0, 17);
|
||||
br = urand(0, 7);
|
||||
st = urand(0, 180);
|
||||
|
||||
LOG_DEBUG("playerbots",
|
||||
"[TABARD] new guild id={} random -> style={}, color={}, borderStyle={}, borderColor={}, bgColor={}",
|
||||
guild->GetId(), st, cl, br, bc, bg);
|
||||
|
||||
// populate guild table with a random tabard design
|
||||
CharacterDatabase.Execute(
|
||||
"UPDATE guild SET EmblemStyle={}, EmblemColor={}, BorderStyle={}, BorderColor={}, BackgroundColor={} "
|
||||
"WHERE guildid={}",
|
||||
st, cl, br, bc, bg, guild->GetId());
|
||||
LOG_DEBUG("playerbots", "[TABARD] UPDATE done for guild id={}", guild->GetId());
|
||||
|
||||
// Immediate reading for log
|
||||
if (QueryResult qr = CharacterDatabase.Query(
|
||||
"SELECT EmblemStyle,EmblemColor,BorderStyle,BorderColor,BackgroundColor FROM guild WHERE guildid={}",
|
||||
guild->GetId()))
|
||||
{
|
||||
Field* f = qr->Fetch();
|
||||
LOG_DEBUG("playerbots",
|
||||
"[TABARD] DB check guild id={} => style={}, color={}, borderStyle={}, borderColor={}, bgColor={}",
|
||||
guild->GetId(), f[0].Get<uint8>(), f[1].Get<uint8>(), f[2].Get<uint8>(), f[3].Get<uint8>(), f[4].Get<uint8>());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string PlayerbotGuildMgr::AssignToGuild(Player* player)
|
||||
{
|
||||
if (!player)
|
||||
return "";
|
||||
|
||||
uint8_t playerFaction = player->GetTeamId();
|
||||
std::vector<GuildCache*> partiallyfilledguilds;
|
||||
partiallyfilledguilds.reserve(_guildCache.size());
|
||||
|
||||
for (auto& keyValue : _guildCache)
|
||||
{
|
||||
GuildCache& cached = keyValue.second;
|
||||
if (!cached.hasRealPlayer && cached.status == 1 && cached.faction == playerFaction)
|
||||
partiallyfilledguilds.push_back(&cached);
|
||||
}
|
||||
|
||||
if (!partiallyfilledguilds.empty())
|
||||
{
|
||||
size_t idx = static_cast<size_t>(urand(0, static_cast<int>(partiallyfilledguilds.size()) - 1));
|
||||
return (partiallyfilledguilds[idx]->name);
|
||||
}
|
||||
|
||||
size_t count = std::count_if(
|
||||
_guildCache.begin(), _guildCache.end(),
|
||||
[](const std::pair<const uint32, GuildCache>& pair)
|
||||
{
|
||||
return !pair.second.hasRealPlayer;
|
||||
}
|
||||
);
|
||||
|
||||
if (count < sPlayerbotAIConfig->randomBotGuildCount)
|
||||
{
|
||||
for (auto& key : _shuffled_guild_keys)
|
||||
{
|
||||
if (_guildNames[key])
|
||||
{
|
||||
LOG_INFO("playerbots","Assigning player [{}] to guild [{}]", player->GetName(), key);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
LOG_ERROR("playerbots","No available guild names left.");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void PlayerbotGuildMgr::OnGuildUpdate(Guild* guild)
|
||||
{
|
||||
auto it = _guildCache.find(guild->GetId());
|
||||
if (it == _guildCache.end())
|
||||
return;
|
||||
|
||||
GuildCache& entry = it->second;
|
||||
entry.memberCount = guild->GetMemberCount();
|
||||
if (entry.memberCount < entry.maxMembers)
|
||||
entry.status = 1;
|
||||
else if (entry.memberCount >= entry.maxMembers)
|
||||
entry.status = 2; // Full
|
||||
std::string guildName = guild->GetName();
|
||||
for (auto& it : _guildNames)
|
||||
{
|
||||
if (it.first == guildName)
|
||||
{
|
||||
it.second = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerbotGuildMgr::ResetGuildCache()
|
||||
{
|
||||
for (auto it = _guildCache.begin(); it != _guildCache.end();)
|
||||
{
|
||||
GuildCache& cached = it->second;
|
||||
cached.memberCount = 0;
|
||||
cached.faction = 2;
|
||||
cached.status = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerbotGuildMgr::LoadGuildNames()
|
||||
{
|
||||
LOG_INFO("playerbots", "Loading guild names from playerbots_guild_names...");
|
||||
|
||||
QueryResult result = CharacterDatabase.Query("SELECT name_id, name FROM playerbots_guild_names");
|
||||
|
||||
if (!result)
|
||||
{
|
||||
LOG_ERROR("playerbots", "No entries found in playerbots_guild_names. List is empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
_guildNames[fields[1].Get<std::string>()] = true;
|
||||
} while (result->NextRow());
|
||||
|
||||
for (auto& pair : _guildNames)
|
||||
_shuffled_guild_keys.push_back(pair.first);
|
||||
|
||||
std::random_device rd;
|
||||
std::mt19937 g(rd());
|
||||
|
||||
std::shuffle(_shuffled_guild_keys.begin(), _shuffled_guild_keys.end(), g);
|
||||
LOG_INFO("playerbots", "Loaded {} guild entries from playerbots_guild_names table.", _guildNames.size());
|
||||
}
|
||||
|
||||
void PlayerbotGuildMgr::ValidateGuildCache()
|
||||
{
|
||||
QueryResult result = CharacterDatabase.Query("SELECT guildid, name FROM guild");
|
||||
if (!result)
|
||||
{
|
||||
LOG_ERROR("playerbots", "No guilds found in database, resetting guild cache");
|
||||
ResetGuildCache();
|
||||
return;
|
||||
}
|
||||
|
||||
std::unordered_map<uint32, std::string> dbGuilds;
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
uint32 guildId = fields[0].Get<uint32>();
|
||||
std::string guildName = fields[1].Get<std::string>();
|
||||
dbGuilds[guildId] = guildName;
|
||||
} while (result->NextRow());
|
||||
|
||||
for (auto it = dbGuilds.begin(); it != dbGuilds.end(); it++)
|
||||
{
|
||||
uint32 guildId = it->first;
|
||||
GuildCache cache;
|
||||
cache.name = it->second;
|
||||
cache.maxMembers = sPlayerbotAIConfig->randomBotGuildSizeMax;
|
||||
|
||||
Guild* guild = sGuildMgr ->GetGuildById(guildId);
|
||||
if (!guild)
|
||||
continue;
|
||||
|
||||
cache.memberCount = guild->GetMemberCount();
|
||||
ObjectGuid leaderGuid = guild->GetLeaderGUID();
|
||||
CharacterCacheEntry const* leaderEntry = sCharacterCache->GetCharacterCacheByGuid(leaderGuid);
|
||||
uint32 leaderAccount = leaderEntry->AccountId;
|
||||
cache.hasRealPlayer = !(sPlayerbotAIConfig->IsInRandomAccountList(leaderAccount));
|
||||
cache.faction = Player::TeamIdForRace(leaderEntry->Race);
|
||||
if (cache.memberCount == 0)
|
||||
cache.status = 0; // empty
|
||||
else if (cache.memberCount < cache.maxMembers)
|
||||
cache.status = 1; // partially filled
|
||||
else
|
||||
cache.status = 2; // full
|
||||
|
||||
_guildCache.insert_or_assign(guildId, cache);
|
||||
for (auto& it : _guildNames)
|
||||
{
|
||||
if (it.first == cache.name)
|
||||
{
|
||||
it.second = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerbotGuildMgr::DeleteBotGuilds()
|
||||
{
|
||||
LOG_INFO("playerbots", "Deleting random bot guilds...");
|
||||
std::vector<uint32> randomBots;
|
||||
|
||||
PlayerbotsDatabasePreparedStatement* stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BOT);
|
||||
stmt->SetData(0, "add");
|
||||
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
uint32 bot = fields[0].Get<uint32>();
|
||||
randomBots.push_back(bot);
|
||||
} while (result->NextRow());
|
||||
}
|
||||
|
||||
for (std::vector<uint32>::iterator i = randomBots.begin(); i != randomBots.end(); ++i)
|
||||
{
|
||||
if (Guild* guild = sGuildMgr->GetGuildByLeader(ObjectGuid::Create<HighGuid::Player>(*i)))
|
||||
guild->Disband();
|
||||
}
|
||||
LOG_INFO("playerbots", "Random bot guilds deleted");
|
||||
}
|
||||
|
||||
bool PlayerbotGuildMgr::IsRealGuild(Player* bot)
|
||||
{
|
||||
if (!bot)
|
||||
return false;
|
||||
uint32 guildId = bot->GetGuildId();
|
||||
if (!guildId)
|
||||
return false;
|
||||
|
||||
return IsRealGuild(guildId);
|
||||
}
|
||||
|
||||
bool PlayerbotGuildMgr::IsRealGuild(uint32 guildId)
|
||||
{
|
||||
if (!guildId)
|
||||
return false;
|
||||
|
||||
auto it = _guildCache.find(guildId);
|
||||
if (it == _guildCache.end())
|
||||
return false;
|
||||
|
||||
return it->second.hasRealPlayer;
|
||||
}
|
||||
|
||||
class BotGuildCacheWorldScript : public WorldScript
|
||||
{
|
||||
public:
|
||||
|
||||
BotGuildCacheWorldScript() : WorldScript("BotGuildCacheWorldScript"), _validateTimer(0){}
|
||||
|
||||
void OnUpdate(uint32 diff) override
|
||||
{
|
||||
_validateTimer += diff;
|
||||
|
||||
if (_validateTimer >= _validateInterval) // Validate every hour
|
||||
{
|
||||
_validateTimer = 0;
|
||||
sPlayerbotGuildMgr->ValidateGuildCache();
|
||||
LOG_INFO("playerbots", "Scheduled guild cache validation");
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint32 _validateInterval = HOUR*IN_MILLISECONDS;
|
||||
uint32 _validateTimer;
|
||||
};
|
||||
|
||||
void PlayerBotsGuildValidationScript()
|
||||
{
|
||||
new BotGuildCacheWorldScript();
|
||||
}
|
||||
52
src/PlayerbotGuildMgr.h
Normal file
52
src/PlayerbotGuildMgr.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#ifndef _PLAYERBOT_PLAYERBOTGUILDMGR_H
|
||||
#define _PLAYERBOT_PLAYERBOTGUILDMGR_H
|
||||
|
||||
#include "Guild.h"
|
||||
#include "Player.h"
|
||||
#include "PlayerbotAI.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class PlayerbotGuildMgr
|
||||
{
|
||||
public:
|
||||
static PlayerbotGuildMgr* instance()
|
||||
{
|
||||
static PlayerbotGuildMgr instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
void Init();
|
||||
std::string AssignToGuild(Player* player);
|
||||
void LoadGuildNames();
|
||||
void ValidateGuildCache();
|
||||
void ResetGuildCache();
|
||||
bool CreateGuild(Player* player, std::string guildName);
|
||||
void OnGuildUpdate (Guild* guild);
|
||||
bool SetGuildEmblem(uint32 guildId);
|
||||
void DeleteBotGuilds();
|
||||
bool IsRealGuild(uint32 guildId);
|
||||
bool IsRealGuild(Player* bot);
|
||||
|
||||
private:
|
||||
PlayerbotGuildMgr();
|
||||
std::unordered_map<std::string, bool> _guildNames;
|
||||
|
||||
struct GuildCache
|
||||
{
|
||||
std::string name;
|
||||
uint8 status;
|
||||
uint32 maxMembers = 0;
|
||||
uint32 memberCount = 0;
|
||||
uint8 faction = 0;
|
||||
bool hasRealPlayer = false;
|
||||
};
|
||||
std::unordered_map<uint32 , GuildCache> _guildCache;
|
||||
std::vector<std::string> _shuffled_guild_keys;
|
||||
};
|
||||
|
||||
void PlayerBotsGuildValidationScript();
|
||||
|
||||
#define sPlayerbotGuildMgr PlayerbotGuildMgr::instance()
|
||||
|
||||
#endif
|
||||
@@ -9,9 +9,10 @@
|
||||
#include <cstring>
|
||||
#include <istream>
|
||||
#include <string>
|
||||
#include <openssl/sha.h>
|
||||
#include <unordered_set>
|
||||
#include <openssl/sha.h>
|
||||
#include <iomanip>
|
||||
#include <algorithm>
|
||||
|
||||
#include "ChannelMgr.h"
|
||||
#include "CharacterCache.h"
|
||||
@@ -27,17 +28,17 @@
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "PlayerbotDbStore.h"
|
||||
#include "PlayerbotFactory.h"
|
||||
#include "PlayerbotOperations.h"
|
||||
#include "PlayerbotSecurity.h"
|
||||
#include "PlayerbotWorldThreadProcessor.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PlayerbotGuildMgr.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "WorldSession.h"
|
||||
#include "ChannelMgr.h"
|
||||
#include "BroadcastHelper.h"
|
||||
#include "PlayerbotDbStore.h"
|
||||
#include "WorldSessionMgr.h"
|
||||
#include "DatabaseEnv.h" // Added for gender choice
|
||||
#include <algorithm> // Added for gender choice
|
||||
#include "DatabaseEnv.h"
|
||||
|
||||
class BotInitGuard
|
||||
{
|
||||
@@ -66,6 +67,7 @@ private:
|
||||
};
|
||||
|
||||
std::unordered_set<ObjectGuid> BotInitGuard::botsBeingInitialized;
|
||||
std::unordered_set<ObjectGuid> PlayerbotHolder::botLoading;
|
||||
|
||||
PlayerbotHolder::PlayerbotHolder() : PlayerbotAIBase(false) {}
|
||||
class PlayerbotLoginQueryHolder : public LoginQueryHolder
|
||||
@@ -74,18 +76,16 @@ private:
|
||||
uint32 masterAccountId;
|
||||
PlayerbotHolder* playerbotHolder;
|
||||
public:
|
||||
PlayerbotLoginQueryHolder(PlayerbotHolder* playerbotHolder, uint32 masterAccount, uint32 accountId, ObjectGuid guid)
|
||||
: LoginQueryHolder(accountId, guid), masterAccountId(masterAccount), playerbotHolder(playerbotHolder)
|
||||
PlayerbotLoginQueryHolder(uint32 masterAccount, uint32 accountId, ObjectGuid guid)
|
||||
: LoginQueryHolder(accountId, guid), masterAccountId(masterAccount)
|
||||
{
|
||||
}
|
||||
|
||||
uint32 GetMasterAccountId() const { return masterAccountId; }
|
||||
PlayerbotHolder* GetPlayerbotHolder() { return playerbotHolder; }
|
||||
};
|
||||
|
||||
void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId)
|
||||
{
|
||||
// bot is loading
|
||||
if (botLoading.find(playerGuid) != botLoading.end())
|
||||
return;
|
||||
|
||||
@@ -142,7 +142,7 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
|
||||
return;
|
||||
}
|
||||
std::shared_ptr<PlayerbotLoginQueryHolder> holder =
|
||||
std::make_shared<PlayerbotLoginQueryHolder>(this, masterAccountId, accountId, playerGuid);
|
||||
std::make_shared<PlayerbotLoginQueryHolder>(masterAccountId, accountId, playerGuid);
|
||||
if (!holder->Initialize())
|
||||
{
|
||||
return;
|
||||
@@ -152,8 +152,27 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
|
||||
|
||||
// Always login in with world session to avoid race condition
|
||||
sWorld->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder))
|
||||
.AfterComplete([this](SQLQueryHolderBase const& holder)
|
||||
{ HandlePlayerBotLoginCallback(static_cast<PlayerbotLoginQueryHolder const&>(holder)); });
|
||||
.AfterComplete(
|
||||
[](SQLQueryHolderBase const& queryHolder)
|
||||
{
|
||||
PlayerbotLoginQueryHolder const& holder = static_cast<PlayerbotLoginQueryHolder const&>(queryHolder);
|
||||
PlayerbotHolder* mgr = sRandomPlayerbotMgr; // could be null
|
||||
uint32 masterAccountId = holder.GetMasterAccountId();
|
||||
|
||||
if (masterAccountId)
|
||||
{
|
||||
// verify and find current world session of master
|
||||
WorldSession* masterSession = sWorldSessionMgr->FindSession(masterAccountId);
|
||||
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
|
||||
if (masterPlayer)
|
||||
mgr = GET_PLAYERBOT_MGR(masterPlayer);
|
||||
}
|
||||
|
||||
if (mgr)
|
||||
mgr->HandlePlayerBotLoginCallback(holder);
|
||||
else
|
||||
PlayerbotHolder::botLoading.erase(holder.GetGuid());
|
||||
});
|
||||
}
|
||||
|
||||
bool PlayerbotHolder::IsAccountLinked(uint32 accountId, uint32 linkedAccountId)
|
||||
@@ -168,8 +187,9 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
|
||||
uint32 botAccountId = holder.GetAccountId();
|
||||
// At login DBC locale should be what the server is set to use by default (as spells etc are hardcoded to ENUS this
|
||||
// allows channels to work as intended)
|
||||
WorldSession* botSession = new WorldSession(botAccountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING,
|
||||
time_t(0), sWorld->GetDefaultDbcLocale(), 0, false, false, 0, true);
|
||||
WorldSession* botSession =
|
||||
new WorldSession(botAccountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING, time_t(0),
|
||||
sWorld->GetDefaultDbcLocale(), 0, false, false, 0, true);
|
||||
|
||||
botSession->HandlePlayerLoginFromDB(holder); // will delete lqh
|
||||
|
||||
@@ -180,24 +200,27 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
|
||||
LOG_DEBUG("mod-playerbots", "Bot player could not be loaded for account ID: {}", botAccountId);
|
||||
botSession->LogoutPlayer(true);
|
||||
delete botSession;
|
||||
botLoading.erase(holder.GetGuid());
|
||||
PlayerbotHolder::botLoading.erase(holder.GetGuid());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 masterAccount = holder.GetMasterAccountId();
|
||||
WorldSession* masterSession = masterAccount ? sWorldSessionMgr->FindSession(masterAccount) : nullptr;
|
||||
uint32 masterAccountId = holder.GetMasterAccountId();
|
||||
WorldSession* masterSession = masterAccountId ? sWorldSessionMgr->FindSession(masterAccountId) : nullptr;
|
||||
|
||||
// Check if masterSession->GetPlayer() is valid
|
||||
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
|
||||
if (masterSession && !masterPlayer)
|
||||
{
|
||||
LOG_DEBUG("mod-playerbots", "Master session found but no player is associated for master account ID: {}", masterAccount);
|
||||
LOG_DEBUG("mod-playerbots", "Master session found but no player is associated for master account ID: {}",
|
||||
masterAccountId);
|
||||
}
|
||||
|
||||
sRandomPlayerbotMgr->OnPlayerLogin(bot);
|
||||
OnBotLogin(bot);
|
||||
auto op = std::make_unique<OnBotLoginOperation>(bot->GetGUID(), masterAccountId);
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(op));
|
||||
|
||||
botLoading.erase(holder.GetGuid());
|
||||
PlayerbotHolder::botLoading.erase(holder.GetGuid());
|
||||
}
|
||||
|
||||
void PlayerbotHolder::UpdateSessions()
|
||||
@@ -316,11 +339,9 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid)
|
||||
if (!botAI)
|
||||
return;
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
if (group && !bot->InBattleground() && !bot->InBattlegroundQueue() && botAI->HasActivePlayerMaster())
|
||||
{
|
||||
sPlayerbotDbStore->Save(botAI);
|
||||
}
|
||||
// Queue group cleanup operation for world thread
|
||||
auto cleanupOp = std::make_unique<BotLogoutGroupCleanupOperation>(guid);
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(cleanupOp));
|
||||
|
||||
LOG_DEBUG("playerbots", "Bot {} logging out", bot->GetName().c_str());
|
||||
bot->SaveToDB(false, false);
|
||||
@@ -549,6 +570,7 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
|
||||
|
||||
botAI->TellMaster("Hello!", PLAYERBOT_SECURITY_TALK);
|
||||
|
||||
// Queue group operations for world thread
|
||||
if (master && master->GetGroup() && !group)
|
||||
{
|
||||
Group* mgroup = master->GetGroup();
|
||||
@@ -556,24 +578,29 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
|
||||
{
|
||||
if (!mgroup->isRaidGroup() && !mgroup->isLFGGroup() && !mgroup->isBGGroup() && !mgroup->isBFGroup())
|
||||
{
|
||||
mgroup->ConvertToRaid();
|
||||
// Queue ConvertToRaid operation
|
||||
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(master->GetGUID());
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp));
|
||||
}
|
||||
if (mgroup->isRaidGroup())
|
||||
{
|
||||
mgroup->AddMember(bot);
|
||||
// Queue AddMember operation
|
||||
auto addOp = std::make_unique<GroupInviteOperation>(master->GetGUID(), bot->GetGUID());
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(addOp));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mgroup->AddMember(bot);
|
||||
// Queue AddMember operation
|
||||
auto addOp = std::make_unique<GroupInviteOperation>(master->GetGUID(), bot->GetGUID());
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(addOp));
|
||||
}
|
||||
}
|
||||
else if (master && !group)
|
||||
{
|
||||
Group* newGroup = new Group();
|
||||
newGroup->Create(master);
|
||||
sGroupMgr->AddGroup(newGroup);
|
||||
newGroup->AddMember(bot);
|
||||
// Queue group creation and AddMember operation
|
||||
auto inviteOp = std::make_unique<GroupInviteOperation>(master->GetGUID(), bot->GetGUID());
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(inviteOp));
|
||||
}
|
||||
// if (master)
|
||||
// {
|
||||
@@ -1167,7 +1194,7 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
|
||||
if (ObjectAccessor::FindConnectedPlayer(guid))
|
||||
continue;
|
||||
uint32 guildId = sCharacterCache->GetCharacterGuildIdByGuid(guid);
|
||||
if (guildId && PlayerbotAI::IsRealGuild(guildId))
|
||||
if (guildId && sPlayerbotGuildMgr->IsRealGuild(guildId))
|
||||
continue;
|
||||
AddPlayerBot(guid, master->GetSession()->GetAccountId());
|
||||
messages.push_back("Add class " + std::string(charname));
|
||||
@@ -1602,8 +1629,26 @@ void PlayerbotMgr::OnBotLoginInternal(Player* const bot)
|
||||
|
||||
void PlayerbotMgr::OnPlayerLogin(Player* player)
|
||||
{
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
WorldSession* session = player->GetSession();
|
||||
if (!session)
|
||||
{
|
||||
LOG_WARN("playerbots", "Unable to register locale priority for player {} because the session is missing", player->GetName());
|
||||
return;
|
||||
}
|
||||
|
||||
// DB locale (source of bot text translation)
|
||||
LocaleConstant const databaseLocale = session->GetSessionDbLocaleIndex();
|
||||
|
||||
// For bot texts (DB-driven), prefer the database locale with a safe fallback.
|
||||
LocaleConstant usedLocale = databaseLocale;
|
||||
if (usedLocale >= MAX_LOCALES)
|
||||
usedLocale = LOCALE_enUS; // fallback
|
||||
|
||||
// set locale priority for bot texts
|
||||
sPlayerbotTextMgr->AddLocalePriority(player->GetSession()->GetSessionDbcLocale());
|
||||
sPlayerbotTextMgr->AddLocalePriority(usedLocale);
|
||||
|
||||
if (sPlayerbotAIConfig->selfBotLevel > 2)
|
||||
HandlePlayerbotCommand("self", player);
|
||||
@@ -1611,7 +1656,7 @@ void PlayerbotMgr::OnPlayerLogin(Player* player)
|
||||
if (!sPlayerbotAIConfig->botAutologin)
|
||||
return;
|
||||
|
||||
uint32 accountId = player->GetSession()->GetAccountId();
|
||||
uint32 accountId = session->GetAccountId();
|
||||
QueryResult results = CharacterDatabase.Query("SELECT name FROM characters WHERE account = {}", accountId);
|
||||
if (results)
|
||||
{
|
||||
|
||||
@@ -60,7 +60,7 @@ protected:
|
||||
virtual void OnBotLoginInternal(Player* const bot) = 0;
|
||||
|
||||
PlayerBotMap playerBots;
|
||||
std::unordered_set<ObjectGuid> botLoading;
|
||||
static std::unordered_set<ObjectGuid> botLoading;
|
||||
};
|
||||
|
||||
class PlayerbotMgr : public PlayerbotHolder
|
||||
|
||||
93
src/PlayerbotOperation.h
Normal file
93
src/PlayerbotOperation.h
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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_OPERATION_H
|
||||
#define _PLAYERBOT_OPERATION_H
|
||||
|
||||
#include "Common.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include <memory>
|
||||
|
||||
/**
|
||||
* @brief Base class for thread-unsafe operations that must be executed in the world thread
|
||||
*
|
||||
* PlayerbotOperation represents an operation that needs to be deferred from a map thread
|
||||
* to the world thread for safe execution. Examples include group modifications, LFG operations,
|
||||
* guild operations, etc.
|
||||
*
|
||||
* Thread Safety:
|
||||
* - The constructor and data members must be thread-safe (use copies, not pointers)
|
||||
* - Execute() is called in the world thread and can safely perform thread-unsafe operations
|
||||
* - Subclasses must not store raw pointers to (core/world thread) game object (use ObjectGuid instead)
|
||||
*/
|
||||
class PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
virtual ~PlayerbotOperation() = default;
|
||||
|
||||
/**
|
||||
* @brief Execute this operation in the world thread
|
||||
*
|
||||
* This method is called by PlayerbotWorldThreadProcessor::Update() which runs in the world thread.
|
||||
* It's safe to perform any thread-unsafe operation here (Group, LFG, Guild, etc.)
|
||||
*
|
||||
* @return true if operation succeeded, false if it failed
|
||||
*/
|
||||
virtual bool Execute() = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the bot GUID this operation is for (optional)
|
||||
*
|
||||
* Used for logging and debugging purposes.
|
||||
*
|
||||
* @return ObjectGuid of the bot, or ObjectGuid::Empty if not applicable
|
||||
*/
|
||||
virtual ObjectGuid GetBotGuid() const { return ObjectGuid::Empty; }
|
||||
|
||||
/**
|
||||
* @brief Get the operation priority (higher = more urgent)
|
||||
*
|
||||
* Priority levels:
|
||||
* - 100: Critical (crash prevention, cleanup operations)
|
||||
* - 50: High (player-facing operations like group invites)
|
||||
* - 10: Normal (background operations)
|
||||
* - 0: Low (statistics, logging)
|
||||
*
|
||||
* @return Priority value (0-100)
|
||||
*/
|
||||
virtual uint32 GetPriority() const { return 10; }
|
||||
|
||||
/**
|
||||
* @brief Get a human-readable name for this operation
|
||||
*
|
||||
* Used for logging and debugging.
|
||||
*
|
||||
* @return Operation name
|
||||
*/
|
||||
virtual std::string GetName() const { return "Unknown Operation"; }
|
||||
|
||||
/**
|
||||
* @brief Check if this operation is still valid
|
||||
*
|
||||
* Called before Execute() to check if the operation should still be executed.
|
||||
* For example, if a bot logged out, group invite operations for that bot can be skipped.
|
||||
*
|
||||
* @return true if operation should be executed, false to skip
|
||||
*/
|
||||
virtual bool IsValid() const { return true; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Comparison operator for priority queue (higher priority first)
|
||||
*/
|
||||
struct PlayerbotOperationComparator
|
||||
{
|
||||
bool operator()(const std::unique_ptr<PlayerbotOperation>& a, const std::unique_ptr<PlayerbotOperation>& b) const
|
||||
{
|
||||
return a->GetPriority() < b->GetPriority(); // Lower priority goes to back of queue
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
513
src/PlayerbotOperations.h
Normal file
513
src/PlayerbotOperations.h
Normal file
@@ -0,0 +1,513 @@
|
||||
/*
|
||||
* 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_OPERATIONS_H
|
||||
#define _PLAYERBOT_OPERATIONS_H
|
||||
|
||||
#include "Group.h"
|
||||
#include "GroupMgr.h"
|
||||
#include "GuildMgr.h"
|
||||
#include "Playerbots.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "PlayerbotOperation.h"
|
||||
#include "Player.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "PlayerbotMgr.h"
|
||||
#include "PlayerbotDbStore.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "WorldSession.h"
|
||||
#include "WorldSessionMgr.h"
|
||||
|
||||
// Group invite operation
|
||||
class GroupInviteOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
GroupInviteOperation(ObjectGuid botGuid, ObjectGuid targetGuid)
|
||||
: m_botGuid(botGuid), m_targetGuid(targetGuid)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
Player* target = ObjectAccessor::FindPlayer(m_targetGuid);
|
||||
|
||||
if (!bot || !target)
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupInviteOperation: Bot or target not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if target is already in a group
|
||||
if (target->GetGroup())
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupInviteOperation: Target {} is already in a group", target->GetName());
|
||||
return false;
|
||||
}
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
|
||||
// Create group if bot doesn't have one
|
||||
if (!group)
|
||||
{
|
||||
group = new Group;
|
||||
if (!group->Create(bot))
|
||||
{
|
||||
delete group;
|
||||
LOG_ERROR("playerbots", "GroupInviteOperation: Failed to create group for bot {}", bot->GetName());
|
||||
return false;
|
||||
}
|
||||
sGroupMgr->AddGroup(group);
|
||||
LOG_DEBUG("playerbots", "GroupInviteOperation: Created new group for bot {}", bot->GetName());
|
||||
}
|
||||
|
||||
// Convert to raid if needed (more than 5 members)
|
||||
if (!group->isRaidGroup() && group->GetMembersCount() >= 5)
|
||||
{
|
||||
group->ConvertToRaid();
|
||||
LOG_DEBUG("playerbots", "GroupInviteOperation: Converted group to raid");
|
||||
}
|
||||
|
||||
// Add member to group
|
||||
if (group->AddMember(target))
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupInviteOperation: Successfully added {} to group", target->GetName());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR("playerbots", "GroupInviteOperation: Failed to add {} to group", target->GetName());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||
|
||||
uint32 GetPriority() const override { return 50; } // High priority (player-facing)
|
||||
|
||||
std::string GetName() const override { return "GroupInvite"; }
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
// Check if bot still exists and is online
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
Player* target = ObjectAccessor::FindPlayer(m_targetGuid);
|
||||
return bot && target;
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectGuid m_botGuid;
|
||||
ObjectGuid m_targetGuid;
|
||||
};
|
||||
|
||||
// Remove member from group
|
||||
class GroupRemoveMemberOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
GroupRemoveMemberOperation(ObjectGuid botGuid, ObjectGuid targetGuid)
|
||||
: m_botGuid(botGuid), m_targetGuid(targetGuid)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
Player* target = ObjectAccessor::FindPlayer(m_targetGuid);
|
||||
|
||||
if (!bot || !target)
|
||||
return false;
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
if (!group)
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupRemoveMemberOperation: Bot is not in a group");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!group->IsMember(target->GetGUID()))
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupRemoveMemberOperation: Target is not in bot's group");
|
||||
return false;
|
||||
}
|
||||
|
||||
group->RemoveMember(target->GetGUID());
|
||||
LOG_DEBUG("playerbots", "GroupRemoveMemberOperation: Removed {} from group", target->GetName());
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||
|
||||
uint32 GetPriority() const override { return 50; }
|
||||
|
||||
std::string GetName() const override { return "GroupRemoveMember"; }
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
return bot != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectGuid m_botGuid;
|
||||
ObjectGuid m_targetGuid;
|
||||
};
|
||||
|
||||
// Convert group to raid
|
||||
class GroupConvertToRaidOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
GroupConvertToRaidOperation(ObjectGuid botGuid) : m_botGuid(botGuid) {}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
if (!bot)
|
||||
return false;
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
if (!group)
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupConvertToRaidOperation: Bot is not in a group");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (group->isRaidGroup())
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupConvertToRaidOperation: Group is already a raid");
|
||||
return true; // Success - already in desired state
|
||||
}
|
||||
|
||||
group->ConvertToRaid();
|
||||
LOG_DEBUG("playerbots", "GroupConvertToRaidOperation: Converted group to raid");
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||
|
||||
uint32 GetPriority() const override { return 50; }
|
||||
|
||||
std::string GetName() const override { return "GroupConvertToRaid"; }
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
return bot != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectGuid m_botGuid;
|
||||
};
|
||||
|
||||
// Set group leader
|
||||
class GroupSetLeaderOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
GroupSetLeaderOperation(ObjectGuid botGuid, ObjectGuid newLeaderGuid)
|
||||
: m_botGuid(botGuid), m_newLeaderGuid(newLeaderGuid)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
Player* newLeader = ObjectAccessor::FindPlayer(m_newLeaderGuid);
|
||||
|
||||
if (!bot || !newLeader)
|
||||
return false;
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
if (!group)
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupSetLeaderOperation: Bot is not in a group");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!group->IsMember(newLeader->GetGUID()))
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupSetLeaderOperation: New leader is not in the group");
|
||||
return false;
|
||||
}
|
||||
|
||||
group->ChangeLeader(newLeader->GetGUID());
|
||||
LOG_DEBUG("playerbots", "GroupSetLeaderOperation: Changed leader to {}", newLeader->GetName());
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||
|
||||
uint32 GetPriority() const override { return 50; }
|
||||
|
||||
std::string GetName() const override { return "GroupSetLeader"; }
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
Player* newLeader = ObjectAccessor::FindPlayer(m_newLeaderGuid);
|
||||
return bot && newLeader;
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectGuid m_botGuid;
|
||||
ObjectGuid m_newLeaderGuid;
|
||||
};
|
||||
|
||||
// Form arena group
|
||||
class ArenaGroupFormationOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
ArenaGroupFormationOperation(ObjectGuid leaderGuid, std::vector<ObjectGuid> memberGuids,
|
||||
uint32 requiredSize, uint32 arenaTeamId, std::string arenaTeamName)
|
||||
: m_leaderGuid(leaderGuid), m_memberGuids(memberGuids),
|
||||
m_requiredSize(requiredSize), m_arenaTeamId(arenaTeamId), m_arenaTeamName(arenaTeamName)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
Player* leader = ObjectAccessor::FindPlayer(m_leaderGuid);
|
||||
if (!leader)
|
||||
{
|
||||
LOG_ERROR("playerbots", "ArenaGroupFormationOperation: Leader not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 1: Remove all members from their existing groups
|
||||
for (const ObjectGuid& memberGuid : m_memberGuids)
|
||||
{
|
||||
Player* member = ObjectAccessor::FindPlayer(memberGuid);
|
||||
if (!member)
|
||||
continue;
|
||||
|
||||
Group* memberGroup = member->GetGroup();
|
||||
if (memberGroup)
|
||||
{
|
||||
memberGroup->RemoveMember(memberGuid);
|
||||
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Removed {} from their existing group",
|
||||
member->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Disband leader's existing group
|
||||
Group* leaderGroup = leader->GetGroup();
|
||||
if (leaderGroup)
|
||||
{
|
||||
leaderGroup->Disband(true);
|
||||
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Disbanded leader's existing group");
|
||||
}
|
||||
|
||||
// Step 3: Create new group with leader
|
||||
Group* newGroup = new Group();
|
||||
if (!newGroup->Create(leader))
|
||||
{
|
||||
delete newGroup;
|
||||
LOG_ERROR("playerbots", "ArenaGroupFormationOperation: Failed to create arena group for leader {}",
|
||||
leader->GetName());
|
||||
return false;
|
||||
}
|
||||
|
||||
sGroupMgr->AddGroup(newGroup);
|
||||
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Created new arena group with leader {}",
|
||||
leader->GetName());
|
||||
|
||||
// Step 4: Add members to the new group
|
||||
uint32 addedMembers = 0;
|
||||
for (const ObjectGuid& memberGuid : m_memberGuids)
|
||||
{
|
||||
Player* member = ObjectAccessor::FindPlayer(memberGuid);
|
||||
if (!member)
|
||||
{
|
||||
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Member {} not found, skipping",
|
||||
memberGuid.ToString());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (member->GetLevel() < 70)
|
||||
{
|
||||
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Member {} is below level 70, skipping",
|
||||
member->GetName());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (newGroup->AddMember(member))
|
||||
{
|
||||
addedMembers++;
|
||||
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Added {} to arena group",
|
||||
member->GetName());
|
||||
}
|
||||
else
|
||||
LOG_ERROR("playerbots", "ArenaGroupFormationOperation: Failed to add {} to arena group",
|
||||
member->GetName());
|
||||
}
|
||||
|
||||
if (addedMembers == 0)
|
||||
{
|
||||
LOG_ERROR("playerbots", "ArenaGroupFormationOperation: No members were added to the arena group");
|
||||
newGroup->Disband();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 5: Teleport members to leader and reset AI
|
||||
for (const ObjectGuid& memberGuid : m_memberGuids)
|
||||
{
|
||||
Player* member = ObjectAccessor::FindPlayer(memberGuid);
|
||||
if (!member || !newGroup->IsMember(memberGuid))
|
||||
continue;
|
||||
|
||||
PlayerbotAI* memberBotAI = sPlayerbotsMgr->GetPlayerbotAI(member);
|
||||
if (memberBotAI)
|
||||
memberBotAI->Reset();
|
||||
|
||||
member->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP);
|
||||
member->TeleportTo(leader->GetMapId(), leader->GetPositionX(), leader->GetPositionY(),
|
||||
leader->GetPositionZ(), 0);
|
||||
|
||||
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Teleported {} to leader", member->GetName());
|
||||
}
|
||||
|
||||
// Check if we have enough members
|
||||
if (newGroup->GetMembersCount() < m_requiredSize)
|
||||
{
|
||||
LOG_INFO("playerbots", "Team #{} <{}> Group is not ready for match (not enough members: {}/{})",
|
||||
m_arenaTeamId, m_arenaTeamName, newGroup->GetMembersCount(), m_requiredSize);
|
||||
newGroup->Disband();
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO("playerbots", "Team #{} <{}> Group is ready for match with {} members",
|
||||
m_arenaTeamId, m_arenaTeamName, newGroup->GetMembersCount());
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_leaderGuid; }
|
||||
|
||||
uint32 GetPriority() const override { return 60; } // Very high priority (arena/BG operations)
|
||||
|
||||
std::string GetName() const override { return "ArenaGroupFormation"; }
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
Player* leader = ObjectAccessor::FindPlayer(m_leaderGuid);
|
||||
return leader != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectGuid m_leaderGuid;
|
||||
std::vector<ObjectGuid> m_memberGuids;
|
||||
uint32 m_requiredSize;
|
||||
uint32 m_arenaTeamId;
|
||||
std::string m_arenaTeamName;
|
||||
};
|
||||
|
||||
// Bot logout group cleanup operation
|
||||
class BotLogoutGroupCleanupOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
BotLogoutGroupCleanupOperation(ObjectGuid botGuid) : m_botGuid(botGuid) {}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
if (!bot)
|
||||
return false;
|
||||
|
||||
PlayerbotAI* botAI = sPlayerbotsMgr->GetPlayerbotAI(bot);
|
||||
if (!botAI)
|
||||
return false;
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
if (group && !bot->InBattleground() && !bot->InBattlegroundQueue() && botAI->HasActivePlayerMaster())
|
||||
sPlayerbotDbStore->Save(botAI);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||
uint32 GetPriority() const override { return 70; }
|
||||
std::string GetName() const override { return "BotLogoutGroupCleanup"; }
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
return bot != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectGuid m_botGuid;
|
||||
};
|
||||
|
||||
// Add player bot operation (for logging in bots from map threads)
|
||||
class AddPlayerBotOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
AddPlayerBotOperation(ObjectGuid botGuid, uint32 masterAccountId)
|
||||
: m_botGuid(botGuid), m_masterAccountId(masterAccountId)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
sRandomPlayerbotMgr->AddPlayerBot(m_botGuid, m_masterAccountId);
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||
|
||||
uint32 GetPriority() const override { return 50; } // High priority
|
||||
|
||||
std::string GetName() const override { return "AddPlayerBot"; }
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
return !ObjectAccessor::FindConnectedPlayer(m_botGuid);
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectGuid m_botGuid;
|
||||
uint32 m_masterAccountId;
|
||||
};
|
||||
|
||||
class OnBotLoginOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
OnBotLoginOperation(ObjectGuid botGuid, uint32 masterAccountId)
|
||||
: m_botGuid(botGuid), m_masterAccountId(masterAccountId)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
// find and verify bot still exists
|
||||
Player* bot = ObjectAccessor::FindConnectedPlayer(m_botGuid);
|
||||
if (!bot)
|
||||
return false;
|
||||
|
||||
PlayerbotHolder* holder = sRandomPlayerbotMgr;
|
||||
if (m_masterAccountId)
|
||||
{
|
||||
WorldSession* masterSession = sWorldSessionMgr->FindSession(m_masterAccountId);
|
||||
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
|
||||
if (masterPlayer)
|
||||
holder = GET_PLAYERBOT_MGR(masterPlayer);
|
||||
}
|
||||
|
||||
if (!holder)
|
||||
return false;
|
||||
|
||||
holder->OnBotLogin(bot);
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||
uint32 GetPriority() const override { return 100; }
|
||||
std::string GetName() const override { return "OnBotLogin"; }
|
||||
|
||||
bool IsValid() const override { return ObjectAccessor::FindConnectedPlayer(m_botGuid) != nullptr; }
|
||||
|
||||
private:
|
||||
ObjectGuid m_botGuid;
|
||||
uint32 m_masterAccountId = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -17,14 +17,28 @@ PlayerbotSecurity::PlayerbotSecurity(Player* const bot) : bot(bot)
|
||||
|
||||
PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* reason, bool ignoreGroup)
|
||||
{
|
||||
// Basic pointer validity checks
|
||||
if (!bot || !from || !from->GetSession())
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_NONE;
|
||||
|
||||
return PLAYERBOT_SECURITY_DENY_ALL;
|
||||
}
|
||||
|
||||
// GMs always have full access
|
||||
if (from->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
|
||||
return PLAYERBOT_SECURITY_ALLOW_ALL;
|
||||
|
||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||
if (!botAI)
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_NONE;
|
||||
|
||||
return PLAYERBOT_SECURITY_DENY_ALL;
|
||||
}
|
||||
|
||||
if (botAI->IsOpposing(from))
|
||||
{
|
||||
if (reason)
|
||||
@@ -35,6 +49,7 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
|
||||
if (sPlayerbotAIConfig->IsInRandomAccountList(account))
|
||||
{
|
||||
// (duplicate check in case of faction change)
|
||||
if (botAI->IsOpposing(from))
|
||||
{
|
||||
if (reason)
|
||||
@@ -43,27 +58,17 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
return PLAYERBOT_SECURITY_DENY_ALL;
|
||||
}
|
||||
|
||||
// if (sLFGMgr->GetState(bot->GetGUID()) != lfg::LFG_STATE_NONE)
|
||||
// {
|
||||
// if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
|
||||
// {
|
||||
// if (reason)
|
||||
// *reason = PLAYERBOT_DENY_LFG;
|
||||
Group* fromGroup = from->GetGroup();
|
||||
Group* botGroup = bot->GetGroup();
|
||||
|
||||
// return PLAYERBOT_SECURITY_TALK;
|
||||
// }
|
||||
// }
|
||||
|
||||
Group* group = from->GetGroup();
|
||||
if (group && group == bot->GetGroup() && !ignoreGroup && botAI->GetMaster() == from)
|
||||
if (fromGroup && botGroup && fromGroup == botGroup && !ignoreGroup)
|
||||
{
|
||||
return PLAYERBOT_SECURITY_ALLOW_ALL;
|
||||
}
|
||||
if (botAI->GetMaster() == from)
|
||||
return PLAYERBOT_SECURITY_ALLOW_ALL;
|
||||
|
||||
if (group && group == bot->GetGroup() && !ignoreGroup && botAI->GetMaster() != from)
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_NOT_YOURS;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
|
||||
@@ -75,27 +80,34 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
|
||||
if (sPlayerbotAIConfig->groupInvitationPermission <= 1 && (int32)bot->GetLevel() - (int8)from->GetLevel() > 5)
|
||||
if (sPlayerbotAIConfig->groupInvitationPermission <= 1)
|
||||
{
|
||||
if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
|
||||
int32 levelDiff = int32(bot->GetLevel()) - int32(from->GetLevel());
|
||||
if (levelDiff > 5)
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_LOW_LEVEL;
|
||||
if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_LOW_LEVEL;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 botGS = (int32)botAI->GetEquipGearScore(bot/*, false, false*/);
|
||||
int32 fromGS = (int32)botAI->GetEquipGearScore(from/*, false, false*/);
|
||||
if (sPlayerbotAIConfig->gearscorecheck)
|
||||
int32 botGS = static_cast<int32>(botAI->GetEquipGearScore(bot));
|
||||
int32 fromGS = static_cast<int32>(botAI->GetEquipGearScore(from));
|
||||
|
||||
if (sPlayerbotAIConfig->gearscorecheck && botGS && bot->GetLevel() > 15 && botGS > fromGS)
|
||||
{
|
||||
if (botGS && bot->GetLevel() > 15 && botGS > fromGS &&
|
||||
static_cast<float>(100 * (botGS - fromGS) / botGS) >=
|
||||
static_cast<float>(12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel()))
|
||||
uint32 diffPct = uint32(100 * (botGS - fromGS) / botGS);
|
||||
uint32 reqPct = uint32(12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel());
|
||||
|
||||
if (diffPct >= reqPct)
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_GEARSCORE;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
}
|
||||
@@ -111,35 +123,17 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
}
|
||||
}
|
||||
|
||||
/*if (bot->isDead())
|
||||
// If the bot is not in the group, we offer an invite
|
||||
botGroup = bot->GetGroup();
|
||||
if (!botGroup)
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_DEAD;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}*/
|
||||
|
||||
group = bot->GetGroup();
|
||||
if (!group)
|
||||
{
|
||||
/*if (bot->GetMapId() != from->GetMapId() || bot->GetDistance(from) > sPlayerbotAIConfig->whisperDistance)
|
||||
{
|
||||
if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_FAR;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
}*/
|
||||
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_INVITE;
|
||||
|
||||
return PLAYERBOT_SECURITY_INVITE;
|
||||
}
|
||||
|
||||
if (!ignoreGroup && group->IsFull())
|
||||
if (!ignoreGroup && botGroup->IsFull())
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_FULL_GROUP;
|
||||
@@ -147,27 +141,22 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
|
||||
if (!ignoreGroup && group->GetLeaderGUID() != bot->GetGUID())
|
||||
if (!ignoreGroup && botGroup->GetLeaderGUID() != bot->GetGUID())
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_NOT_LEADER;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_IS_LEADER;
|
||||
|
||||
return PLAYERBOT_SECURITY_INVITE;
|
||||
}
|
||||
|
||||
// The bot is the group leader, you can invite the initiator
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_INVITE;
|
||||
*reason = PLAYERBOT_DENY_IS_LEADER;
|
||||
|
||||
return PLAYERBOT_SECURITY_INVITE;
|
||||
}
|
||||
|
||||
// Non-random bots: only their master has full access
|
||||
if (botAI->GetMaster() == from)
|
||||
return PLAYERBOT_SECURITY_ALLOW_ALL;
|
||||
|
||||
@@ -179,8 +168,13 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
|
||||
bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent, Player* from, bool ignoreGroup)
|
||||
{
|
||||
// If something is wrong with the pointers, we silently refuse
|
||||
if (!bot || !from || !from->GetSession())
|
||||
return false;
|
||||
|
||||
DenyReason reason = PLAYERBOT_DENY_NONE;
|
||||
PlayerbotSecurityLevel realLevel = LevelFor(from, &reason, ignoreGroup);
|
||||
|
||||
if (realLevel >= level || from == bot)
|
||||
return true;
|
||||
|
||||
@@ -189,11 +183,17 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
|
||||
return false;
|
||||
|
||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||
Player* master = botAI->GetMaster();
|
||||
if (master && botAI && botAI->IsOpposing(master) && master->GetSession()->GetSecurity() < SEC_GAMEMASTER)
|
||||
if (!botAI)
|
||||
return false;
|
||||
|
||||
Player* master = botAI->GetMaster();
|
||||
if (master && botAI->IsOpposing(master))
|
||||
if (WorldSession* session = master->GetSession())
|
||||
if (session->GetSecurity() < SEC_GAMEMASTER)
|
||||
return false;
|
||||
|
||||
std::ostringstream out;
|
||||
|
||||
switch (realLevel)
|
||||
{
|
||||
case PLAYERBOT_SECURITY_DENY_ALL:
|
||||
@@ -206,19 +206,20 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
|
||||
out << "I'll do it later";
|
||||
break;
|
||||
case PLAYERBOT_DENY_LOW_LEVEL:
|
||||
out << "You are too low level: |cffff0000" << (uint32)from->GetLevel() << "|cffffffff/|cff00ff00"
|
||||
<< (uint32)bot->GetLevel();
|
||||
out << "You are too low level: |cffff0000" << uint32(from->GetLevel()) << "|cffffffff/|cff00ff00"
|
||||
<< uint32(bot->GetLevel());
|
||||
break;
|
||||
case PLAYERBOT_DENY_GEARSCORE:
|
||||
{
|
||||
int botGS = (int)botAI->GetEquipGearScore(bot/*, false, false*/);
|
||||
int fromGS = (int)botAI->GetEquipGearScore(from/*, false, false*/);
|
||||
int botGS = int(botAI->GetEquipGearScore(bot));
|
||||
int fromGS = int(botAI->GetEquipGearScore(from));
|
||||
int diff = (100 * (botGS - fromGS) / botGS);
|
||||
int req = 12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel();
|
||||
|
||||
out << "Your gearscore is too low: |cffff0000" << fromGS << "|cffffffff/|cff00ff00" << botGS
|
||||
<< " |cffff0000" << diff << "%|cffffffff/|cff00ff00" << req << "%";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case PLAYERBOT_DENY_NOT_YOURS:
|
||||
out << "I have a master already";
|
||||
break;
|
||||
@@ -237,13 +238,10 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
|
||||
case PLAYERBOT_DENY_FAR:
|
||||
{
|
||||
out << "You must be closer to invite me to your group. I am in ";
|
||||
|
||||
if (AreaTableEntry const* entry = sAreaTableStore.LookupEntry(bot->GetAreaId()))
|
||||
{
|
||||
out << " |cffffffff(|cffff0000" << entry->area_name[0] << "|cffffffff)";
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case PLAYERBOT_DENY_FULL_GROUP:
|
||||
out << "I am in a full group. Will do it later";
|
||||
break;
|
||||
@@ -251,15 +249,10 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
|
||||
out << "I am currently leading a group. I can invite you if you want.";
|
||||
break;
|
||||
case PLAYERBOT_DENY_NOT_LEADER:
|
||||
if (botAI->GetGroupMaster())
|
||||
{
|
||||
out << "I am in a group with " << botAI->GetGroupMaster()->GetName()
|
||||
<< ". You can ask him for invite.";
|
||||
}
|
||||
if (Player* leader = botAI->GetGroupLeader())
|
||||
out << "I am in a group with " << leader->GetName() << ". You can ask him for invite.";
|
||||
else
|
||||
{
|
||||
out << "I am in a group with someone else. You can ask him for invite.";
|
||||
}
|
||||
break;
|
||||
case PLAYERBOT_DENY_BG:
|
||||
out << "I am in a queue for BG. Will do it later";
|
||||
@@ -283,10 +276,14 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
|
||||
std::string const text = out.str();
|
||||
ObjectGuid guid = from->GetGUID();
|
||||
time_t lastSaid = whispers[guid][text];
|
||||
|
||||
if (!lastSaid || (time(nullptr) - lastSaid) >= sPlayerbotAIConfig->repeatDelay / 1000)
|
||||
{
|
||||
whispers[guid][text] = time(nullptr);
|
||||
bot->Whisper(text, LANG_UNIVERSAL, from);
|
||||
|
||||
// Additional protection against crashes during logout
|
||||
if (bot->IsInWorld() && from->IsInWorld())
|
||||
bot->Whisper(text, LANG_UNIVERSAL, from);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -190,26 +190,29 @@ bool PlayerbotTextMgr::GetBotText(std::string name, std::string& text, std::map<
|
||||
|
||||
void PlayerbotTextMgr::AddLocalePriority(uint32 locale)
|
||||
{
|
||||
if (!locale)
|
||||
if (locale >= MAX_LOCALES)
|
||||
{
|
||||
LOG_WARN("playerbots", "Ignoring locale {} for bot texts because it exceeds MAX_LOCALES ({})", locale, MAX_LOCALES - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
botTextLocalePriority[locale]++;
|
||||
}
|
||||
|
||||
uint32 PlayerbotTextMgr::GetLocalePriority()
|
||||
{
|
||||
uint32 topLocale = 0;
|
||||
|
||||
// if no real players online, reset top locale
|
||||
if (!sWorldSessionMgr->GetActiveSessionCount())
|
||||
uint32 const activeSessions = sWorldSessionMgr->GetActiveSessionCount();
|
||||
if (!activeSessions)
|
||||
{
|
||||
ResetLocalePriority();
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32 topLocale = 0;
|
||||
for (uint8 i = 0; i < MAX_LOCALES; ++i)
|
||||
{
|
||||
if (botTextLocalePriority[i] > topLocale)
|
||||
if (botTextLocalePriority[i] > botTextLocalePriority[topLocale])
|
||||
topLocale = i;
|
||||
}
|
||||
|
||||
|
||||
217
src/PlayerbotWorldThreadProcessor.cpp
Normal file
217
src/PlayerbotWorldThreadProcessor.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* 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 "PlayerbotWorldThreadProcessor.h"
|
||||
|
||||
#include "Log.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
PlayerbotWorldThreadProcessor::PlayerbotWorldThreadProcessor()
|
||||
: m_enabled(true), m_maxQueueSize(10000), m_batchSize(100), m_queueWarningThreshold(80),
|
||||
m_timeSinceLastUpdate(0), m_updateInterval(50) // Process at least every 50ms
|
||||
{
|
||||
LOG_INFO("playerbots", "PlayerbotWorldThreadProcessor initialized");
|
||||
}
|
||||
|
||||
PlayerbotWorldThreadProcessor::~PlayerbotWorldThreadProcessor() { ClearQueue(); }
|
||||
|
||||
PlayerbotWorldThreadProcessor* PlayerbotWorldThreadProcessor::instance()
|
||||
{
|
||||
static PlayerbotWorldThreadProcessor instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
void PlayerbotWorldThreadProcessor::Update(uint32 diff)
|
||||
{
|
||||
if (!m_enabled)
|
||||
return;
|
||||
|
||||
// Accumulate time
|
||||
m_timeSinceLastUpdate += diff;
|
||||
|
||||
// Don't process too frequently to reduce overhead
|
||||
if (m_timeSinceLastUpdate < m_updateInterval)
|
||||
return;
|
||||
|
||||
m_timeSinceLastUpdate = 0;
|
||||
|
||||
// Check queue health (warn if getting full)
|
||||
CheckQueueHealth();
|
||||
|
||||
// Process a batch of operations
|
||||
ProcessBatch();
|
||||
}
|
||||
|
||||
bool PlayerbotWorldThreadProcessor::QueueOperation(std::unique_ptr<PlayerbotOperation> operation)
|
||||
{
|
||||
if (!operation)
|
||||
{
|
||||
LOG_ERROR("playerbots", "Attempted to queue null operation");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_queueMutex);
|
||||
|
||||
// Check if queue is full
|
||||
if (m_operationQueue.size() >= m_maxQueueSize)
|
||||
{
|
||||
LOG_ERROR("playerbots",
|
||||
"PlayerbotWorldThreadProcessor queue is full ({} operations). Dropping operation: {}",
|
||||
m_maxQueueSize, operation->GetName());
|
||||
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
m_stats.totalOperationsSkipped++;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Queue the operation
|
||||
m_operationQueue.push(std::move(operation));
|
||||
|
||||
// Update statistics
|
||||
{
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
m_stats.currentQueueSize = static_cast<uint32>(m_operationQueue.size());
|
||||
m_stats.maxQueueSize = std::max(m_stats.maxQueueSize, m_stats.currentQueueSize);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PlayerbotWorldThreadProcessor::ProcessBatch()
|
||||
{
|
||||
// Extract a batch of operations from the queue
|
||||
std::vector<std::unique_ptr<PlayerbotOperation>> batch;
|
||||
batch.reserve(m_batchSize);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_queueMutex);
|
||||
|
||||
// Extract up to batchSize operations
|
||||
while (!m_operationQueue.empty() && batch.size() < m_batchSize)
|
||||
{
|
||||
batch.push_back(std::move(m_operationQueue.front()));
|
||||
m_operationQueue.pop();
|
||||
}
|
||||
|
||||
// Update current queue size stat
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
m_stats.currentQueueSize = static_cast<uint32>(m_operationQueue.size());
|
||||
}
|
||||
|
||||
// Execute operations outside of lock to avoid blocking queue
|
||||
uint32 totalExecutionTime = 0;
|
||||
for (auto& operation : batch)
|
||||
{
|
||||
if (!operation)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
// Check if operation is still valid
|
||||
if (!operation->IsValid())
|
||||
{
|
||||
LOG_DEBUG("playerbots", "Skipping invalid operation: {}", operation->GetName());
|
||||
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
m_stats.totalOperationsSkipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Time the execution
|
||||
uint32 startTime = getMSTime();
|
||||
|
||||
// Execute the operation
|
||||
bool success = operation->Execute();
|
||||
|
||||
uint32 executionTime = GetMSTimeDiffToNow(startTime);
|
||||
totalExecutionTime += executionTime;
|
||||
|
||||
// Log slow operations
|
||||
if (executionTime > 100)
|
||||
LOG_WARN("playerbots", "Slow operation: {} took {}ms", operation->GetName(), executionTime);
|
||||
|
||||
// Update statistics
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
if (success)
|
||||
m_stats.totalOperationsProcessed++;
|
||||
else
|
||||
{
|
||||
m_stats.totalOperationsFailed++;
|
||||
LOG_DEBUG("playerbots", "Operation failed: {}", operation->GetName());
|
||||
}
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
LOG_ERROR("playerbots", "Exception in operation {}: {}", operation->GetName(), e.what());
|
||||
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
m_stats.totalOperationsFailed++;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_ERROR("playerbots", "Unknown exception in operation {}", operation->GetName());
|
||||
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
m_stats.totalOperationsFailed++;
|
||||
}
|
||||
}
|
||||
|
||||
// Update average execution time
|
||||
if (!batch.empty())
|
||||
{
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
uint32 avgTime = totalExecutionTime / static_cast<uint32>(batch.size());
|
||||
// Exponential moving average
|
||||
m_stats.averageExecutionTimeMs =
|
||||
(m_stats.averageExecutionTimeMs * 9 + avgTime) / 10; // 90% old, 10% new
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerbotWorldThreadProcessor::CheckQueueHealth()
|
||||
{
|
||||
uint32 queueSize = GetQueueSize();
|
||||
uint32 threshold = (m_maxQueueSize * m_queueWarningThreshold) / 100;
|
||||
|
||||
if (queueSize >= threshold)
|
||||
{
|
||||
LOG_WARN("playerbots",
|
||||
"PlayerbotWorldThreadProcessor queue is {}% full ({}/{}). "
|
||||
"Consider increasing update frequency or batch size.",
|
||||
(queueSize * 100) / m_maxQueueSize, queueSize, m_maxQueueSize);
|
||||
}
|
||||
}
|
||||
|
||||
uint32 PlayerbotWorldThreadProcessor::GetQueueSize() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_queueMutex);
|
||||
return static_cast<uint32>(m_operationQueue.size());
|
||||
}
|
||||
|
||||
void PlayerbotWorldThreadProcessor::ClearQueue()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_queueMutex);
|
||||
|
||||
uint32 cleared = static_cast<uint32>(m_operationQueue.size());
|
||||
if (cleared > 0)
|
||||
LOG_INFO("playerbots", "Clearing {} queued operations", cleared);
|
||||
|
||||
// Clear the queue
|
||||
while (!m_operationQueue.empty())
|
||||
{
|
||||
m_operationQueue.pop();
|
||||
}
|
||||
|
||||
// Reset queue size stat
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
m_stats.currentQueueSize = 0;
|
||||
}
|
||||
|
||||
PlayerbotWorldThreadProcessor::Statistics PlayerbotWorldThreadProcessor::GetStatistics() const
|
||||
{
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
return m_stats; // Return a copy
|
||||
}
|
||||
142
src/PlayerbotWorldThreadProcessor.h
Normal file
142
src/PlayerbotWorldThreadProcessor.h
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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_WORLD_THREAD_PROCESSOR_H
|
||||
#define _PLAYERBOT_WORLD_THREAD_PROCESSOR_H
|
||||
|
||||
#include "Common.h"
|
||||
#include "PlayerbotOperation.h"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
||||
/**
|
||||
* @brief Processes thread-unsafe bot operations in the world thread
|
||||
*
|
||||
* The PlayerbotWorldThreadProcessor manages a queue of operations that must be executed
|
||||
* in the world thread rather than map threads. This ensures thread safety for operations
|
||||
* like group modifications, LFG, guilds, battlegrounds, etc.
|
||||
*
|
||||
* Architecture:
|
||||
* - Map threads queue operations via QueueOperation()
|
||||
* - World thread processes operations via Update() (called from WorldScript::OnUpdate)
|
||||
* - Operations are processed in priority order
|
||||
* - Thread-safe queue protected by mutex
|
||||
*
|
||||
* Usage:
|
||||
* auto op = std::make_unique<MyOperation>(botGuid, params);
|
||||
* sPlayerbotWorldProcessor->QueueOperation(std::move(op));
|
||||
*/
|
||||
class PlayerbotWorldThreadProcessor
|
||||
{
|
||||
public:
|
||||
PlayerbotWorldThreadProcessor();
|
||||
~PlayerbotWorldThreadProcessor();
|
||||
|
||||
static PlayerbotWorldThreadProcessor* instance();
|
||||
|
||||
/**
|
||||
* @brief Update and process queued operations (called from world thread)
|
||||
*
|
||||
* This method should be called from WorldScript::OnUpdate hook, which runs in the world thread.
|
||||
* It processes a batch of queued operations.
|
||||
*
|
||||
* @param diff Time since last update in milliseconds
|
||||
*/
|
||||
void Update(uint32 diff);
|
||||
|
||||
/**
|
||||
* @brief Queue an operation for execution in the world thread
|
||||
*
|
||||
* Thread-safe method that can be called from any thread (typically map threads).
|
||||
* The operation will be executed later during Update().
|
||||
*
|
||||
* @param operation Unique pointer to the operation (ownership is transferred)
|
||||
* @return true if operation was queued, false if queue is full
|
||||
*/
|
||||
bool QueueOperation(std::unique_ptr<PlayerbotOperation> operation);
|
||||
|
||||
/**
|
||||
* @brief Get current queue size
|
||||
*
|
||||
* Thread-safe method for monitoring queue size.
|
||||
*
|
||||
* @return Number of operations waiting to be processed
|
||||
*/
|
||||
uint32 GetQueueSize() const;
|
||||
|
||||
/**
|
||||
* @brief Clear all queued operations
|
||||
*
|
||||
* Used during shutdown or emergency situations.
|
||||
*/
|
||||
void ClearQueue();
|
||||
|
||||
/**
|
||||
* @brief Get statistics about operation processing
|
||||
*/
|
||||
struct Statistics
|
||||
{
|
||||
uint64 totalOperationsProcessed = 0;
|
||||
uint64 totalOperationsFailed = 0;
|
||||
uint64 totalOperationsSkipped = 0;
|
||||
uint32 currentQueueSize = 0;
|
||||
uint32 maxQueueSize = 0;
|
||||
uint32 averageExecutionTimeMs = 0;
|
||||
};
|
||||
|
||||
Statistics GetStatistics() const;
|
||||
|
||||
/**
|
||||
* @brief Enable/disable operation processing
|
||||
*
|
||||
* When disabled, operations are still queued but not processed.
|
||||
* Useful for testing or temporary suspension.
|
||||
*
|
||||
* @param enabled true to enable processing, false to disable
|
||||
*/
|
||||
void SetEnabled(bool enabled) { m_enabled = enabled; }
|
||||
|
||||
bool IsEnabled() const { return m_enabled; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Process a single batch of operations
|
||||
*
|
||||
* Extracts operations from queue and executes them.
|
||||
* Called internally by Update().
|
||||
*/
|
||||
void ProcessBatch();
|
||||
|
||||
/**
|
||||
* @brief Check if queue is approaching capacity
|
||||
*
|
||||
* Logs warning if queue is getting full.
|
||||
*/
|
||||
void CheckQueueHealth();
|
||||
|
||||
// Thread-safe queue
|
||||
mutable std::mutex m_queueMutex;
|
||||
std::queue<std::unique_ptr<PlayerbotOperation>> m_operationQueue;
|
||||
|
||||
// Configuration
|
||||
bool m_enabled;
|
||||
uint32 m_maxQueueSize; // Maximum operations in queue
|
||||
uint32 m_batchSize; // Operations to process per Update()
|
||||
uint32 m_queueWarningThreshold; // Warn when queue reaches this percentage
|
||||
|
||||
// Statistics
|
||||
mutable std::mutex m_statsMutex;
|
||||
Statistics m_stats;
|
||||
|
||||
// Timing
|
||||
uint32 m_timeSinceLastUpdate;
|
||||
uint32 m_updateInterval; // Minimum ms between updates
|
||||
};
|
||||
|
||||
#define sPlayerbotWorldProcessor PlayerbotWorldThreadProcessor::instance()
|
||||
|
||||
#endif
|
||||
@@ -25,6 +25,9 @@
|
||||
#include "Metric.h"
|
||||
#include "PlayerScript.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "PlayerbotGuildMgr.h"
|
||||
#include "PlayerbotSpellCache.h"
|
||||
#include "PlayerbotWorldThreadProcessor.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "cs_playerbots.h"
|
||||
@@ -81,12 +84,12 @@ public:
|
||||
PlayerbotsPlayerScript() : PlayerScript("PlayerbotsPlayerScript", {
|
||||
PLAYERHOOK_ON_LOGIN,
|
||||
PLAYERHOOK_ON_AFTER_UPDATE,
|
||||
PLAYERHOOK_ON_CHAT,
|
||||
PLAYERHOOK_ON_CHAT_WITH_CHANNEL,
|
||||
PLAYERHOOK_ON_CHAT_WITH_GROUP,
|
||||
PLAYERHOOK_ON_BEFORE_CRITERIA_PROGRESS,
|
||||
PLAYERHOOK_ON_BEFORE_ACHI_COMPLETE,
|
||||
PLAYERHOOK_CAN_PLAYER_USE_PRIVATE_CHAT,
|
||||
PLAYERHOOK_CAN_PLAYER_USE_GROUP_CHAT,
|
||||
PLAYERHOOK_CAN_PLAYER_USE_GUILD_CHAT,
|
||||
PLAYERHOOK_CAN_PLAYER_USE_CHANNEL_CHAT,
|
||||
PLAYERHOOK_ON_GIVE_EXP,
|
||||
PLAYERHOOK_ON_BEFORE_TELEPORT
|
||||
}) {}
|
||||
@@ -122,24 +125,49 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
bool OnPlayerBeforeTeleport(Player* player, uint32 mapid, float /*x*/, float /*y*/, float /*z*/, float /*orientation*/, uint32 /*options*/, Unit* /*target*/) override
|
||||
bool OnPlayerBeforeTeleport(Player* /*player*/, uint32 /*mapid*/, float /*x*/, float /*y*/, float /*z*/, float /*orientation*/, uint32 /*options*/, Unit* /*target*/) override
|
||||
{
|
||||
// Only apply to bots to prevent affecting real players
|
||||
if (!player || !player->GetSession()->IsBot())
|
||||
/* for now commmented out until proven its actually required
|
||||
* havent seen any proof CleanVisibilityReferences() is needed
|
||||
|
||||
// If the player is not safe to touch, do nothing
|
||||
if (!player)
|
||||
return true;
|
||||
|
||||
// If changing maps, proactively clean visibility references to prevent
|
||||
// stale pointers in other players' visibility maps during the teleport.
|
||||
// This fixes a race condition where:
|
||||
// 1. Bot A teleports and its visible objects start getting cleaned up
|
||||
// 2. Bot B is simultaneously updating visibility and tries to access objects in Bot A's old visibility map
|
||||
// 3. Those objects may already be freed, causing a segmentation fault
|
||||
if (player->GetMapId() != mapid && player->IsInWorld())
|
||||
{
|
||||
player->GetObjectVisibilityContainer().CleanVisibilityReferences();
|
||||
}
|
||||
// If same map or not in world do nothing
|
||||
if (!player->IsInWorld() || player->GetMapId() == mapid)
|
||||
return true;
|
||||
|
||||
return true; // Allow teleport to continue
|
||||
// If real player do nothing
|
||||
PlayerbotAI* ai = GET_PLAYERBOT_AI(player);
|
||||
if (!ai || ai->IsRealPlayer())
|
||||
return true;
|
||||
|
||||
// Cross-map bot teleport: defer visibility reference cleanup.
|
||||
// CleanVisibilityReferences() erases this bot's GUID from other objects' visibility containers.
|
||||
// This is intentionally done via the event queue (instead of directly here) because erasing
|
||||
// from other players' visibility maps inside the teleport call stack can hit unsafe re-entrancy
|
||||
// or iterator invalidation while visibility updates are in progress
|
||||
ObjectGuid guid = player->GetGUID();
|
||||
player->m_Events.AddEventAtOffset(
|
||||
[guid, mapid]()
|
||||
{
|
||||
// do nothing, if the player is not safe to touch
|
||||
Player* p = ObjectAccessor::FindPlayer(guid);
|
||||
if (!p || !p->IsInWorld() || p->IsDuringRemoveFromWorld())
|
||||
return;
|
||||
|
||||
// do nothing if we are already on the target map
|
||||
if (p->GetMapId() == mapid)
|
||||
return;
|
||||
|
||||
p->GetObjectVisibilityContainer().CleanVisibilityReferences();
|
||||
},
|
||||
Milliseconds(0));
|
||||
|
||||
*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnPlayerAfterUpdate(Player* player, uint32 diff) override
|
||||
@@ -163,14 +191,17 @@ public:
|
||||
{
|
||||
botAI->HandleCommand(type, msg, player);
|
||||
|
||||
return false;
|
||||
// hotfix; otherwise the server will crash when whispering logout
|
||||
// https://github.com/mod-playerbots/mod-playerbots/pull/1838
|
||||
// TODO: find the root cause and solve it. (does not happen in party chat)
|
||||
if (msg == "logout")
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Group* group) override
|
||||
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Group* group) override
|
||||
{
|
||||
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||
{
|
||||
@@ -182,9 +213,10 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg) override
|
||||
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Guild* guild) override
|
||||
{
|
||||
if (type == CHAT_MSG_GUILD)
|
||||
{
|
||||
@@ -203,9 +235,10 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Channel* channel) override
|
||||
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Channel* channel) override
|
||||
{
|
||||
if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player))
|
||||
{
|
||||
@@ -216,6 +249,7 @@ public:
|
||||
}
|
||||
|
||||
sRandomPlayerbotMgr->HandleCommand(type, msg, player);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OnPlayerBeforeAchievementComplete(Player* player, AchievementEntry const* achievement) override
|
||||
@@ -300,7 +334,8 @@ class PlayerbotsWorldScript : public WorldScript
|
||||
{
|
||||
public:
|
||||
PlayerbotsWorldScript() : WorldScript("PlayerbotsWorldScript", {
|
||||
WORLDHOOK_ON_BEFORE_WORLD_INITIALIZED
|
||||
WORLDHOOK_ON_BEFORE_WORLD_INITIALIZED,
|
||||
WORLDHOOK_ON_UPDATE
|
||||
}) {}
|
||||
|
||||
void OnBeforeWorldInitialized() override
|
||||
@@ -329,6 +364,16 @@ public:
|
||||
|
||||
LOG_INFO("server.loading", ">> Loaded playerbots config in {} ms", GetMSTimeDiffToNow(oldMSTime));
|
||||
LOG_INFO("server.loading", " ");
|
||||
|
||||
sPlayerbotSpellCache->Initialize();
|
||||
|
||||
LOG_INFO("server.loading", "Playerbots World Thread Processor initialized");
|
||||
}
|
||||
|
||||
void OnUpdate(uint32 diff) override
|
||||
{
|
||||
sPlayerbotWorldProcessor->Update(diff);
|
||||
sRandomPlayerbotMgr->UpdateAI(diff); // World thread only
|
||||
}
|
||||
};
|
||||
|
||||
@@ -390,8 +435,7 @@ public:
|
||||
|
||||
void OnPlayerbotUpdate(uint32 diff) override
|
||||
{
|
||||
sRandomPlayerbotMgr->UpdateAI(diff);
|
||||
sRandomPlayerbotMgr->UpdateSessions();
|
||||
sRandomPlayerbotMgr->UpdateSessions(); // Per-bot updates only
|
||||
}
|
||||
|
||||
void OnPlayerbotUpdateSessions(Player* player) override
|
||||
@@ -459,6 +503,8 @@ public:
|
||||
void OnBattlegroundEnd(Battleground* bg, TeamId /*winnerTeam*/) override { bgStrategies.erase(bg->GetInstanceID()); }
|
||||
};
|
||||
|
||||
void AddPlayerbotsSecureLoginScripts();
|
||||
|
||||
void AddPlayerbotsScripts()
|
||||
{
|
||||
new PlayerbotsDatabaseScript();
|
||||
@@ -468,6 +514,7 @@ void AddPlayerbotsScripts()
|
||||
new PlayerbotsWorldScript();
|
||||
new PlayerbotsScript();
|
||||
new PlayerBotsBGScript();
|
||||
|
||||
AddPlayerbotsSecureLoginScripts();
|
||||
AddSC_playerbots_commandscript();
|
||||
PlayerBotsGuildValidationScript();
|
||||
}
|
||||
|
||||
82
src/PlayerbotsSecureLogin.cpp
Normal file
82
src/PlayerbotsSecureLogin.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
#include "ScriptMgr.h"
|
||||
#include "Opcodes.h"
|
||||
#include "Player.h"
|
||||
#include "ObjectAccessor.h"
|
||||
|
||||
#include "Playerbots.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
static Player* FindOnlineAltbotByGuid(ObjectGuid guid)
|
||||
{
|
||||
if (!guid)
|
||||
return nullptr;
|
||||
|
||||
Player* p = ObjectAccessor::FindPlayer(guid);
|
||||
if (!p)
|
||||
return nullptr;
|
||||
|
||||
PlayerbotAI* ai = GET_PLAYERBOT_AI(p);
|
||||
if (!ai || ai->IsRealPlayer())
|
||||
return nullptr;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static void ForceLogoutViaPlayerbotHolder(Player* target)
|
||||
{
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
PlayerbotAI* ai = GET_PLAYERBOT_AI(target);
|
||||
if (!ai)
|
||||
return;
|
||||
|
||||
if (Player* master = ai->GetMaster())
|
||||
{
|
||||
if (PlayerbotMgr* mgr = GET_PLAYERBOT_MGR(master))
|
||||
{
|
||||
mgr->LogoutPlayerBot(target->GetGUID());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (sRandomPlayerbotMgr)
|
||||
{
|
||||
sRandomPlayerbotMgr->LogoutPlayerBot(target->GetGUID());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PlayerbotsSecureLoginServerScript : public ServerScript
|
||||
{
|
||||
public:
|
||||
PlayerbotsSecureLoginServerScript()
|
||||
: ServerScript("PlayerbotsSecureLoginServerScript", { SERVERHOOK_CAN_PACKET_RECEIVE }) {}
|
||||
|
||||
bool CanPacketReceive(WorldSession* /*session*/, WorldPacket& packet) override
|
||||
{
|
||||
if (packet.GetOpcode() != CMSG_PLAYER_LOGIN)
|
||||
return true;
|
||||
|
||||
auto const oldPos = packet.rpos();
|
||||
ObjectGuid loginGuid;
|
||||
packet >> loginGuid;
|
||||
packet.rpos(oldPos);
|
||||
|
||||
if (!loginGuid)
|
||||
return true;
|
||||
|
||||
Player* existingAltbot = FindOnlineAltbotByGuid(loginGuid);
|
||||
if (existingAltbot)
|
||||
ForceLogoutViaPlayerbotHolder(existingAltbot);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
void AddPlayerbotsSecureLoginScripts()
|
||||
{
|
||||
new PlayerbotsSecureLoginServerScript();
|
||||
}
|
||||
@@ -2834,22 +2834,20 @@ inline bool ContainsInternal(ItemTemplate const* proto, uint32 skillId)
|
||||
CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates();
|
||||
for (CreatureTemplateContainer::const_iterator itr = creatures->begin(); itr != creatures->end(); ++itr)
|
||||
{
|
||||
if (itr->second.trainer_type != TRAINER_TYPE_TRADESKILLS)
|
||||
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(itr->first);
|
||||
|
||||
if (!trainer)
|
||||
continue;
|
||||
|
||||
uint32 trainerId = itr->second.Entry;
|
||||
TrainerSpellData const* trainer_spells = sObjectMgr->GetNpcTrainerSpells(trainerId);
|
||||
if (!trainer_spells)
|
||||
if (trainer->GetTrainerType() != Trainer::Type::Tradeskill)
|
||||
continue;
|
||||
|
||||
for (TrainerSpellMap::const_iterator iter = trainer_spells->spellList.begin();
|
||||
iter != trainer_spells->spellList.end(); ++iter)
|
||||
for (auto& spell : trainer->GetSpells())
|
||||
{
|
||||
TrainerSpell const* tSpell = &iter->second;
|
||||
if (!tSpell || tSpell->reqSkill != skillId)
|
||||
if (spell.ReqSkillLine != skillId)
|
||||
continue;
|
||||
|
||||
if (IsCraftedBy(proto, tSpell->spell))
|
||||
if (IsCraftedBy(proto, spell.SpellId))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "GuildMgr.h"
|
||||
#include "PlayerbotFactory.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PlayerbotGuildMgr.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "SocialMgr.h"
|
||||
@@ -754,187 +755,6 @@ void RandomPlayerbotFactory::CreateRandomBots()
|
||||
sPlayerbotAIConfig->randomBotAccounts.size(), totalRandomBotChars);
|
||||
}
|
||||
|
||||
void RandomPlayerbotFactory::CreateRandomGuilds()
|
||||
{
|
||||
std::vector<uint32> randomBots;
|
||||
|
||||
PlayerbotsDatabasePreparedStatement* stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BOT);
|
||||
stmt->SetData(0, "add");
|
||||
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
uint32 bot = fields[0].Get<uint32>();
|
||||
randomBots.push_back(bot);
|
||||
} while (result->NextRow());
|
||||
}
|
||||
|
||||
if (sPlayerbotAIConfig->deleteRandomBotGuilds)
|
||||
{
|
||||
LOG_INFO("playerbots", "Deleting random bot guilds...");
|
||||
for (std::vector<uint32>::iterator i = randomBots.begin(); i != randomBots.end(); ++i)
|
||||
{
|
||||
if (Guild* guild = sGuildMgr->GetGuildByLeader(ObjectGuid::Create<HighGuid::Player>(*i)))
|
||||
guild->Disband();
|
||||
}
|
||||
|
||||
LOG_INFO("playerbots", "Random bot guilds deleted");
|
||||
}
|
||||
|
||||
std::unordered_set<uint32> botAccounts;
|
||||
botAccounts.reserve(sPlayerbotAIConfig->randomBotAccounts.size());
|
||||
for (uint32 acc : sPlayerbotAIConfig->randomBotAccounts)
|
||||
botAccounts.insert(acc);
|
||||
|
||||
// Recount bot guilds directly from the database (does not depend on connected bots)
|
||||
uint32 guildNumber = 0;
|
||||
sPlayerbotAIConfig->randomBotGuilds.clear();
|
||||
sPlayerbotAIConfig->randomBotGuilds.shrink_to_fit(); // avoids accumulating old capacity
|
||||
|
||||
if (!botAccounts.empty())
|
||||
{
|
||||
if (QueryResult res = CharacterDatabase.Query(
|
||||
// We only retrieve what is necessary (guildid, leader account)
|
||||
"SELECT g.guildid, c.account "
|
||||
"FROM guild g JOIN characters c ON g.leaderguid = c.guid"))
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* f = res->Fetch();
|
||||
const uint32 guildId = f[0].Get<uint32>();
|
||||
const uint32 accountId = f[1].Get<uint32>();
|
||||
|
||||
// Determine if guild leader's account is a bot account.
|
||||
if (botAccounts.find(accountId) != botAccounts.end())
|
||||
{
|
||||
++guildNumber;
|
||||
sPlayerbotAIConfig->randomBotGuilds.push_back(guildId);
|
||||
}
|
||||
} while (res->NextRow());
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO("playerbots", "{}/{} random bot guilds exist in guild table",guildNumber, sPlayerbotAIConfig->randomBotGuildCount);
|
||||
if (guildNumber >= sPlayerbotAIConfig->randomBotGuildCount)
|
||||
{
|
||||
LOG_DEBUG("playerbots", "No new random guilds required");
|
||||
return;
|
||||
}
|
||||
|
||||
// We list the available leaders (online bots, not in guilds)
|
||||
GuidVector availableLeaders;
|
||||
availableLeaders.reserve(randomBots.size()); // limit reallocs
|
||||
for (const uint32 botLowGuid : randomBots)
|
||||
{
|
||||
ObjectGuid leader = ObjectGuid::Create<HighGuid::Player>(botLowGuid);
|
||||
if (sGuildMgr->GetGuildByLeader(leader))
|
||||
{
|
||||
// already GuildLeader -> ignored
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Player* player = ObjectAccessor::FindPlayer(leader))
|
||||
{
|
||||
if (!player->GetGuildId())
|
||||
availableLeaders.push_back(leader);
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_DEBUG("playerbots", "{} available leaders for new guilds found", availableLeaders.size());
|
||||
|
||||
// Create up to randomBotGuildCount by counting only EFFECTIVE creations
|
||||
uint32 createdThisRun = 0;
|
||||
for (; guildNumber < sPlayerbotAIConfig->randomBotGuildCount; /* ++guildNumber -> done only if creation */)
|
||||
{
|
||||
std::string const guildName = CreateRandomGuildName();
|
||||
if (guildName.empty())
|
||||
break; // no more names available in playerbots_guild_names
|
||||
|
||||
if (sGuildMgr->GetGuildByName(guildName))
|
||||
continue; // name already taken, skip
|
||||
|
||||
if (availableLeaders.empty())
|
||||
{
|
||||
LOG_ERROR("playerbots", "No leaders for random guilds available");
|
||||
break; // no more leaders: we can no longer progress without distorting the counter
|
||||
}
|
||||
|
||||
uint32 index = urand(0, availableLeaders.size() - 1);
|
||||
ObjectGuid leader = availableLeaders[index];
|
||||
availableLeaders.erase(availableLeaders.begin() + index); // Removes the chosen leader to avoid re-selecting it repeatedly
|
||||
|
||||
Player* player = ObjectAccessor::FindPlayer(leader);
|
||||
if (!player)
|
||||
{
|
||||
LOG_ERROR("playerbots", "ObjectAccessor Cannot find player to set leader for guild {} . Skipped...",
|
||||
guildName.c_str());
|
||||
// we will try with other leaders in the next round (guildNumber is not incremented)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (player->GetGuildId())
|
||||
{
|
||||
// leader already in guild -> we don't advance the counter, we move on to the next one
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG_DEBUG("playerbots", "Creating guild name='{}' leader='{}'...", guildName.c_str(), player->GetName().c_str());
|
||||
|
||||
Guild* guild = new Guild();
|
||||
if (!guild->Create(player, guildName))
|
||||
{
|
||||
LOG_ERROR("playerbots", "Error creating guild [ {} ] with leader [ {} ]", guildName.c_str(),
|
||||
player->GetName().c_str());
|
||||
delete guild;
|
||||
continue;
|
||||
}
|
||||
|
||||
sGuildMgr->AddGuild(guild);
|
||||
|
||||
LOG_DEBUG("playerbots", "Guild created: id={} name='{}'", guild->GetId(), guildName.c_str());
|
||||
|
||||
// create random emblem
|
||||
uint32 st, cl, br, bc, bg;
|
||||
bg = urand(0, 51);
|
||||
bc = urand(0, 17);
|
||||
cl = urand(0, 17);
|
||||
br = urand(0, 7);
|
||||
st = urand(0, 180);
|
||||
|
||||
LOG_DEBUG("playerbots",
|
||||
"[TABARD] new guild id={} random -> style={}, color={}, borderStyle={}, borderColor={}, bgColor={}",
|
||||
guild->GetId(), st, cl, br, bc, bg);
|
||||
|
||||
// populate guild table with a random tabard design
|
||||
CharacterDatabase.Execute(
|
||||
"UPDATE guild SET EmblemStyle={}, EmblemColor={}, BorderStyle={}, BorderColor={}, BackgroundColor={} "
|
||||
"WHERE guildid={}",
|
||||
st, cl, br, bc, bg, guild->GetId());
|
||||
LOG_DEBUG("playerbots", "[TABARD] UPDATE done for guild id={}", guild->GetId());
|
||||
|
||||
// Immediate reading for log
|
||||
if (QueryResult qr = CharacterDatabase.Query(
|
||||
"SELECT EmblemStyle,EmblemColor,BorderStyle,BorderColor,BackgroundColor FROM guild WHERE guildid={}",
|
||||
guild->GetId()))
|
||||
{
|
||||
Field* f = qr->Fetch();
|
||||
LOG_DEBUG("playerbots",
|
||||
"[TABARD] DB check guild id={} => style={}, color={}, borderStyle={}, borderColor={}, bgColor={}",
|
||||
guild->GetId(), f[0].Get<uint8>(), f[1].Get<uint8>(), f[2].Get<uint8>(), f[3].Get<uint8>(), f[4].Get<uint8>());
|
||||
}
|
||||
|
||||
sPlayerbotAIConfig->randomBotGuilds.push_back(guild->GetId());
|
||||
// The guild is only counted if it is actually created
|
||||
++guildNumber;
|
||||
++createdThisRun;
|
||||
}
|
||||
|
||||
// Shows the true total and how many were created during this run
|
||||
LOG_INFO("playerbots", "{} random bot guilds created this run)", createdThisRun);
|
||||
}
|
||||
|
||||
std::string const RandomPlayerbotFactory::CreateRandomGuildName()
|
||||
{
|
||||
std::string guildName = "";
|
||||
|
||||
@@ -51,7 +51,6 @@ public:
|
||||
|
||||
Player* CreateRandomBot(WorldSession* session, uint8 cls, std::unordered_map<NameRaceAndGender, std::vector<std::string>>& names);
|
||||
static void CreateRandomBots();
|
||||
static void CreateRandomGuilds();
|
||||
static void CreateRandomArenaTeams(ArenaType slot, uint32 count);
|
||||
static std::string const CreateRandomGuildName();
|
||||
static uint32 CalculateTotalAccountCount();
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Define.h"
|
||||
#include "FleeManager.h"
|
||||
#include "GameTime.h"
|
||||
#include "GridNotifiers.h"
|
||||
#include "GridNotifiersImpl.h"
|
||||
#include "GuildMgr.h"
|
||||
@@ -670,9 +669,9 @@ void RandomPlayerbotMgr::AssignAccountTypes()
|
||||
uint32 toAssign = neededAddClassAccounts - existingAddClassAccounts;
|
||||
uint32 assigned = 0;
|
||||
|
||||
for (int i = allRandomBotAccounts.size() - 1; i >= 0 && assigned < toAssign; i--)
|
||||
for (size_t idx = allRandomBotAccounts.size(); idx-- > 0 && assigned < toAssign;)
|
||||
{
|
||||
uint32 accountId = allRandomBotAccounts[i];
|
||||
uint32 accountId = allRandomBotAccounts[idx];
|
||||
if (currentAssignments[accountId] == 0) // Unassigned
|
||||
{
|
||||
PlayerbotsDatabase.Execute("UPDATE playerbots_account_type SET account_type = 2, assignment_date = NOW() WHERE account_id = {}", accountId);
|
||||
@@ -1425,7 +1424,7 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot)
|
||||
LOG_DEBUG("playerbots", "Bot #{}: log out", bot);
|
||||
|
||||
SetEventValue(bot, "add", 0, 0);
|
||||
currentBots.erase(std::remove(currentBots.begin(), currentBots.end(), bot), currentBots.end());
|
||||
currentBots.remove(bot);
|
||||
|
||||
if (player)
|
||||
LogoutPlayerBot(botGUID);
|
||||
@@ -1480,10 +1479,10 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot)
|
||||
if (!sRandomPlayerbotMgr->IsRandomBot(player))
|
||||
update = false;
|
||||
|
||||
if (player->GetGroup() && botAI->GetGroupMaster())
|
||||
if (player->GetGroup() && botAI->GetGroupLeader())
|
||||
{
|
||||
PlayerbotAI* groupMasterBotAI = GET_PLAYERBOT_AI(botAI->GetGroupMaster());
|
||||
if (!groupMasterBotAI || groupMasterBotAI->IsRealPlayer())
|
||||
PlayerbotAI* groupLeaderBotAI = GET_PLAYERBOT_AI(botAI->GetGroupLeader());
|
||||
if (!groupLeaderBotAI || groupLeaderBotAI->IsRealPlayer())
|
||||
{
|
||||
update = false;
|
||||
}
|
||||
@@ -1655,6 +1654,10 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector<WorldLocation>&
|
||||
if (bot->IsBeingTeleported() || !bot->IsInWorld())
|
||||
return;
|
||||
|
||||
// no teleport / movement update when rooted.
|
||||
if (bot->IsRooted())
|
||||
return;
|
||||
|
||||
// ignore when in queue for battle grounds.
|
||||
if (bot->InBattlegroundQueue())
|
||||
return;
|
||||
@@ -2712,69 +2715,73 @@ std::vector<uint32> RandomPlayerbotMgr::GetBgBots(uint32 bracket)
|
||||
return std::move(BgBots);
|
||||
}
|
||||
|
||||
uint32 RandomPlayerbotMgr::GetEventValue(uint32 bot, std::string const event)
|
||||
CachedEvent* RandomPlayerbotMgr::FindEvent(uint32 bot, std::string const& event)
|
||||
{
|
||||
// load all events at once on first event load
|
||||
if (eventCache[bot].empty())
|
||||
BotEventCache& cache = eventCache[bot];
|
||||
|
||||
// Load once
|
||||
if (!cache.loaded)
|
||||
{
|
||||
cache.events.clear();
|
||||
|
||||
PlayerbotsDatabasePreparedStatement* stmt =
|
||||
PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BY_OWNER_AND_BOT);
|
||||
stmt->SetData(0, 0);
|
||||
stmt->SetData(1, bot);
|
||||
|
||||
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
std::string const eventName = fields[0].Get<std::string>();
|
||||
|
||||
CachedEvent e;
|
||||
e.value = fields[1].Get<uint32>();
|
||||
e.lastChangeTime = fields[2].Get<uint32>();
|
||||
e.validIn = fields[3].Get<uint32>();
|
||||
e.data = fields[4].Get<std::string>();
|
||||
eventCache[bot][eventName] = std::move(e);
|
||||
|
||||
cache.events.emplace(fields[0].Get<std::string>(), std::move(e));
|
||||
} while (result->NextRow());
|
||||
}
|
||||
|
||||
cache.loaded = true;
|
||||
}
|
||||
|
||||
CachedEvent& e = eventCache[bot][event];
|
||||
/*if (e.IsEmpty())
|
||||
auto it = cache.events.find(event);
|
||||
if (it == cache.events.end())
|
||||
return nullptr;
|
||||
|
||||
CachedEvent& e = it->second;
|
||||
|
||||
// remove expired events
|
||||
if (e.validIn && (NowSeconds() - e.lastChangeTime) >= e.validIn && event != "specNo" && event != "specLink")
|
||||
{
|
||||
QueryResult results = PlayerbotsDatabase.Query("SELECT `value`, `time`, validIn, `data` FROM
|
||||
playerbots_random_bots WHERE owner = 0 AND bot = {} AND event = {}", bot, event.c_str());
|
||||
|
||||
if (results)
|
||||
{
|
||||
Field* fields = results->Fetch();
|
||||
e.value = fields[0].Get<uint32>();
|
||||
e.lastChangeTime = fields[1].Get<uint32>();
|
||||
e.validIn = fields[2].Get<uint32>();
|
||||
e.data = fields[3].Get<std::string>();
|
||||
}
|
||||
cache.events.erase(it);
|
||||
return nullptr;
|
||||
}
|
||||
*/
|
||||
|
||||
if ((time(0) - e.lastChangeTime) >= e.validIn && event != "specNo" && event != "specLink")
|
||||
e.value = 0;
|
||||
|
||||
return e.value;
|
||||
return &e;
|
||||
}
|
||||
|
||||
std::string const RandomPlayerbotMgr::GetEventData(uint32 bot, std::string const event)
|
||||
uint32 RandomPlayerbotMgr::GetEventValue(uint32 bot, std::string const& event)
|
||||
{
|
||||
std::string data = "";
|
||||
if (GetEventValue(bot, event))
|
||||
{
|
||||
CachedEvent e = eventCache[bot][event];
|
||||
data = e.data;
|
||||
}
|
||||
if (CachedEvent* e = FindEvent(bot, event))
|
||||
return e->value;
|
||||
|
||||
return data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const event, uint32 value, uint32 validIn,
|
||||
std::string const data)
|
||||
std::string RandomPlayerbotMgr::GetEventData(uint32 bot, std::string const& event)
|
||||
{
|
||||
if (CachedEvent* e = FindEvent(bot, event))
|
||||
return e->data;
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const& event, uint32 value, uint32 validIn,
|
||||
std::string const& data)
|
||||
{
|
||||
PlayerbotsDatabaseTransaction trans = PlayerbotsDatabase.BeginTransaction();
|
||||
|
||||
@@ -2790,43 +2797,55 @@ uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const event, ui
|
||||
stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_INS_RANDOM_BOTS);
|
||||
stmt->SetData(0, 0);
|
||||
stmt->SetData(1, bot);
|
||||
stmt->SetData(2, static_cast<uint32>(GameTime::GetGameTime().count()));
|
||||
stmt->SetData(2, NowSeconds());
|
||||
stmt->SetData(3, validIn);
|
||||
stmt->SetData(4, event.c_str());
|
||||
stmt->SetData(5, value);
|
||||
if (data != "")
|
||||
{
|
||||
|
||||
if (!data.empty())
|
||||
stmt->SetData(6, data.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
stmt->SetData(6);
|
||||
}
|
||||
stmt->SetData(6); // NULL
|
||||
|
||||
trans->Append(stmt);
|
||||
}
|
||||
|
||||
PlayerbotsDatabase.CommitTransaction(trans);
|
||||
|
||||
CachedEvent e(value, (uint32)time(nullptr), validIn, data);
|
||||
eventCache[bot][event] = std::move(e);
|
||||
// Update in-memory cache
|
||||
BotEventCache& cache = eventCache[bot];
|
||||
cache.loaded = true;
|
||||
|
||||
if (!value)
|
||||
{
|
||||
cache.events.erase(event);
|
||||
return 0;
|
||||
}
|
||||
|
||||
CachedEvent& e = cache.events[event]; // create-on-write is OK here
|
||||
e.value = value;
|
||||
e.lastChangeTime = NowSeconds();
|
||||
e.validIn = validIn;
|
||||
e.data = data;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32 RandomPlayerbotMgr::GetValue(uint32 bot, std::string const type) { return GetEventValue(bot, type); }
|
||||
uint32 RandomPlayerbotMgr::GetValue(uint32 bot, std::string const& type) { return GetEventValue(bot, type); }
|
||||
|
||||
uint32 RandomPlayerbotMgr::GetValue(Player* bot, std::string const type)
|
||||
uint32 RandomPlayerbotMgr::GetValue(Player* bot, std::string const& type)
|
||||
{
|
||||
return GetValue(bot->GetGUID().GetCounter(), type);
|
||||
}
|
||||
|
||||
std::string const RandomPlayerbotMgr::GetData(uint32 bot, std::string const type) { return GetEventData(bot, type); }
|
||||
std::string RandomPlayerbotMgr::GetData(uint32 bot, std::string const& type) { return GetEventData(bot, type); }
|
||||
|
||||
void RandomPlayerbotMgr::SetValue(uint32 bot, std::string const type, uint32 value, std::string const data)
|
||||
void RandomPlayerbotMgr::SetValue(uint32 bot, std::string const& type, uint32 value, std::string const& data)
|
||||
{
|
||||
SetEventValue(bot, type, value, sPlayerbotAIConfig->maxRandomBotInWorldTime, data);
|
||||
}
|
||||
|
||||
void RandomPlayerbotMgr::SetValue(Player* bot, std::string const type, uint32 value, std::string const data)
|
||||
void RandomPlayerbotMgr::SetValue(Player* bot, std::string const& type, uint32 value, std::string const& data)
|
||||
{
|
||||
SetValue(bot->GetGUID().GetCounter(), type, value, data);
|
||||
}
|
||||
@@ -3115,7 +3134,7 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player)
|
||||
void RandomPlayerbotMgr::OnPlayerLoginError(uint32 bot)
|
||||
{
|
||||
SetEventValue(bot, "add", 0, 0);
|
||||
currentBots.erase(std::remove(currentBots.begin(), currentBots.end(), bot), currentBots.end());
|
||||
currentBots.remove(bot);
|
||||
}
|
||||
|
||||
Player* RandomPlayerbotMgr::GetRandomPlayer()
|
||||
@@ -3497,7 +3516,8 @@ void RandomPlayerbotMgr::Remove(Player* bot)
|
||||
stmt->SetData(1, owner.GetCounter());
|
||||
PlayerbotsDatabase.Execute(stmt);
|
||||
|
||||
eventCache[owner.GetCounter()].clear();
|
||||
uint32 botId = owner.GetCounter();
|
||||
eventCache.erase(botId);
|
||||
|
||||
LogoutPlayerBot(owner);
|
||||
}
|
||||
@@ -3514,7 +3534,7 @@ CreatureData const* RandomPlayerbotMgr::GetCreatureDataByEntry(uint32 entry)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ObjectGuid const RandomPlayerbotMgr::GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId)
|
||||
ObjectGuid RandomPlayerbotMgr::GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId)
|
||||
{
|
||||
ObjectGuid battleMasterGUID = ObjectGuid::Empty;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "NewRpgInfo.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "PlayerbotMgr.h"
|
||||
#include "GameTime.h"
|
||||
|
||||
struct BattlegroundInfo
|
||||
{
|
||||
@@ -45,25 +46,20 @@ class ChatHandler;
|
||||
class PerformanceMonitorOperation;
|
||||
class WorldLocation;
|
||||
|
||||
class CachedEvent
|
||||
struct CachedEvent
|
||||
{
|
||||
public:
|
||||
CachedEvent() : value(0), lastChangeTime(0), validIn(0), data("") {}
|
||||
CachedEvent(const CachedEvent& other)
|
||||
: value(other.value), lastChangeTime(other.lastChangeTime), validIn(other.validIn), data(other.data)
|
||||
{
|
||||
}
|
||||
CachedEvent(uint32 value, uint32 lastChangeTime, uint32 validIn, std::string const data = "")
|
||||
: value(value), lastChangeTime(lastChangeTime), validIn(validIn), data(data)
|
||||
{
|
||||
}
|
||||
|
||||
bool IsEmpty() { return !lastChangeTime; }
|
||||
|
||||
uint32 value;
|
||||
uint32 lastChangeTime;
|
||||
uint32 validIn;
|
||||
uint32 value = 0;
|
||||
uint32 lastChangeTime = 0;
|
||||
uint32 validIn = 0;
|
||||
std::string data;
|
||||
|
||||
bool IsEmpty() const { return !lastChangeTime; }
|
||||
};
|
||||
|
||||
struct BotEventCache
|
||||
{
|
||||
bool loaded = false;
|
||||
std::unordered_map<std::string, CachedEvent> events;
|
||||
};
|
||||
|
||||
// https://gist.github.com/bradley219/5373998
|
||||
@@ -139,13 +135,13 @@ public:
|
||||
void Revive(Player* player);
|
||||
void ChangeStrategy(Player* player);
|
||||
void ChangeStrategyOnce(Player* player);
|
||||
uint32 GetValue(Player* bot, std::string const type);
|
||||
uint32 GetValue(uint32 bot, std::string const type);
|
||||
std::string const GetData(uint32 bot, std::string const type);
|
||||
void SetValue(uint32 bot, std::string const type, uint32 value, std::string const data = "");
|
||||
void SetValue(Player* bot, std::string const type, uint32 value, std::string const data = "");
|
||||
uint32 GetValue(Player* bot, std::string const& type);
|
||||
uint32 GetValue(uint32 bot, std::string const& type);
|
||||
std::string GetData(uint32 bot, std::string const& type);
|
||||
void SetValue(uint32 bot, std::string const& type, uint32 value, std::string const& data = "");
|
||||
void SetValue(Player* bot, std::string const& type, uint32 value, std::string const& data = "");
|
||||
void Remove(Player* bot);
|
||||
ObjectGuid const GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId);
|
||||
ObjectGuid GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId);
|
||||
CreatureData const* GetCreatureDataByEntry(uint32 entry);
|
||||
void LoadBattleMastersCache();
|
||||
std::map<uint32, std::map<uint32, BattlegroundInfo>> BattlegroundData;
|
||||
@@ -203,10 +199,11 @@ private:
|
||||
bool _isBotInitializing = true;
|
||||
bool _isBotLogging = true;
|
||||
NewRpgStatistic rpgStasticTotal;
|
||||
uint32 GetEventValue(uint32 bot, std::string const event);
|
||||
std::string const GetEventData(uint32 bot, std::string const event);
|
||||
uint32 SetEventValue(uint32 bot, std::string const event, uint32 value, uint32 validIn,
|
||||
std::string const data = "");
|
||||
CachedEvent* FindEvent(uint32 bot, std::string const& event);
|
||||
uint32 GetEventValue(uint32 bot, std::string const& event);
|
||||
std::string GetEventData(uint32 bot, std::string const& event);
|
||||
uint32 SetEventValue(uint32 bot, std::string const& event, uint32 value, uint32 validIn,
|
||||
std::string const& data = "");
|
||||
void GetBots();
|
||||
std::vector<uint32> GetBgBots(uint32 bracket);
|
||||
time_t BgCheckTimer;
|
||||
@@ -228,7 +225,7 @@ private:
|
||||
// std::map<uint32, std::vector<WorldLocation>> rpgLocsCache;
|
||||
std::map<uint32, std::map<uint32, std::vector<WorldLocation>>> rpgLocsCacheLevel;
|
||||
std::map<TeamId, std::map<BattlegroundTypeId, std::vector<uint32>>> BattleMastersCache;
|
||||
std::map<uint32, std::map<std::string, CachedEvent>> eventCache;
|
||||
std::unordered_map<uint32, BotEventCache> eventCache;
|
||||
std::list<uint32> currentBots;
|
||||
uint32 bgBotsCount;
|
||||
uint32 playersLevel;
|
||||
@@ -238,6 +235,7 @@ private:
|
||||
std::vector<uint32> addClassTypeAccounts; // Accounts marked as AddClass (type 2)
|
||||
|
||||
//void ScaleBotActivity(); // Deprecated function
|
||||
static inline uint32 NowSeconds() { return static_cast<uint32>(GameTime::GetGameTime().count()); }
|
||||
};
|
||||
|
||||
#define sRandomPlayerbotMgr RandomPlayerbotMgr::instance()
|
||||
|
||||
@@ -41,13 +41,17 @@ bool ServerFacade::IsDistanceLessOrEqualThan(float dist1, float dist2) { return
|
||||
|
||||
void ServerFacade::SetFacingTo(Player* bot, WorldObject* wo, bool force)
|
||||
{
|
||||
if (!bot)
|
||||
return;
|
||||
|
||||
float angle = bot->GetAngle(wo);
|
||||
// if (!force && bot->isMoving())
|
||||
// bot->SetFacingTo(bot->GetAngle(wo));
|
||||
// else
|
||||
// {
|
||||
bot->SetOrientation(angle);
|
||||
bot->SendMovementFlagUpdate();
|
||||
if (!bot->IsRooted())
|
||||
bot->SendMovementFlagUpdate();
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
@@ -218,6 +218,11 @@ bool WorldPosition::isUnderWater()
|
||||
: false;
|
||||
};
|
||||
|
||||
bool WorldPosition::IsValid()
|
||||
{
|
||||
return !(GetMapId() == MAPID_INVALID && GetPositionX() == 0 && GetPositionY() == 0 && GetPositionZ() == 0);
|
||||
}
|
||||
|
||||
WorldPosition WorldPosition::relPoint(WorldPosition* center)
|
||||
{
|
||||
return WorldPosition(GetMapId(), GetPositionX() - center->GetPositionX(), GetPositionY() - center->GetPositionY(),
|
||||
@@ -3404,13 +3409,14 @@ void TravelMgr::LoadQuestTravelTable()
|
||||
{
|
||||
Strategy* strat = con->GetStrategy(stratName);
|
||||
|
||||
if (strat->getDefaultActions())
|
||||
for (uint32 i = 0; i < NextAction::size(strat->getDefaultActions()); i++)
|
||||
{
|
||||
NextAction* nextAction = strat->getDefaultActions()[i];
|
||||
const std::vector<NextAction> defaultActions = strat->getDefaultActions();
|
||||
|
||||
if (defaultActions.size() > 0)
|
||||
{
|
||||
for (NextAction nextAction : defaultActions)
|
||||
{
|
||||
std::ostringstream aout;
|
||||
aout << nextAction->getRelevance() << "," << nextAction->getName()
|
||||
aout << nextAction.getRelevance() << "," << nextAction.getName()
|
||||
<< ",,S:" << stratName;
|
||||
|
||||
if (actions.find(aout.str().c_str()) != actions.end())
|
||||
@@ -3422,27 +3428,24 @@ void TravelMgr::LoadQuestTravelTable()
|
||||
|
||||
actions.insert_or_assign(aout.str().c_str(), classSpecLevel);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<TriggerNode*> triggers;
|
||||
strat->InitTriggers(triggers);
|
||||
for (auto& triggerNode : triggers)
|
||||
{
|
||||
// out << " TN:" << triggerNode->getName();
|
||||
|
||||
for (TriggerNode*& triggerNode : triggers)
|
||||
{
|
||||
if (Trigger* trigger = con->GetTrigger(triggerNode->getName()))
|
||||
{
|
||||
triggerNode->setTrigger(trigger);
|
||||
|
||||
NextAction** nextActions = triggerNode->getHandlers();
|
||||
std::vector<NextAction> nextActions = triggerNode->getHandlers();
|
||||
|
||||
for (uint32 i = 0; i < NextAction::size(nextActions); i++)
|
||||
// for (uint32_t i = 0; i < nextActions.size(); ++i)
|
||||
for (NextAction nextAction : nextActions)
|
||||
{
|
||||
NextAction* nextAction = nextActions[i];
|
||||
// out << " A:" << nextAction->getName() << "(" <<
|
||||
// nextAction->getRelevance() << ")";
|
||||
|
||||
std::ostringstream aout;
|
||||
aout << nextAction->getRelevance() << "," << nextAction->getName()
|
||||
aout << nextAction.getRelevance() << "," << nextAction.getName()
|
||||
<< "," << triggerNode->getName() << "," << stratName;
|
||||
|
||||
if (actions.find(aout.str().c_str()) != actions.end())
|
||||
|
||||
@@ -141,6 +141,7 @@ public:
|
||||
bool isOverworld();
|
||||
bool isInWater();
|
||||
bool isUnderWater();
|
||||
bool IsValid();
|
||||
|
||||
WorldPosition relPoint(WorldPosition* center);
|
||||
WorldPosition offset(WorldPosition* center);
|
||||
|
||||
45
src/database/PlayerbotSpellCache.cpp
Normal file
45
src/database/PlayerbotSpellCache.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "PlayerbotSpellCache.h"
|
||||
|
||||
void PlayerbotSpellCache::Initialize()
|
||||
{
|
||||
LOG_INFO("playerbots",
|
||||
"Playerbots: ListSpellsAction caches initialized");
|
||||
for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j)
|
||||
{
|
||||
if (SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j))
|
||||
skillSpells[skillLine->Spell] = skillLine;
|
||||
}
|
||||
|
||||
// Fill the vendorItems cache once from the world database.
|
||||
QueryResult results = WorldDatabase.Query("SELECT item FROM npc_vendor WHERE maxcount = 0");
|
||||
if (results)
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = results->Fetch();
|
||||
int32 entry = fields[0].Get<int32>();
|
||||
if (entry <= 0)
|
||||
continue;
|
||||
|
||||
vendorItems.insert(static_cast<uint32>(entry));
|
||||
}
|
||||
while (results->NextRow());
|
||||
}
|
||||
|
||||
LOG_DEBUG("playerbots",
|
||||
"ListSpellsAction: initialized caches (skillSpells={}, vendorItems={}).",
|
||||
skillSpells.size(), vendorItems.size());
|
||||
}
|
||||
|
||||
SkillLineAbilityEntry const* PlayerbotSpellCache::GetSkillLine(uint32 spellId) const
|
||||
{
|
||||
auto itr = skillSpells.find(spellId);
|
||||
if (itr != skillSpells.end())
|
||||
return itr->second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool PlayerbotSpellCache::IsItemBuyable(uint32 itemId) const
|
||||
{
|
||||
return vendorItems.find(itemId) != vendorItems.end();
|
||||
}
|
||||
34
src/database/PlayerbotSpellCache.h
Normal file
34
src/database/PlayerbotSpellCache.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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_PLAYERBOTSPELLCACHE_H
|
||||
#define _PLAYERBOT_PLAYERBOTSPELLCACHE_H
|
||||
|
||||
#include "Playerbots.h"
|
||||
|
||||
class PlayerbotSpellCache
|
||||
{
|
||||
public:
|
||||
static PlayerbotSpellCache* Instance()
|
||||
{
|
||||
static PlayerbotSpellCache instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
void Initialize(); // call once on startup
|
||||
|
||||
SkillLineAbilityEntry const* GetSkillLine(uint32 spellId) const;
|
||||
bool IsItemBuyable(uint32 itemId) const;
|
||||
|
||||
private:
|
||||
PlayerbotSpellCache() = default;
|
||||
|
||||
std::map<uint32, SkillLineAbilityEntry const*> skillSpells;
|
||||
std::set<uint32> vendorItems;
|
||||
};
|
||||
|
||||
#define sPlayerbotSpellCache PlayerbotSpellCache::Instance()
|
||||
|
||||
#endif
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "PlayerbotAI.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "PlayerbotDbStore.h"
|
||||
#include "PlayerbotGuildMgr.h"
|
||||
#include "Playerbots.h"
|
||||
#include "QuestDef.h"
|
||||
#include "RandomItemMgr.h"
|
||||
@@ -121,7 +122,11 @@ void PlayerbotFactory::Init()
|
||||
uint32 maxStoreSize = sSpellMgr->GetSpellInfoStoreSize();
|
||||
for (uint32 id = 1; id < maxStoreSize; ++id)
|
||||
{
|
||||
if (id == 47181 || id == 50358 || id == 47242 || id == 52639 || id == 47147 || id == 7218) // Test Enchant
|
||||
if (id == 7218 || id == 19927 || id == 44119 || id == 47147 || id == 47181 ||
|
||||
id == 47242 || id == 50358 || id == 52639) // Test Enchants
|
||||
continue;
|
||||
|
||||
if (id == 35791 || id == 39405) // Grandfathered TBC Enchants
|
||||
continue;
|
||||
|
||||
if (id == 15463 || id == 15490) // Legendary Arcane Amalgamation
|
||||
@@ -1102,8 +1107,6 @@ void PlayerbotFactory::ResetQuests()
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerbotFactory::InitSpells() { InitAvailableSpells(); }
|
||||
|
||||
void PlayerbotFactory::InitTalentsTree(bool increment /*false*/, bool use_template /*true*/, bool reset /*false*/)
|
||||
{
|
||||
uint32 specTab;
|
||||
@@ -2525,66 +2528,35 @@ void PlayerbotFactory::InitAvailableSpells()
|
||||
for (CreatureTemplateContainer::const_iterator i = creatureTemplateContainer->begin();
|
||||
i != creatureTemplateContainer->end(); ++i)
|
||||
{
|
||||
CreatureTemplate const& co = i->second;
|
||||
if (co.trainer_type != TRAINER_TYPE_TRADESKILLS && co.trainer_type != TRAINER_TYPE_CLASS)
|
||||
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(i->first);
|
||||
|
||||
if (!trainer)
|
||||
continue;
|
||||
|
||||
if (co.trainer_type == TRAINER_TYPE_CLASS && co.trainer_class != bot->getClass())
|
||||
if (trainer->GetTrainerType() != Trainer::Type::Tradeskill &&
|
||||
trainer->GetTrainerType() != Trainer::Type::Class)
|
||||
continue;
|
||||
|
||||
uint32 trainerId = co.Entry;
|
||||
trainerIdCache[bot->getClass()].push_back(trainerId);
|
||||
if (trainer->GetTrainerType() == Trainer::Type::Class &&
|
||||
!trainer->IsTrainerValidForPlayer(bot))
|
||||
continue;
|
||||
|
||||
trainerIdCache[bot->getClass()].push_back(i->first);
|
||||
}
|
||||
}
|
||||
for (uint32 trainerId : trainerIdCache[bot->getClass()])
|
||||
{
|
||||
TrainerSpellData const* trainer_spells = sObjectMgr->GetNpcTrainerSpells(trainerId);
|
||||
if (!trainer_spells)
|
||||
trainer_spells = sObjectMgr->GetNpcTrainerSpells(trainerId);
|
||||
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(trainerId);
|
||||
|
||||
if (!trainer_spells)
|
||||
continue;
|
||||
|
||||
for (TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin();
|
||||
itr != trainer_spells->spellList.end(); ++itr)
|
||||
for (auto& spell : trainer->GetSpells())
|
||||
{
|
||||
TrainerSpell const* tSpell = &itr->second;
|
||||
|
||||
if (!tSpell)
|
||||
if (!trainer->CanTeachSpell(bot, trainer->GetSpell(spell.SpellId)))
|
||||
continue;
|
||||
|
||||
if (tSpell->learnedSpell[0] && !bot->IsSpellFitByClassAndRace(tSpell->learnedSpell[0]))
|
||||
continue;
|
||||
|
||||
TrainerSpellState state = bot->GetTrainerSpellState(tSpell);
|
||||
if (state != TRAINER_SPELL_GREEN)
|
||||
continue;
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(tSpell->spell);
|
||||
bool learn = true;
|
||||
for (uint8 j = 0; j < 3; ++j)
|
||||
{
|
||||
if (!tSpell->learnedSpell[j] && !bot->IsSpellFitByClassAndRace(tSpell->learnedSpell[j]))
|
||||
continue;
|
||||
|
||||
if (spellInfo->Effects[j].Effect == SPELL_EFFECT_PROFICIENCY ||
|
||||
(spellInfo->Effects[j].Effect == SPELL_EFFECT_SKILL_STEP &&
|
||||
spellInfo->Effects[j].MiscValue != SKILL_RIDING) ||
|
||||
spellInfo->Effects[j].Effect == SPELL_EFFECT_DUAL_WIELD)
|
||||
{
|
||||
learn = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!learn)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tSpell->IsCastable())
|
||||
bot->CastSpell(bot, tSpell->spell, true);
|
||||
if (spell.IsCastable())
|
||||
bot->CastSpell(bot, spell.SpellId, true);
|
||||
else
|
||||
bot->learnSpell(tSpell->learnedSpell[0], false);
|
||||
bot->learnSpell(spell.SpellId, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3965,45 +3937,33 @@ void PlayerbotFactory::InitInventoryEquip()
|
||||
void PlayerbotFactory::InitGuild()
|
||||
{
|
||||
if (bot->GetGuildId())
|
||||
return;
|
||||
|
||||
// bot->SaveToDB(false, false);
|
||||
|
||||
// add guild tabard
|
||||
if (bot->GetGuildId() && !bot->HasItemCount(5976, 1))
|
||||
StoreItem(5976, 1);
|
||||
|
||||
if (sPlayerbotAIConfig->randomBotGuilds.empty())
|
||||
RandomPlayerbotFactory::CreateRandomGuilds();
|
||||
|
||||
std::vector<uint32> guilds;
|
||||
for (std::vector<uint32>::iterator i = sPlayerbotAIConfig->randomBotGuilds.begin();
|
||||
i != sPlayerbotAIConfig->randomBotGuilds.end(); ++i)
|
||||
guilds.push_back(*i);
|
||||
|
||||
if (guilds.empty())
|
||||
{
|
||||
LOG_ERROR("playerbots", "No random guilds available");
|
||||
if (!bot->HasItemCount(5976, 1) && bot->GetLevel() > 9)
|
||||
StoreItem(5976, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index = urand(0, guilds.size() - 1);
|
||||
uint32 guildId = guilds[index];
|
||||
Guild* guild = sGuildMgr->GetGuildById(guildId);
|
||||
std::string guildName = sPlayerbotGuildMgr->AssignToGuild(bot);
|
||||
if (guildName.empty())
|
||||
return;
|
||||
|
||||
Guild* guild = sGuildMgr->GetGuildByName(guildName);
|
||||
if (!guild)
|
||||
{
|
||||
LOG_ERROR("playerbots", "Invalid guild {}", guildId);
|
||||
if (!sPlayerbotGuildMgr->CreateGuild(bot, guildName))
|
||||
LOG_ERROR("playerbots","Failed to create guild {} for bot {}", guildName, bot->GetName());
|
||||
return;
|
||||
}
|
||||
|
||||
if (guild->GetMemberSize() < urand(10, sPlayerbotAIConfig->randomBotGuildSizeMax))
|
||||
guild->AddMember(bot->GetGUID(), urand(GR_OFFICER, GR_INITIATE));
|
||||
|
||||
else
|
||||
{
|
||||
if (guild->AddMember(bot->GetGUID(),urand(GR_OFFICER, GR_INITIATE)))
|
||||
sPlayerbotGuildMgr->OnGuildUpdate(guild);
|
||||
else
|
||||
LOG_ERROR("playerbots","Bot {} failed to join guild {}.", bot->GetName(), guildName);
|
||||
}
|
||||
// add guild tabard
|
||||
if (bot->GetGuildId() && bot->GetLevel() > 9 && urand(0, 4) && !bot->HasItemCount(5976, 1))
|
||||
StoreItem(5976, 1);
|
||||
|
||||
// bot->SaveToDB(false, false);
|
||||
}
|
||||
|
||||
void PlayerbotFactory::InitImmersive()
|
||||
@@ -4099,6 +4059,7 @@ void PlayerbotFactory::InitImmersive()
|
||||
|
||||
void PlayerbotFactory::InitArenaTeam()
|
||||
{
|
||||
|
||||
if (!sPlayerbotAIConfig->IsInRandomAccountList(bot->GetSession()->GetAccountId()))
|
||||
return;
|
||||
|
||||
@@ -4185,10 +4146,34 @@ void PlayerbotFactory::InitArenaTeam()
|
||||
|
||||
if (botcaptain && botcaptain->GetTeamId() == bot->GetTeamId()) // need?
|
||||
{
|
||||
// Add bot to arena team
|
||||
arenateam->AddMember(bot->GetGUID());
|
||||
arenateam->SaveToDB();
|
||||
|
||||
// Only synchronize ratings once the team is full (avoid redundant work)
|
||||
// The captain was added with incorrect ratings when the team was created,
|
||||
// so we fix everyone's ratings once the roster is complete
|
||||
if (arenateam->GetMembersSize() >= (uint32)arenateam->GetType())
|
||||
{
|
||||
uint32 teamRating = arenateam->GetRating();
|
||||
|
||||
// Use SetRatingForAll to align all members with team rating
|
||||
arenateam->SetRatingForAll(teamRating);
|
||||
|
||||
// For bot-only teams, keep MMR synchronized with team rating
|
||||
// This ensures matchmaking reflects the artificial team strength (1000-2000 range)
|
||||
// instead of being influenced by the global CONFIG_ARENA_START_MATCHMAKER_RATING
|
||||
for (auto& member : arenateam->GetMembers())
|
||||
{
|
||||
// Set MMR to match personal rating (which already matches team rating)
|
||||
member.MatchMakerRating = member.PersonalRating;
|
||||
member.MaxMMR = std::max(member.MaxMMR, member.PersonalRating);
|
||||
}
|
||||
// Force save all member data to database
|
||||
arenateam->SaveToDB(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
arenateams.erase(arenateams.begin() + index);
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,6 @@ private:
|
||||
void InitTradeSkills();
|
||||
void UpdateTradeSkills();
|
||||
void SetRandomSkill(uint16 id);
|
||||
void InitSpells();
|
||||
void ClearSpells();
|
||||
void ClearSkills();
|
||||
void InitTalents(uint32 specNo);
|
||||
|
||||
@@ -37,7 +37,7 @@ StatsWeightCalculator::StatsWeightCalculator(Player* player) : player_(player)
|
||||
tab = AiFactory::GetPlayerSpecTab(player);
|
||||
collector_ = std::make_unique<StatsCollector>(type_, cls);
|
||||
|
||||
if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_UNHOLY)
|
||||
if (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_UNHOLY)
|
||||
hitOverflowType_ = CollectorType::SPELL;
|
||||
else if (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT)
|
||||
hitOverflowType_ = CollectorType::SPELL;
|
||||
@@ -193,7 +193,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_MELEE_DPS] += 0.01f;
|
||||
stats_weights_[STATS_TYPE_RANGED_DPS] += 0.01f;
|
||||
|
||||
if (cls == CLASS_HUNTER && (tab == HUNTER_TAB_BEASTMASTERY || tab == HUNTER_TAB_SURVIVAL))
|
||||
if (cls == CLASS_HUNTER && (tab == HUNTER_TAB_BEAST_MASTERY || tab == HUNTER_TAB_SURVIVAL))
|
||||
{
|
||||
stats_weights_[STATS_TYPE_AGILITY] += 2.5f;
|
||||
stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f;
|
||||
@@ -249,7 +249,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_EXPERTISE] += 2.1f;
|
||||
stats_weights_[STATS_TYPE_MELEE_DPS] += 5.0f;
|
||||
}
|
||||
else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY) // fury
|
||||
else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY)
|
||||
{
|
||||
stats_weights_[STATS_TYPE_AGILITY] += 1.8f;
|
||||
stats_weights_[STATS_TYPE_STRENGTH] += 2.6f;
|
||||
@@ -261,7 +261,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_EXPERTISE] += 2.5f;
|
||||
stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f;
|
||||
}
|
||||
else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_ARMS) // arm
|
||||
else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_ARMS)
|
||||
{
|
||||
stats_weights_[STATS_TYPE_AGILITY] += 1.6f;
|
||||
stats_weights_[STATS_TYPE_STRENGTH] += 2.3f;
|
||||
@@ -273,7 +273,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_EXPERTISE] += 1.4f;
|
||||
stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f;
|
||||
}
|
||||
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_FROST) // frost dk
|
||||
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_FROST)
|
||||
{
|
||||
stats_weights_[STATS_TYPE_AGILITY] += 1.7f;
|
||||
stats_weights_[STATS_TYPE_STRENGTH] += 2.8f;
|
||||
@@ -285,7 +285,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_EXPERTISE] += 2.5f;
|
||||
stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f;
|
||||
}
|
||||
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_UNHOLY)
|
||||
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_UNHOLY)
|
||||
{
|
||||
stats_weights_[STATS_TYPE_AGILITY] += 0.9f;
|
||||
stats_weights_[STATS_TYPE_STRENGTH] += 2.5f;
|
||||
@@ -297,7 +297,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_EXPERTISE] += 1.5f;
|
||||
stats_weights_[STATS_TYPE_MELEE_DPS] += 5.0f;
|
||||
}
|
||||
else if (cls == CLASS_PALADIN && tab == PALADIN_TAB_RETRIBUTION) // retribution
|
||||
else if (cls == CLASS_PALADIN && tab == PALADIN_TAB_RETRIBUTION)
|
||||
{
|
||||
stats_weights_[STATS_TYPE_AGILITY] += 1.6f;
|
||||
stats_weights_[STATS_TYPE_STRENGTH] += 2.5f;
|
||||
@@ -311,7 +311,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_EXPERTISE] += 2.0f;
|
||||
stats_weights_[STATS_TYPE_MELEE_DPS] += 9.0f;
|
||||
}
|
||||
else if ((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT)) // enhancement
|
||||
else if ((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT))
|
||||
{
|
||||
stats_weights_[STATS_TYPE_AGILITY] += 1.4f;
|
||||
stats_weights_[STATS_TYPE_STRENGTH] += 1.1f;
|
||||
@@ -325,9 +325,10 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_EXPERTISE] += 2.0f;
|
||||
stats_weights_[STATS_TYPE_MELEE_DPS] += 8.5f;
|
||||
}
|
||||
else if (cls == CLASS_WARLOCK || (cls == CLASS_MAGE && tab != MAGE_TAB_FIRE) ||
|
||||
(cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW) || // shadow
|
||||
(cls == CLASS_DRUID && tab == DRUID_TAB_BALANCE)) // balance
|
||||
else if (cls == CLASS_WARLOCK ||
|
||||
(cls == CLASS_MAGE && tab != MAGE_TAB_FIRE) ||
|
||||
(cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW) ||
|
||||
(cls == CLASS_DRUID && tab == DRUID_TAB_BALANCE))
|
||||
{
|
||||
stats_weights_[STATS_TYPE_INTELLECT] += 0.3f;
|
||||
stats_weights_[STATS_TYPE_SPIRIT] += 0.6f;
|
||||
@@ -355,8 +356,8 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_CRIT] += 0.8f;
|
||||
stats_weights_[STATS_TYPE_HASTE] += 1.0f;
|
||||
}
|
||||
else if ((cls == CLASS_PALADIN && tab == PALADIN_TAB_HOLY) || // holy
|
||||
(cls == CLASS_SHAMAN && tab == SHAMAN_TAB_RESTORATION)) // heal
|
||||
else if ((cls == CLASS_PALADIN && tab == PALADIN_TAB_HOLY) ||
|
||||
(cls == CLASS_SHAMAN && tab == SHAMAN_TAB_RESTORATION))
|
||||
{
|
||||
stats_weights_[STATS_TYPE_INTELLECT] += 0.9f;
|
||||
stats_weights_[STATS_TYPE_SPIRIT] += 0.15f;
|
||||
@@ -365,7 +366,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_CRIT] += 0.6f;
|
||||
stats_weights_[STATS_TYPE_HASTE] += 0.8f;
|
||||
}
|
||||
else if ((cls == CLASS_PRIEST && tab != PRIEST_TAB_SHADOW) || // discipline / holy
|
||||
else if ((cls == CLASS_PRIEST && tab != PRIEST_TAB_SHADOW) ||
|
||||
(cls == CLASS_DRUID && tab == DRUID_TAB_RESTORATION))
|
||||
{
|
||||
stats_weights_[STATS_TYPE_INTELLECT] += 0.8f;
|
||||
@@ -396,7 +397,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_EXPERTISE] += 3.0f;
|
||||
stats_weights_[STATS_TYPE_MELEE_DPS] += 2.0f;
|
||||
}
|
||||
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_BLOOD)
|
||||
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_BLOOD)
|
||||
{
|
||||
stats_weights_[STATS_TYPE_AGILITY] += 2.0f;
|
||||
stats_weights_[STATS_TYPE_STRENGTH] += 1.0f;
|
||||
@@ -539,7 +540,7 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
|
||||
// spec without double hand
|
||||
// enhancement, rogue, ice dk, unholy dk, shield tank, fury warrior without titan's grip but with duel wield
|
||||
if (((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && player_->CanDualWield()) ||
|
||||
(cls == CLASS_ROGUE) || (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_FROST) ||
|
||||
(cls == CLASS_ROGUE) || (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_FROST) ||
|
||||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanTitanGrip() &&
|
||||
player_->CanDualWield()) ||
|
||||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_PROTECTION) ||
|
||||
@@ -556,7 +557,7 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
|
||||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanDualWield()) ||
|
||||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_ARMS) || (cls == CLASS_DRUID && tab == DRUID_TAB_FERAL) ||
|
||||
(cls == CLASS_PALADIN && tab == PALADIN_TAB_RETRIBUTION) ||
|
||||
(cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_BLOOD) ||
|
||||
(cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_BLOOD) ||
|
||||
(cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && !player_->CanDualWield()))
|
||||
{
|
||||
weight_ *= 0.1;
|
||||
|
||||
@@ -8,90 +8,6 @@
|
||||
#include "Playerbots.h"
|
||||
#include "Timer.h"
|
||||
|
||||
uint32 NextAction::size(NextAction** actions)
|
||||
{
|
||||
if (!actions)
|
||||
return 0;
|
||||
|
||||
uint32 size = 0;
|
||||
for (size = 0; actions[size];)
|
||||
++size;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
NextAction** NextAction::clone(NextAction** actions)
|
||||
{
|
||||
if (!actions)
|
||||
return nullptr;
|
||||
|
||||
uint32 size = NextAction::size(actions);
|
||||
|
||||
NextAction** res = new NextAction*[size + 1];
|
||||
for (uint32 i = 0; i < size; i++)
|
||||
res[i] = new NextAction(*actions[i]);
|
||||
|
||||
res[size] = nullptr;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
NextAction** NextAction::merge(NextAction** left, NextAction** right)
|
||||
{
|
||||
uint32 leftSize = NextAction::size(left);
|
||||
uint32 rightSize = NextAction::size(right);
|
||||
|
||||
NextAction** res = new NextAction*[leftSize + rightSize + 1];
|
||||
|
||||
for (uint32 i = 0; i < leftSize; i++)
|
||||
res[i] = new NextAction(*left[i]);
|
||||
|
||||
for (uint32 i = 0; i < rightSize; i++)
|
||||
res[leftSize + i] = new NextAction(*right[i]);
|
||||
|
||||
res[leftSize + rightSize] = nullptr;
|
||||
|
||||
NextAction::destroy(left);
|
||||
NextAction::destroy(right);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
NextAction** NextAction::array(uint32 nil, ...)
|
||||
{
|
||||
va_list vl;
|
||||
va_start(vl, nil);
|
||||
|
||||
uint32 size = 0;
|
||||
NextAction* cur = nullptr;
|
||||
do
|
||||
{
|
||||
cur = va_arg(vl, NextAction*);
|
||||
++size;
|
||||
} while (cur);
|
||||
|
||||
va_end(vl);
|
||||
|
||||
NextAction** res = new NextAction*[size];
|
||||
va_start(vl, nil);
|
||||
for (uint32 i = 0; i < size; i++)
|
||||
res[i] = va_arg(vl, NextAction*);
|
||||
va_end(vl);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void NextAction::destroy(NextAction** actions)
|
||||
{
|
||||
if (!actions)
|
||||
return;
|
||||
|
||||
for (uint32 i = 0; actions[i]; i++)
|
||||
delete actions[i];
|
||||
|
||||
delete[] actions;
|
||||
}
|
||||
|
||||
Value<Unit*>* Action::GetTargetValue() { return context->GetValue<Unit*>(GetTargetName()); }
|
||||
|
||||
Unit* Action::GetTarget() { return GetTargetValue()->Get(); }
|
||||
@@ -101,4 +17,4 @@ ActionBasket::ActionBasket(ActionNode* action, float relevance, bool skipPrerequ
|
||||
{
|
||||
}
|
||||
|
||||
bool ActionBasket::isExpired(uint32 msecs) { return getMSTime() - created >= msecs; }
|
||||
bool ActionBasket::isExpired(uint32_t msecs) { return getMSTime() - created >= msecs; }
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_ACTION_H
|
||||
#define _PLAYERBOT_ACTION_H
|
||||
#pragma once
|
||||
|
||||
#include "AiObject.h"
|
||||
#include "Common.h"
|
||||
@@ -24,15 +23,26 @@ public:
|
||||
std::string const getName() { return name; }
|
||||
float getRelevance() { return relevance; }
|
||||
|
||||
static uint32 size(NextAction** actions);
|
||||
static NextAction** clone(NextAction** actions);
|
||||
static NextAction** merge(NextAction** what, NextAction** with);
|
||||
static NextAction** array(uint32 nil, ...);
|
||||
static void destroy(NextAction** actions);
|
||||
static std::vector<NextAction> merge(std::vector<NextAction> const& what, std::vector<NextAction> const& with)
|
||||
{
|
||||
std::vector<NextAction> result = {};
|
||||
|
||||
for (NextAction const& action : what)
|
||||
{
|
||||
result.push_back(action);
|
||||
}
|
||||
|
||||
for (NextAction const& action : with)
|
||||
{
|
||||
result.push_back(action);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
private:
|
||||
float relevance;
|
||||
std::string const name;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
class Action : public AiNamedObject
|
||||
@@ -52,9 +62,9 @@ public:
|
||||
virtual bool Execute([[maybe_unused]] Event event) { return true; }
|
||||
virtual bool isPossible() { return true; }
|
||||
virtual bool isUseful() { return true; }
|
||||
virtual NextAction** getPrerequisites() { return nullptr; }
|
||||
virtual NextAction** getAlternatives() { return nullptr; }
|
||||
virtual NextAction** getContinuers() { return nullptr; }
|
||||
virtual std::vector<NextAction> getPrerequisites() { return {}; }
|
||||
virtual std::vector<NextAction> getAlternatives() { return {}; }
|
||||
virtual std::vector<NextAction> getContinuers() { return {}; }
|
||||
virtual ActionThreatType getThreatType() { return ActionThreatType::None; }
|
||||
void Update() {}
|
||||
void Reset() {}
|
||||
@@ -73,39 +83,44 @@ protected:
|
||||
class ActionNode
|
||||
{
|
||||
public:
|
||||
ActionNode(std::string const name, NextAction** prerequisites = nullptr, NextAction** alternatives = nullptr,
|
||||
NextAction** continuers = nullptr)
|
||||
: name(name), action(nullptr), continuers(continuers), alternatives(alternatives), prerequisites(prerequisites)
|
||||
{
|
||||
} // reorder arguments - whipowill
|
||||
ActionNode(
|
||||
std::string name,
|
||||
std::vector<NextAction> prerequisites = {},
|
||||
std::vector<NextAction> alternatives = {},
|
||||
std::vector<NextAction> continuers = {}
|
||||
) :
|
||||
name(std::move(name)),
|
||||
action(nullptr),
|
||||
continuers(continuers),
|
||||
alternatives(alternatives),
|
||||
prerequisites(prerequisites)
|
||||
{}
|
||||
|
||||
virtual ~ActionNode()
|
||||
{
|
||||
NextAction::destroy(prerequisites);
|
||||
NextAction::destroy(alternatives);
|
||||
NextAction::destroy(continuers);
|
||||
}
|
||||
virtual ~ActionNode() = default;
|
||||
|
||||
Action* getAction() { return action; }
|
||||
void setAction(Action* action) { this->action = action; }
|
||||
std::string const getName() { return name; }
|
||||
const std::string getName() { return name; }
|
||||
|
||||
NextAction** getContinuers() { return NextAction::merge(NextAction::clone(continuers), action->getContinuers()); }
|
||||
NextAction** getAlternatives()
|
||||
std::vector<NextAction> getContinuers()
|
||||
{
|
||||
return NextAction::merge(NextAction::clone(alternatives), action->getAlternatives());
|
||||
return NextAction::merge(this->continuers, action->getContinuers());
|
||||
}
|
||||
NextAction** getPrerequisites()
|
||||
std::vector<NextAction> getAlternatives()
|
||||
{
|
||||
return NextAction::merge(NextAction::clone(prerequisites), action->getPrerequisites());
|
||||
return NextAction::merge(this->alternatives, action->getAlternatives());
|
||||
}
|
||||
std::vector<NextAction> getPrerequisites()
|
||||
{
|
||||
return NextAction::merge(this->prerequisites, action->getPrerequisites());
|
||||
}
|
||||
|
||||
private:
|
||||
std::string const name;
|
||||
const std::string name;
|
||||
Action* action;
|
||||
NextAction** continuers;
|
||||
NextAction** alternatives;
|
||||
NextAction** prerequisites;
|
||||
std::vector<NextAction> continuers;
|
||||
std::vector<NextAction> alternatives;
|
||||
std::vector<NextAction> prerequisites;
|
||||
};
|
||||
|
||||
class ActionBasket
|
||||
@@ -121,14 +136,12 @@ public:
|
||||
bool isSkipPrerequisites() { return skipPrerequisites; }
|
||||
void AmendRelevance(float k) { relevance *= k; }
|
||||
void setRelevance(float relevance) { this->relevance = relevance; }
|
||||
bool isExpired(uint32 msecs);
|
||||
bool isExpired(uint32_t msecs);
|
||||
|
||||
private:
|
||||
ActionNode* action;
|
||||
float relevance;
|
||||
bool skipPrerequisites;
|
||||
Event event;
|
||||
uint32 created;
|
||||
uint32_t created;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -42,9 +42,6 @@ protected:
|
||||
// TRIGGERS
|
||||
//
|
||||
|
||||
#define NEXT_TRIGGERS(name, relevance) \
|
||||
virtual NextAction* getNextAction() { return new NextAction(name, relevance); }
|
||||
|
||||
#define BEGIN_TRIGGER(clazz, super) \
|
||||
class clazz : public super \
|
||||
{ \
|
||||
@@ -78,14 +75,6 @@ protected:
|
||||
clazz(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, spell) {} \
|
||||
}
|
||||
|
||||
#define BUFF_PARTY_TRIGGER_A(clazz, spell) \
|
||||
class clazz : public BuffOnPartyTrigger \
|
||||
{ \
|
||||
public: \
|
||||
clazz(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, spell) {} \
|
||||
bool IsActive() override; \
|
||||
}
|
||||
|
||||
#define DEBUFF_TRIGGER(clazz, spell) \
|
||||
class clazz : public DebuffTrigger \
|
||||
{ \
|
||||
@@ -296,14 +285,6 @@ protected:
|
||||
clazz(PlayerbotAI* botAI) : CastHealingSpellAction(botAI, spell) {} \
|
||||
}
|
||||
|
||||
#define HEAL_ACTION_U(clazz, spell, useful) \
|
||||
class clazz : public CastHealingSpellAction \
|
||||
{ \
|
||||
public: \
|
||||
clazz(PlayerbotAI* botAI) : CastHealingSpellAction(botAI, spell) {} \
|
||||
bool isUseful() override { return useful; } \
|
||||
}
|
||||
|
||||
#define HEAL_PARTY_ACTION(clazz, spell, estAmount, manaEfficiency) \
|
||||
class clazz : public HealPartyMemberAction \
|
||||
{ \
|
||||
@@ -404,14 +385,6 @@ protected:
|
||||
clazz(PlayerbotAI* botAI) : CastReachTargetSpellAction(botAI, spell, range) {} \
|
||||
}
|
||||
|
||||
#define REACH_ACTION_U(clazz, spell, range, useful) \
|
||||
class clazz : public CastReachTargetSpellAction \
|
||||
{ \
|
||||
public: \
|
||||
clazz(PlayerbotAI* botAI) : CastReachTargetSpellAction(botAI, spell, range) {} \
|
||||
bool isUseful() override { return useful; } \
|
||||
}
|
||||
|
||||
#define ENEMY_HEALER_ACTION(clazz, spell) \
|
||||
class clazz : public CastSpellOnEnemyHealerAction \
|
||||
{ \
|
||||
@@ -440,10 +413,6 @@ protected:
|
||||
clazz(PlayerbotAI* botAI) : CastProtectSpellAction(botAI, spell) {} \
|
||||
}
|
||||
|
||||
#define END_RANGED_SPELL_ACTION() \
|
||||
} \
|
||||
;
|
||||
|
||||
#define BEGIN_SPELL_ACTION(clazz, name) \
|
||||
class clazz : public CastSpellAction \
|
||||
{ \
|
||||
@@ -472,42 +441,4 @@ protected:
|
||||
public: \
|
||||
clazz(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, name) {}
|
||||
|
||||
#define END_RANGED_SPELL_ACTION() \
|
||||
} \
|
||||
;
|
||||
|
||||
#define BEGIN_BUFF_ON_PARTY_ACTION(clazz, name) \
|
||||
class clazz : public BuffOnPartyAction \
|
||||
{ \
|
||||
public: \
|
||||
clazz(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, name) {}
|
||||
|
||||
//
|
||||
// Action node
|
||||
//
|
||||
|
||||
// node_name , action, prerequisite
|
||||
#define ACTION_NODE_P(name, spell, pre) \
|
||||
static ActionNode* name([[maybe_unused]] PlayerbotAI* botAI) \
|
||||
{ \
|
||||
return new ActionNode(spell, /*P*/ NextAction::array(0, new NextAction(pre), nullptr), /*A*/ nullptr, \
|
||||
/*C*/ nullptr); \
|
||||
}
|
||||
|
||||
// node_name , action, alternative
|
||||
#define ACTION_NODE_A(name, spell, alt) \
|
||||
static ActionNode* name([[maybe_unused]] PlayerbotAI* botAI) \
|
||||
{ \
|
||||
return new ActionNode(spell, /*P*/ nullptr, /*A*/ NextAction::array(0, new NextAction(alt), nullptr), \
|
||||
/*C*/ nullptr); \
|
||||
}
|
||||
|
||||
// node_name , action, continuer
|
||||
#define ACTION_NODE_C(name, spell, con) \
|
||||
static ActionNode* name([[maybe_unused]] PlayerbotAI* botAI) \
|
||||
{ \
|
||||
return new ActionNode(spell, /*P*/ nullptr, /*A*/ nullptr, \
|
||||
/*C*/ NextAction::array(0, new NextAction(con), nullptr)); \
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -43,8 +43,6 @@
|
||||
#include "raids/magtheridon/RaidMagtheridonTriggerContext.h"
|
||||
#include "raids/gruulslair/RaidGruulsLairActionContext.h"
|
||||
#include "raids/gruulslair/RaidGruulsLairTriggerContext.h"
|
||||
#include "raids/naxxramas/RaidNaxxActionContext.h"
|
||||
#include "raids/naxxramas/RaidNaxxTriggerContext.h"
|
||||
#include "raids/eyeofeternity/RaidEoEActionContext.h"
|
||||
#include "raids/eyeofeternity/RaidEoETriggerContext.h"
|
||||
#include "raids/vaultofarchavon/RaidVoAActionContext.h"
|
||||
@@ -117,7 +115,6 @@ void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList<Act
|
||||
actionContexts.Add(new RaidKarazhanActionContext());
|
||||
actionContexts.Add(new RaidMagtheridonActionContext());
|
||||
actionContexts.Add(new RaidGruulsLairActionContext());
|
||||
actionContexts.Add(new RaidNaxxActionContext());
|
||||
actionContexts.Add(new RaidOsActionContext());
|
||||
actionContexts.Add(new RaidEoEActionContext());
|
||||
actionContexts.Add(new RaidVoAActionContext());
|
||||
@@ -152,7 +149,6 @@ void AiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContextList<Tr
|
||||
triggerContexts.Add(new RaidKarazhanTriggerContext());
|
||||
triggerContexts.Add(new RaidMagtheridonTriggerContext());
|
||||
triggerContexts.Add(new RaidGruulsLairTriggerContext());
|
||||
triggerContexts.Add(new RaidNaxxTriggerContext());
|
||||
triggerContexts.Add(new RaidOsTriggerContext());
|
||||
triggerContexts.Add(new RaidEoETriggerContext());
|
||||
triggerContexts.Add(new RaidVoATriggerContext());
|
||||
|
||||
@@ -6,36 +6,40 @@
|
||||
#include "CustomStrategy.h"
|
||||
|
||||
#include <regex>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "Playerbots.h"
|
||||
|
||||
std::map<std::string, std::string> CustomStrategy::actionLinesCache;
|
||||
|
||||
NextAction* toNextAction(std::string const action)
|
||||
NextAction toNextAction(std::string const action)
|
||||
{
|
||||
std::vector<std::string> tokens = split(action, '!');
|
||||
if (tokens.size() == 2 && !tokens[0].empty())
|
||||
return new NextAction(tokens[0], atof(tokens[1].c_str()));
|
||||
else if (tokens.size() == 1 && !tokens[0].empty())
|
||||
return new NextAction(tokens[0], ACTION_NORMAL);
|
||||
|
||||
if (tokens[0].empty())
|
||||
throw std::invalid_argument("Invalid action");
|
||||
|
||||
if (tokens.size() == 2)
|
||||
return NextAction(tokens[0], atof(tokens[1].c_str()));
|
||||
|
||||
if (tokens.size() == 1)
|
||||
return NextAction(tokens[0], ACTION_NORMAL);
|
||||
|
||||
LOG_ERROR("playerbots", "Invalid action {}", action.c_str());
|
||||
return nullptr;
|
||||
|
||||
throw std::invalid_argument("Invalid action");
|
||||
}
|
||||
|
||||
NextAction** toNextActionArray(std::string const actions)
|
||||
std::vector<NextAction> toNextActionArray(const std::string actions)
|
||||
{
|
||||
std::vector<std::string> tokens = split(actions, ',');
|
||||
NextAction** res = new NextAction*[tokens.size() + 1];
|
||||
const std::vector<std::string> tokens = split(actions, ',');
|
||||
std::vector<NextAction> res = {};
|
||||
|
||||
uint32 index = 0;
|
||||
for (std::vector<std::string>::iterator i = tokens.begin(); i != tokens.end(); ++i)
|
||||
for (const std::string token : tokens)
|
||||
{
|
||||
if (NextAction* na = toNextAction(*i))
|
||||
res[index++] = na;
|
||||
res.push_back(toNextAction(token));
|
||||
}
|
||||
|
||||
res[index++] = nullptr;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@@ -258,48 +258,45 @@ ActionNode* Engine::CreateActionNode(std::string const name)
|
||||
return node;
|
||||
|
||||
return new ActionNode(name,
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {});
|
||||
}
|
||||
|
||||
bool Engine::MultiplyAndPush(NextAction** actions, float forceRelevance, bool skipPrerequisites, Event event,
|
||||
char const* pushType)
|
||||
bool Engine::MultiplyAndPush(
|
||||
std::vector<NextAction> actions,
|
||||
float forceRelevance,
|
||||
bool skipPrerequisites,
|
||||
Event event,
|
||||
char const* pushType
|
||||
)
|
||||
{
|
||||
bool pushed = false;
|
||||
if (actions)
|
||||
|
||||
for (NextAction nextAction : actions)
|
||||
{
|
||||
for (uint32 j = 0; actions[j]; j++)
|
||||
ActionNode* action = this->CreateActionNode(nextAction.getName());
|
||||
|
||||
this->InitializeAction(action);
|
||||
|
||||
float k = nextAction.getRelevance();
|
||||
|
||||
if (forceRelevance > 0.0f)
|
||||
{
|
||||
if (NextAction* nextAction = actions[j])
|
||||
{
|
||||
ActionNode* action = CreateActionNode(nextAction->getName());
|
||||
InitializeAction(action);
|
||||
|
||||
float k = nextAction->getRelevance();
|
||||
if (forceRelevance > 0.0f)
|
||||
{
|
||||
k = forceRelevance;
|
||||
}
|
||||
|
||||
if (k > 0)
|
||||
{
|
||||
LogAction("PUSH:%s - %f (%s)", action->getName().c_str(), k, pushType);
|
||||
queue.Push(new ActionBasket(action, k, skipPrerequisites, event));
|
||||
pushed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
delete action;
|
||||
}
|
||||
|
||||
delete nextAction;
|
||||
}
|
||||
else
|
||||
break;
|
||||
k = forceRelevance;
|
||||
}
|
||||
|
||||
delete[] actions;
|
||||
if (k > 0)
|
||||
{
|
||||
this->LogAction("PUSH:%s - %f (%s)", action->getName().c_str(), k, pushType);
|
||||
queue.Push(new ActionBasket(action, k, skipPrerequisites, event));
|
||||
pushed = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
delete action;
|
||||
|
||||
}
|
||||
|
||||
return pushed;
|
||||
@@ -530,10 +527,10 @@ std::vector<std::string> Engine::GetStrategies()
|
||||
|
||||
void Engine::PushAgain(ActionNode* actionNode, float relevance, Event event)
|
||||
{
|
||||
NextAction** nextAction = new NextAction*[2];
|
||||
nextAction[0] = new NextAction(actionNode->getName(), relevance);
|
||||
nextAction[1] = nullptr;
|
||||
std::vector<NextAction> nextAction = { NextAction(actionNode->getName(), relevance) };
|
||||
|
||||
MultiplyAndPush(nextAction, relevance, true, event, "again");
|
||||
|
||||
delete actionNode;
|
||||
}
|
||||
|
||||
@@ -563,6 +560,13 @@ bool Engine::ListenAndExecute(Action* action, Event event)
|
||||
{
|
||||
bool actionExecuted = false;
|
||||
|
||||
if (action == nullptr)
|
||||
{
|
||||
LOG_ERROR("playerbots", "Action is nullptr");
|
||||
|
||||
return actionExecuted;
|
||||
}
|
||||
|
||||
if (actionExecutionListeners.Before(action, event))
|
||||
{
|
||||
actionExecuted = actionExecutionListeners.AllowExecution(action, event) ? action->Execute(event) : true;
|
||||
|
||||
@@ -90,7 +90,7 @@ public:
|
||||
bool testMode;
|
||||
|
||||
private:
|
||||
bool MultiplyAndPush(NextAction** actions, float forceRelevance, bool skipPrerequisites, Event event,
|
||||
bool MultiplyAndPush(std::vector<NextAction> actions, float forceRelevance, bool skipPrerequisites, Event event,
|
||||
const char* pushType);
|
||||
void Reset();
|
||||
void ProcessTriggers(bool minimal);
|
||||
|
||||
@@ -28,90 +28,112 @@ public:
|
||||
private:
|
||||
static ActionNode* melee([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("melee",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"melee",
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* healthstone([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("healthstone",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ NextAction::array(0, new NextAction("healing potion"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"healthstone",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("healing potion") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* follow_master_random([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("be near",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ NextAction::array(0, new NextAction("follow"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"be near",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("follow") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* attack_anything([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("attack anything",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"attack anything",
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* move_random([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("move random",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ NextAction::array(0, new NextAction("stay line"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"move random",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("stay line") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* move_to_loot([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("move to loot",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"move to loot",
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* food([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("food",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"food",
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* drink([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("drink",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"drink",
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* mana_potion([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("mana potion",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"mana potion",
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* healing_potion([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("healing potion",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ NextAction::array(0, new NextAction("food"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"healing potion",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("food") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* flee([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("flee",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"flee",
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ public:
|
||||
Strategy(PlayerbotAI* botAI);
|
||||
virtual ~Strategy() {}
|
||||
|
||||
virtual NextAction** getDefaultActions() { return nullptr; }
|
||||
virtual std::vector<NextAction> getDefaultActions() { return {}; }
|
||||
virtual void InitTriggers([[maybe_unused]] std::vector<TriggerNode*>& triggers) {}
|
||||
virtual void InitMultipliers([[maybe_unused]] std::vector<Multiplier*>& multipliers) {}
|
||||
virtual std::string const getName() = 0;
|
||||
|
||||
@@ -120,6 +120,8 @@ public:
|
||||
creators["formation"] = &StrategyContext::combat_formation;
|
||||
creators["move from group"] = &StrategyContext::move_from_group;
|
||||
creators["worldbuff"] = &StrategyContext::world_buff;
|
||||
creators["use bobber"] = &StrategyContext::bobber_strategy;
|
||||
creators["master fishing"] = &StrategyContext::master_fishing;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -188,6 +190,8 @@ private:
|
||||
static Strategy* combat_formation(PlayerbotAI* botAI) { return new CombatFormationStrategy(botAI); }
|
||||
static Strategy* move_from_group(PlayerbotAI* botAI) { return new MoveFromGroupStrategy(botAI); }
|
||||
static Strategy* world_buff(PlayerbotAI* botAI) { return new WorldBuffStrategy(botAI); }
|
||||
static Strategy* bobber_strategy(PlayerbotAI* botAI) { return new UseBobberStrategy(botAI); }
|
||||
static Strategy* master_fishing(PlayerbotAI* botAI) { return new MasterFishingStrategy(botAI); }
|
||||
};
|
||||
|
||||
class MovementStrategyContext : public NamedObjectContext<Strategy>
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_TRIGGER_H
|
||||
#define _PLAYERBOT_TRIGGER_H
|
||||
#pragma once
|
||||
|
||||
#include "Action.h"
|
||||
#include "Common.h"
|
||||
@@ -15,7 +14,11 @@ class Unit;
|
||||
class Trigger : public AiNamedObject
|
||||
{
|
||||
public:
|
||||
Trigger(PlayerbotAI* botAI, std::string const name = "trigger", int32 checkInterval = 1);
|
||||
Trigger(
|
||||
PlayerbotAI* botAI,
|
||||
const std::string name = "trigger",
|
||||
int32_t checkInterval = 1
|
||||
);
|
||||
|
||||
virtual ~Trigger() {}
|
||||
|
||||
@@ -23,7 +26,7 @@ public:
|
||||
virtual void ExternalEvent([[maybe_unused]] std::string const param, [[maybe_unused]] Player* owner = nullptr) {}
|
||||
virtual void ExternalEvent([[maybe_unused]] WorldPacket& packet, [[maybe_unused]] Player* owner = nullptr) {}
|
||||
virtual bool IsActive() { return false; }
|
||||
virtual NextAction** getHandlers() { return nullptr; }
|
||||
virtual std::vector<NextAction> getHandlers() { return {}; }
|
||||
void Update() {}
|
||||
virtual void Reset() {}
|
||||
virtual Unit* GetTarget();
|
||||
@@ -33,32 +36,49 @@ public:
|
||||
bool needCheck(uint32 now);
|
||||
|
||||
protected:
|
||||
int32 checkInterval;
|
||||
uint32 lastCheckTime;
|
||||
int32_t checkInterval;
|
||||
uint32_t lastCheckTime;
|
||||
};
|
||||
|
||||
class TriggerNode
|
||||
{
|
||||
public:
|
||||
TriggerNode(std::string const name, NextAction** handlers = nullptr)
|
||||
: trigger(nullptr), handlers(handlers), name(name)
|
||||
{
|
||||
} // reorder args - whipowill
|
||||
|
||||
virtual ~TriggerNode() { NextAction::destroy(handlers); }
|
||||
TriggerNode(
|
||||
const std::string& name,
|
||||
std::vector<NextAction> handlers = {}
|
||||
) :
|
||||
trigger(nullptr),
|
||||
handlers(std::move(handlers)),
|
||||
name(name)
|
||||
{}
|
||||
|
||||
Trigger* getTrigger() { return trigger; }
|
||||
void setTrigger(Trigger* trigger) { this->trigger = trigger; }
|
||||
std::string const getName() { return name; }
|
||||
const std::string getName() { return name; }
|
||||
|
||||
NextAction** getHandlers() { return NextAction::merge(NextAction::clone(handlers), trigger->getHandlers()); }
|
||||
std::vector<NextAction> getHandlers()
|
||||
{
|
||||
std::vector<NextAction> result = this->handlers;
|
||||
|
||||
float getFirstRelevance() { return handlers[0] ? handlers[0]->getRelevance() : -1; }
|
||||
if (trigger != nullptr)
|
||||
{
|
||||
std::vector<NextAction> extra = trigger->getHandlers();
|
||||
result.insert(result.end(), extra.begin(), extra.end());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
float getFirstRelevance()
|
||||
{
|
||||
if (this->handlers.size() > 0)
|
||||
return this->handlers[0].getRelevance();
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private:
|
||||
Trigger* trigger;
|
||||
NextAction** handlers;
|
||||
std::string const name;
|
||||
std::vector<NextAction> handlers;
|
||||
const std::string name;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -59,7 +59,7 @@ bool AcceptInvitationAction::Execute(Event event)
|
||||
|
||||
if (sPlayerbotAIConfig->summonWhenGroup && bot->GetDistance(inviter) > sPlayerbotAIConfig->sightDistance)
|
||||
{
|
||||
Teleport(inviter, bot);
|
||||
Teleport(inviter, bot, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,5 @@ bool AcceptResurrectAction::Execute(Event event)
|
||||
packet << uint8(1); // accept
|
||||
bot->GetSession()->HandleResurrectResponseOpcode(packet); // queue the packet to get around race condition
|
||||
|
||||
botAI->ChangeEngine(BOT_STATE_NON_COMBAT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
#include "OutfitAction.h"
|
||||
#include "PositionAction.h"
|
||||
#include "DropQuestAction.h"
|
||||
#include "RaidNaxxActions.h"
|
||||
#include "RandomBotUpdateAction.h"
|
||||
#include "ReachTargetActions.h"
|
||||
#include "ReleaseSpiritAction.h"
|
||||
@@ -64,6 +63,7 @@
|
||||
#include "WorldBuffAction.h"
|
||||
#include "XpGainAction.h"
|
||||
#include "NewRpgAction.h"
|
||||
#include "FishingAction.h"
|
||||
#include "CancelChannelAction.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
@@ -121,7 +121,7 @@ public:
|
||||
creators["shoot"] = &ActionContext::shoot;
|
||||
creators["follow"] = &ActionContext::follow;
|
||||
creators["move from group"] = &ActionContext::move_from_group;
|
||||
creators["flee to master"] = &ActionContext::flee_to_master;
|
||||
creators["flee to group leader"] = &ActionContext::flee_to_group_leader;
|
||||
creators["runaway"] = &ActionContext::runaway;
|
||||
creators["stay"] = &ActionContext::stay;
|
||||
creators["sit"] = &ActionContext::sit;
|
||||
@@ -191,6 +191,11 @@ public:
|
||||
creators["buy tabard"] = &ActionContext::buy_tabard;
|
||||
creators["guild manage nearby"] = &ActionContext::guild_manage_nearby;
|
||||
creators["clean quest log"] = &ActionContext::clean_quest_log;
|
||||
creators["move near water"] = &ActionContext::move_near_water;
|
||||
creators["go fishing"] = &ActionContext::go_fishing;
|
||||
creators["use fishing bobber"] = &ActionContext::use_fishing_bobber;
|
||||
creators["end master fishing"] = &ActionContext::end_master_fishing;
|
||||
creators["remove bobber strategy"] = &ActionContext::remove_bobber_strategy;
|
||||
creators["roll"] = &ActionContext::roll_action;
|
||||
creators["cancel channel"] = &ActionContext::cancel_channel;
|
||||
|
||||
@@ -318,7 +323,7 @@ private:
|
||||
static Action* runaway(PlayerbotAI* botAI) { return new RunAwayAction(botAI); }
|
||||
static Action* follow(PlayerbotAI* botAI) { return new FollowAction(botAI); }
|
||||
static Action* move_from_group(PlayerbotAI* botAI) { return new MoveFromGroupAction(botAI); }
|
||||
static Action* flee_to_master(PlayerbotAI* botAI) { return new FleeToMasterAction(botAI); }
|
||||
static Action* flee_to_group_leader(PlayerbotAI* botAI) { return new FleeToGroupLeaderAction(botAI); }
|
||||
static Action* add_gathering_loot(PlayerbotAI* botAI) { return new AddGatheringLootAction(botAI); }
|
||||
static Action* add_loot(PlayerbotAI* botAI) { return new AddLootAction(botAI); }
|
||||
static Action* add_all_loot(PlayerbotAI* botAI) { return new AddAllLootAction(botAI); }
|
||||
@@ -380,6 +385,11 @@ private:
|
||||
static Action* buy_tabard(PlayerbotAI* botAI) { return new BuyTabardAction(botAI); }
|
||||
static Action* guild_manage_nearby(PlayerbotAI* botAI) { return new GuildManageNearbyAction(botAI); }
|
||||
static Action* clean_quest_log(PlayerbotAI* botAI) { return new CleanQuestLogAction(botAI); }
|
||||
static Action* move_near_water(PlayerbotAI* botAI) { return new MoveNearWaterAction(botAI); }
|
||||
static Action* go_fishing(PlayerbotAI* botAI) { return new FishingAction(botAI);}
|
||||
static Action* use_fishing_bobber(PlayerbotAI* botAI) { return new UseBobberAction(botAI);}
|
||||
static Action* end_master_fishing(PlayerbotAI* botAI) { return new EndMasterFishingAction(botAI); }
|
||||
static Action* remove_bobber_strategy(PlayerbotAI* botAI) { return new RemoveBobberStrategyAction(botAI); }
|
||||
static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); }
|
||||
|
||||
// BG Tactics
|
||||
|
||||
@@ -40,7 +40,14 @@ bool ReachAreaTriggerAction::Execute(Event event)
|
||||
return true;
|
||||
}
|
||||
|
||||
bot->GetMotionMaster()->MovePoint(at->map, at->x, at->y, at->z);
|
||||
bot->GetMotionMaster()->MovePoint(
|
||||
/*id*/ at->map,
|
||||
/*coords*/ at->x, at->y, at->z,
|
||||
/*forcedMovement*/ FORCED_MOVEMENT_NONE,
|
||||
/*speed*/ 0.0f, // default speed (not handled here)
|
||||
/*orientation*/ 0.0f, // keep current orientation of bot
|
||||
/*generatePath*/ true, // true => terrain path (2d mmap); false => straight spline (3d vmap)
|
||||
/*forceDestination*/ false);
|
||||
|
||||
float distance = bot->GetDistance(at->x, at->y, at->z);
|
||||
float delay = 1000.0f * distance / bot->GetSpeed(MOVE_RUN) + sPlayerbotAIConfig->reactDelay;
|
||||
|
||||
@@ -15,20 +15,19 @@
|
||||
#include "SharedDefines.h"
|
||||
#include "Unit.h"
|
||||
|
||||
bool AttackAction::Execute(Event event)
|
||||
bool AttackAction::Execute(Event /*event*/)
|
||||
{
|
||||
Unit* target = GetTarget();
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
if (!target->IsInWorld())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Attack(target);
|
||||
}
|
||||
|
||||
bool AttackMyTargetAction::Execute(Event event)
|
||||
bool AttackMyTargetAction::Execute(Event /*event*/)
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
@@ -51,7 +50,7 @@ bool AttackMyTargetAction::Execute(Event event)
|
||||
return result;
|
||||
}
|
||||
|
||||
bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
||||
{
|
||||
Unit* oldTarget = context->GetValue<Unit*>("current target")->Get();
|
||||
bool shouldMelee = bot->IsWithinMeleeRange(target) || botAI->IsMelee(bot);
|
||||
@@ -81,12 +80,15 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is no longer in the world.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((sPlayerbotAIConfig->IsInPvpProhibitedZone(bot->GetZoneId()) ||
|
||||
sPlayerbotAIConfig->IsInPvpProhibitedArea(bot->GetAreaId()))
|
||||
&& (target->IsPlayer() || target->IsPet()))
|
||||
// Check if bot OR target is in prohibited zone/area (skip for duels)
|
||||
if ((target->IsPlayer() || target->IsPet()) &&
|
||||
(!bot->duel || bot->duel->Opponent != target) &&
|
||||
(sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()) ||
|
||||
sPlayerbotAIConfig->IsPvpProhibited(target->GetZoneId(), target->GetAreaId())))
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError("I cannot attack other players in PvP prohibited areas.");
|
||||
@@ -98,6 +100,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is friendly to me.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -105,6 +108,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is dead.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -112,6 +116,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is not in my sight.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -119,6 +124,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError("I am already attacking " + std::string(target->GetName()) + ".");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -153,10 +159,9 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
bot->StopMoving();
|
||||
}
|
||||
|
||||
if (IsMovingAllowed() && !bot->HasInArc(CAST_ANGLE_IN_FRONT, target))
|
||||
{
|
||||
if (botAI->CanMove() && !bot->HasInArc(CAST_ANGLE_IN_FRONT, target))
|
||||
sServerFacade->SetFacingTo(bot, target);
|
||||
}
|
||||
|
||||
botAI->ChangeEngine(BOT_STATE_COMBAT);
|
||||
|
||||
bot->Attack(target, shouldMelee);
|
||||
@@ -186,4 +191,4 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
|
||||
bool AttackDuelOpponentAction::isUseful() { return AI_VALUE(Unit*, "duel target"); }
|
||||
|
||||
bool AttackDuelOpponentAction::Execute(Event event) { return Attack(AI_VALUE(Unit*, "duel target")); }
|
||||
bool AttackDuelOpponentAction::Execute(Event /*event*/) { return Attack(AI_VALUE(Unit*, "duel target")); }
|
||||
|
||||
@@ -90,18 +90,25 @@ void AutoMaintenanceOnLevelupAction::LearnQuestSpells(std::ostringstream* out)
|
||||
//uint32 questId = i->first; //not used, line marked for removal.
|
||||
Quest const* quest = i->second;
|
||||
|
||||
if (!quest->GetRequiredClasses() || quest->IsRepeatable() || quest->GetMinLevel() < 10)
|
||||
// only process class-specific quests to learn class-related spells, cuz
|
||||
// we don't want all these bunch of entries to be handled!
|
||||
if (!quest->GetRequiredClasses())
|
||||
continue;
|
||||
|
||||
if (!bot->SatisfyQuestClass(quest, false) || quest->GetMinLevel() > bot->GetLevel() ||
|
||||
!bot->SatisfyQuestRace(quest, false))
|
||||
// skip quests that are repeatable, too low level, or above bots' level
|
||||
if (quest->IsRepeatable() || quest->GetMinLevel() < 10 || quest->GetMinLevel() > bot->GetLevel())
|
||||
continue;
|
||||
|
||||
if (quest->GetRewSpellCast() > 0)
|
||||
// skip if bot doesnt satisfy class, race, or skill requirements
|
||||
if (!bot->SatisfyQuestClass(quest, false) || !bot->SatisfyQuestRace(quest, false) ||
|
||||
!bot->SatisfyQuestSkill(quest, false))
|
||||
continue;
|
||||
|
||||
if (quest->GetRewSpellCast() > 0) // RewardSpell - expected route
|
||||
{
|
||||
LearnSpell(quest->GetRewSpellCast(), out);
|
||||
}
|
||||
else if (quest->GetRewSpell() > 0)
|
||||
else if (quest->GetRewSpell() > 0) // RewardDisplaySpell - fallback
|
||||
{
|
||||
LearnSpell(quest->GetRewSpell(), out);
|
||||
}
|
||||
@@ -123,35 +130,37 @@ std::string const AutoMaintenanceOnLevelupAction::FormatSpell(SpellInfo const* s
|
||||
|
||||
void AutoMaintenanceOnLevelupAction::LearnSpell(uint32 spellId, std::ostringstream* out)
|
||||
{
|
||||
SpellInfo const* proto = sSpellMgr->GetSpellInfo(spellId);
|
||||
if (!proto)
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
if (!spellInfo)
|
||||
return;
|
||||
|
||||
bool learned = false;
|
||||
for (uint8 j = 0; j < 3; ++j)
|
||||
SpellInfo const* triggeredInfo;
|
||||
|
||||
// find effect with learn spell and check if we have this spell
|
||||
bool found = false;
|
||||
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
||||
{
|
||||
if (proto->Effects[j].Effect == SPELL_EFFECT_LEARN_SPELL)
|
||||
if (spellInfo->Effects[i].Effect == SPELL_EFFECT_LEARN_SPELL && spellInfo->Effects[i].TriggerSpell &&
|
||||
!bot->HasSpell(spellInfo->Effects[i].TriggerSpell))
|
||||
{
|
||||
uint32 learnedSpell = proto->Effects[j].TriggerSpell;
|
||||
triggeredInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[i].TriggerSpell);
|
||||
|
||||
if (!bot->HasSpell(learnedSpell))
|
||||
{
|
||||
bot->learnSpell(learnedSpell);
|
||||
*out << FormatSpell(sSpellMgr->GetSpellInfo(learnedSpell)) << ", ";
|
||||
}
|
||||
// do not learn profession specialties!
|
||||
if (!triggeredInfo || triggeredInfo->Effects[0].Effect == SPELL_EFFECT_TRADE_SKILL)
|
||||
break;
|
||||
|
||||
learned = true;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!learned)
|
||||
{
|
||||
if (!bot->HasSpell(spellId))
|
||||
{
|
||||
bot->learnSpell(spellId);
|
||||
*out << FormatSpell(proto) << ", ";
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
return;
|
||||
|
||||
// NOTE: When rewarding quests, core casts spells instead of learning them,
|
||||
// but we sacrifice safe cast checks here in favor of performance/speed
|
||||
bot->learnSpell(triggeredInfo->Id);
|
||||
*out << FormatSpell(triggeredInfo) << ", ";
|
||||
}
|
||||
|
||||
void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip()
|
||||
|
||||
@@ -224,42 +224,36 @@ bool BuyAction::Execute(Event event)
|
||||
|
||||
bool BuyAction::BuyItem(VendorItemData const* tItems, ObjectGuid vendorguid, ItemTemplate const* proto)
|
||||
{
|
||||
uint32 oldCount = AI_VALUE2(uint32, "item count", proto->Name1);
|
||||
|
||||
if (!tItems)
|
||||
if (!tItems || !proto)
|
||||
return false;
|
||||
|
||||
uint32 itemId = proto->ItemId;
|
||||
for (uint32 slot = 0; slot < tItems->GetItemCount(); slot++)
|
||||
uint32 oldCount = bot->GetItemCount(itemId, false);
|
||||
|
||||
for (uint32 slot = 0; slot < tItems->GetItemCount(); ++slot)
|
||||
{
|
||||
if (tItems->GetItem(slot)->item == itemId)
|
||||
if (tItems->GetItem(slot)->item != itemId)
|
||||
continue;
|
||||
|
||||
uint32 botMoney = bot->GetMoney();
|
||||
if (botAI->HasCheat(BotCheatMask::gold))
|
||||
bot->SetMoney(10000000);
|
||||
|
||||
bot->BuyItemFromVendorSlot(vendorguid, slot, itemId, 1, NULL_BAG, NULL_SLOT);
|
||||
|
||||
if (botAI->HasCheat(BotCheatMask::gold))
|
||||
bot->SetMoney(botMoney);
|
||||
|
||||
uint32 newCount = bot->GetItemCount(itemId, false);
|
||||
if (newCount > oldCount)
|
||||
{
|
||||
uint32 botMoney = bot->GetMoney();
|
||||
if (botAI->HasCheat(BotCheatMask::gold))
|
||||
{
|
||||
bot->SetMoney(10000000);
|
||||
}
|
||||
|
||||
bot->BuyItemFromVendorSlot(vendorguid, slot, itemId, 1, NULL_BAG, NULL_SLOT);
|
||||
|
||||
if (botAI->HasCheat(BotCheatMask::gold))
|
||||
{
|
||||
bot->SetMoney(botMoney);
|
||||
}
|
||||
|
||||
if (oldCount <
|
||||
AI_VALUE2(
|
||||
uint32, "item count",
|
||||
proto->Name1)) // BuyItem Always returns false (unless unique) so we have to check the item counts.
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "Buying " << ChatHelper::FormatItem(proto);
|
||||
botAI->TellMaster(out.str());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
std::ostringstream out;
|
||||
out << "Buying " << ChatHelper::FormatItem(proto);
|
||||
botAI->TellMaster(out.str());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -185,7 +185,6 @@ public:
|
||||
creators["guild remove"] = &ChatActionContext::guild_remove;
|
||||
creators["guild leave"] = &ChatActionContext::guild_leave;
|
||||
creators["rtsc"] = &ChatActionContext::rtsc;
|
||||
creators["naxx chat shortcut"] = &ChatActionContext::naxx_chat_shortcut;
|
||||
creators["bwl chat shortcut"] = &ChatActionContext::bwl_chat_shortcut;
|
||||
creators["tell estimated dps"] = &ChatActionContext::tell_estimated_dps;
|
||||
creators["join"] = &ChatActionContext::join;
|
||||
@@ -298,7 +297,6 @@ private:
|
||||
static Action* guild_remove(PlayerbotAI* botAI) { return new GuildRemoveAction(botAI); }
|
||||
static Action* guild_leave(PlayerbotAI* botAI) { return new GuildLeaveAction(botAI); }
|
||||
static Action* rtsc(PlayerbotAI* botAI) { return new RTSCAction(botAI); }
|
||||
static Action* naxx_chat_shortcut(PlayerbotAI* ai) { return new NaxxChatShortcutAction(ai); }
|
||||
static Action* bwl_chat_shortcut(PlayerbotAI* ai) { return new BwlChatShortcutAction(ai); }
|
||||
static Action* tell_estimated_dps(PlayerbotAI* ai) { return new TellEstimatedDpsAction(ai); }
|
||||
static Action* join(PlayerbotAI* ai) { return new JoinGroupAction(ai); }
|
||||
|
||||
@@ -241,20 +241,6 @@ bool MaxDpsChatShortcutAction::Execute(Event event)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NaxxChatShortcutAction::Execute(Event event)
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
botAI->Reset();
|
||||
botAI->ChangeStrategy("+naxx", BOT_STATE_NON_COMBAT);
|
||||
botAI->ChangeStrategy("+naxx", BOT_STATE_COMBAT);
|
||||
botAI->TellMasterNoFacing("Add Naxx Strategies!");
|
||||
// bot->Say("Add Naxx Strategies!", LANG_UNIVERSAL);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BwlChatShortcutAction::Execute(Event event)
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
|
||||
@@ -85,13 +85,6 @@ public:
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class NaxxChatShortcutAction : public Action
|
||||
{
|
||||
public:
|
||||
NaxxChatShortcutAction(PlayerbotAI* ai) : Action(ai, "naxx chat shortcut") {}
|
||||
virtual bool Execute(Event event);
|
||||
};
|
||||
|
||||
class BwlChatShortcutAction : public Action
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -81,7 +81,7 @@ bool CheckMountStateAction::isUseful()
|
||||
// to mostly be an issue in tunnels of WSG and AV)
|
||||
float posZ = bot->GetPositionZ();
|
||||
float groundLevel = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), posZ);
|
||||
if (!bot->IsMounted() && posZ < groundLevel)
|
||||
if (!bot->IsMounted() && !bot->HasWaterWalkAura() && posZ < groundLevel)
|
||||
return false;
|
||||
|
||||
// Not useful when bot does not have mount strat and is not currently mounted
|
||||
|
||||
@@ -78,20 +78,17 @@ float ChooseRpgTargetAction::getMaxRelevance(GuidPosition guidP)
|
||||
if (!trigger->IsActive())
|
||||
continue;
|
||||
|
||||
NextAction** nextActions = triggerNode->getHandlers();
|
||||
std::vector<NextAction> nextActions = triggerNode->getHandlers();
|
||||
|
||||
bool isRpg = false;
|
||||
|
||||
for (int32 i = 0; i < NextAction::size(nextActions); i++)
|
||||
for (NextAction nextAction : nextActions)
|
||||
{
|
||||
NextAction* nextAction = nextActions[i];
|
||||
|
||||
Action* action = botAI->GetAiObjectContext()->GetAction(nextAction->getName());
|
||||
Action* action = botAI->GetAiObjectContext()->GetAction(nextAction.getName());
|
||||
|
||||
if (dynamic_cast<RpgEnabled*>(action))
|
||||
isRpg = true;
|
||||
}
|
||||
NextAction::destroy(nextActions);
|
||||
|
||||
if (isRpg)
|
||||
{
|
||||
@@ -311,7 +308,7 @@ bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldObject* target)
|
||||
bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos)
|
||||
{
|
||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||
Player* gmaster = botAI->GetGroupMaster();
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
Player* realMaster = botAI->GetMaster();
|
||||
AiObjectContext* context = botAI->GetAiObjectContext();
|
||||
|
||||
@@ -327,30 +324,30 @@ bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!gmaster || bot == gmaster)
|
||||
if (!groupLeader || bot == groupLeader)
|
||||
return true;
|
||||
|
||||
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
|
||||
return true;
|
||||
|
||||
if (bot->GetDistance(gmaster) > sPlayerbotAIConfig->rpgDistance * 2)
|
||||
if (bot->GetDistance(groupLeader) > sPlayerbotAIConfig->rpgDistance * 2)
|
||||
return false;
|
||||
|
||||
Formation* formation = AI_VALUE(Formation*, "formation");
|
||||
float distance = gmaster->GetDistance2d(pos.getX(), pos.getY());
|
||||
float distance = groupLeader->GetDistance2d(pos.getX(), pos.getY());
|
||||
|
||||
if (!botAI->HasActivePlayerMaster() && distance < 50.0f)
|
||||
{
|
||||
Player* player = gmaster;
|
||||
if (gmaster && !gmaster->isMoving() ||
|
||||
Player* player = groupLeader;
|
||||
if (groupLeader && !groupLeader->isMoving() ||
|
||||
PAI_VALUE(WorldPosition, "last long move").distance(pos) < sPlayerbotAIConfig->reactDistance)
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((inDungeon || !gmaster->HasPlayerFlag(PLAYER_FLAGS_RESTING)) && realMaster == gmaster && distance > 5.0f)
|
||||
if ((inDungeon || !groupLeader->HasPlayerFlag(PLAYER_FLAGS_RESTING)) && realMaster == groupLeader && distance > 5.0f)
|
||||
return false;
|
||||
|
||||
if (!gmaster->isMoving() && distance < 25.0f)
|
||||
if (!groupLeader->isMoving() && distance < 25.0f)
|
||||
return true;
|
||||
|
||||
if (distance < formation->GetMaxDistance())
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "LootObjectStack.h"
|
||||
#include "NewRpgStrategy.h"
|
||||
#include "Playerbots.h"
|
||||
#include "RtiTargetValue.h"
|
||||
#include "PossibleRpgTargetsValue.h"
|
||||
#include "PvpTriggers.h"
|
||||
#include "ServerFacade.h"
|
||||
@@ -87,9 +88,7 @@ bool DropTargetAction::Execute(Event event)
|
||||
{
|
||||
Spell const* spell = bot->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL); // Get the current spell being cast by the bot
|
||||
if (spell && spell->m_spellInfo->Id == 75) //Check spell is not nullptr before accessing m_spellInfo
|
||||
{
|
||||
bot->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); // Interrupt Auto Shot
|
||||
}
|
||||
}
|
||||
bot->AttackStop();
|
||||
|
||||
@@ -142,6 +141,23 @@ bool AttackRtiTargetAction::Execute(Event event)
|
||||
{
|
||||
Unit* rtiTarget = AI_VALUE(Unit*, "rti target");
|
||||
|
||||
// Fallback: if the "rti target" value did not resolve a valid unit yet,
|
||||
// try to resolve the raid icon directly from the group.
|
||||
if (!rtiTarget)
|
||||
{
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
std::string const rti = AI_VALUE(std::string, "rti");
|
||||
int32 const index = RtiTargetValue::GetRtiIndex(rti);
|
||||
if (index >= 0)
|
||||
{
|
||||
ObjectGuid const guid = group->GetTargetIcon(index);
|
||||
if (!guid.IsEmpty())
|
||||
rtiTarget = botAI->GetUnit(guid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rtiTarget && rtiTarget->IsInWorld() && rtiTarget->GetMapId() == bot->GetMapId())
|
||||
{
|
||||
botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Set({rtiTarget->GetGUID()});
|
||||
@@ -153,9 +169,7 @@ bool AttackRtiTargetAction::Execute(Event event)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
botAI->TellError("I dont see my rti attack target");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ void ChooseTravelTargetAction::getNewTarget(TravelTarget* newTarget, TravelTarge
|
||||
void ChooseTravelTargetAction::setNewTarget(TravelTarget* newTarget, TravelTarget* oldTarget)
|
||||
{
|
||||
// Tell the master where we are going.
|
||||
if (!bot->GetGroup() || (botAI->GetGroupMaster() == bot))
|
||||
if (!bot->GetGroup() || (botAI->GetGroupLeader() == bot))
|
||||
ReportTravelTarget(newTarget, oldTarget);
|
||||
|
||||
// If we are heading to a creature/npc clear it from the ignore list.
|
||||
|
||||
@@ -344,6 +344,27 @@ bool EquipUpgradesAction::Execute(Event event)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.GetSource() == "item push result")
|
||||
{
|
||||
WorldPacket p(event.getPacket());
|
||||
p.rpos(0);
|
||||
ObjectGuid playerGuid;
|
||||
uint32 received, created, sendChatMessage, itemSlot, itemId;
|
||||
uint8 bagSlot;
|
||||
|
||||
p >> playerGuid;
|
||||
p >> received;
|
||||
p >> created;
|
||||
p >> sendChatMessage;
|
||||
p >> bagSlot;
|
||||
p >> itemSlot;
|
||||
p >> itemId;
|
||||
|
||||
ItemTemplate const* item = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (item->Class == ITEM_CLASS_TRADE_GOODS && item->SubClass == ITEM_SUBCLASS_MEAT)
|
||||
return false;
|
||||
}
|
||||
|
||||
CollectItemsVisitor visitor;
|
||||
IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS);
|
||||
|
||||
|
||||
493
src/strategy/actions/FishingAction.cpp
Normal file
493
src/strategy/actions/FishingAction.cpp
Normal file
@@ -0,0 +1,493 @@
|
||||
/*
|
||||
* 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 "FishingAction.h"
|
||||
#include "FishValues.h"
|
||||
#include "Event.h"
|
||||
|
||||
#include "GridNotifiers.h"
|
||||
#include "GridNotifiersImpl.h"
|
||||
#include "ItemPackets.h"
|
||||
#include "LastMovementValue.h"
|
||||
#include "Map.h"
|
||||
#include "MovementActions.h"
|
||||
#include "Object.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "PlayerbotTextMgr.h"
|
||||
#include "Playerbots.h"
|
||||
#include "Position.h"
|
||||
|
||||
uint32 const FISHING_SPELL = 7620;
|
||||
uint32 const FISHING_POLE = 6256;
|
||||
uint32 const FISHING_BOBBER = 35591;
|
||||
float const MIN_DISTANCE_TO_WATER = 10.0f; // Minimum spell distance
|
||||
float const MAX_DISTANCE_TO_WATER = 20.0f; // Maximum spell distance
|
||||
float const HEIGHT_ABOVE_WATER_TOLERANCE = 1.0f; // Can stand in up to 1 unit of water and still fish.
|
||||
float const SEARCH_INCREMENT = 2.5f;
|
||||
float const HEIGHT_SEARCH_BUFFER = 10.0f; // Height buffer to prevent potentially missing the model the bot is standing on.
|
||||
float const SEARCH_LAND_BUFFER = 0.5f;
|
||||
uint32 const FISHING_LOCATION_TIMEOUT = 180000; //Three minutes
|
||||
|
||||
static bool IsFishingPole(Item* const item)
|
||||
{
|
||||
if (!item)
|
||||
return false;
|
||||
const ItemTemplate* proto = item->GetTemplate();
|
||||
return proto && proto->Class == ITEM_CLASS_WEAPON &&
|
||||
proto->SubClass == ITEM_SUBCLASS_WEAPON_FISHING_POLE;
|
||||
}
|
||||
|
||||
float HasFishableWaterOrLand(float x, float y, float z, Map* map, uint32 phaseMask, bool checkForLand=false)
|
||||
{
|
||||
if (!map)
|
||||
return INVALID_HEIGHT;
|
||||
|
||||
LiquidData const& liq = map->GetLiquidData(phaseMask, x, y, z+HEIGHT_ABOVE_WATER_TOLERANCE, DEFAULT_COLLISION_HEIGHT, MAP_ALL_LIQUIDS);
|
||||
float ground = map->GetHeight(phaseMask, x, y, z + HEIGHT_SEARCH_BUFFER, true);
|
||||
if (liq.Entry == MAP_LIQUID_TYPE_NO_WATER)
|
||||
{
|
||||
if (checkForLand)
|
||||
return ground;
|
||||
return INVALID_HEIGHT;
|
||||
}
|
||||
if (checkForLand)
|
||||
{
|
||||
if (ground > liq.Level - HEIGHT_ABOVE_WATER_TOLERANCE)
|
||||
return ground;
|
||||
return INVALID_HEIGHT;
|
||||
}
|
||||
|
||||
if (liq.Level + HEIGHT_ABOVE_WATER_TOLERANCE > ground)
|
||||
{
|
||||
if (abs(liq.DepthLevel) < 0.5f) // too shallow to fish in.
|
||||
return INVALID_HEIGHT;
|
||||
return liq.Level;
|
||||
}
|
||||
return INVALID_HEIGHT;
|
||||
}
|
||||
|
||||
bool HasLosToWater(Player* bot, float wx, float wy, float waterZ)
|
||||
{
|
||||
float z = bot->GetCollisionHeight() + bot->GetPositionZ();
|
||||
return bot->GetMap()->isInLineOfSight(
|
||||
bot->GetPositionX(), bot->GetPositionY(), z,
|
||||
wx, wy, waterZ,
|
||||
bot->GetPhaseMask(),
|
||||
LINEOFSIGHT_ALL_CHECKS,
|
||||
VMAP::ModelIgnoreFlags::Nothing);
|
||||
}
|
||||
|
||||
WorldPosition FindLandFromPosition(PlayerbotAI* botAI, float startDistance, float endDistance, float increment, float orientation, WorldPosition targetPos, float fishingSearchWindow, bool checkLOS = true)
|
||||
{
|
||||
Player* bot = botAI->GetBot();
|
||||
Map* map = bot->GetMap();
|
||||
uint32 phaseMask = bot->GetPhaseMask();
|
||||
Player* master = botAI->GetMaster();
|
||||
|
||||
float targetX = targetPos.GetPositionX();
|
||||
float targetY = targetPos.GetPositionY();
|
||||
float targetZ = targetPos.GetPositionZ();
|
||||
|
||||
for (float dist = startDistance; dist <= endDistance; dist += increment)
|
||||
{
|
||||
//step backwards from position to bot to find edge of shore.
|
||||
float checkX = targetX - dist * cos(orientation);
|
||||
float checkY = targetY - dist * sin(orientation);
|
||||
|
||||
float groundZ = map->GetHeight(phaseMask, checkX, checkY, targetZ + HEIGHT_SEARCH_BUFFER, true);
|
||||
|
||||
if (groundZ == INVALID_HEIGHT)
|
||||
continue;
|
||||
|
||||
LiquidData const& liq = map->GetLiquidData(phaseMask, checkX, checkY, targetZ, DEFAULT_COLLISION_HEIGHT, MAP_ALL_LIQUIDS);
|
||||
if (liq.Entry == MAP_LIQUID_TYPE_NO_WATER || groundZ > liq.DepthLevel + HEIGHT_ABOVE_WATER_TOLERANCE)
|
||||
{
|
||||
if (checkLOS)
|
||||
{
|
||||
bool hasLOS = map->isInLineOfSight(checkX, checkY, groundZ, targetX, targetY, targetZ, phaseMask, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::Nothing);
|
||||
if (!hasLOS)
|
||||
continue;
|
||||
}
|
||||
// Add a distance check for the position to prevent the bot from moving out of range to the master.
|
||||
if (master && botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT) && master->GetDistance(checkX, checkY, groundZ) > fishingSearchWindow - SEARCH_LAND_BUFFER)
|
||||
continue;
|
||||
|
||||
return WorldPosition(bot->GetMapId(), checkX, checkY, groundZ);
|
||||
}
|
||||
}
|
||||
|
||||
return WorldPosition();
|
||||
}
|
||||
|
||||
WorldPosition FindLandRadialFromPosition (PlayerbotAI* botAI, WorldPosition targetPos, float startDistance, float endDistance, float increment, float fishingSearchWindow, int angles = 16)
|
||||
{
|
||||
Player* bot = botAI->GetBot();
|
||||
const int numDirections = angles;
|
||||
std::vector<WorldPosition> boundaryPoints;
|
||||
Player* master = botAI->GetMaster();
|
||||
if (!master)
|
||||
return WorldPosition();
|
||||
|
||||
Map* map = bot->GetMap();
|
||||
uint32 phaseMask = bot->GetPhaseMask();
|
||||
|
||||
float targetX = targetPos.GetPositionX();
|
||||
float targetY = targetPos.GetPositionY();
|
||||
float targetZ = targetPos.GetPositionZ();
|
||||
|
||||
for (float dist = startDistance; dist <= endDistance; dist += increment)
|
||||
{
|
||||
for (int i = 0; i < numDirections; ++i)
|
||||
{
|
||||
float angle = (2.0f * M_PI * i) / numDirections;
|
||||
float checkX = targetX - cos(angle) * dist;
|
||||
float checkY = targetY - sin(angle) * dist;
|
||||
|
||||
float groundZ = HasFishableWaterOrLand(checkX, checkY, targetZ, map, phaseMask, true);
|
||||
|
||||
if (groundZ == INVALID_HEIGHT)
|
||||
continue;
|
||||
|
||||
if (map->isInLineOfSight(checkX, checkY, groundZ, targetX, targetY, targetZ, phaseMask, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::Nothing) && master->GetDistance(checkX, checkY, groundZ) > fishingSearchWindow - SEARCH_LAND_BUFFER)
|
||||
continue;
|
||||
|
||||
boundaryPoints.emplace_back(WorldPosition(bot->GetMapId(), checkX, checkY, groundZ));
|
||||
}
|
||||
|
||||
if (!boundaryPoints.empty())
|
||||
break;
|
||||
}
|
||||
|
||||
if (boundaryPoints.empty())
|
||||
return WorldPosition();
|
||||
|
||||
if (boundaryPoints.size() == 1)
|
||||
return boundaryPoints[0];
|
||||
|
||||
float minDistance = FLT_MAX;
|
||||
WorldLocation closestPoint = WorldPosition();
|
||||
for (auto const& pos : boundaryPoints)
|
||||
{
|
||||
float distance = bot->GetExactDist2d(&pos);
|
||||
if (distance < minDistance)
|
||||
{
|
||||
minDistance = distance;
|
||||
closestPoint = pos;
|
||||
}
|
||||
}
|
||||
return closestPoint;
|
||||
}
|
||||
|
||||
WorldPosition FindWaterRadial(Player* bot, float x, float y, float z, Map* map, uint32 phaseMask, float minDistance, float maxDistance, float increment, bool checkLOS, int numDirections)
|
||||
{
|
||||
std::vector<WorldPosition> boundaryPoints;
|
||||
|
||||
float dist = minDistance;
|
||||
while (dist <= maxDistance)
|
||||
{
|
||||
for (int i = 0; i < numDirections; ++i)
|
||||
{
|
||||
float angle = (2.0f * M_PI * i) / numDirections;
|
||||
float checkX = x + cos(angle) * dist;
|
||||
float checkY = y + sin(angle) * dist;
|
||||
|
||||
float waterZ = HasFishableWaterOrLand(checkX, checkY, z, map, phaseMask);
|
||||
|
||||
if (waterZ == INVALID_HEIGHT)
|
||||
continue;
|
||||
|
||||
if (checkLOS && !HasLosToWater(bot, checkX, checkY, waterZ))
|
||||
continue;
|
||||
|
||||
boundaryPoints.emplace_back(WorldPosition(bot->GetMapId(), checkX, checkY, waterZ));
|
||||
}
|
||||
|
||||
if (!boundaryPoints.empty())
|
||||
break;
|
||||
|
||||
dist += increment;
|
||||
}
|
||||
|
||||
if (boundaryPoints.empty())
|
||||
return WorldPosition();
|
||||
|
||||
if (boundaryPoints.size() == 1)
|
||||
return boundaryPoints[0];
|
||||
// return the central point in the identified positions in to try to be perpendicular to the shore.
|
||||
return boundaryPoints[boundaryPoints.size() / 2];
|
||||
}
|
||||
|
||||
WorldPosition FindFishingHole(PlayerbotAI* botAI)
|
||||
{
|
||||
Player* player = botAI->GetBot();
|
||||
GuidVector gos = PAI_VALUE(GuidVector, "nearest game objects no los");
|
||||
GameObject* nearestFishingHole = nullptr;
|
||||
float minDist = std::numeric_limits<float>::max();
|
||||
for (auto const& guid : gos)
|
||||
{
|
||||
GameObject* go = botAI->GetGameObject(guid);
|
||||
if (!go)
|
||||
continue;
|
||||
if (go->GetGoType() == GAMEOBJECT_TYPE_FISHINGHOLE)
|
||||
{
|
||||
float dist = player->GetDistance2d(go);
|
||||
if (dist < minDist)
|
||||
{
|
||||
minDist = dist;
|
||||
nearestFishingHole = go;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nearestFishingHole)
|
||||
return WorldPosition(nearestFishingHole->GetMapId(), nearestFishingHole->GetPositionX(), nearestFishingHole->GetPositionY(), nearestFishingHole->GetPositionZ());
|
||||
|
||||
return WorldPosition();
|
||||
}
|
||||
|
||||
bool MoveNearWaterAction::Execute(Event event)
|
||||
{
|
||||
WorldPosition landSpot = AI_VALUE(WorldPosition, "fishing spot");
|
||||
if (landSpot.IsValid())
|
||||
return MoveTo(landSpot.GetMapId(), landSpot.GetPositionX(), landSpot.GetPositionY(), landSpot.GetPositionZ());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MoveNearWaterAction::isUseful()
|
||||
{
|
||||
if (!AI_VALUE(bool, "can fish"))
|
||||
return false;
|
||||
FishingSpotValue* fishingSpotValueObject = (FishingSpotValue*)context->GetValue<WorldPosition>("fishing spot");
|
||||
WorldPosition pos = fishingSpotValueObject->Get();
|
||||
return !pos.IsValid() || fishingSpotValueObject->IsStale(FISHING_LOCATION_TIMEOUT) || pos != bot->GetPosition();
|
||||
}
|
||||
|
||||
bool MoveNearWaterAction::isPossible()
|
||||
{
|
||||
Player* master = botAI->GetMaster();
|
||||
float fishingSearchWindow;
|
||||
|
||||
if (master)
|
||||
fishingSearchWindow = sPlayerbotAIConfig->fishingDistanceFromMaster;
|
||||
else
|
||||
fishingSearchWindow = sPlayerbotAIConfig->fishingDistance;
|
||||
|
||||
WorldPosition fishingHole = FindFishingHole(botAI);
|
||||
|
||||
if (fishingHole.IsValid())
|
||||
{
|
||||
float distance = bot->GetExactDist2d(&fishingHole);
|
||||
bool hasLOS = bot->IsWithinLOS(fishingHole.GetPositionX(), fishingHole.GetPositionY(), fishingHole.GetPositionZ());
|
||||
// Water spot is in range, and we have LOS to it. Set bot position to fishing spot and do not move
|
||||
if (distance >= MIN_DISTANCE_TO_WATER &&
|
||||
distance <= MAX_DISTANCE_TO_WATER && hasLOS)
|
||||
{
|
||||
SET_AI_VALUE(WorldPosition, "fishing spot", WorldPosition(WorldPosition(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ())));
|
||||
return false;
|
||||
}
|
||||
// Water spot is out of range, lets look for a spot to move to for the fishing hole.
|
||||
if (distance > MAX_DISTANCE_TO_WATER || distance < MIN_DISTANCE_TO_WATER)
|
||||
{
|
||||
float angle = bot->GetAngle(fishingHole.GetPositionX(), fishingHole.GetPositionY());
|
||||
WorldPosition landSpot = FindLandRadialFromPosition(botAI, fishingHole, MIN_DISTANCE_TO_WATER, MAX_DISTANCE_TO_WATER, SEARCH_INCREMENT, fishingSearchWindow, 32);
|
||||
if (landSpot.IsValid())
|
||||
{
|
||||
SET_AI_VALUE(WorldPosition, "fishing spot", landSpot);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Lets find some water where we can fish.
|
||||
WorldPosition water = FindWaterRadial(
|
||||
bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||
bot->GetMap(), bot->GetPhaseMask(),
|
||||
MIN_DISTANCE_TO_WATER,
|
||||
fishingSearchWindow + MAX_DISTANCE_TO_WATER,
|
||||
SEARCH_INCREMENT, false);
|
||||
|
||||
if (!water.IsValid())
|
||||
return false;
|
||||
|
||||
bool hasLOS = bot->IsWithinLOS(water.GetPositionX(), water.GetPositionY(), water.GetPositionZ());
|
||||
float angle = bot->GetAngle(water.GetPositionX(), water.GetPositionY());
|
||||
WorldPosition landSpot =
|
||||
FindLandFromPosition(botAI, 0.0f, MAX_DISTANCE_TO_WATER, 1.0f, angle, water, fishingSearchWindow, false);
|
||||
|
||||
if (landSpot.IsValid())
|
||||
{
|
||||
SET_AI_VALUE(WorldPosition, "fishing spot", landSpot);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EquipFishingPoleAction::Execute(Event event)
|
||||
{
|
||||
if (!_pole)
|
||||
return false;
|
||||
|
||||
WorldPacket eqPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
|
||||
eqPacket << _pole->GetGUID() << uint8(EQUIPMENT_SLOT_MAINHAND);
|
||||
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(eqPacket));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EquipFishingPoleAction::isUseful()
|
||||
{
|
||||
Item* mainHand = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
|
||||
if (IsFishingPole(mainHand))
|
||||
return false;
|
||||
|
||||
for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; ++slot)
|
||||
{
|
||||
if (Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
|
||||
{
|
||||
if (IsFishingPole(item))
|
||||
{
|
||||
_pole = item;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag)
|
||||
{
|
||||
if (Bag* pBag = bot->GetBagByPos(bag))
|
||||
{
|
||||
for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
|
||||
{
|
||||
if (Item* item = pBag->GetItemByPos(j))
|
||||
{
|
||||
if (IsFishingPole(item))
|
||||
{
|
||||
_pole = item;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||
{
|
||||
bot->StoreNewItemInBestSlots(FISHING_POLE, 1); // Try to get a fishing pole
|
||||
return true;
|
||||
}
|
||||
|
||||
Player* master = botAI->GetMaster();
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
std::string masterName = master->GetName();
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"no_fishing_pole_error", "I don't have a Fishing Pole",{});
|
||||
botAI->Whisper(text, masterName);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FishingAction::Execute(Event event)
|
||||
{
|
||||
WorldPosition target = WorldPosition();
|
||||
WorldPosition fishingHole = FindFishingHole(botAI);
|
||||
if (fishingHole.IsValid())
|
||||
{
|
||||
Position pos = fishingHole;
|
||||
float distance = bot->GetExactDist2d(&pos);
|
||||
bool hasLOS = bot->IsWithinLOS(fishingHole.GetPositionX(), fishingHole.GetPositionY(), fishingHole.GetPositionZ());
|
||||
if (distance < MAX_DISTANCE_TO_WATER &&
|
||||
distance > MIN_DISTANCE_TO_WATER && hasLOS)
|
||||
target = fishingHole;
|
||||
}
|
||||
if (!target.IsValid())
|
||||
{
|
||||
target = FindWaterRadial(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||||
bot->GetPositionZ(), bot->GetMap(), bot->GetPhaseMask(),
|
||||
MIN_DISTANCE_TO_WATER, MAX_DISTANCE_TO_WATER, SEARCH_INCREMENT, true, 32);
|
||||
if (!target.IsValid())
|
||||
return false;
|
||||
}
|
||||
Position pos = target;
|
||||
|
||||
if (!bot->HasInArc(1.0, &pos, 1.0))
|
||||
{
|
||||
float angle = bot->GetAngle(pos.GetPositionX(), pos.GetPositionY());
|
||||
bot->SetOrientation(angle);
|
||||
if (!bot->IsRooted())
|
||||
bot->SendMovementFlagUpdate();
|
||||
}
|
||||
|
||||
EquipFishingPoleAction equipAction(botAI);
|
||||
if (equipAction.isUseful())
|
||||
return equipAction.Execute(event);
|
||||
|
||||
botAI->CastSpell(FISHING_SPELL, bot);
|
||||
botAI->ChangeStrategy("+use bobber", BOT_STATE_NON_COMBAT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FishingAction::isUseful()
|
||||
{
|
||||
if (!AI_VALUE(bool, "can fish"))
|
||||
return false;
|
||||
FishingSpotValue* fishingSpotValueObject = (FishingSpotValue*)context->GetValue<WorldPosition>("fishing spot");
|
||||
WorldPosition pos = fishingSpotValueObject->Get();
|
||||
|
||||
return pos.IsValid() && !fishingSpotValueObject->IsStale(FISHING_LOCATION_TIMEOUT) && pos == bot->GetPosition();
|
||||
}
|
||||
|
||||
bool UseBobberAction::isUseful()
|
||||
{
|
||||
return AI_VALUE(bool, "can use fishing bobber");
|
||||
}
|
||||
|
||||
bool UseBobberAction::Execute(Event event)
|
||||
{
|
||||
GuidVector gos = AI_VALUE(GuidVector, "nearest game objects no los");
|
||||
for (auto const& guid : gos)
|
||||
{
|
||||
if (GameObject* go = botAI->GetGameObject(guid))
|
||||
{
|
||||
if (go->GetEntry() != FISHING_BOBBER)
|
||||
continue;
|
||||
if (go->GetOwnerGUID() != bot->GetGUID())
|
||||
continue;
|
||||
if (go->getLootState() == GO_READY)
|
||||
{
|
||||
go->Use(bot);
|
||||
botAI->ChangeStrategy("-use bobber", BOT_STATE_NON_COMBAT);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EndMasterFishingAction::Execute(Event event)
|
||||
{
|
||||
botAI->ChangeStrategy("-master fishing", BOT_STATE_NON_COMBAT);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EndMasterFishingAction::isUseful()
|
||||
{
|
||||
FishingSpotValue* fishingSpotValueObject = (FishingSpotValue*)context->GetValue<WorldPosition>("fishing spot");
|
||||
WorldPosition pos = fishingSpotValueObject->Get();
|
||||
if (pos.IsValid() && !fishingSpotValueObject->IsStale(FISHING_LOCATION_TIMEOUT) && pos == bot->GetPosition())
|
||||
return false;
|
||||
|
||||
WorldPosition nearWater = FindWaterRadial(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||
bot->GetMap(), bot->GetPhaseMask(), MIN_DISTANCE_TO_WATER, sPlayerbotAIConfig->endFishingWithMaster, 10.0f);
|
||||
return !nearWater.IsValid();
|
||||
}
|
||||
|
||||
bool RemoveBobberStrategyAction::Execute(Event event)
|
||||
{
|
||||
botAI->ChangeStrategy("-use bobber", BOT_STATE_NON_COMBAT);
|
||||
return true;
|
||||
}
|
||||
71
src/strategy/actions/FishingAction.h
Normal file
71
src/strategy/actions/FishingAction.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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_FISHINGACTION_H
|
||||
#define _PLAYERBOT_FISHINGACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "MovementActions.h"
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
extern const uint32 FISHING_SPELL;
|
||||
extern const uint32 FISHING_POLE;
|
||||
extern const uint32 FISHING_BOBBER;
|
||||
|
||||
WorldPosition FindWaterRadial(Player* bot, float x, float y, float z, Map* map, uint32 phaseMask, float minDistance, float maxDistance, float increment, bool checkLOS=false, int numDirections = 16);
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class FishingAction : public Action
|
||||
{
|
||||
public:
|
||||
FishingAction(PlayerbotAI* botAI) : Action(botAI, "go fishing"){}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class EquipFishingPoleAction : public Action
|
||||
{
|
||||
public:
|
||||
EquipFishingPoleAction(PlayerbotAI* botAI) : Action(botAI, "equip fishing pole") {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
private:
|
||||
Item* _pole = nullptr;
|
||||
};
|
||||
|
||||
class MoveNearWaterAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
MoveNearWaterAction(PlayerbotAI* botAI): MovementAction(botAI, "move near water") {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
bool isPossible() override;
|
||||
};
|
||||
|
||||
class UseBobberAction : public Action
|
||||
{
|
||||
public:
|
||||
UseBobberAction(PlayerbotAI* botAI) : Action(botAI, "use fishing bobber") {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class EndMasterFishingAction : public Action
|
||||
{
|
||||
public:
|
||||
EndMasterFishingAction(PlayerbotAI* botAI) : Action(botAI, "end master fishing") {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class RemoveBobberStrategyAction : public Action
|
||||
{
|
||||
public:
|
||||
RemoveBobberStrategyAction(PlayerbotAI* botAI) : Action(botAI, "remove bobber strategy") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
#endif
|
||||
@@ -70,7 +70,7 @@ bool FollowAction::isUseful()
|
||||
if (!target.empty())
|
||||
fTarget = AI_VALUE(Unit*, target);
|
||||
else
|
||||
fTarget = AI_VALUE(Unit*, "master target");
|
||||
fTarget = AI_VALUE(Unit*, "group leader");
|
||||
|
||||
if (fTarget)
|
||||
{
|
||||
@@ -97,6 +97,8 @@ bool FollowAction::isUseful()
|
||||
|
||||
distance = bot->GetDistance(loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ());
|
||||
}
|
||||
if (botAI->HasStrategy("master fishing", BOT_STATE_NON_COMBAT))
|
||||
return sServerFacade->IsDistanceGreaterThan(distance, sPlayerbotAIConfig->fishingDistanceFromMaster);
|
||||
|
||||
return sServerFacade->IsDistanceGreaterThan(distance, formation->GetMaxDistance());
|
||||
}
|
||||
@@ -114,9 +116,9 @@ bool FollowAction::CanDeadFollow(Unit* target)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FleeToMasterAction::Execute(Event event)
|
||||
bool FleeToGroupLeaderAction::Execute(Event event)
|
||||
{
|
||||
Unit* fTarget = AI_VALUE(Unit*, "master target");
|
||||
Unit* fTarget = AI_VALUE(Unit*, "group leader");
|
||||
bool canFollow = Follow(fTarget);
|
||||
if (!canFollow)
|
||||
{
|
||||
@@ -146,22 +148,22 @@ bool FleeToMasterAction::Execute(Event event)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FleeToMasterAction::isUseful()
|
||||
bool FleeToGroupLeaderAction::isUseful()
|
||||
{
|
||||
if (!botAI->GetGroupMaster())
|
||||
if (!botAI->GetGroupLeader())
|
||||
return false;
|
||||
|
||||
if (botAI->GetGroupMaster() == bot)
|
||||
if (botAI->GetGroupLeader() == bot)
|
||||
return false;
|
||||
|
||||
Unit* target = AI_VALUE(Unit*, "current target");
|
||||
if (target && botAI->GetGroupMaster()->GetTarget() == target->GetGUID())
|
||||
if (target && botAI->GetGroupLeader()->GetTarget() == target->GetGUID())
|
||||
return false;
|
||||
|
||||
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
|
||||
return false;
|
||||
|
||||
Unit* fTarget = AI_VALUE(Unit*, "master target");
|
||||
Unit* fTarget = AI_VALUE(Unit*, "group leader");
|
||||
|
||||
if (!CanDeadFollow(fTarget))
|
||||
return false;
|
||||
|
||||
@@ -20,10 +20,10 @@ public:
|
||||
bool CanDeadFollow(Unit* target);
|
||||
};
|
||||
|
||||
class FleeToMasterAction : public FollowAction
|
||||
class FleeToGroupLeaderAction : public FollowAction
|
||||
{
|
||||
public:
|
||||
FleeToMasterAction(PlayerbotAI* botAI) : FollowAction(botAI, "flee to master") {}
|
||||
FleeToGroupLeaderAction(PlayerbotAI* botAI) : FollowAction(botAI, "flee to group leader") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
|
||||
@@ -265,11 +265,6 @@ CastShootAction::CastShootAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "s
|
||||
}
|
||||
}
|
||||
|
||||
NextAction** CastSpellAction::getPrerequisites()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Value<Unit*>* CastDebuffSpellOnAttackerAction::GetTargetValue()
|
||||
{
|
||||
return context->GetValue<Unit*>("attacker without aura", spell);
|
||||
|
||||
@@ -27,7 +27,11 @@ public:
|
||||
bool isUseful() override;
|
||||
ActionThreatType getThreatType() override { return ActionThreatType::Single; }
|
||||
|
||||
NextAction** getPrerequisites() override;
|
||||
std::vector<NextAction> getPrerequisites() override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string const getSpell() { return spell; }
|
||||
|
||||
protected:
|
||||
@@ -193,10 +197,12 @@ public:
|
||||
ResurrectPartyMemberAction(PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) {}
|
||||
|
||||
std::string const GetTargetName() override { return "party member to resurrect"; }
|
||||
NextAction** getPrerequisites() override
|
||||
std::vector<NextAction> getPrerequisites() override
|
||||
{
|
||||
return NextAction::merge(NextAction::array(0, new NextAction("reach party member to resurrect"), NULL),
|
||||
Action::getPrerequisites());
|
||||
return NextAction::merge(
|
||||
{ NextAction("reach party member to resurrect") },
|
||||
Action::getPrerequisites()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -58,6 +58,14 @@ Player* GuidManageAction::GetPlayer(Event event)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void GuidManageAction::SendPacket(WorldPacket const& packet)
|
||||
{
|
||||
// make a heap copy because QueuePacket takes ownership
|
||||
WorldPacket* data = new WorldPacket(packet);
|
||||
|
||||
bot->GetSession()->QueuePacket(data);
|
||||
}
|
||||
|
||||
bool GuidManageAction::Execute(Event event)
|
||||
{
|
||||
Player* player = GetPlayer(event);
|
||||
@@ -84,12 +92,6 @@ bool GuildInviteAction::isUseful()
|
||||
return bot->GetGuildId() && sGuildMgr->GetGuildById(bot->GetGuildId())->HasRankRight(bot, GR_RIGHT_INVITE);
|
||||
}
|
||||
|
||||
void GuildInviteAction::SendPacket(WorldPacket packet)
|
||||
{
|
||||
WorldPackets::Guild::GuildInviteByName data = WorldPacket(packet);
|
||||
bot->GetSession()->HandleGuildInviteOpcode(data);
|
||||
}
|
||||
|
||||
bool GuildInviteAction::PlayerIsValid(Player* member)
|
||||
{
|
||||
return !member->GetGuildId() && (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD) ||
|
||||
@@ -101,12 +103,6 @@ bool GuildPromoteAction::isUseful()
|
||||
return bot->GetGuildId() && sGuildMgr->GetGuildById(bot->GetGuildId())->HasRankRight(bot, GR_RIGHT_PROMOTE);
|
||||
}
|
||||
|
||||
void GuildPromoteAction::SendPacket(WorldPacket packet)
|
||||
{
|
||||
WorldPackets::Guild::GuildPromoteMember data = WorldPacket(packet);
|
||||
bot->GetSession()->HandleGuildPromoteOpcode(data);
|
||||
}
|
||||
|
||||
bool GuildPromoteAction::PlayerIsValid(Player* member)
|
||||
{
|
||||
return member->GetGuildId() == bot->GetGuildId() && GetRankId(bot) < GetRankId(member) - 1;
|
||||
@@ -117,12 +113,6 @@ bool GuildDemoteAction::isUseful()
|
||||
return bot->GetGuildId() && sGuildMgr->GetGuildById(bot->GetGuildId())->HasRankRight(bot, GR_RIGHT_DEMOTE);
|
||||
}
|
||||
|
||||
void GuildDemoteAction::SendPacket(WorldPacket packet)
|
||||
{
|
||||
WorldPackets::Guild::GuildDemoteMember data = WorldPacket(packet);
|
||||
bot->GetSession()->HandleGuildDemoteOpcode(data);
|
||||
}
|
||||
|
||||
bool GuildDemoteAction::PlayerIsValid(Player* member)
|
||||
{
|
||||
return member->GetGuildId() == bot->GetGuildId() && GetRankId(bot) < GetRankId(member);
|
||||
@@ -133,12 +123,6 @@ bool GuildRemoveAction::isUseful()
|
||||
return bot->GetGuildId() && sGuildMgr->GetGuildById(bot->GetGuildId())->HasRankRight(bot, GR_RIGHT_REMOVE);
|
||||
}
|
||||
|
||||
void GuildRemoveAction::SendPacket(WorldPacket packet)
|
||||
{
|
||||
WorldPackets::Guild::GuildOfficerRemoveMember data = WorldPacket(packet);
|
||||
bot->GetSession()->HandleGuildRemoveOpcode(data);
|
||||
}
|
||||
|
||||
bool GuildRemoveAction::PlayerIsValid(Player* member)
|
||||
{
|
||||
return member->GetGuildId() == bot->GetGuildId() && GetRankId(bot) < GetRankId(member);
|
||||
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
bool isUseful() override { return false; }
|
||||
|
||||
protected:
|
||||
virtual void SendPacket(WorldPacket data){};
|
||||
virtual void SendPacket(WorldPacket const& packet);
|
||||
virtual Player* GetPlayer(Event event);
|
||||
virtual bool PlayerIsValid(Player* member);
|
||||
virtual uint8 GetRankId(Player* member);
|
||||
@@ -44,7 +44,6 @@ public:
|
||||
bool isUseful() override;
|
||||
|
||||
protected:
|
||||
void SendPacket(WorldPacket data) override;
|
||||
bool PlayerIsValid(Player* member) override;
|
||||
};
|
||||
|
||||
@@ -59,7 +58,6 @@ public:
|
||||
bool isUseful() override;
|
||||
|
||||
protected:
|
||||
void SendPacket(WorldPacket data) override;
|
||||
bool PlayerIsValid(Player* member) override;
|
||||
};
|
||||
|
||||
@@ -74,7 +72,6 @@ public:
|
||||
bool isUseful() override;
|
||||
|
||||
protected:
|
||||
void SendPacket(WorldPacket data) override;
|
||||
bool PlayerIsValid(Player* member) override;
|
||||
};
|
||||
|
||||
@@ -89,7 +86,6 @@ public:
|
||||
bool isUseful() override;
|
||||
|
||||
protected:
|
||||
void SendPacket(WorldPacket data) override;
|
||||
bool PlayerIsValid(Player* member) override;
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
#include "Event.h"
|
||||
#include "GuildMgr.h"
|
||||
#include "Log.h"
|
||||
#include "PlayerbotOperations.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PlayerbotWorldThreadProcessor.h"
|
||||
#include "ServerFacade.h"
|
||||
|
||||
bool InviteToGroupAction::Invite(Player* inviter, Player* player)
|
||||
@@ -27,7 +29,10 @@ bool InviteToGroupAction::Invite(Player* inviter, Player* player)
|
||||
{
|
||||
if (GET_PLAYERBOT_AI(player) && !GET_PLAYERBOT_AI(player)->IsRealPlayer())
|
||||
if (!group->isRaidGroup() && group->GetMembersCount() > 4)
|
||||
group->ConvertToRaid();
|
||||
{
|
||||
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(inviter->GetGUID());
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp));
|
||||
}
|
||||
}
|
||||
|
||||
WorldPacket p;
|
||||
@@ -89,7 +94,10 @@ bool InviteNearbyToGroupAction::Execute(Event event)
|
||||
// When inviting the 5th member of the group convert to raid for future invites.
|
||||
if (group && botAI->GetGrouperType() > GrouperType::LEADER_5 && !group->isRaidGroup() &&
|
||||
bot->GetGroup()->GetMembersCount() > 3)
|
||||
group->ConvertToRaid();
|
||||
{
|
||||
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(bot->GetGUID());
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp));
|
||||
}
|
||||
|
||||
if (sPlayerbotAIConfig->inviteChat && sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||
{
|
||||
@@ -133,7 +141,7 @@ bool InviteNearbyToGroupAction::isUseful()
|
||||
if (group->isRaidGroup() && group->IsFull())
|
||||
return false;
|
||||
|
||||
if (botAI->GetGroupMaster() != bot)
|
||||
if (botAI->GetGroupLeader() != bot)
|
||||
return false;
|
||||
|
||||
uint32 memberCount = group->GetMembersCount();
|
||||
@@ -221,7 +229,8 @@ bool InviteGuildToGroupAction::Execute(Event event)
|
||||
if (group && botAI->GetGrouperType() > GrouperType::LEADER_5 && !group->isRaidGroup() &&
|
||||
bot->GetGroup()->GetMembersCount() > 3)
|
||||
{
|
||||
group->ConvertToRaid();
|
||||
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(bot->GetGUID());
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp));
|
||||
}
|
||||
|
||||
if (sPlayerbotAIConfig->inviteChat &&
|
||||
@@ -362,7 +371,10 @@ bool LfgAction::Execute(Event event)
|
||||
if (param.empty() || param == "5" || group->isRaidGroup())
|
||||
return false; // Group or raid is full so stop trying.
|
||||
else
|
||||
group->ConvertToRaid(); // We want a raid but are in a group so convert and continue.
|
||||
{
|
||||
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(requester->GetGUID());
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp));
|
||||
}
|
||||
}
|
||||
|
||||
Group::MemberSlotList const& groupSlot = group->GetMemberSlots();
|
||||
|
||||
@@ -109,22 +109,22 @@ bool LeaveFarAwayAction::isUseful()
|
||||
if (!bot->GetGroup())
|
||||
return false;
|
||||
|
||||
Player* master = botAI->GetGroupMaster();
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
Player* trueMaster = botAI->GetMaster();
|
||||
if (!master || (bot == master && !botAI->IsRealPlayer()))
|
||||
if (!groupLeader || (bot == groupLeader && !botAI->IsRealPlayer()))
|
||||
return false;
|
||||
|
||||
PlayerbotAI* masterBotAI = nullptr;
|
||||
if (master)
|
||||
masterBotAI = GET_PLAYERBOT_AI(master);
|
||||
if (master && !masterBotAI)
|
||||
PlayerbotAI* groupLeaderBotAI = nullptr;
|
||||
if (groupLeader)
|
||||
groupLeaderBotAI = GET_PLAYERBOT_AI(groupLeader);
|
||||
if (groupLeader && !groupLeaderBotAI)
|
||||
return false;
|
||||
|
||||
if (trueMaster && !GET_PLAYERBOT_AI(trueMaster))
|
||||
return false;
|
||||
|
||||
if (botAI->IsAlt() &&
|
||||
(!masterBotAI || masterBotAI->IsRealPlayer())) // Don't leave group when alt grouped with player master.
|
||||
(!groupLeaderBotAI || groupLeaderBotAI->IsRealPlayer())) // Don't leave group when alt grouped with player groupLeader.
|
||||
return false;
|
||||
|
||||
if (botAI->GetGrouperType() == GrouperType::SOLO)
|
||||
@@ -138,19 +138,19 @@ bool LeaveFarAwayAction::isUseful()
|
||||
if (dCount > 4 && !botAI->HasRealPlayerMaster())
|
||||
return true;
|
||||
|
||||
if (bot->GetGuildId() == master->GetGuildId())
|
||||
if (bot->GetGuildId() == groupLeader->GetGuildId())
|
||||
{
|
||||
if (bot->GetLevel() > master->GetLevel() + 5)
|
||||
if (bot->GetLevel() > groupLeader->GetLevel() + 5)
|
||||
{
|
||||
if (AI_VALUE(bool, "should get money"))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (abs(int32(master->GetLevel() - bot->GetLevel())) > 4)
|
||||
if (abs(int32(groupLeader->GetLevel() - bot->GetLevel())) > 4)
|
||||
return true;
|
||||
|
||||
if (bot->GetMapId() != master->GetMapId() || bot->GetDistance2d(master) >= 2 * sPlayerbotAIConfig->rpgDistance)
|
||||
if (bot->GetMapId() != groupLeader->GetMapId() || bot->GetDistance2d(groupLeader) >= 2 * sPlayerbotAIConfig->rpgDistance)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -7,90 +7,43 @@
|
||||
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PlayerbotSpellCache.h"
|
||||
|
||||
std::map<uint32, SkillLineAbilityEntry const*> ListSpellsAction::skillSpells;
|
||||
std::set<uint32> ListSpellsAction::vendorItems;
|
||||
using SpellListEntry = std::pair<uint32, std::string>;
|
||||
|
||||
bool CompareSpells(const std::pair<uint32, std::string>& s1, const std::pair<uint32, std::string>& s2)
|
||||
// CHANGE: Simplified and cheap comparator used in MapUpdater worker thread.
|
||||
// It now avoids scanning the entire SkillLineAbilityStore for each comparison
|
||||
// and only relies on spell school and spell name to keep sorting fast and bounded.
|
||||
// lhs = the left element, rhs = the right element.
|
||||
static bool CompareSpells(SpellListEntry const& lhSpell, SpellListEntry const& rhSpell)
|
||||
{
|
||||
SpellInfo const* si1 = sSpellMgr->GetSpellInfo(s1.first);
|
||||
SpellInfo const* si2 = sSpellMgr->GetSpellInfo(s2.first);
|
||||
if (!si1 || !si2)
|
||||
SpellInfo const* lhSpellInfo = sSpellMgr->GetSpellInfo(lhSpell.first);
|
||||
SpellInfo const* rhSpellInfo = sSpellMgr->GetSpellInfo(rhSpell.first);
|
||||
|
||||
if (!lhSpellInfo || !rhSpellInfo)
|
||||
{
|
||||
LOG_ERROR("playerbots", "SpellInfo missing. {} {}", s1.first, s2.first);
|
||||
return false;
|
||||
}
|
||||
uint32 p1 = si1->SchoolMask * 20000;
|
||||
uint32 p2 = si2->SchoolMask * 20000;
|
||||
|
||||
uint32 skill1 = 0, skill2 = 0;
|
||||
uint32 skillValue1 = 0, skillValue2 = 0;
|
||||
for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j)
|
||||
{
|
||||
if (SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j))
|
||||
{
|
||||
if (skillLine->Spell == s1.first)
|
||||
{
|
||||
skill1 = skillLine->SkillLine;
|
||||
skillValue1 = skillLine->TrivialSkillLineRankLow;
|
||||
}
|
||||
|
||||
if (skillLine->Spell == s2.first)
|
||||
{
|
||||
skill2 = skillLine->SkillLine;
|
||||
skillValue2 = skillLine->TrivialSkillLineRankLow;
|
||||
}
|
||||
}
|
||||
|
||||
if (skill1 && skill2)
|
||||
break;
|
||||
LOG_ERROR("playerbots", "SpellInfo missing for spell {} or {}", lhSpell.first, rhSpell.first);
|
||||
// Fallback: order by spell id to keep comparator strict and deterministic.
|
||||
return lhSpell.first < rhSpell.first;
|
||||
}
|
||||
|
||||
p1 += skill1 * 500;
|
||||
p2 += skill2 * 500;
|
||||
uint32 lhsKey = lhSpellInfo->SchoolMask;
|
||||
uint32 rhsKey = rhSpellInfo->SchoolMask;
|
||||
|
||||
p1 += skillValue1;
|
||||
p2 += skillValue2;
|
||||
|
||||
if (p1 == p2)
|
||||
if (lhsKey == rhsKey)
|
||||
{
|
||||
return strcmp(si1->SpellName[0], si2->SpellName[0]) > 0;
|
||||
}
|
||||
// Defensive check: if DBC data is broken and spell names are nullptr,
|
||||
// fall back to id ordering instead of risking a crash in std::strcmp.
|
||||
if (!lhSpellInfo->SpellName[0] || !rhSpellInfo->SpellName[0])
|
||||
return lhSpell.first < rhSpell.first;
|
||||
|
||||
return p1 > p2;
|
||||
return std::strcmp(lhSpellInfo->SpellName[0], rhSpellInfo->SpellName[0]) > 0;
|
||||
}
|
||||
return lhsKey > rhsKey;
|
||||
}
|
||||
|
||||
std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::string filter)
|
||||
{
|
||||
if (skillSpells.empty())
|
||||
{
|
||||
for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j)
|
||||
{
|
||||
if (SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j))
|
||||
skillSpells[skillLine->Spell] = skillLine;
|
||||
}
|
||||
}
|
||||
|
||||
if (vendorItems.empty())
|
||||
{
|
||||
QueryResult results = WorldDatabase.Query("SELECT item FROM npc_vendor WHERE maxcount = 0");
|
||||
if (results)
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = results->Fetch();
|
||||
int32 entry = fields[0].Get<int32>();
|
||||
if (entry <= 0)
|
||||
continue;
|
||||
|
||||
vendorItems.insert(entry);
|
||||
} while (results->NextRow());
|
||||
}
|
||||
}
|
||||
|
||||
std::ostringstream posOut;
|
||||
std::ostringstream negOut;
|
||||
|
||||
uint32 skill = 0;
|
||||
|
||||
std::vector<std::string> ss = split(filter, ' ');
|
||||
@@ -99,13 +52,15 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
skill = chat->parseSkill(ss[0]);
|
||||
if (skill != SKILL_NONE)
|
||||
{
|
||||
filter = ss.size() > 1 ? filter = ss[1] : "";
|
||||
filter = ss.size() > 1 ? ss[1] : "";
|
||||
}
|
||||
|
||||
if (ss[0] == "first" && ss[1] == "aid")
|
||||
// Guard access to ss[1]/ss[2] to avoid out-of-bounds
|
||||
// when the player only types "first" without "aid".
|
||||
if (ss[0] == "first" && ss.size() > 1 && ss[1] == "aid")
|
||||
{
|
||||
skill = SKILL_FIRST_AID;
|
||||
filter = ss.size() > 2 ? filter = ss[2] : "";
|
||||
filter = ss.size() > 2 ? ss[2] : "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,26 +70,57 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
|
||||
uint32 minLevel = 0;
|
||||
uint32 maxLevel = 0;
|
||||
if (filter.find("-") != std::string::npos)
|
||||
if (filter.find('-') != std::string::npos)
|
||||
{
|
||||
std::vector<std::string> ff = split(filter, '-');
|
||||
minLevel = atoi(ff[0].c_str());
|
||||
maxLevel = atoi(ff[1].c_str());
|
||||
filter = "";
|
||||
if (ff.size() >= 2)
|
||||
{
|
||||
minLevel = std::atoi(ff[0].c_str());
|
||||
maxLevel = std::atoi(ff[1].c_str());
|
||||
if (minLevel > maxLevel)
|
||||
std::swap(minLevel, maxLevel);
|
||||
}
|
||||
filter.clear();
|
||||
}
|
||||
|
||||
bool craftableOnly = false;
|
||||
if (filter.find("+") != std::string::npos)
|
||||
bool canCraftNow = false;
|
||||
if (filter.find('+') != std::string::npos)
|
||||
{
|
||||
craftableOnly = true;
|
||||
canCraftNow = true;
|
||||
|
||||
// Support "+<skill>" syntax (e.g. "spells +tailoring" or "spells tailoring+").
|
||||
// If no explicit skill was detected yet, try to parse the filter (without '+')
|
||||
// as a profession/skill name so that craftable-only filters still work with skills.
|
||||
if (skill == SKILL_NONE)
|
||||
{
|
||||
std::string skillFilter = filter;
|
||||
|
||||
// Remove '+' before trying to interpret the first token as a skill name.
|
||||
skillFilter.erase(remove(skillFilter.begin(), skillFilter.end(), '+'), skillFilter.end());
|
||||
|
||||
std::vector<std::string> skillTokens = split(skillFilter, ' ');
|
||||
if (!skillTokens.empty())
|
||||
{
|
||||
uint32 parsedSkill = chat->parseSkill(skillTokens[0]);
|
||||
if (parsedSkill != SKILL_NONE)
|
||||
{
|
||||
skill = parsedSkill;
|
||||
|
||||
// Any remaining text after the skill token becomes the "name" filter
|
||||
// (e.g. "spells +tailoring cloth" -> skill = tailoring, filter = "cloth").
|
||||
filter = skillTokens.size() > 1 ? skillTokens[1] : "";
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finally remove '+' from the filter that will be used for name/range parsing.
|
||||
filter.erase(remove(filter.begin(), filter.end(), '+'), filter.end());
|
||||
}
|
||||
|
||||
uint32 slot = chat->parseSlot(filter);
|
||||
if (slot != EQUIPMENT_SLOT_END)
|
||||
filter = "";
|
||||
filter.clear();
|
||||
|
||||
std::vector<std::pair<uint32, std::string>> spells;
|
||||
std::vector<SpellListEntry> spells;
|
||||
for (PlayerSpellMap::iterator itr = bot->GetSpellMap().begin(); itr != bot->GetSpellMap().end(); ++itr)
|
||||
{
|
||||
if (itr->second->State == PLAYERSPELL_REMOVED || !itr->second->Active)
|
||||
@@ -150,7 +136,7 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
if (spellInfo->IsPassive())
|
||||
continue;
|
||||
|
||||
SkillLineAbilityEntry const* skillLine = skillSpells[itr->first];
|
||||
SkillLineAbilityEntry const* skillLine = sPlayerbotSpellCache->GetSkillLine(itr->first);
|
||||
if (skill != SKILL_NONE && (!skillLine || skillLine->SkillLine != skill))
|
||||
continue;
|
||||
|
||||
@@ -162,7 +148,7 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
continue;
|
||||
|
||||
bool first = true;
|
||||
int32 craftCount = -1;
|
||||
int32 craftsPossible = -1;
|
||||
std::ostringstream materials;
|
||||
for (uint32 x = 0; x < MAX_SPELL_REAGENTS; ++x)
|
||||
{
|
||||
@@ -189,12 +175,12 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
|
||||
FindItemByIdVisitor visitor(itemid);
|
||||
uint32 reagentsInInventory = InventoryAction::GetItemCount(&visitor);
|
||||
bool buyable = (vendorItems.find(itemid) != vendorItems.end());
|
||||
bool buyable = sPlayerbotSpellCache->IsItemBuyable(itemid);
|
||||
if (!buyable)
|
||||
{
|
||||
uint32 craftable = reagentsInInventory / reagentsRequired;
|
||||
if (craftCount < 0 || craftCount > craftable)
|
||||
craftCount = craftable;
|
||||
if (craftsPossible < 0 || craftsPossible > static_cast<int32>(craftable))
|
||||
craftsPossible = static_cast<int32>(craftable);
|
||||
}
|
||||
|
||||
if (reagentsInInventory)
|
||||
@@ -205,8 +191,8 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
}
|
||||
}
|
||||
|
||||
if (craftCount < 0)
|
||||
craftCount = 0;
|
||||
if (craftsPossible < 0)
|
||||
craftsPossible = 0;
|
||||
|
||||
std::ostringstream out;
|
||||
bool filtered = false;
|
||||
@@ -218,8 +204,8 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
{
|
||||
if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(spellInfo->Effects[i].ItemType))
|
||||
{
|
||||
if (craftCount)
|
||||
out << "|cffffff00(x" << craftCount << ")|r ";
|
||||
if (craftsPossible)
|
||||
out << "|cffffff00(x" << craftsPossible << ")|r ";
|
||||
|
||||
out << chat->FormatItem(proto);
|
||||
|
||||
@@ -246,7 +232,7 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
if (filtered)
|
||||
continue;
|
||||
|
||||
if (craftableOnly && !craftCount)
|
||||
if (canCraftNow && !craftsPossible)
|
||||
continue;
|
||||
|
||||
out << materials.str();
|
||||
@@ -275,10 +261,9 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
continue;
|
||||
|
||||
if (itr->first == 0)
|
||||
{
|
||||
LOG_ERROR("playerbots", "?! {}", itr->first);
|
||||
}
|
||||
spells.push_back(std::pair<uint32, std::string>(itr->first, out.str()));
|
||||
|
||||
spells.emplace_back(itr->first, out.str());
|
||||
alreadySeenList += spellInfo->SpellName[0];
|
||||
alreadySeenList += ",";
|
||||
}
|
||||
@@ -294,25 +279,28 @@ bool ListSpellsAction::Execute(Event event)
|
||||
|
||||
std::string const filter = event.getParam();
|
||||
|
||||
std::vector<std::pair<uint32, std::string>> spells = GetSpellList(filter);
|
||||
std::vector<SpellListEntry> spells = GetSpellList(filter);
|
||||
|
||||
if (spells.empty())
|
||||
{
|
||||
// CHANGE: Give early feedback when no spells match the filter.
|
||||
botAI->TellMaster("No spells found.");
|
||||
return true;
|
||||
}
|
||||
|
||||
botAI->TellMaster("=== Spells ===");
|
||||
|
||||
std::sort(spells.begin(), spells.end(), CompareSpells);
|
||||
|
||||
uint32 count = 0;
|
||||
for (std::vector<std::pair<uint32, std::string>>::iterator i = spells.begin(); i != spells.end(); ++i)
|
||||
{
|
||||
// CHANGE: Send the full spell list again so client-side addons
|
||||
// (e.g. Multibot / Unbot) can reconstruct the
|
||||
// complete spellbook for configuration. The heavy part that caused
|
||||
// freezes before was the old CompareSpells implementation scanning
|
||||
// the entire SkillLineAbility DBC on every comparison. With the new
|
||||
// cheap comparator above, sending all lines here is safe and keeps
|
||||
// behaviour compatible with existing addons.
|
||||
for (std::vector<SpellListEntry>::const_iterator i = spells.begin(); i != spells.end(); ++i)
|
||||
botAI->TellMasterNoFacing(i->second);
|
||||
|
||||
// if (++count >= 50)
|
||||
// {
|
||||
// std::ostringstream msg;
|
||||
// msg << (spells.size() - 50) << " more...";
|
||||
// botAI->TellMasterNoFacing(msg.str());
|
||||
// break;
|
||||
// }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,8 @@ public:
|
||||
bool Execute(Event event) override;
|
||||
virtual std::vector<std::pair<uint32, std::string>> GetSpellList(std::string filter = "");
|
||||
|
||||
private:
|
||||
static std::map<uint32, SkillLineAbilityEntry const*> skillSpells;
|
||||
static std::set<uint32> vendorItems;
|
||||
static void InitSpellCaches();
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -18,7 +18,7 @@ bool MoveToTravelTargetAction::Execute(Event event)
|
||||
WorldLocation location = *target->getPosition();
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
if (group && !urand(0, 1) && bot == botAI->GetGroupMaster() && !bot->IsInCombat())
|
||||
if (group && !urand(0, 1) && bot == botAI->GetGroupLeader() && !bot->IsInCombat())
|
||||
{
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
|
||||
#include "Corpse.h"
|
||||
#include "Event.h"
|
||||
#include "FleeManager.h"
|
||||
#include "G3D/Vector3.h"
|
||||
@@ -41,7 +42,6 @@
|
||||
#include "Unit.h"
|
||||
#include "Vehicle.h"
|
||||
#include "WaypointMovementGenerator.h"
|
||||
#include "Corpse.h"
|
||||
|
||||
MovementAction::MovementAction(PlayerbotAI* botAI, std::string const name) : Action(botAI, name)
|
||||
{
|
||||
@@ -192,14 +192,15 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool generatePath = !bot->IsFlying() && !bot->isSwimming();
|
||||
bool disableMoveSplinePath = sPlayerbotAIConfig->disableMoveSplinePath >= 2 ||
|
||||
(sPlayerbotAIConfig->disableMoveSplinePath == 1 && bot->InBattleground());
|
||||
bool disableMoveSplinePath =
|
||||
sPlayerbotAIConfig->disableMoveSplinePath >= 2 ||
|
||||
(sPlayerbotAIConfig->disableMoveSplinePath == 1 && bot->InBattleground());
|
||||
if (Vehicle* vehicle = bot->GetVehicle())
|
||||
{
|
||||
VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(bot);
|
||||
Unit* vehicleBase = vehicle->GetBase();
|
||||
// If the mover (vehicle) can fly, we DO NOT want an mmaps path (2D ground) => disable pathfinding
|
||||
generatePath = !vehicleBase || !vehicleBase->CanFly();
|
||||
if (!vehicleBase || !seat || !seat->CanControl()) // is passenger and cant move anyway
|
||||
return false;
|
||||
@@ -207,22 +208,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
||||
float distance = vehicleBase->GetExactDist(x, y, z); // use vehicle distance, not bot
|
||||
if (distance > 0.01f)
|
||||
{
|
||||
MotionMaster& mm = *vehicleBase->GetMotionMaster(); // need to move vehicle, not bot
|
||||
// Disable ground pathing if the bot/master/vehicle are flying
|
||||
auto isFlying = [](Unit* u){ return u && (u->HasUnitMovementFlag(MOVEMENTFLAG_FLYING) || u->IsInFlight()); };
|
||||
bool allowPathVeh = generatePath;
|
||||
Unit* masterVeh = botAI ? botAI->GetMaster() : nullptr;
|
||||
if (isFlying(vehicleBase) || isFlying(bot) || isFlying(masterVeh))
|
||||
allowPathVeh = false;
|
||||
mm.Clear();
|
||||
if (!backwards)
|
||||
{
|
||||
mm.MovePoint(0, x, y, z, FORCED_MOVEMENT_NONE, 0.0f, 0.0f, allowPathVeh);
|
||||
}
|
||||
else
|
||||
{
|
||||
mm.MovePointBackwards(0, x, y, z, allowPathVeh);
|
||||
}
|
||||
DoMovePoint(vehicleBase, x, y, z, generatePath, backwards);
|
||||
float speed = backwards ? vehicleBase->GetSpeed(MOVE_RUN_BACK) : vehicleBase->GetSpeed(MOVE_RUN);
|
||||
float delay = 1000.0f * (distance / speed);
|
||||
if (lessDelay)
|
||||
@@ -248,23 +234,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
||||
// bot->CastStop();
|
||||
// botAI->InterruptSpell();
|
||||
// }
|
||||
|
||||
MotionMaster& mm = *bot->GetMotionMaster();
|
||||
// No ground pathfinding if the bot/master are flying => allow true 3D (Z) movement
|
||||
auto isFlying = [](Unit* u){ return u && (u->HasUnitMovementFlag(MOVEMENTFLAG_FLYING) || u->IsInFlight()); };
|
||||
bool allowPath = generatePath;
|
||||
Unit* master = botAI ? botAI->GetMaster() : nullptr;
|
||||
if (isFlying(bot) || isFlying(master))
|
||||
allowPath = false;
|
||||
mm.Clear();
|
||||
if (!backwards)
|
||||
{
|
||||
mm.MovePoint(0, x, y, z, FORCED_MOVEMENT_NONE, 0.0f, 0.0f, allowPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
mm.MovePointBackwards(0, x, y, z, allowPath);
|
||||
}
|
||||
DoMovePoint(bot, x, y, z, generatePath, backwards);
|
||||
float delay = 1000.0f * MoveDelay(distance, backwards);
|
||||
if (lessDelay)
|
||||
{
|
||||
@@ -282,9 +252,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
||||
Movement::PointsArray path =
|
||||
SearchForBestPath(x, y, z, modifiedZ, sPlayerbotAIConfig->maxMovementSearchTime, normal_only);
|
||||
if (modifiedZ == INVALID_HEIGHT)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
float distance = bot->GetExactDist(x, y, modifiedZ);
|
||||
if (distance > 0.01f)
|
||||
{
|
||||
@@ -296,24 +264,8 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
||||
// bot->CastStop();
|
||||
// botAI->InterruptSpell();
|
||||
// }
|
||||
|
||||
MotionMaster& mm = *bot->GetMotionMaster();
|
||||
G3D::Vector3 endP = path.back();
|
||||
// No ground pathfinding if the bot/master are flying => allow true 3D (Z) movement
|
||||
auto isFlying = [](Unit* u){ return u && (u->HasUnitMovementFlag(MOVEMENTFLAG_FLYING) || u->IsInFlight()); };
|
||||
bool allowPath = generatePath;
|
||||
Unit* master = botAI ? botAI->GetMaster() : nullptr;
|
||||
if (isFlying(bot) || isFlying(master))
|
||||
allowPath = false;
|
||||
mm.Clear();
|
||||
if (!backwards)
|
||||
{
|
||||
mm.MovePoint(0, x, y, z, FORCED_MOVEMENT_NONE, 0.0f, 0.0f, allowPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
mm.MovePointBackwards(0, x, y, z, allowPath);
|
||||
}
|
||||
DoMovePoint(bot, x, y, z, generatePath, backwards);
|
||||
float delay = 1000.0f * MoveDelay(distance, backwards);
|
||||
if (lessDelay)
|
||||
{
|
||||
@@ -581,9 +533,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
||||
// bool goTaxi = bot->ActivateTaxiPathTo({ tEntry->from, tEntry->to }, unit, 1);
|
||||
|
||||
// if (botAI->HasCheat(BotCheatMask::gold))
|
||||
// {
|
||||
// bot->SetMoney(botMoney);
|
||||
// }
|
||||
// LOG_DEBUG("playerbots", "goTaxi");
|
||||
// return goTaxi;
|
||||
// }
|
||||
@@ -874,7 +824,7 @@ bool MovementAction::ReachCombatTo(Unit* target, float distance)
|
||||
|
||||
float deltaAngle = Position::NormalizeOrientation(targetOrientation - target->GetAngle(bot));
|
||||
if (deltaAngle > M_PI)
|
||||
deltaAngle -= 2.0f * M_PI; // -PI..PI
|
||||
deltaAngle -= 2.0f * M_PI; // -PI..PI
|
||||
// if target is moving forward and moving far away, predict the position
|
||||
bool behind = fabs(deltaAngle) > M_PI_2;
|
||||
if (target->HasUnitMovementFlag(MOVEMENTFLAG_FORWARD) && behind)
|
||||
@@ -882,8 +832,8 @@ bool MovementAction::ReachCombatTo(Unit* target, float distance)
|
||||
float predictDis = std::min(3.0f, target->GetObjectSize() * 2);
|
||||
tx += cos(target->GetOrientation()) * predictDis;
|
||||
ty += sin(target->GetOrientation()) * predictDis;
|
||||
if (!target->GetMap()->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(),
|
||||
tx, ty, tz))
|
||||
if (!target->GetMap()->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(),
|
||||
target->GetPositionZ(), tx, ty, tz))
|
||||
{
|
||||
tx = target->GetPositionX();
|
||||
ty = target->GetPositionY();
|
||||
@@ -996,104 +946,91 @@ bool MovementAction::IsWaitingForLastMove(MovementPriority priority)
|
||||
|
||||
bool MovementAction::IsMovingAllowed()
|
||||
{
|
||||
// do not allow if not vehicle driver
|
||||
if (botAI->IsInVehicle() && !botAI->IsInVehicle(true))
|
||||
return false;
|
||||
|
||||
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
|
||||
bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() ||
|
||||
bot->HasConfuseAura() || bot->IsCharmed() || bot->HasStunAura() ||
|
||||
bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
||||
return false;
|
||||
|
||||
if (bot->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// if (bot->HasUnitMovementFlag(MOVEMENTFLAG_FALLING))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
return bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FLIGHT_MOTION_TYPE;
|
||||
return botAI->CanMove();
|
||||
}
|
||||
|
||||
bool MovementAction::Follow(Unit* target, float distance) { return Follow(target, distance, GetFollowAngle()); }
|
||||
|
||||
void MovementAction::UpdateMovementState()
|
||||
{
|
||||
int8 botInLiquidState = bot->GetLiquidData().Status;
|
||||
const bool isCurrentlyRestricted = // see if the bot is currently slowed, rooted, or otherwise unable to move
|
||||
bot->HasUnitState(UNIT_STATE_LOST_CONTROL) ||
|
||||
bot->IsRooted() ||
|
||||
bot->isFrozen() ||
|
||||
bot->IsPolymorphed();
|
||||
|
||||
if (botInLiquidState == LIQUID_MAP_IN_WATER || botInLiquidState == LIQUID_MAP_UNDER_WATER)
|
||||
// no update movement flags while movement is current restricted.
|
||||
if (!isCurrentlyRestricted && bot->IsAlive())
|
||||
{
|
||||
bot->SetSwim(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
bot->SetSwim(false);
|
||||
}
|
||||
// state flags
|
||||
const auto master = botAI ? botAI->GetMaster() : nullptr; // real player or not
|
||||
const bool masterIsFlying = master ? master->HasUnitMovementFlag(MOVEMENTFLAG_FLYING) : true;
|
||||
const bool masterIsSwimming = master ? master->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) : true;
|
||||
const auto liquidState = bot->GetLiquidData().Status; // default LIQUID_MAP_NO_WATER
|
||||
const float gZ = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
|
||||
const bool wantsToFly = bot->HasIncreaseMountedFlightSpeedAura() || bot->HasFlyAura();
|
||||
const bool isFlying = bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING);
|
||||
const bool isWaterArea = liquidState != LIQUID_MAP_NO_WATER;
|
||||
const bool isUnderWater = liquidState == LIQUID_MAP_UNDER_WATER;
|
||||
const bool isInWater = liquidState == LIQUID_MAP_IN_WATER;
|
||||
const bool isWaterWalking = bot->HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
|
||||
const bool isSwimming = bot->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
|
||||
const bool wantsToWaterWalk = bot->HasWaterWalkAura();
|
||||
const bool wantsToSwim = isInWater || isUnderWater;
|
||||
const bool onGroundZ = (bot->GetPositionZ() < gZ + 1.f) && !isWaterArea;
|
||||
bool movementFlagsUpdated = false;
|
||||
|
||||
bool onGround = bot->GetPositionZ() <
|
||||
bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ()) + 1.0f;
|
||||
// handle water state
|
||||
if (isWaterArea && !isFlying)
|
||||
{
|
||||
// water walking
|
||||
if (wantsToWaterWalk && !isWaterWalking && !masterIsSwimming)
|
||||
{
|
||||
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
|
||||
bot->AddUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
|
||||
movementFlagsUpdated = true;
|
||||
}
|
||||
// swimming
|
||||
else if (wantsToSwim && !isSwimming && masterIsSwimming)
|
||||
{
|
||||
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
|
||||
bot->AddUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
|
||||
movementFlagsUpdated = true;
|
||||
}
|
||||
}
|
||||
else if (isSwimming || isWaterWalking)
|
||||
{
|
||||
// reset water flags
|
||||
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
|
||||
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
|
||||
movementFlagsUpdated = true;
|
||||
}
|
||||
|
||||
// Keep bot->SendMovementFlagUpdate() withing the if statements to not intefere with bot behavior on ground/(shallow) waters
|
||||
|
||||
bool hasFlightAura = bot->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) || bot->HasAuraType(SPELL_AURA_FLY);
|
||||
if (hasFlightAura)
|
||||
{
|
||||
bool changed = false;
|
||||
if (!bot->HasUnitMovementFlag(MOVEMENTFLAG_CAN_FLY))
|
||||
// handle flying state
|
||||
if (wantsToFly && !isFlying && masterIsFlying)
|
||||
{
|
||||
bot->AddUnitMovementFlag(MOVEMENTFLAG_CAN_FLY);
|
||||
changed = true;
|
||||
}
|
||||
if (!bot->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY))
|
||||
{
|
||||
bot->AddUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
|
||||
changed = true;
|
||||
}
|
||||
if (!bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING))
|
||||
{
|
||||
bot->AddUnitMovementFlag(MOVEMENTFLAG_FLYING);
|
||||
changed = true;
|
||||
movementFlagsUpdated = true;
|
||||
}
|
||||
if (changed)
|
||||
bot->SendMovementFlagUpdate();
|
||||
}
|
||||
else if (!hasFlightAura)
|
||||
{
|
||||
bool changed = false;
|
||||
if (bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING))
|
||||
{
|
||||
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_FLYING);
|
||||
changed = true;
|
||||
}
|
||||
if (bot->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY))
|
||||
{
|
||||
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
|
||||
changed = true;
|
||||
}
|
||||
if (bot->HasUnitMovementFlag(MOVEMENTFLAG_CAN_FLY))
|
||||
else if ((!wantsToFly || onGroundZ) && isFlying)
|
||||
{
|
||||
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_CAN_FLY);
|
||||
changed = true;
|
||||
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
|
||||
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_FLYING);
|
||||
movementFlagsUpdated = true;
|
||||
}
|
||||
if (changed)
|
||||
|
||||
// detect if movement restrictions have been lifted, CC just ended.
|
||||
if (wasMovementRestricted)
|
||||
movementFlagsUpdated = true; // refresh movement state to ensure animations play correctly
|
||||
|
||||
if (movementFlagsUpdated)
|
||||
bot->SendMovementFlagUpdate();
|
||||
}
|
||||
|
||||
// See if the bot is currently slowed, rooted, or otherwise unable to move
|
||||
bool isCurrentlyRestricted = bot->isFrozen() || bot->IsPolymorphed() || bot->HasRootAura() || bot->HasStunAura() ||
|
||||
bot->HasConfuseAura() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL);
|
||||
|
||||
// Detect if movement restrictions have been lifted
|
||||
if (wasMovementRestricted && !isCurrentlyRestricted && bot->IsAlive())
|
||||
{
|
||||
// CC just ended - refresh movement state to ensure animations play correctly
|
||||
bot->SendMovementFlagUpdate();
|
||||
}
|
||||
|
||||
// Save current state for the next check
|
||||
// Save current state for the next check
|
||||
wasMovementRestricted = isCurrentlyRestricted;
|
||||
|
||||
// Temporary speed increase in group
|
||||
@@ -1145,7 +1082,7 @@ void MovementAction::UpdateMovementState()
|
||||
// {
|
||||
// if (Unit* pTarget = sServerFacade->GetChaseTarget(bot))
|
||||
// {
|
||||
// if (pTarget != botAI->GetGroupMaster())
|
||||
// if (pTarget != botAI->GetGroupLeader())
|
||||
// return;
|
||||
|
||||
// if (!bot->IsWithinMeleeRange(pTarget))
|
||||
@@ -1180,6 +1117,13 @@ bool MovementAction::Follow(Unit* target, float distance, float angle)
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
if (!bot->InBattleground() && sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target),
|
||||
sPlayerbotAIConfig->followDistance))
|
||||
{
|
||||
// botAI->TellError("No need to follow");
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
if (!bot->InBattleground()
|
||||
&& sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target->GetPositionX(),
|
||||
@@ -1297,17 +1241,21 @@ bool MovementAction::Follow(Unit* target, float distance, float angle)
|
||||
return MoveTo(target, sPlayerbotAIConfig->followDistance);
|
||||
}
|
||||
|
||||
if (sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target),
|
||||
sPlayerbotAIConfig->followDistance))
|
||||
{
|
||||
// botAI->TellError("No need to follow");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target->IsFriendlyTo(bot) && bot->IsMounted() && AI_VALUE(GuidVector, "all targets").empty())
|
||||
distance += angle;
|
||||
|
||||
// Do not cancel follow if the 2D distance is short but the Z still differs (e.g., master above).
|
||||
float dz1 = fabs(bot->GetPositionZ() - target->GetPositionZ());
|
||||
if (!bot->InBattleground()
|
||||
&& sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target), sPlayerbotAIConfig->followDistance)
|
||||
&& dz1 < sPlayerbotAIConfig->contactDistance)
|
||||
if (!bot->InBattleground() && sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target),
|
||||
sPlayerbotAIConfig->followDistance))
|
||||
{
|
||||
// botAI->TellError("No need to follow");
|
||||
return false; // truly in range (2D and Z) => no need to move
|
||||
return false;
|
||||
}
|
||||
|
||||
bot->HandleEmoteCommand(0);
|
||||
@@ -1388,7 +1336,7 @@ float MovementAction::MoveDelay(float distance, bool backwards)
|
||||
}
|
||||
else
|
||||
{
|
||||
speed = backwards ? bot->GetSpeed(MOVE_RUN_BACK) :bot->GetSpeed(MOVE_RUN);
|
||||
speed = backwards ? bot->GetSpeed(MOVE_RUN_BACK) : bot->GetSpeed(MOVE_RUN);
|
||||
}
|
||||
float delay = distance / speed;
|
||||
return delay;
|
||||
@@ -1418,8 +1366,7 @@ void MovementAction::SetNextMovementDelay(float delayMillis)
|
||||
{
|
||||
AI_VALUE(LastMovement&, "last movement")
|
||||
.Set(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetOrientation(),
|
||||
delayMillis,
|
||||
MovementPriority::MOVEMENT_FORCED);
|
||||
delayMillis, MovementPriority::MOVEMENT_FORCED);
|
||||
}
|
||||
|
||||
bool MovementAction::Flee(Unit* target)
|
||||
@@ -1633,7 +1580,8 @@ bool MovementAction::MoveAway(Unit* target, float distance, bool backwards)
|
||||
dz = bot->GetPositionZ();
|
||||
exact = false;
|
||||
}
|
||||
if (MoveTo(target->GetMapId(), dx, dy, dz, false, false, true, exact, MovementPriority::MOVEMENT_COMBAT, false, backwards))
|
||||
if (MoveTo(target->GetMapId(), dx, dy, dz, false, false, true, exact, MovementPriority::MOVEMENT_COMBAT, false,
|
||||
backwards))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -1655,7 +1603,8 @@ bool MovementAction::MoveAway(Unit* target, float distance, bool backwards)
|
||||
dz = bot->GetPositionZ();
|
||||
exact = false;
|
||||
}
|
||||
if (MoveTo(target->GetMapId(), dx, dy, dz, false, false, true, exact, MovementPriority::MOVEMENT_COMBAT, false, backwards))
|
||||
if (MoveTo(target->GetMapId(), dx, dy, dz, false, false, true, exact, MovementPriority::MOVEMENT_COMBAT, false,
|
||||
backwards))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -1704,7 +1653,7 @@ bool MovementAction::Move(float angle, float distance)
|
||||
float x = bot->GetPositionX() + cos(angle) * distance;
|
||||
float y = bot->GetPositionY() + sin(angle) * distance;
|
||||
|
||||
//TODO do we need GetMapWaterOrGroundLevel() if we're using CheckCollisionAndGetValidCoords() ?
|
||||
// TODO do we need GetMapWaterOrGroundLevel() if we're using CheckCollisionAndGetValidCoords() ?
|
||||
float z = bot->GetMapWaterOrGroundLevel(x, y, bot->GetPositionZ());
|
||||
if (z == -100000.0f || z == -200000.0f)
|
||||
z = bot->GetPositionZ();
|
||||
@@ -1758,9 +1707,7 @@ bool MovementAction::MoveInside(uint32 mapId, float x, float y, float z, float d
|
||||
// min_length = gen.getPathLength();
|
||||
// current_z = modified_z;
|
||||
// if (abs(current_z - z) < 0.5f)
|
||||
// {
|
||||
// return current_z;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// for (delta = range / 2 + step; delta <= range; delta += 2) {
|
||||
@@ -1857,6 +1804,46 @@ const Movement::PointsArray MovementAction::SearchForBestPath(float x, float y,
|
||||
return result;
|
||||
}
|
||||
|
||||
void MovementAction::DoMovePoint(Unit* unit, float x, float y, float z, bool generatePath, bool backwards)
|
||||
{
|
||||
if (!unit)
|
||||
return;
|
||||
|
||||
MotionMaster* mm = unit->GetMotionMaster();
|
||||
if (!mm)
|
||||
return;
|
||||
|
||||
// enable water walking
|
||||
if (unit->HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING))
|
||||
{
|
||||
float gZ = unit->GetMapWaterOrGroundLevel(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ());
|
||||
unit->UpdatePosition(unit->GetPositionX(), unit->GetPositionY(), gZ, false);
|
||||
// z = gZ; no overwrite Z axe otherwise you cant steer the bots into swimming when water walking.
|
||||
}
|
||||
|
||||
mm->Clear();
|
||||
if (backwards)
|
||||
{
|
||||
mm->MovePointBackwards(
|
||||
/*id*/ 0,
|
||||
/*coords*/ x, y, z,
|
||||
/*generatePath*/ generatePath,
|
||||
/*forceDestination*/ false);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
mm->MovePoint(
|
||||
/*id*/ 0,
|
||||
/*coords*/ x, y, z,
|
||||
/*forcedMovement*/ FORCED_MOVEMENT_NONE,
|
||||
/*speed*/ 0.f,
|
||||
/*orientation*/ 0.f,
|
||||
/*generatePath*/ generatePath, // true => terrain path (2d mmap); false => straight spline (3d vmap)
|
||||
/*forceDestination*/ false);
|
||||
}
|
||||
}
|
||||
|
||||
bool FleeAction::Execute(Event event)
|
||||
{
|
||||
return MoveAway(AI_VALUE(Unit*, "current target"), sPlayerbotAIConfig->fleeDistance, true);
|
||||
@@ -1937,7 +1924,8 @@ bool AvoidAoeAction::AvoidAuraWithDynamicObj()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(spellInfo->Id) != sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
|
||||
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(spellInfo->Id) !=
|
||||
sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
|
||||
return false;
|
||||
|
||||
DynamicObject* dynOwner = aura->GetDynobjOwner();
|
||||
@@ -2002,7 +1990,8 @@ bool AvoidAoeAction::AvoidGameObjectWithDamage()
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(spellId) != sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
|
||||
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(spellId) !=
|
||||
sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
|
||||
continue;
|
||||
|
||||
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
@@ -2028,7 +2017,8 @@ bool AvoidAoeAction::AvoidGameObjectWithDamage()
|
||||
lastTellTimer = time(NULL);
|
||||
lastMoveTimer = getMSTime();
|
||||
std::ostringstream out;
|
||||
out << "I'm avoiding " << name.str() << " (" << spellInfo->Id << ")" << " Radius " << radius << " - [Trap]";
|
||||
out << "I'm avoiding " << name.str() << " (" << spellInfo->Id << ")" << " Radius " << radius
|
||||
<< " - [Trap]";
|
||||
bot->Say(out.str(), LANG_UNIVERSAL);
|
||||
}
|
||||
return true;
|
||||
@@ -2071,7 +2061,8 @@ bool AvoidAoeAction::AvoidUnitWithDamageAura()
|
||||
sSpellMgr->GetSpellInfo(spellInfo->Effects[aurEff->GetEffIndex()].TriggerSpell);
|
||||
if (!triggerSpellInfo)
|
||||
continue;
|
||||
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(triggerSpellInfo->Id) != sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
|
||||
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(triggerSpellInfo->Id) !=
|
||||
sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
|
||||
return false;
|
||||
for (int j = 0; j < MAX_SPELL_EFFECTS; j++)
|
||||
{
|
||||
@@ -2093,7 +2084,8 @@ bool AvoidAoeAction::AvoidUnitWithDamageAura()
|
||||
lastTellTimer = time(NULL);
|
||||
lastMoveTimer = getMSTime();
|
||||
std::ostringstream out;
|
||||
out << "I'm avoiding " << name.str() << " (" << triggerSpellInfo->Id << ")" << " Radius " << radius << " - [Unit Trigger]";
|
||||
out << "I'm avoiding " << name.str() << " (" << triggerSpellInfo->Id << ")"
|
||||
<< " Radius " << radius << " - [Unit Trigger]";
|
||||
bot->Say(out.str(), LANG_UNIVERSAL);
|
||||
}
|
||||
}
|
||||
@@ -2112,7 +2104,8 @@ Position MovementAction::BestPositionForMeleeToFlee(Position pos, float radius)
|
||||
if (currentTarget)
|
||||
{
|
||||
// Normally, move to left or right is the best position
|
||||
bool isTanking = (!currentTarget->isFrozen() && !currentTarget->HasRootAura()) && (currentTarget->GetVictim() == bot);
|
||||
bool isTanking = (!currentTarget->isFrozen()
|
||||
&& !currentTarget->HasRootAura()) && (currentTarget->GetVictim() == bot);
|
||||
float angle = bot->GetAngle(currentTarget);
|
||||
float angleLeft = angle + (float)M_PI / 2;
|
||||
float angleRight = angle - (float)M_PI / 2;
|
||||
@@ -2327,8 +2320,7 @@ bool CombatFormationMoveAction::isUseful()
|
||||
bool CombatFormationMoveAction::Execute(Event event)
|
||||
{
|
||||
float dis = AI_VALUE(float, "disperse distance");
|
||||
if (dis <= 0.0f ||
|
||||
(!bot->IsInCombat() && botAI->HasStrategy("stay", BotState::BOT_STATE_NON_COMBAT)) ||
|
||||
if (dis <= 0.0f || (!bot->IsInCombat() && botAI->HasStrategy("stay", BotState::BOT_STATE_NON_COMBAT)) ||
|
||||
(bot->IsInCombat() && botAI->HasStrategy("stay", BotState::BOT_STATE_COMBAT)))
|
||||
return false;
|
||||
Player* playerToLeave = NearestGroupMember(dis);
|
||||
@@ -2478,7 +2470,7 @@ bool TankFaceAction::Execute(Event event)
|
||||
|
||||
float deltaAngle = Position::NormalizeOrientation(averageAngle - target->GetAngle(bot));
|
||||
if (deltaAngle > M_PI)
|
||||
deltaAngle -= 2.0f * M_PI; // -PI..PI
|
||||
deltaAngle -= 2.0f * M_PI; // -PI..PI
|
||||
|
||||
float tolerable = M_PI_2;
|
||||
|
||||
@@ -2489,12 +2481,13 @@ bool TankFaceAction::Execute(Event event)
|
||||
float goodAngle2 = Position::NormalizeOrientation(averageAngle - M_PI * 3 / 5);
|
||||
|
||||
// if dist < bot->GetMeleeRange(target) / 2, target will move backward
|
||||
float dist = std::max(bot->GetExactDist(target), bot->GetMeleeRange(target) / 2) - bot->GetCombatReach() - target->GetCombatReach();
|
||||
float dist = std::max(bot->GetExactDist(target), bot->GetMeleeRange(target) / 2) - bot->GetCombatReach() -
|
||||
target->GetCombatReach();
|
||||
std::vector<Position> availablePos;
|
||||
float x, y, z;
|
||||
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle1);
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||
x, y, z))
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||||
bot->GetPositionZ(), x, y, z))
|
||||
{
|
||||
/// @todo: movement control now is a mess, prepare to rewrite
|
||||
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
||||
@@ -2506,8 +2499,8 @@ bool TankFaceAction::Execute(Event event)
|
||||
}
|
||||
}
|
||||
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle2);
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||
x, y, z))
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||||
bot->GetPositionZ(), x, y, z))
|
||||
{
|
||||
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
||||
Position pos(x, y, z);
|
||||
@@ -2520,13 +2513,15 @@ bool TankFaceAction::Execute(Event event)
|
||||
if (availablePos.empty())
|
||||
return false;
|
||||
Position nearest = GetNearestPosition(availablePos);
|
||||
return MoveTo(bot->GetMapId(), nearest.GetPositionX(), nearest.GetPositionY(), nearest.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT);
|
||||
return MoveTo(bot->GetMapId(), nearest.GetPositionX(), nearest.GetPositionY(), nearest.GetPositionZ(), false, false,
|
||||
false, true, MovementPriority::MOVEMENT_COMBAT);
|
||||
}
|
||||
|
||||
bool RearFlankAction::isUseful()
|
||||
{
|
||||
Unit* target = AI_VALUE(Unit*, "current target");
|
||||
if (!target) { return false; }
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
// Need to double the front angle check to account for mirrored angle.
|
||||
bool inFront = target->HasInArc(2.f * minAngle, bot);
|
||||
@@ -2540,7 +2535,8 @@ bool RearFlankAction::isUseful()
|
||||
bool RearFlankAction::Execute(Event event)
|
||||
{
|
||||
Unit* target = AI_VALUE(Unit*, "current target");
|
||||
if (!target) { return false; }
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
float angle = frand(minAngle, maxAngle);
|
||||
float baseDistance = bot->GetMeleeRange(target) * 0.5f;
|
||||
@@ -2559,8 +2555,8 @@ bool RearFlankAction::Execute(Event event)
|
||||
destination = &rightFlank;
|
||||
}
|
||||
|
||||
return MoveTo(bot->GetMapId(), destination->GetPositionX(), destination->GetPositionY(), destination->GetPositionZ(),
|
||||
false, false, false, true, MovementPriority::MOVEMENT_COMBAT);
|
||||
return MoveTo(bot->GetMapId(), destination->GetPositionX(), destination->GetPositionY(),
|
||||
destination->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT);
|
||||
}
|
||||
|
||||
bool DisperseSetAction::Execute(Event event)
|
||||
@@ -2647,7 +2643,7 @@ bool DisperseSetAction::Execute(Event event)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RunAwayAction::Execute(Event event) { return Flee(AI_VALUE(Unit*, "master target")); }
|
||||
bool RunAwayAction::Execute(Event event) { return Flee(AI_VALUE(Unit*, "group leader")); }
|
||||
|
||||
bool MoveToLootAction::Execute(Event event)
|
||||
{
|
||||
@@ -2688,9 +2684,8 @@ bool SetFacingTargetAction::isUseful() { return !AI_VALUE2(bool, "facing", "curr
|
||||
bool SetFacingTargetAction::isPossible()
|
||||
{
|
||||
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
|
||||
bot->IsBeingTeleported() || bot->HasConfuseAura() || bot->IsCharmed() ||
|
||||
bot->HasStunAura() || bot->IsInFlight() ||
|
||||
bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
||||
bot->IsBeingTeleported() || bot->HasConfuseAura() || bot->IsCharmed() || bot->HasStunAura() ||
|
||||
bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
@@ -2710,7 +2705,7 @@ bool SetBehindTargetAction::Execute(Event event)
|
||||
|
||||
float deltaAngle = Position::NormalizeOrientation(target->GetOrientation() - target->GetAngle(bot));
|
||||
if (deltaAngle > M_PI)
|
||||
deltaAngle -= 2.0f * M_PI; // -PI..PI
|
||||
deltaAngle -= 2.0f * M_PI; // -PI..PI
|
||||
|
||||
float tolerable = M_PI_2;
|
||||
|
||||
@@ -2720,12 +2715,13 @@ bool SetBehindTargetAction::Execute(Event event)
|
||||
float goodAngle1 = Position::NormalizeOrientation(target->GetOrientation() + M_PI * 3 / 5);
|
||||
float goodAngle2 = Position::NormalizeOrientation(target->GetOrientation() - M_PI * 3 / 5);
|
||||
|
||||
float dist = std::max(bot->GetExactDist(target), bot->GetMeleeRange(target) / 2) - bot->GetCombatReach() - target->GetCombatReach();
|
||||
float dist = std::max(bot->GetExactDist(target), bot->GetMeleeRange(target) / 2) - bot->GetCombatReach() -
|
||||
target->GetCombatReach();
|
||||
std::vector<Position> availablePos;
|
||||
float x, y, z;
|
||||
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle1);
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||
x, y, z))
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||||
bot->GetPositionZ(), x, y, z))
|
||||
{
|
||||
/// @todo: movement control now is a mess, prepare to rewrite
|
||||
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
||||
@@ -2737,8 +2733,8 @@ bool SetBehindTargetAction::Execute(Event event)
|
||||
}
|
||||
}
|
||||
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle2);
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||
x, y, z))
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||||
bot->GetPositionZ(), x, y, z))
|
||||
{
|
||||
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
||||
Position pos(x, y, z);
|
||||
@@ -2751,7 +2747,8 @@ bool SetBehindTargetAction::Execute(Event event)
|
||||
if (availablePos.empty())
|
||||
return false;
|
||||
Position nearest = GetNearestPosition(availablePos);
|
||||
return MoveTo(bot->GetMapId(), nearest.GetPositionX(), nearest.GetPositionY(), nearest.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT);
|
||||
return MoveTo(bot->GetMapId(), nearest.GetPositionX(), nearest.GetPositionY(), nearest.GetPositionZ(), false, false,
|
||||
false, true, MovementPriority::MOVEMENT_COMBAT);
|
||||
}
|
||||
|
||||
bool MoveOutOfCollisionAction::Execute(Event event)
|
||||
@@ -2822,7 +2819,7 @@ bool MoveFromGroupAction::Execute(Event event)
|
||||
{
|
||||
float distance = atoi(event.getParam().c_str());
|
||||
if (!distance)
|
||||
distance = 20.0f; // flee distance from config is too small for this
|
||||
distance = 20.0f; // flee distance from config is too small for this
|
||||
return MoveFromGroup(distance);
|
||||
}
|
||||
|
||||
@@ -2905,10 +2902,7 @@ bool MoveAwayFromCreatureAction::Execute(Event event)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MoveAwayFromCreatureAction::isPossible()
|
||||
{
|
||||
return bot->CanFreeMove();
|
||||
}
|
||||
bool MoveAwayFromCreatureAction::isPossible() { return bot->CanFreeMove(); }
|
||||
|
||||
bool MoveAwayFromPlayerWithDebuffAction::Execute(Event event)
|
||||
{
|
||||
@@ -2995,7 +2989,4 @@ bool MoveAwayFromPlayerWithDebuffAction::Execute(Event event)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MoveAwayFromPlayerWithDebuffAction::isPossible()
|
||||
{
|
||||
return bot->CanFreeMove();
|
||||
}
|
||||
bool MoveAwayFromPlayerWithDebuffAction::isPossible() { return bot->CanFreeMove(); }
|
||||
|
||||
@@ -29,12 +29,17 @@ public:
|
||||
|
||||
protected:
|
||||
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, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
||||
bool MoveNear(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig->contactDistance,
|
||||
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
||||
bool MoveToLOS(WorldObject* target, bool ranged = false);
|
||||
bool MoveTo(uint32 mapId, float x, float y, float z, bool idle = false, bool react = false,
|
||||
bool normal_only = false, bool exact_waypoint = false, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL, bool lessDelay = false, bool backwards = false);
|
||||
bool MoveTo(WorldObject* target, float distance = 0.0f, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
||||
bool MoveNear(WorldObject* target, float distance = sPlayerbotAIConfig->contactDistance, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
||||
bool normal_only = false, bool exact_waypoint = false,
|
||||
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL, bool lessDelay = false,
|
||||
bool backwards = false);
|
||||
bool MoveTo(WorldObject* target, float distance = 0.0f,
|
||||
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
||||
bool MoveNear(WorldObject* target, float distance = sPlayerbotAIConfig->contactDistance,
|
||||
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
||||
float GetFollowAngle();
|
||||
bool Follow(Unit* target, float distance = sPlayerbotAIConfig->followDistance);
|
||||
bool Follow(Unit* target, float distance, float angle);
|
||||
@@ -51,10 +56,11 @@ protected:
|
||||
bool Flee(Unit* target);
|
||||
void ClearIdleState();
|
||||
void UpdateMovementState();
|
||||
bool MoveAway(Unit* target, float distance = sPlayerbotAIConfig -> fleeDistance, bool backwards = false);
|
||||
bool MoveAway(Unit* target, float distance = sPlayerbotAIConfig->fleeDistance, bool backwards = false);
|
||||
bool MoveFromGroup(float distance);
|
||||
bool Move(float angle, float distance);
|
||||
bool MoveInside(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig->followDistance, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
||||
bool MoveInside(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig->followDistance,
|
||||
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
||||
void CreateWp(Player* wpOwner, float x, float y, float z, float o, uint32 entry, bool important = false);
|
||||
Position BestPositionForMeleeToFlee(Position pos, float radius);
|
||||
Position BestPositionForRangedToFlee(Position pos, float radius);
|
||||
@@ -74,6 +80,7 @@ private:
|
||||
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;
|
||||
void DoMovePoint(Unit* unit, float x, float y, float z, bool generatePath, bool backwards);
|
||||
};
|
||||
|
||||
class FleeAction : public MovementAction
|
||||
@@ -149,17 +156,18 @@ public:
|
||||
|
||||
class RearFlankAction : public MovementAction
|
||||
{
|
||||
// 90 degree minimum angle prevents any frontal cleaves/breaths and avoids parry-hasting the boss.
|
||||
// 120 degree maximum angle leaves a 120 degree symmetrical cone at the tail end which is usually enough to avoid tail swipes.
|
||||
// Some dragons or mobs may have different danger zone angles, override if needed.
|
||||
// 90 degree minimum angle prevents any frontal cleaves/breaths and avoids parry-hasting the boss.
|
||||
// 120 degree maximum angle leaves a 120 degree symmetrical cone at the tail end which is usually enough to avoid
|
||||
// tail swipes. Some dragons or mobs may have different danger zone angles, override if needed.
|
||||
public:
|
||||
RearFlankAction(PlayerbotAI* botAI, float distance = 0.0f, float minAngle = ANGLE_90_DEG, float maxAngle = ANGLE_120_DEG)
|
||||
RearFlankAction(PlayerbotAI* botAI, float distance = 0.0f, float minAngle = ANGLE_90_DEG,
|
||||
float maxAngle = ANGLE_120_DEG)
|
||||
: MovementAction(botAI, "rear flank")
|
||||
{
|
||||
this->distance = distance;
|
||||
this->minAngle = minAngle;
|
||||
this->maxAngle = maxAngle;
|
||||
}
|
||||
{
|
||||
this->distance = distance;
|
||||
this->minAngle = minAngle;
|
||||
this->maxAngle = maxAngle;
|
||||
}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
@@ -297,7 +305,9 @@ class MoveAwayFromCreatureAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
MoveAwayFromCreatureAction(PlayerbotAI* botAI, std::string name, uint32 creatureId, float range, bool alive = true)
|
||||
: MovementAction(botAI, name), creatureId(creatureId), range(range), alive(alive) {}
|
||||
: MovementAction(botAI, name), creatureId(creatureId), range(range), alive(alive)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isPossible() override;
|
||||
@@ -312,7 +322,9 @@ class MoveAwayFromPlayerWithDebuffAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
MoveAwayFromPlayerWithDebuffAction(PlayerbotAI* botAI, std::string name, uint32 spellId, float range)
|
||||
: MovementAction(botAI, name), spellId(spellId), range(range) {}
|
||||
: MovementAction(botAI, name), spellId(spellId), range(range)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isPossible() override;
|
||||
|
||||
@@ -6,16 +6,17 @@
|
||||
#include "PassLeadershipToMasterAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "PlayerbotOperations.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PlayerbotWorldThreadProcessor.h"
|
||||
|
||||
bool PassLeadershipToMasterAction::Execute(Event event)
|
||||
{
|
||||
if (Player* master = GetMaster())
|
||||
if (master && master != bot && bot->GetGroup() && bot->GetGroup()->IsMember(master->GetGUID()))
|
||||
{
|
||||
WorldPacket p(SMSG_GROUP_SET_LEADER, 8);
|
||||
p << master->GetGUID();
|
||||
bot->GetSession()->HandleGroupSetLeaderOpcode(p);
|
||||
auto setLeaderOp = std::make_unique<GroupSetLeaderOperation>(bot->GetGUID(), master->GetGUID());
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(setLeaderOp));
|
||||
|
||||
if (!message.empty())
|
||||
botAI->TellMasterNoFacing(message);
|
||||
|
||||
@@ -18,14 +18,14 @@ bool PetsAction::Execute(Event event)
|
||||
// Extract the command parameter from the event (e.g., "aggressive", "defensive", "attack", etc.)
|
||||
std::string param = event.getParam();
|
||||
if (param.empty() && !defaultCmd.empty())
|
||||
{
|
||||
param = defaultCmd;
|
||||
}
|
||||
|
||||
if (param.empty())
|
||||
{
|
||||
// If no parameter is provided, show usage instructions and return.
|
||||
botAI->TellError("Usage: pet <aggressive|defensive|passive|stance|attack|follow|stay>");
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_usage_error", "Usage: pet <aggressive|defensive|passive|stance|attack|follow|stay>", {});
|
||||
botAI->TellError(text);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,9 @@ bool PetsAction::Execute(Event event)
|
||||
// If no pets or guardians are found, notify and return.
|
||||
if (targets.empty())
|
||||
{
|
||||
botAI->TellError("You have no pet or guardian pet.");
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_no_pet_error", "You have no pet or guardian pet.", {});
|
||||
botAI->TellError(text);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -63,42 +65,54 @@ bool PetsAction::Execute(Event event)
|
||||
if (param == "aggressive")
|
||||
{
|
||||
react = REACT_AGGRESSIVE;
|
||||
stanceText = "aggressive";
|
||||
stanceText = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_aggressive", "aggressive", {});
|
||||
}
|
||||
else if (param == "defensive")
|
||||
{
|
||||
react = REACT_DEFENSIVE;
|
||||
stanceText = "defensive";
|
||||
stanceText = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_defensive", "defensive", {});
|
||||
}
|
||||
else if (param == "passive")
|
||||
{
|
||||
react = REACT_PASSIVE;
|
||||
stanceText = "passive";
|
||||
stanceText = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_passive", "passive", {});
|
||||
}
|
||||
// The "stance" command simply reports the current stance of each pet/guardian.
|
||||
else if (param == "stance")
|
||||
{
|
||||
for (Creature* target : targets)
|
||||
{
|
||||
std::string type = target->IsPet() ? "pet" : "guardian";
|
||||
std::string type = target->IsPet() ?
|
||||
sPlayerbotTextMgr->GetBotTextOrDefault("pet_type_pet", "pet", {}) :
|
||||
sPlayerbotTextMgr->GetBotTextOrDefault("pet_type_guardian", "guardian", {});
|
||||
std::string name = target->GetName();
|
||||
std::string stance;
|
||||
switch (target->GetReactState())
|
||||
{
|
||||
case REACT_AGGRESSIVE:
|
||||
stance = "aggressive";
|
||||
stance = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_aggressive", "aggressive", {});
|
||||
break;
|
||||
case REACT_DEFENSIVE:
|
||||
stance = "defensive";
|
||||
stance = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_defensive", "defensive", {});
|
||||
break;
|
||||
case REACT_PASSIVE:
|
||||
stance = "passive";
|
||||
stance = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_passive", "passive", {});
|
||||
break;
|
||||
default:
|
||||
stance = "unknown";
|
||||
stance = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_unknown", "unknown", {});
|
||||
break;
|
||||
}
|
||||
botAI->TellMaster("Current stance of " + type + " \"" + name + "\": " + stance + ".");
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_report", "Current stance of %type \"%name\": %stance.",
|
||||
{{"type", type}, {"name", name}, {"stance", stance}});
|
||||
botAI->TellMaster(text);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -113,25 +127,38 @@ bool PetsAction::Execute(Event event)
|
||||
{
|
||||
ObjectGuid masterTargetGuid = master->GetTarget();
|
||||
if (!masterTargetGuid.IsEmpty())
|
||||
{
|
||||
targetUnit = botAI->GetUnit(masterTargetGuid);
|
||||
}
|
||||
}
|
||||
|
||||
// If no valid target is selected, show an error and return.
|
||||
if (!targetUnit)
|
||||
{
|
||||
botAI->TellError("No valid target selected by master.");
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_no_target_error", "No valid target selected by master.", {});
|
||||
botAI->TellError(text);
|
||||
return false;
|
||||
}
|
||||
if (!targetUnit->IsAlive())
|
||||
{
|
||||
botAI->TellError("Target is not alive.");
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_target_dead_error", "Target is not alive.", {});
|
||||
botAI->TellError(text);
|
||||
return false;
|
||||
}
|
||||
if (!bot->IsValidAttackTarget(targetUnit))
|
||||
{
|
||||
botAI->TellError("Target is not a valid attack target for the bot.");
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_invalid_target_error", "Target is not a valid attack target for the bot.", {});
|
||||
botAI->TellError(text);
|
||||
return false;
|
||||
}
|
||||
if (sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()) &&
|
||||
(targetUnit->IsPlayer() || targetUnit->IsPet()) &&
|
||||
(!bot->duel || bot->duel->Opponent != targetUnit))
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_pvp_prohibited_error", "I cannot command my pet to attack players in PvP prohibited areas.", {});
|
||||
botAI->TellError(text);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -182,9 +209,17 @@ bool PetsAction::Execute(Event event)
|
||||
}
|
||||
// Inform the master if the command succeeded or failed.
|
||||
if (didAttack && sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
botAI->TellMaster("Pet commanded to attack your target.");
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_attack_success", "Pet commanded to attack your target.", {});
|
||||
botAI->TellMaster(text);
|
||||
}
|
||||
else if (!didAttack)
|
||||
botAI->TellError("Pet did not attack. (Already attacking or unable to attack target)");
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_attack_failed", "Pet did not attack. (Already attacking or unable to attack target)", {});
|
||||
botAI->TellError(text);
|
||||
}
|
||||
return didAttack;
|
||||
}
|
||||
// The "follow" command makes all pets/guardians follow the bot.
|
||||
@@ -192,7 +227,11 @@ bool PetsAction::Execute(Event event)
|
||||
{
|
||||
botAI->PetFollow();
|
||||
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
botAI->TellMaster("Pet commanded to follow.");
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_follow_success", "Pet commanded to follow.", {});
|
||||
botAI->TellMaster(text);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// The "stay" command causes all pets/guardians to stop and stay in place.
|
||||
@@ -229,14 +268,20 @@ bool PetsAction::Execute(Event event)
|
||||
}
|
||||
}
|
||||
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
botAI->TellMaster("Pet commanded to stay.");
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stay_success", "Pet commanded to stay.", {});
|
||||
botAI->TellMaster(text);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Unknown command: show usage instructions and return.
|
||||
else
|
||||
{
|
||||
botAI->TellError("Unknown pet command: " + param +
|
||||
". Use: pet <aggressive|defensive|passive|stance|attack|follow|stay>");
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_unknown_command_error", "Unknown pet command: %param. Use: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
{{"param", param}});
|
||||
botAI->TellError(text);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -251,7 +296,12 @@ bool PetsAction::Execute(Event event)
|
||||
|
||||
// Inform the master of the new stance if debug is enabled.
|
||||
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
botAI->TellMaster("Pet stance set to " + stanceText + ".");
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_set_success", "Pet stance set to %stance.",
|
||||
{{"stance", stanceText}});
|
||||
botAI->TellMaster(text);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,10 @@ bool RandomBotUpdateAction::Execute(Event event)
|
||||
if (!sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||
return false;
|
||||
|
||||
if (bot->GetGroup() && botAI->GetGroupMaster())
|
||||
if (bot->GetGroup() && botAI->GetGroupLeader())
|
||||
{
|
||||
PlayerbotAI* groupMasterBotAI = GET_PLAYERBOT_AI(botAI->GetGroupMaster());
|
||||
if (!groupMasterBotAI || groupMasterBotAI->IsRealPlayer())
|
||||
PlayerbotAI* groupLeaderBotAI = GET_PLAYERBOT_AI(botAI->GetGroupLeader());
|
||||
if (!groupLeaderBotAI || groupLeaderBotAI->IsRealPlayer())
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "ReadyCheckAction.h"
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "ReadyCheckAction.h"
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
@@ -27,14 +30,17 @@ std::string const formatPercent(std::string const name, uint8 value, float perce
|
||||
class ReadyChecker
|
||||
{
|
||||
public:
|
||||
virtual ~ReadyChecker() = default;
|
||||
virtual bool Check(PlayerbotAI* botAI, AiObjectContext* context) = 0;
|
||||
virtual std::string const getName() = 0;
|
||||
virtual bool PrintAlways() { return true; }
|
||||
|
||||
static std::vector<ReadyChecker*> checkers;
|
||||
static std::vector<std::unique_ptr<ReadyChecker>> checkers;
|
||||
static std::once_flag initFlag;
|
||||
};
|
||||
|
||||
std::vector<ReadyChecker*> ReadyChecker::checkers;
|
||||
std::vector<std::unique_ptr<ReadyChecker>> ReadyChecker::checkers;
|
||||
std::once_flag ReadyChecker::initFlag;
|
||||
|
||||
class HealthChecker : public ReadyChecker
|
||||
{
|
||||
@@ -160,25 +166,30 @@ bool ReadyCheckAction::Execute(Event event)
|
||||
|
||||
bool ReadyCheckAction::ReadyCheck()
|
||||
{
|
||||
if (ReadyChecker::checkers.empty())
|
||||
{
|
||||
ReadyChecker::checkers.push_back(new HealthChecker());
|
||||
ReadyChecker::checkers.push_back(new ManaChecker());
|
||||
ReadyChecker::checkers.push_back(new DistanceChecker());
|
||||
ReadyChecker::checkers.push_back(new HunterChecker());
|
||||
std::call_once(
|
||||
ReadyChecker::initFlag,
|
||||
[]()
|
||||
{
|
||||
ReadyChecker::checkers.reserve(8);
|
||||
|
||||
ReadyChecker::checkers.push_back(new ItemCountChecker("food", "Food"));
|
||||
ReadyChecker::checkers.push_back(new ManaPotionChecker("drink", "Water"));
|
||||
ReadyChecker::checkers.push_back(new ItemCountChecker("healing potion", "Hpot"));
|
||||
ReadyChecker::checkers.push_back(new ManaPotionChecker("mana potion", "Mpot"));
|
||||
}
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<HealthChecker>());
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<ManaChecker>());
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<DistanceChecker>());
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<HunterChecker>());
|
||||
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<ItemCountChecker>("food", "Food"));
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<ManaPotionChecker>("drink", "Water"));
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<ItemCountChecker>("healing potion", "Hpot"));
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<ManaPotionChecker>("mana potion", "Mpot"));
|
||||
});
|
||||
|
||||
bool result = true;
|
||||
for (std::vector<ReadyChecker*>::iterator i = ReadyChecker::checkers.begin(); i != ReadyChecker::checkers.end();
|
||||
++i)
|
||||
for (auto const& checkerPtr : ReadyChecker::checkers)
|
||||
{
|
||||
ReadyChecker* checker = *i;
|
||||
bool ok = checker->Check(botAI, context);
|
||||
if (!checkerPtr)
|
||||
continue;
|
||||
|
||||
bool ok = checkerPtr->Check(botAI, context);
|
||||
result = result && ok;
|
||||
}
|
||||
|
||||
|
||||
@@ -168,15 +168,15 @@ bool AutoReleaseSpiritAction::ShouldAutoRelease() const
|
||||
if (!bot->GetGroup())
|
||||
return true;
|
||||
|
||||
Player* groupMaster = botAI->GetGroupMaster();
|
||||
if (!groupMaster || groupMaster == bot)
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
if (!groupLeader || groupLeader == bot)
|
||||
return true;
|
||||
|
||||
if (!botAI->HasActivePlayerMaster())
|
||||
return true;
|
||||
|
||||
if (botAI->HasActivePlayerMaster() &&
|
||||
groupMaster->GetMapId() == bot->GetMapId() &&
|
||||
groupLeader->GetMapId() == bot->GetMapId() &&
|
||||
bot->GetMap() &&
|
||||
(bot->GetMap()->IsRaid() || bot->GetMap()->IsDungeon()))
|
||||
{
|
||||
@@ -184,7 +184,7 @@ bool AutoReleaseSpiritAction::ShouldAutoRelease() const
|
||||
}
|
||||
|
||||
return sServerFacade->IsDistanceGreaterThan(
|
||||
AI_VALUE2(float, "distance", "master target"),
|
||||
AI_VALUE2(float, "distance", "group leader"),
|
||||
sPlayerbotAIConfig->sightDistance);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,4 +16,4 @@ bool ResetInstancesAction::Execute(Event event)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResetInstancesAction::isUseful() { return botAI->GetGroupMaster() == bot; };
|
||||
bool ResetInstancesAction::isUseful() { return botAI->GetGroupLeader() == bot; };
|
||||
|
||||
@@ -17,14 +17,14 @@
|
||||
|
||||
bool ReviveFromCorpseAction::Execute(Event event)
|
||||
{
|
||||
Player* master = botAI->GetGroupMaster();
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
Corpse* corpse = bot->GetCorpse();
|
||||
|
||||
// follow master when master revives
|
||||
// follow group Leader when group Leader revives
|
||||
WorldPacket& p = event.getPacket();
|
||||
if (!p.empty() && p.GetOpcode() == CMSG_RECLAIM_CORPSE && master && !corpse && bot->IsAlive())
|
||||
if (!p.empty() && p.GetOpcode() == CMSG_RECLAIM_CORPSE && groupLeader && !corpse && bot->IsAlive())
|
||||
{
|
||||
if (sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "master target"),
|
||||
if (sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "group leader"),
|
||||
sPlayerbotAIConfig->farDistance))
|
||||
{
|
||||
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
|
||||
@@ -43,10 +43,10 @@ bool ReviveFromCorpseAction::Execute(Event event)
|
||||
// time(nullptr))
|
||||
// return false;
|
||||
|
||||
if (master)
|
||||
if (groupLeader)
|
||||
{
|
||||
if (!GET_PLAYERBOT_AI(master) && master->isDead() && master->GetCorpse() &&
|
||||
sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "master target"),
|
||||
if (!GET_PLAYERBOT_AI(groupLeader) && groupLeader->isDead() && groupLeader->GetCorpse() &&
|
||||
sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "group leader"),
|
||||
sPlayerbotAIConfig->farDistance))
|
||||
return false;
|
||||
}
|
||||
@@ -79,15 +79,15 @@ bool FindCorpseAction::Execute(Event event)
|
||||
if (bot->InBattleground())
|
||||
return false;
|
||||
|
||||
Player* master = botAI->GetGroupMaster();
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
Corpse* corpse = bot->GetCorpse();
|
||||
if (!corpse)
|
||||
return false;
|
||||
|
||||
// if (master)
|
||||
// if (groupLeader)
|
||||
// {
|
||||
// if (!GET_PLAYERBOT_AI(master) &&
|
||||
// sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "master target"),
|
||||
// if (!GET_PLAYERBOT_AI(groupLeader) &&
|
||||
// sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "group leader"),
|
||||
// sPlayerbotAIConfig->farDistance)) return false;
|
||||
// }
|
||||
|
||||
@@ -110,20 +110,20 @@ bool FindCorpseAction::Execute(Event event)
|
||||
WorldPosition botPos(bot);
|
||||
WorldPosition corpsePos(corpse);
|
||||
WorldPosition moveToPos = corpsePos;
|
||||
WorldPosition masterPos(master);
|
||||
WorldPosition leaderPos(groupLeader);
|
||||
|
||||
float reclaimDist = CORPSE_RECLAIM_RADIUS - 5.0f;
|
||||
float corpseDist = botPos.distance(corpsePos);
|
||||
int64 deadTime = time(nullptr) - corpse->GetGhostTime();
|
||||
|
||||
bool moveToMaster = master && master != bot && masterPos.fDist(corpsePos) < reclaimDist;
|
||||
bool moveToLeader = groupLeader && groupLeader != bot && leaderPos.fDist(corpsePos) < reclaimDist;
|
||||
|
||||
// Should we ressurect? If so, return false.
|
||||
if (corpseDist < reclaimDist)
|
||||
{
|
||||
if (moveToMaster) // We are near master.
|
||||
if (moveToLeader) // We are near group leader.
|
||||
{
|
||||
if (botPos.fDist(masterPos) < sPlayerbotAIConfig->spellDistance)
|
||||
if (botPos.fDist(leaderPos) < sPlayerbotAIConfig->spellDistance)
|
||||
return false;
|
||||
}
|
||||
else if (deadTime > 8 * MINUTE) // We have walked too long already.
|
||||
@@ -140,8 +140,8 @@ bool FindCorpseAction::Execute(Event event)
|
||||
// If we are getting close move to a save ressurrection spot instead of just the corpse.
|
||||
if (corpseDist < sPlayerbotAIConfig->reactDistance)
|
||||
{
|
||||
if (moveToMaster)
|
||||
moveToPos = masterPos;
|
||||
if (moveToLeader)
|
||||
moveToPos = leaderPos;
|
||||
else
|
||||
{
|
||||
FleeManager manager(bot, reclaimDist, 0.0, urand(0, 1), moveToPos);
|
||||
@@ -215,12 +215,12 @@ GraveyardStruct const* SpiritHealerAction::GetGrave(bool startZone)
|
||||
if (!startZone && ClosestGrave)
|
||||
return ClosestGrave;
|
||||
|
||||
if (botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT) && botAI->GetGroupMaster() && botAI->GetGroupMaster() != bot)
|
||||
if (botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT) && botAI->GetGroupLeader() && botAI->GetGroupLeader() != bot)
|
||||
{
|
||||
Player* master = botAI->GetGroupMaster();
|
||||
if (master && master != bot)
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
if (groupLeader && groupLeader != bot)
|
||||
{
|
||||
ClosestGrave = sGraveyard->GetClosestGraveyard(master, bot->GetTeamId());
|
||||
ClosestGrave = sGraveyard->GetClosestGraveyard(groupLeader, bot->GetTeamId());
|
||||
|
||||
if (ClosestGrave)
|
||||
return ClosestGrave;
|
||||
|
||||
@@ -35,8 +35,8 @@ bool RewardAction::Execute(Event event)
|
||||
return true;
|
||||
}
|
||||
|
||||
Unit* mtar = AI_VALUE(Unit*, "master target");
|
||||
if (mtar && Reward(itemId, mtar))
|
||||
Unit* groupLeaderUnit = AI_VALUE(Unit*, "group leader");
|
||||
if (groupLeaderUnit && Reward(itemId, groupLeaderUnit))
|
||||
return true;
|
||||
|
||||
botAI->TellError("Cannot talk to quest giver");
|
||||
|
||||
@@ -68,17 +68,15 @@ bool RpgAction::SetNextRpgAction()
|
||||
|
||||
triggerNode->setTrigger(trigger);
|
||||
|
||||
NextAction** nextActions = triggerNode->getHandlers();
|
||||
std::vector<NextAction> nextActions = triggerNode->getHandlers();
|
||||
|
||||
Trigger* trigger = triggerNode->getTrigger();
|
||||
|
||||
bool isChecked = false;
|
||||
|
||||
for (int32 i = 0; i < NextAction::size(nextActions); i++)
|
||||
for (NextAction nextAction : nextActions)
|
||||
{
|
||||
NextAction* nextAction = nextActions[i];
|
||||
|
||||
if (nextAction->getRelevance() > 5.0f)
|
||||
if (nextAction.getRelevance() > 5.0f)
|
||||
continue;
|
||||
|
||||
if (!isChecked && !trigger->IsActive())
|
||||
@@ -86,14 +84,13 @@ bool RpgAction::SetNextRpgAction()
|
||||
|
||||
isChecked = true;
|
||||
|
||||
Action* action = botAI->GetAiObjectContext()->GetAction(nextAction->getName());
|
||||
Action* action = botAI->GetAiObjectContext()->GetAction(nextAction.getName());
|
||||
if (!dynamic_cast<RpgEnabled*>(action) || !action->isPossible() || !action->isUseful())
|
||||
continue;
|
||||
|
||||
actions.push_back(action);
|
||||
relevances.push_back((nextAction->getRelevance() - 1) * 500);
|
||||
relevances.push_back((nextAction.getRelevance() - 1) * 500);
|
||||
}
|
||||
NextAction::destroy(nextActions);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ void RpgHelper::setFacing(GuidPosition guidPosition)
|
||||
|
||||
void RpgHelper::setDelay(bool waitForGroup)
|
||||
{
|
||||
if (!botAI->HasRealPlayerMaster() || (waitForGroup && botAI->GetGroupMaster() == bot && bot->GetGroup()))
|
||||
if (!botAI->HasRealPlayerMaster() || (waitForGroup && botAI->GetGroupLeader() == bot && bot->GetGroup()))
|
||||
botAI->SetNextCheckDelay(sPlayerbotAIConfig->rpgDelay);
|
||||
else
|
||||
botAI->SetNextCheckDelay(sPlayerbotAIConfig->rpgDelay / 5);
|
||||
|
||||
@@ -22,8 +22,8 @@ bool SecurityCheckAction::Execute(Event event)
|
||||
ItemQualities threshold = group->GetLootThreshold();
|
||||
if (method == MASTER_LOOT || method == FREE_FOR_ALL || threshold > ITEM_QUALITY_UNCOMMON)
|
||||
{
|
||||
if ((botAI->GetGroupMaster()->GetSession()->GetSecurity() == SEC_PLAYER) &&
|
||||
(!bot->GetGuildId() || bot->GetGuildId() != botAI->GetGroupMaster()->GetGuildId()))
|
||||
if ((botAI->GetGroupLeader()->GetSession()->GetSecurity() == SEC_PLAYER) &&
|
||||
(!bot->GetGuildId() || bot->GetGuildId() != botAI->GetGroupLeader()->GetGuildId()))
|
||||
{
|
||||
botAI->TellError("I will play with this loot type only if I'm in your guild :/");
|
||||
botAI->ChangeStrategy("+passive,+stay", BOT_STATE_NON_COMBAT);
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
#include "RTSCValues.h"
|
||||
#include "RtscAction.h"
|
||||
#include "PositionValue.h"
|
||||
#include "ByteBuffer.h"
|
||||
|
||||
std::set<uint32> const FISHING_SPELLS = {7620, 7731, 7732, 18248, 33095, 51294};
|
||||
|
||||
Creature* SeeSpellAction::CreateWps(Player* wpOwner, float x, float y, float z, float o, uint32 entry, Creature* lastWp,
|
||||
bool important)
|
||||
@@ -31,27 +34,62 @@ Creature* SeeSpellAction::CreateWps(Player* wpOwner, float x, float y, float z,
|
||||
|
||||
bool SeeSpellAction::Execute(Event event)
|
||||
{
|
||||
WorldPacket p(event.getPacket()); //
|
||||
// RTSC packet data
|
||||
WorldPacket p(event.getPacket());
|
||||
uint8 castCount;
|
||||
uint32 spellId;
|
||||
uint8 castCount, castFlags;
|
||||
uint8 castFlags;
|
||||
|
||||
// check RTSC header size = castCount (uint8) + spellId (uint32) + castFlags (uint8)
|
||||
uint32 const rtscHeaderSize = sizeof(uint8) + sizeof(uint32) + sizeof(uint8);
|
||||
if (p.size() < rtscHeaderSize)
|
||||
{
|
||||
LOG_WARN("playerbots", "SeeSpellAction: Corrupt RTSC packet size={}, expected>={}", p.size(), rtscHeaderSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
Player* master = botAI->GetMaster();
|
||||
|
||||
p.rpos(0);
|
||||
p >> castCount >> spellId >> castFlags;
|
||||
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
// read RTSC packet data
|
||||
p.rpos(0); // set read position to start
|
||||
p >> castCount >> spellId >> castFlags;
|
||||
|
||||
// if (!botAI->HasStrategy("RTSC", botAI->GetState()))
|
||||
// return false;
|
||||
|
||||
if (FISHING_SPELLS.find(spellId) != FISHING_SPELLS.end())
|
||||
{
|
||||
if (AI_VALUE(bool, "can fish") && sPlayerbotAIConfig->enableFishingWithMaster)
|
||||
{
|
||||
botAI->ChangeStrategy("+master fishing", BOT_STATE_NON_COMBAT);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spellId != RTSC_MOVE_SPELL)
|
||||
return false;
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
|
||||
// should not throw exception,just defensive measure to prevent any crashes when core function breaks.
|
||||
SpellCastTargets targets;
|
||||
targets.Read(p, botAI->GetMaster());
|
||||
try
|
||||
{
|
||||
targets.Read(p, master);
|
||||
if (!targets.GetDst())
|
||||
{
|
||||
// do not dereference a null destination; ignore malformed RTSC packets instead of crashing
|
||||
LOG_WARN("playerbots", "SeeSpellAction: (malformed) RTSC payload does not contain full targets data");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (ByteBufferException const&)
|
||||
{
|
||||
// ignore malformed RTSC packets instead of crashing
|
||||
LOG_WARN("playerbots", "SeeSpellAction: Failed deserialization (malformed) RTSC payload");
|
||||
return false;
|
||||
}
|
||||
|
||||
WorldPosition spellPosition(master->GetMapId(), targets.GetDst()->_position);
|
||||
SET_AI_VALUE(WorldPosition, "see spell location", spellPosition);
|
||||
|
||||
@@ -22,7 +22,7 @@ bool OutOfReactRangeAction::Execute(Event event)
|
||||
|
||||
bool OutOfReactRangeAction::isUseful()
|
||||
{
|
||||
bool canFollow = Follow(AI_VALUE(Unit*, "master target"));
|
||||
bool canFollow = Follow(AI_VALUE(Unit*, "group leader"));
|
||||
if (!canFollow)
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "PlayerbotFactory.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
void TrainerAction::Learn(uint32 cost, TrainerSpell const* tSpell, std::ostringstream& msg)
|
||||
void TrainerAction::Learn(uint32 cost, const Trainer::Spell tSpell, std::ostringstream& msg)
|
||||
{
|
||||
if (sPlayerbotAIConfig->autoTrainSpells != "free" && !botAI->HasCheat(BotCheatMask::gold))
|
||||
{
|
||||
@@ -23,7 +23,7 @@ void TrainerAction::Learn(uint32 cost, TrainerSpell const* tSpell, std::ostrings
|
||||
bot->ModifyMoney(-int32(cost));
|
||||
}
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(tSpell->spell);
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(tSpell.SpellId);
|
||||
if (!spellInfo)
|
||||
return;
|
||||
|
||||
@@ -41,10 +41,8 @@ void TrainerAction::Learn(uint32 cost, TrainerSpell const* tSpell, std::ostrings
|
||||
}
|
||||
}
|
||||
|
||||
if (!learned && !bot->HasSpell(tSpell->spell))
|
||||
{
|
||||
bot->learnSpell(tSpell->spell);
|
||||
}
|
||||
if (!learned && !bot->HasSpell(tSpell.SpellId))
|
||||
bot->learnSpell(tSpell.SpellId);
|
||||
|
||||
msg << " - learned";
|
||||
}
|
||||
@@ -53,37 +51,35 @@ void TrainerAction::Iterate(Creature* creature, TrainerSpellAction action, Spell
|
||||
{
|
||||
TellHeader(creature);
|
||||
|
||||
TrainerSpellData const* trainer_spells = creature->GetTrainerSpells();
|
||||
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(creature->GetEntry());
|
||||
|
||||
if (!trainer)
|
||||
return;
|
||||
|
||||
float fDiscountMod = bot->GetReputationPriceDiscount(creature);
|
||||
uint32 totalCost = 0;
|
||||
|
||||
for (TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin();
|
||||
itr != trainer_spells->spellList.end(); ++itr)
|
||||
for (auto& spell : trainer->GetSpells())
|
||||
{
|
||||
TrainerSpell const* tSpell = &itr->second;
|
||||
if (!tSpell)
|
||||
if (!trainer->CanTeachSpell(bot, trainer->GetSpell(spell.SpellId)))
|
||||
continue;
|
||||
|
||||
TrainerSpellState state = bot->GetTrainerSpellState(tSpell);
|
||||
if (state != TRAINER_SPELL_GREEN)
|
||||
if (!spells.empty() && spells.find(spell.SpellId) == spells.end())
|
||||
continue;
|
||||
|
||||
uint32 spellId = tSpell->spell;
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell.SpellId);
|
||||
|
||||
if (!spellInfo)
|
||||
continue;
|
||||
|
||||
if (!spells.empty() && spells.find(tSpell->spell) == spells.end())
|
||||
continue;
|
||||
|
||||
uint32 cost = uint32(floor(tSpell->spellCost * fDiscountMod));
|
||||
uint32 cost = uint32(floor(spell.MoneyCost * fDiscountMod));
|
||||
totalCost += cost;
|
||||
|
||||
std::ostringstream out;
|
||||
out << chat->FormatSpell(spellInfo) << chat->formatMoney(cost);
|
||||
|
||||
if (action)
|
||||
(this->*action)(cost, tSpell, out);
|
||||
(this->*action)(cost, spell, out);
|
||||
|
||||
botAI->TellMaster(out);
|
||||
}
|
||||
@@ -112,15 +108,14 @@ bool TrainerAction::Execute(Event event)
|
||||
if (!creature || !creature->IsTrainer())
|
||||
return false;
|
||||
|
||||
if (!creature->IsValidTrainerForPlayer(bot))
|
||||
{
|
||||
botAI->TellError("This trainer cannot teach me");
|
||||
return false;
|
||||
}
|
||||
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(creature->GetEntry());
|
||||
|
||||
// check present spell in trainer spell list
|
||||
TrainerSpellData const* cSpells = creature->GetTrainerSpells();
|
||||
if (!cSpells)
|
||||
if (!trainer || !trainer->IsTrainerValidForPlayer(bot))
|
||||
return false;
|
||||
|
||||
std::vector<Trainer::Spell> trainer_spells = trainer->GetSpells();
|
||||
|
||||
if (trainer_spells.empty())
|
||||
{
|
||||
botAI->TellError("No spells can be learned from this trainer");
|
||||
return false;
|
||||
@@ -133,7 +128,7 @@ bool TrainerAction::Execute(Event event)
|
||||
|
||||
if (text.find("learn") != std::string::npos || sRandomPlayerbotMgr->IsRandomBot(bot) ||
|
||||
(sPlayerbotAIConfig->autoTrainSpells != "no" &&
|
||||
(creature->GetCreatureTemplate()->trainer_type != TRAINER_TYPE_TRADESKILLS ||
|
||||
(trainer->GetTrainerType() != Trainer::Type::Tradeskill ||
|
||||
!botAI->HasActivePlayerMaster()))) // Todo rewrite to only exclude start primary profession skills and make
|
||||
// config dependent.
|
||||
Iterate(creature, &TrainerAction::Learn, spells);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "Action.h"
|
||||
#include "ChatHelper.h"
|
||||
#include "Trainer.h"
|
||||
|
||||
class Creature;
|
||||
class PlayerbotAI;
|
||||
@@ -22,9 +23,9 @@ public:
|
||||
bool Execute(Event event) override;
|
||||
|
||||
private:
|
||||
typedef void (TrainerAction::*TrainerSpellAction)(uint32, TrainerSpell const*, std::ostringstream& msg);
|
||||
typedef void (TrainerAction::*TrainerSpellAction)(uint32, const Trainer::Spell, std::ostringstream& msg);
|
||||
void Iterate(Creature* creature, TrainerSpellAction action, SpellIds& spells);
|
||||
void Learn(uint32 cost, TrainerSpell const* tSpell, std::ostringstream& msg);
|
||||
void Learn(uint32 cost, const Trainer::Spell tSpell, std::ostringstream& msg);
|
||||
void TellHeader(Creature* creature);
|
||||
void TellFooter(uint32 totalCost);
|
||||
};
|
||||
|
||||
@@ -64,7 +64,7 @@ bool MoveToDarkPortalAction::Execute(Event event)
|
||||
{
|
||||
if (bot->GetGroup())
|
||||
if (bot->GetGroup()->GetLeaderGUID() != bot->GetGUID() &&
|
||||
!GET_PLAYERBOT_AI(GET_PLAYERBOT_AI(bot)->GetGroupMaster()))
|
||||
!GET_PLAYERBOT_AI(GET_PLAYERBOT_AI(bot)->GetGroupLeader()))
|
||||
return false;
|
||||
|
||||
if (bot->GetLevel() > 57)
|
||||
|
||||
@@ -52,7 +52,7 @@ bool UseMeetingStoneAction::Execute(Event event)
|
||||
if (!goInfo || goInfo->entry != 179944)
|
||||
return false;
|
||||
|
||||
return Teleport(master, bot);
|
||||
return Teleport(master, bot, false);
|
||||
}
|
||||
|
||||
bool SummonAction::Execute(Event event)
|
||||
@@ -70,16 +70,16 @@ bool SummonAction::Execute(Event event)
|
||||
{
|
||||
// botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Set({});
|
||||
AI_VALUE(std::list<FleeInfo>&, "recently flee info").clear();
|
||||
return Teleport(master, bot);
|
||||
return Teleport(master, bot, true);
|
||||
}
|
||||
|
||||
if (SummonUsingGos(master, bot) || SummonUsingNpcs(master, bot))
|
||||
if (SummonUsingGos(master, bot, true) || SummonUsingNpcs(master, bot, true))
|
||||
{
|
||||
botAI->TellMasterNoFacing("Hello!");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SummonUsingGos(bot, master) || SummonUsingNpcs(bot, master))
|
||||
if (SummonUsingGos(bot, master, true) || SummonUsingNpcs(bot, master, true))
|
||||
{
|
||||
botAI->TellMasterNoFacing("Welcome!");
|
||||
return true;
|
||||
@@ -88,7 +88,7 @@ bool SummonAction::Execute(Event event)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SummonAction::SummonUsingGos(Player* summoner, Player* player)
|
||||
bool SummonAction::SummonUsingGos(Player* summoner, Player* player, bool preserveAuras)
|
||||
{
|
||||
std::list<GameObject*> targets;
|
||||
AnyGameObjectInObjectRangeCheck u_check(summoner, sPlayerbotAIConfig->sightDistance);
|
||||
@@ -98,14 +98,14 @@ bool SummonAction::SummonUsingGos(Player* summoner, Player* player)
|
||||
for (GameObject* go : targets)
|
||||
{
|
||||
if (go->isSpawned() && go->GetGoType() == GAMEOBJECT_TYPE_MEETINGSTONE)
|
||||
return Teleport(summoner, player);
|
||||
return Teleport(summoner, player, preserveAuras);
|
||||
}
|
||||
|
||||
botAI->TellError(summoner == bot ? "There is no meeting stone nearby" : "There is no meeting stone near you");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player)
|
||||
bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player, bool preserveAuras)
|
||||
{
|
||||
if (!sPlayerbotAIConfig->summonAtInnkeepersEnabled)
|
||||
return false;
|
||||
@@ -139,7 +139,7 @@ bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player)
|
||||
Spell spell(player, spellInfo, TRIGGERED_NONE);
|
||||
spell.SendSpellCooldown();
|
||||
|
||||
return Teleport(summoner, player);
|
||||
return Teleport(summoner, player, preserveAuras);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SummonAction::Teleport(Player* summoner, Player* player)
|
||||
bool SummonAction::Teleport(Player* summoner, Player* player, bool preserveAuras)
|
||||
{
|
||||
// Player* master = GetMaster();
|
||||
if (!summoner)
|
||||
@@ -208,7 +208,11 @@ bool SummonAction::Teleport(Player* summoner, Player* player)
|
||||
|
||||
player->GetMotionMaster()->Clear();
|
||||
AI_VALUE(LastMovement&, "last movement").clear();
|
||||
player->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP);
|
||||
|
||||
if (!preserveAuras)
|
||||
player->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED |
|
||||
AURA_INTERRUPT_FLAG_CHANGE_MAP);
|
||||
|
||||
player->TeleportTo(mapId, x, y, z, 0);
|
||||
|
||||
if (botAI->HasStrategy("stay", botAI->GetState()))
|
||||
|
||||
@@ -19,9 +19,9 @@ public:
|
||||
bool Execute(Event event) override;
|
||||
|
||||
protected:
|
||||
bool Teleport(Player* summoner, Player* player);
|
||||
bool SummonUsingGos(Player* summoner, Player* player);
|
||||
bool SummonUsingNpcs(Player* summoner, Player* player);
|
||||
bool Teleport(Player* summoner, Player* player, bool preserveAuras);
|
||||
bool SummonUsingGos(Player* summoner, Player* player, bool preserveAuras);
|
||||
bool SummonUsingNpcs(Player* summoner, Player* player, bool preserveAuras);
|
||||
};
|
||||
|
||||
class UseMeetingStoneAction : public SummonAction
|
||||
|
||||
@@ -12,25 +12,10 @@ class BloodDKStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
|
||||
public:
|
||||
BloodDKStrategyActionNodeFactory()
|
||||
{
|
||||
// creators["melee"] = &melee;
|
||||
// creators["blood strike"] = &blood_strike;
|
||||
creators["rune strike"] = &rune_strike;
|
||||
creators["heart strike"] = &heart_strike;
|
||||
creators["death strike"] = &death_strike;
|
||||
// creators["death grip"] = &death_grip;
|
||||
// creators["plague strike"] = &plague_strike;
|
||||
// creators["pestilence"] = &pestilence;
|
||||
creators["icy touch"] = &icy_touch;
|
||||
// creators["obliterate"] = &obliterate;
|
||||
// creators["blood boil"] = &blood_boil;
|
||||
// creators["mark of_blood"] = &mark_of_blood;
|
||||
// creators["blood presence"] = &blood_presence;
|
||||
// creators["rune tap"] = &rune_tap;
|
||||
// creators["vampiric blood"] = &vampiric_blood;
|
||||
// creators["death pact"] = &death_pact;
|
||||
// creators["death rune_mastery"] = &death_rune_mastery;
|
||||
// creators["hysteria"] = &hysteria;
|
||||
// creators["dancing weapon"] = &dancing_weapon;
|
||||
creators["dark command"] = &dark_command;
|
||||
creators["taunt spell"] = &dark_command;
|
||||
}
|
||||
@@ -38,39 +23,61 @@ public:
|
||||
private:
|
||||
static ActionNode* rune_strike([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("rune strike",
|
||||
/*P*/ NextAction::array(0, new NextAction("frost presence"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"rune strike",
|
||||
{
|
||||
NextAction("frost presence")
|
||||
},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
static ActionNode* icy_touch([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("icy touch",
|
||||
/*P*/ NextAction::array(0, new NextAction("frost presence"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"icy touch",
|
||||
{
|
||||
NextAction("frost presence")
|
||||
},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
static ActionNode* heart_strike([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("heart strike",
|
||||
/*P*/ NextAction::array(0, new NextAction("frost presence"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"heart strike",
|
||||
{
|
||||
NextAction("frost presence")
|
||||
},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* death_strike([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("death strike",
|
||||
/*P*/ NextAction::array(0, new NextAction("frost presence"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"death strike",
|
||||
{
|
||||
NextAction("frost presence")
|
||||
},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
static ActionNode* dark_command([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("dark command",
|
||||
/*P*/ NextAction::array(0, new NextAction("frost presence"), NULL),
|
||||
/*A*/ NextAction::array(0, new NextAction("death grip"), NULL),
|
||||
/*C*/ NULL);
|
||||
return new ActionNode(
|
||||
"dark command",
|
||||
{
|
||||
NextAction("frost presence")
|
||||
},
|
||||
/*A*/ {
|
||||
NextAction("death grip")
|
||||
},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -79,33 +86,80 @@ BloodDKStrategy::BloodDKStrategy(PlayerbotAI* botAI) : GenericDKStrategy(botAI)
|
||||
actionNodeFactories.Add(new BloodDKStrategyActionNodeFactory());
|
||||
}
|
||||
|
||||
NextAction** BloodDKStrategy::getDefaultActions()
|
||||
std::vector<NextAction> BloodDKStrategy::getDefaultActions()
|
||||
{
|
||||
return NextAction::array(
|
||||
0, new NextAction("rune strike", ACTION_DEFAULT + 0.8f), new NextAction("icy touch", ACTION_DEFAULT + 0.7f),
|
||||
new NextAction("heart strike", ACTION_DEFAULT + 0.6f), new NextAction("blood strike", ACTION_DEFAULT + 0.5f),
|
||||
new NextAction("dancing rune weapon", ACTION_DEFAULT + 0.4f),
|
||||
new NextAction("death coil", ACTION_DEFAULT + 0.3f), new NextAction("plague strike", ACTION_DEFAULT + 0.2f),
|
||||
new NextAction("horn of winter", ACTION_DEFAULT + 0.1f), new NextAction("melee", ACTION_DEFAULT), NULL);
|
||||
return {
|
||||
NextAction("rune strike", ACTION_DEFAULT + 0.8f),
|
||||
NextAction("icy touch", ACTION_DEFAULT + 0.7f),
|
||||
NextAction("heart strike", ACTION_DEFAULT + 0.6f),
|
||||
NextAction("blood strike", ACTION_DEFAULT + 0.5f),
|
||||
NextAction("dancing rune weapon", ACTION_DEFAULT + 0.4f),
|
||||
NextAction("death coil", ACTION_DEFAULT + 0.3f),
|
||||
NextAction("plague strike", ACTION_DEFAULT + 0.2f),
|
||||
NextAction("horn of winter", ACTION_DEFAULT + 0.1f),
|
||||
NextAction("melee", ACTION_DEFAULT)
|
||||
};
|
||||
}
|
||||
|
||||
void BloodDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
GenericDKStrategy::InitTriggers(triggers);
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"rune strike", NextAction::array(0, new NextAction("rune strike", ACTION_NORMAL + 3), nullptr)));
|
||||
triggers.push_back(
|
||||
new TriggerNode("blood tap", NextAction::array(0, new NextAction("blood tap", ACTION_HIGH + 5), nullptr)));
|
||||
new TriggerNode(
|
||||
"rune strike",
|
||||
{
|
||||
NextAction("rune strike", ACTION_NORMAL + 3)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("lose aggro", NextAction::array(0, new NextAction("dark command", ACTION_HIGH + 3), nullptr)));
|
||||
new TriggerNode(
|
||||
"blood tap",
|
||||
{
|
||||
NextAction("blood tap", ACTION_HIGH + 5)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("low health", NextAction::array(0, new NextAction("army of the dead", ACTION_HIGH + 4),
|
||||
new NextAction("death strike", ACTION_HIGH + 3), nullptr)));
|
||||
new TriggerNode(
|
||||
"lose aggro",
|
||||
{
|
||||
NextAction("dark command", ACTION_HIGH + 3)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("critical health", NextAction::array(0, new NextAction("vampiric blood", ACTION_HIGH + 5), nullptr)));
|
||||
new TriggerNode(
|
||||
"low health",
|
||||
{
|
||||
NextAction("army of the dead", ACTION_HIGH + 4),
|
||||
NextAction("death strike", ACTION_HIGH + 3)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("icy touch", NextAction::array(0, new NextAction("icy touch", ACTION_HIGH + 2), nullptr)));
|
||||
triggers.push_back(new TriggerNode(
|
||||
"plague strike", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 2), nullptr)));
|
||||
new TriggerNode(
|
||||
"critical health",
|
||||
{
|
||||
NextAction("vampiric blood", ACTION_HIGH + 5)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"icy touch",
|
||||
{
|
||||
NextAction("icy touch", ACTION_HIGH + 2)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"plague strike",
|
||||
{
|
||||
NextAction("plague strike", ACTION_HIGH + 2)
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public:
|
||||
|
||||
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
||||
std::string const getName() override { return "blood"; }
|
||||
NextAction** getDefaultActions() override;
|
||||
std::vector<NextAction> getDefaultActions() override;
|
||||
uint32 GetType() const override { return STRATEGY_TYPE_TANK | STRATEGY_TYPE_MELEE; }
|
||||
};
|
||||
|
||||
|
||||
@@ -11,39 +11,40 @@
|
||||
#include "SpellInfo.h"
|
||||
#include "SpellMgr.h"
|
||||
|
||||
NextAction** CastDeathchillAction::getPrerequisites()
|
||||
std::vector<NextAction> CastDeathchillAction::getPrerequisites()
|
||||
{
|
||||
return NextAction::merge(NextAction::array(0, new NextAction("frost presence"), nullptr),
|
||||
return NextAction::merge({ NextAction("frost presence") },
|
||||
CastSpellAction::getPrerequisites());
|
||||
}
|
||||
|
||||
NextAction** CastUnholyMeleeSpellAction::getPrerequisites()
|
||||
std::vector<NextAction> CastUnholyMeleeSpellAction::getPrerequisites()
|
||||
{
|
||||
return NextAction::merge(NextAction::array(0, new NextAction("unholy presence"), nullptr),
|
||||
return NextAction::merge({ NextAction("unholy presence") },
|
||||
CastMeleeSpellAction::getPrerequisites());
|
||||
}
|
||||
|
||||
NextAction** CastFrostMeleeSpellAction::getPrerequisites()
|
||||
std::vector<NextAction> CastFrostMeleeSpellAction::getPrerequisites()
|
||||
{
|
||||
return NextAction::merge(NextAction::array(0, new NextAction("frost presence"), nullptr),
|
||||
return NextAction::merge({ NextAction("frost presence") },
|
||||
CastMeleeSpellAction::getPrerequisites());
|
||||
}
|
||||
|
||||
NextAction** CastBloodMeleeSpellAction::getPrerequisites()
|
||||
std::vector<NextAction> CastBloodMeleeSpellAction::getPrerequisites()
|
||||
{
|
||||
return NextAction::merge(NextAction::array(0, new NextAction("blood presence"), nullptr),
|
||||
return NextAction::merge({ NextAction("blood presence") },
|
||||
CastMeleeSpellAction::getPrerequisites());
|
||||
}
|
||||
|
||||
bool CastRaiseDeadAction::Execute(Event event)
|
||||
{
|
||||
bool result = CastBuffSpellAction::Execute(event);
|
||||
const bool result = CastBuffSpellAction::Execute(event);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
uint32 spellId = AI_VALUE2(uint32, "spell id", spell);
|
||||
// SpellInfo const *spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
|
||||
const uint32_t spellId = AI_VALUE2(uint32_t, "spell id", spell);
|
||||
|
||||
bot->AddSpellCooldown(spellId, 0, 3 * 60 * 1000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class CastDeathchillAction : public CastBuffSpellAction
|
||||
public:
|
||||
CastDeathchillAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "deathchill") {}
|
||||
|
||||
NextAction** getPrerequisites() override;
|
||||
std::vector<NextAction> getPrerequisites() override;
|
||||
};
|
||||
|
||||
class CastDarkCommandAction : public CastSpellAction
|
||||
@@ -52,7 +52,7 @@ class CastUnholyMeleeSpellAction : public CastMeleeSpellAction
|
||||
public:
|
||||
CastUnholyMeleeSpellAction(PlayerbotAI* botAI, std::string const spell) : CastMeleeSpellAction(botAI, spell) {}
|
||||
|
||||
NextAction** getPrerequisites() override;
|
||||
std::vector<NextAction> getPrerequisites() override;
|
||||
};
|
||||
|
||||
// Frost presence
|
||||
@@ -61,7 +61,7 @@ class CastFrostMeleeSpellAction : public CastMeleeSpellAction
|
||||
public:
|
||||
CastFrostMeleeSpellAction(PlayerbotAI* botAI, std::string const spell) : CastMeleeSpellAction(botAI, spell) {}
|
||||
|
||||
NextAction** getPrerequisites() override;
|
||||
std::vector<NextAction> getPrerequisites() override;
|
||||
};
|
||||
|
||||
// Blood presence
|
||||
@@ -70,7 +70,7 @@ class CastBloodMeleeSpellAction : public CastMeleeSpellAction
|
||||
public:
|
||||
CastBloodMeleeSpellAction(PlayerbotAI* botAI, std::string const spell) : CastMeleeSpellAction(botAI, spell) {}
|
||||
|
||||
NextAction** getPrerequisites() override;
|
||||
std::vector<NextAction> getPrerequisites() override;
|
||||
};
|
||||
|
||||
class CastRuneStrikeAction : public CastMeleeSpellAction
|
||||
@@ -79,10 +79,6 @@ public:
|
||||
CastRuneStrikeAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "rune strike") {}
|
||||
};
|
||||
|
||||
// debuff
|
||||
// BEGIN_DEBUFF_ACTION(CastPestilenceAction, "pestilence")
|
||||
// END_SPELL_ACTION()
|
||||
|
||||
class CastPestilenceAction : public CastSpellAction
|
||||
{
|
||||
public:
|
||||
@@ -90,20 +86,12 @@ public:
|
||||
ActionThreatType getThreatType() override { return ActionThreatType::None; }
|
||||
};
|
||||
|
||||
// debuff
|
||||
// BEGIN_DEBUFF_ACTION(CastHowlingBlastAction, "howling blast")
|
||||
// END_SPELL_ACTION()
|
||||
|
||||
class CastHowlingBlastAction : public CastSpellAction
|
||||
{
|
||||
public:
|
||||
CastHowlingBlastAction(PlayerbotAI* ai) : CastSpellAction(ai, "howling blast") {}
|
||||
};
|
||||
|
||||
// debuff it
|
||||
// BEGIN_DEBUFF_ACTION(CastIcyTouchAction, "icy touch")
|
||||
// END_SPELL_ACTION()
|
||||
|
||||
class CastIcyTouchAction : public CastSpellAction
|
||||
{
|
||||
public:
|
||||
@@ -126,8 +114,6 @@ class CastPlagueStrikeAction : public CastSpellAction
|
||||
public:
|
||||
CastPlagueStrikeAction(PlayerbotAI* ai) : CastSpellAction(ai, "plague strike") {}
|
||||
};
|
||||
// BEGIN_DEBUFF_ACTION(CastPlagueStrikeAction, "plague strike")
|
||||
// END_SPELL_ACTION()
|
||||
|
||||
class CastPlagueStrikeOnAttackerAction : public CastDebuffSpellOnMeleeAttackerAction
|
||||
{
|
||||
|
||||
@@ -16,66 +16,68 @@ public:
|
||||
creators["obliterate"] = &obliterate;
|
||||
creators["howling blast"] = &howling_blast;
|
||||
creators["frost strike"] = &frost_strike;
|
||||
// creators["chains of ice"] = &chains_of_ice;
|
||||
creators["rune strike"] = &rune_strike;
|
||||
// creators["icy clutch"] = &icy_clutch;
|
||||
// creators["horn of winter"] = &horn_of_winter;
|
||||
// creators["killing machine"] = &killing_machine;
|
||||
// creators["frost presence"] = &frost_presence;
|
||||
// creators["deathchill"] = &deathchill;
|
||||
// creators["icebound fortitude"] = &icebound_fortitude;
|
||||
// creators["mind freeze"] = &mind_freeze;
|
||||
// creators["hungering cold"] = &hungering_cold;
|
||||
creators["unbreakable armor"] = &unbreakable_armor;
|
||||
// creators["improved icy talons"] = &improved_icy_talons;
|
||||
}
|
||||
|
||||
private:
|
||||
static ActionNode* icy_touch([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("icy touch",
|
||||
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"icy touch",
|
||||
/*P*/ { NextAction("blood presence") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* obliterate([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("obliterate",
|
||||
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"obliterate",
|
||||
/*P*/ { NextAction("blood presence") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* rune_strike([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("rune strike",
|
||||
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
|
||||
/*A*/ NextAction::array(0, new NextAction("melee"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"rune strike",
|
||||
/*P*/ { NextAction("blood presence") },
|
||||
/*A*/ { NextAction("melee") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* frost_strike([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("frost strike",
|
||||
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"frost strike",
|
||||
/*P*/ { NextAction("blood presence") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* howling_blast([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("howling blast",
|
||||
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"howling blast",
|
||||
/*P*/ { NextAction("blood presence") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
static ActionNode* unbreakable_armor([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("unbreakable armor",
|
||||
/*P*/ NextAction::array(0, new NextAction("blood tap"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"unbreakable armor",
|
||||
/*P*/ { NextAction("blood tap") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -84,41 +86,84 @@ FrostDKStrategy::FrostDKStrategy(PlayerbotAI* botAI) : GenericDKStrategy(botAI)
|
||||
actionNodeFactories.Add(new FrostDKStrategyActionNodeFactory());
|
||||
}
|
||||
|
||||
NextAction** FrostDKStrategy::getDefaultActions()
|
||||
std::vector<NextAction> FrostDKStrategy::getDefaultActions()
|
||||
{
|
||||
return NextAction::array(
|
||||
0, new NextAction("obliterate", ACTION_DEFAULT + 0.7f),
|
||||
new NextAction("frost strike", ACTION_DEFAULT + 0.4f),
|
||||
new NextAction("empower rune weapon", ACTION_DEFAULT + 0.3f),
|
||||
new NextAction("horn of winter", ACTION_DEFAULT + 0.1f), new NextAction("melee", ACTION_DEFAULT), NULL);
|
||||
return {
|
||||
NextAction("obliterate", ACTION_DEFAULT + 0.7f),
|
||||
NextAction("frost strike", ACTION_DEFAULT + 0.4f),
|
||||
NextAction("empower rune weapon", ACTION_DEFAULT + 0.3f),
|
||||
NextAction("horn of winter", ACTION_DEFAULT + 0.1f),
|
||||
NextAction("melee", ACTION_DEFAULT)
|
||||
};
|
||||
}
|
||||
|
||||
void FrostDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
GenericDKStrategy::InitTriggers(triggers);
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"unbreakable armor", NextAction::array(0, new NextAction("unbreakable armor", ACTION_DEFAULT + 0.6f), nullptr)));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"freezing fog", NextAction::array(0, new NextAction("howling blast", ACTION_DEFAULT + 0.5f), nullptr)));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"high blood rune", NextAction::array(0, new NextAction("blood strike", ACTION_DEFAULT + 0.2f), nullptr)));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"army of the dead", NextAction::array(0, new NextAction("army of the dead", ACTION_HIGH + 6), nullptr)));
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"unbreakable armor",
|
||||
{
|
||||
NextAction("unbreakable armor", ACTION_DEFAULT + 0.6f)
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
triggers.push_back(
|
||||
new TriggerNode("icy touch", NextAction::array(0, new NextAction("icy touch", ACTION_HIGH + 2), nullptr)));
|
||||
triggers.push_back(new TriggerNode(
|
||||
"plague strike", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 2), nullptr)));
|
||||
// triggers.push_back(new TriggerNode("empower rune weapon", NextAction::array(0, new NextAction("empower rune
|
||||
// weapon", ACTION_NORMAL + 4), nullptr)));
|
||||
new TriggerNode(
|
||||
"freezing fog",
|
||||
{
|
||||
NextAction("howling blast", ACTION_DEFAULT + 0.5f)
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"high blood rune",
|
||||
{
|
||||
NextAction("blood strike", ACTION_DEFAULT + 0.2f)
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"army of the dead",
|
||||
{
|
||||
NextAction("army of the dead", ACTION_HIGH + 6)
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"icy touch",
|
||||
{
|
||||
NextAction("icy touch", ACTION_HIGH + 2)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"plague strike",
|
||||
{
|
||||
NextAction("plague strike", ACTION_HIGH + 2)
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
void FrostDKAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
triggers.push_back(
|
||||
new TriggerNode("medium aoe", NextAction::array(0, new NextAction("howling blast", ACTION_HIGH + 4), nullptr)));
|
||||
new TriggerNode(
|
||||
"medium aoe",
|
||||
{
|
||||
NextAction("howling blast", ACTION_HIGH + 4)
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public:
|
||||
|
||||
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
||||
std::string const getName() override { return "frost"; }
|
||||
NextAction** getDefaultActions() override;
|
||||
std::vector<NextAction> getDefaultActions() override;
|
||||
uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_MELEE; }
|
||||
};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user