Compare commits

..

1 Commits

Author SHA1 Message Date
bash
c191edf280 MoveToTravelTargetAction prevent delay when in combat 2025-08-13 00:54:46 +02:00
89 changed files with 696 additions and 3809 deletions

View File

@@ -2,8 +2,6 @@
<a href="https://github.com/liyunfan1223/mod-playerbots/blob/master/README.md">English</a> <a href="https://github.com/liyunfan1223/mod-playerbots/blob/master/README.md">English</a>
| |
<a href="https://github.com/liyunfan1223/mod-playerbots/blob/master/README_CN.md">中文</a> <a href="https://github.com/liyunfan1223/mod-playerbots/blob/master/README_CN.md">中文</a>
|
<a href="https://github.com/brighton-chi/mod-playerbots/blob/readme/README_ES.md">Español</a>
</p> </p>
@@ -18,25 +16,23 @@
</div> </div>
# Playerbots Module # Playerbots Module
`mod-playerbots` is an [AzerothCore](https://www.azerothcore.org/) module that adds player-like bots to a server. The project is based off [IKE3's Playerbots](https://github.com/ike3/mangosbot) and requires a custom branch of AzerothCore to compile and run: [liyunfan1223/azerothcore-wotlk/tree/Playerbot](https://github.com/liyunfan1223/azerothcore-wotlk/tree/Playerbot). `mod-playerbots` is an [AzerothCore](https://www.azerothcore.org/) module that adds player-like bots to a server. The project is based off [IKE3's Playerbots](https://github.com/ike3/mangosbot). Features include:
Features include: - Bots that utilize real player data, allowing players to interact with their other characters, form parties, level up, and more;
- Random bots that wander through the world and behave like players, simulating the MMO experience;
- The ability to log in alt characters as bots, allowing players to interact with their other characters, form parties, level up, and more; - Bots capable of running raids and battlegrounds;
- Random bots that wander through the world, complete quests, and otherwise behave like players, simulating the MMO experience;
- Bots capable of running most raids and battlegrounds;
- Highly configurable settings to define how bots behave; - Highly configurable settings to define how bots behave;
- Excellent performance, even when running thousands of bots. - Excellent performance, even when running thousands of bots.
**This project is still under development**. If you encounter any errors or experience crashes, we kindly request that you [report them as GitHub issues](https://github.com/liyunfan1223/mod-playerbots/issues/new?template=bug_report.md). Your valuable feedback will help us improve this project collaboratively. **This project is still under development**. If you encounter any errors or experience crashes, we kindly request that you [report them as GitHub issues](https://github.com/liyunfan1223/mod-playerbots/issues/new?template=bug_report.md). Your valuable feedback will help us improve this project collaboratively.
`mod-playerbots` has a **[Discord server](https://discord.gg/NQm5QShwf9)** where you can discuss the project, ask questions, and get involved in the community! **Playerbots Module** has a **[Discord server](https://discord.gg/NQm5QShwf9)** where you can discuss the project.
## Installation ## Installation
### Classic Installation ### Classic Installation
As noted above, `mod-playerbots` requires a custom branch of AzerothCore: [liyunfan1223/azerothcore-wotlk/tree/Playerbot](https://github.com/liyunfan1223/azerothcore-wotlk/tree/Playerbot). To install the module, simply run: `mod-playerbots` requires a custom branch of AzerothCore to work: [liyunfan1223/azerothcore-wotlk/tree/Playerbot](https://github.com/liyunfan1223/azerothcore-wotlk/tree/Playerbot). To install the module, simply run:
```bash ```bash
git clone https://github.com/liyunfan1223/azerothcore-wotlk.git --branch=Playerbot git clone https://github.com/liyunfan1223/azerothcore-wotlk.git --branch=Playerbot
@@ -85,21 +81,21 @@ Use `docker compose up -d --build` to build and run the server. For more informa
## Documentation ## Documentation
The [Playerbots Wiki](https://github.com/liyunfan1223/mod-playerbots/wiki) contains an extensive overview of addons, commands, raids with programmed bot strategies, and recommended performance configurations. Please note that documentation may be incomplete or out-of-date in some sections. Contributions are welcome. The [Playerbots Wiki](https://github.com/liyunfan1223/mod-playerbots/wiki) contains an extensive overview of addons, commands, and recommended configurations. Please note that documentation may be incomplete or out-of-date in some sections. Contributions are welcome.
## Frequently Asked Questions ## Frequently Asked Questions
- **Why aren't my bots casting spells?** Please make sure that the necessary English DBC file (enUS) is present. - **Why aren't my bots casting spells?** Please make sure that the necessary English DBC file (enUS) is present.
- **What platforms are supported?** We support Ubuntu, Windows, and macOS. Other Linux distros may work, but will not receive support. - **What platforms are supported?** We support Ubuntu, Windows, and macOS. Other Linux distros may work, but will not receive support.
- **Why isn't my source compiling?** Please ensure that you are compiling with the required [custom branch of AzerothCore](https://github.com/liyunfan1223/azerothcore-wotlk/tree/Playerbot). Additionally, please [check the build status of our CI](https://github.com/liyunfan1223/mod-playerbots/actions). If the latest build is failing, rever to the last successful commit until we address the issue. - **Why isn't my source compiling?** Please [check the build status of our CI](https://github.com/liyunfan1223/mod-playerbots/actions). If the latest build is failing, rever to the last successful commit until we address the issue.
## Addons ## Addons
Typically, bots are controlled via chat commands. For larger bot groups, this can be unwieldy. As an alternative, community members have developed client Add-Ons to allow controlling bots through the in-game UI. We recommend you check out their projects: Typically, bots are controlled via chat commands. For larger bot groups, this can be unwieldy. As an alternative, community members have developed client Add-Ons to allow controlling bots through the in-game UI. We recommend you check out their projects:
- [Multibot](https://github.com/Macx-Lio/MultiBot) (by Macx-Lio), which includes English, Chinese, French, German, Korean, Russian, and Spanish support [note: active development is temporarily continuing on a fork in Macx-Lio's absence (https://github.com/Wishmaster117/MultiBot)] - [Multibot](https://github.com/Macx-Lio/MultiBot) (by Macx-Lio)
- [Unbot Addon (zh)](https://github.com/liyunfan1223/unbot-addon) (Chinese version by Liyunfan) [note: no longer under active development] - [Unbot Addon (zh)](https://github.com/liyunfan1223/unbot-addon) (Chinese version by Liyunfan)
- [Unbot Addon (en)](https://github.com/noisiver/unbot-addon/tree/english) (English version translated by @Revision) [note: no longer under active development] - [Unbot Addon (en)](https://github.com/noisiver/unbot-addon/tree/english) (English version translated by @Revision)
## Acknowledgements ## Acknowledgements

View File

@@ -174,6 +174,10 @@ AiPlayerbot.SummonWhenGroup = 1
# Selfbot permission level (0 = disabled, 1 = GM only (default), 2 = all players, 3 = activate on login) # Selfbot permission level (0 = disabled, 1 = GM only (default), 2 = all players, 3 = activate on login)
AiPlayerbot.SelfBotLevel = 1 AiPlayerbot.SelfBotLevel = 1
# Give free food to bots
# Default: 1 (enabled)
AiPlayerbot.FreeFood = 1
# Non-GM player can only use init=auto to initialize bots based on their own level and gearscore # Non-GM player can only use init=auto to initialize bots based on their own level and gearscore
# Default: 0 (non-GM player can use any intialization commands) # Default: 0 (non-GM player can use any intialization commands)
AiPlayerbot.AutoInitOnly = 0 AiPlayerbot.AutoInitOnly = 0
@@ -492,7 +496,6 @@ AiPlayerbot.AutoGearQualityLimit = 3
AiPlayerbot.AutoGearScoreLimit = 0 AiPlayerbot.AutoGearScoreLimit = 0
# Enable/Disable cheats for bots # Enable/Disable cheats for bots
# "food" (bots eat or drink without using food or drinks from their inventory)
# "gold" (bots have infinite gold) # "gold" (bots have infinite gold)
# "health" (bots have infinite health) # "health" (bots have infinite health)
# "mana" (bots have infinite mana) # "mana" (bots have infinite mana)
@@ -501,7 +504,7 @@ AiPlayerbot.AutoGearScoreLimit = 0
# "raid" (bots use cheats implemented into raid strategies) # "raid" (bots use cheats implemented into raid strategies)
# To use multiple cheats, separate them by commas below (e.g., to enable all, use "gold,health,mana,power,raid,taxi") # To use multiple cheats, separate them by commas below (e.g., to enable all, use "gold,health,mana,power,raid,taxi")
# Default: taxi and raid are enabled # Default: taxi and raid are enabled
AiPlayerbot.BotCheats = "food,taxi,raid" AiPlayerbot.BotCheats = "taxi,raid"
# #
# #
@@ -659,7 +662,7 @@ AiPlayerbot.RandomGearScoreLimit = 0
# Default: 1 (enabled) # Default: 1 (enabled)
AiPlayerbot.IncrementalGearInit = 1 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 their equipment (Maxlevel + 1 to disable)
# Default: 60 # Default: 60
AiPlayerbot.MinEnchantingBotLevel = 60 AiPlayerbot.MinEnchantingBotLevel = 60
@@ -696,16 +699,6 @@ AiPlayerbot.AutoUpgradeEquip = 1
# Default: 0 (disabled) # Default: 0 (disabled)
AiPlayerbot.HunterWolfPet = 0 AiPlayerbot.HunterWolfPet = 0
# Default pet stance when a bot summons a pet
# 0 = Passive, 1 = Defensive, 2 = Aggressive
# Default: 1 (Defensive)
AiPlayerbot.DefaultPetStance = 1
# Enable/disable debug messages about pet commands
# 0 = Disabled, 1 = Enabled
# Default = 0 (disabled)
AiPlayerbot.PetChatCommandDebug = 0
# Prohibit hunter bots from creating pets with any family ID listed below in ExcludedHunterPetFamilies # Prohibit hunter bots from creating pets with any family ID listed below in ExcludedHunterPetFamilies
# See the creature_family database table for all pet families by ID (note: ID for spiders is 3) # See the creature_family database table for all pet families by ID (note: ID for spiders is 3)
AiPlayerbot.ExcludedHunterPetFamilies = "" AiPlayerbot.ExcludedHunterPetFamilies = ""
@@ -811,13 +804,12 @@ AiPlayerbot.OpenGoSpell = 6477
# #
# Additional randombot strategies # 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-based default strategies. These rules are processed after the defaults.
# Example: "+threat,-potions" AiPlayerbot.RandomBotCombatStrategies = "+dps,+dps assist,-threat"
AiPlayerbot.RandomBotCombatStrategies = ""
AiPlayerbot.RandomBotNonCombatStrategies = "" AiPlayerbot.RandomBotNonCombatStrategies = ""
# Additional altbot strategies # 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-based default strategies. These rules are processed after the defaults.
AiPlayerbot.CombatStrategies = "" AiPlayerbot.CombatStrategies = ""
AiPlayerbot.NonCombatStrategies = "" AiPlayerbot.NonCombatStrategies = ""
@@ -1014,20 +1006,6 @@ AiPlayerbot.RandomBotMaps = 0,1,530,571
# Default: 0.25 # Default: 0.25
AiPlayerbot.ProbTeleToBankers = 0.25 AiPlayerbot.ProbTeleToBankers = 0.25
# Control probability weights for bots teleporting to Capital city bankers
# Sum of weights need not be 100. Set to 0 to disable teleporting to the city.
AiPlayerbot.EnableWeightTeleToCityBankers = 1
AiPlayerbot.TeleToStormwindWeight = 2
AiPlayerbot.TeleToIronforgeWeight = 1
AiPlayerbot.TeleToDarnassusWeight = 1
AiPlayerbot.TeleToExodarWeight = 1
AiPlayerbot.TeleToOrgrimmarWeight = 2
AiPlayerbot.TeleToUndercityWeight = 1
AiPlayerbot.TeleToThunderBluffWeight = 1
AiPlayerbot.TeleToSilvermoonCityWeight = 1
AiPlayerbot.TeleToShattrathCityWeight = 1
AiPlayerbot.TeleToDalaranWeight = 1
# How far randombots are teleported after death # How far randombots are teleported after death
AiPlayerbot.RandomBotTeleportDistance = 100 AiPlayerbot.RandomBotTeleportDistance = 100
@@ -1132,7 +1110,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" 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) # 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"
# Improve reaction speeds in battlegrounds and arenas (may cause lag) # Improve reaction speeds in battlegrounds and arenas (may cause lag)
AiPlayerbot.FastReactInBG = 1 AiPlayerbot.FastReactInBG = 1
@@ -1997,15 +1975,13 @@ AiPlayerbot.AllowedLogFiles = ""
#################################################################################################### ####################################################################################################
# A list of gameObject GUID's that are not allowed for bots to interact with. # A list of gameObject GUID's that are not allowed for bots to interact with.
# #
AiPlayerbot.DisallowedGameObjects = 176213,17155,2656,74448,19020,3719,3658,3705,3706,105579,75293,2857,179490,141596,160836,160845,179516,176224,181085,176112,128308,128403,165739,165738,175245,175970,176325,176327,123329 AiPlayerbot.DisallowedGameObjects = 176213,17155,2656,74448,19020,3719,3658,3705,3706,105579,75293,17155,2857,179490
# #
# List of GUID's: # List of GUID's:
# QuestItems: # QuestItems:
# 176213 = Blood of Heroes, 17155 = Defias Gunpowder, 2656 = Waterlogged Envelope, 123329 = Baelogs Chest # 176213 = Blood of Heroes, 17155 = Defias Gunpowder, 2656 = Waterlogged Envelope
# Chests: # Chests:
# Large Solid Chest = 74448, Box of Assorted Parts = 19020, Food Crate = 3719, Water Barrel = 3658, Barrel of Milk = 3705, Barrel of sweet Nectar = 3706, Tattered Chest = 105579, Large bettered Chest = 75293, Solid Chest = 2857, Battered Foodlocker = 179490, Witch Doctor's Chest = 141596, Relic Coffer = 160836, Dark Coffer = 160845, Fengus's Chest = 179516, Supply Crate = 176224/181085, Malor's Strongbox = 176112 # Large Solid Chest = 74448, Box of Assorted Parts = 19020, Food Crate = 3719, Water Barrel = 3658, Barrel of Milk = 3705, Barrel of sweet Nectar = 3706, Tattered Chest = 105579, Large bettered Chest = 75293, Solid Chest = 2857, Battered Foodlocker = 179490
# Other:
# Shallow Grave (Zul'Farrak) = 128308/128403, Grim Guzzler Boar (Blackrock Depths) = 165739, Dark Iron Ale Mug (Blackrock Depths) = 165738, Father Flame (Blackrock Spire) = 175245, Unforged Runic Breastplate (Blackrock Spire) = 175970, Blacksmithing Plans (Stratholme) = 176325/176327
# Feel free to edit and help to complete. # Feel free to edit and help to complete.
# #
#################################################################################################### ####################################################################################################

View File

@@ -1,3 +0,0 @@
DELETE FROM spell_dbc WHERE ID = 30758;
INSERT INTO spell_dbc (`ID`,`Category`,`DispelType`,`Mechanic`,`Attributes`,`AttributesEx`,`AttributesEx2`,`AttributesEx3`,`AttributesEx4`,`AttributesEx5`,`AttributesEx6`,`AttributesEx7`,`ShapeshiftMask`,`unk_320_2`,`ShapeshiftExclude`,`unk_320_3`,`Targets`,`TargetCreatureType`,`RequiresSpellFocus`,`FacingCasterFlags`,`CasterAuraState`,`TargetAuraState`,`ExcludeCasterAuraState`,`ExcludeTargetAuraState`,`CasterAuraSpell`,`TargetAuraSpell`,`ExcludeCasterAuraSpell`,`ExcludeTargetAuraSpell`,`CastingTimeIndex`,`RecoveryTime`,`CategoryRecoveryTime`,`InterruptFlags`,`AuraInterruptFlags`,`ChannelInterruptFlags`,`ProcTypeMask`,`ProcChance`,`ProcCharges`,`MaxLevel`,`BaseLevel`,`SpellLevel`,`DurationIndex`,`PowerType`,`ManaCost`,`ManaCostPerLevel`,`ManaPerSecond`,`ManaPerSecondPerLevel`,`RangeIndex`,`Speed`,`ModalNextSpell`,`CumulativeAura`,`Totem_1`,`Totem_2`,`Reagent_1`,`Reagent_2`,`Reagent_3`,`Reagent_4`,`Reagent_5`,`Reagent_6`,`Reagent_7`,`Reagent_8`,`ReagentCount_1`,`ReagentCount_2`,`ReagentCount_3`,`ReagentCount_4`,`ReagentCount_5`,`ReagentCount_6`,`ReagentCount_7`,`ReagentCount_8`,`EquippedItemClass`,`EquippedItemSubclass`,`EquippedItemInvTypes`,`Effect_1`,`Effect_2`,`Effect_3`,`EffectDieSides_1`,`EffectDieSides_2`,`EffectDieSides_3`,`EffectRealPointsPerLevel_1`,`EffectRealPointsPerLevel_2`,`EffectRealPointsPerLevel_3`,`EffectBasePoints_1`,`EffectBasePoints_2`,`EffectBasePoints_3`,`EffectMechanic_1`,`EffectMechanic_2`,`EffectMechanic_3`,`ImplicitTargetA_1`,`ImplicitTargetA_2`,`ImplicitTargetA_3`,`ImplicitTargetB_1`,`ImplicitTargetB_2`,`ImplicitTargetB_3`,`EffectRadiusIndex_1`,`EffectRadiusIndex_2`,`EffectRadiusIndex_3`,`EffectAura_1`,`EffectAura_2`,`EffectAura_3`,`EffectAuraPeriod_1`,`EffectAuraPeriod_2`,`EffectAuraPeriod_3`,`EffectMultipleValue_1`,`EffectMultipleValue_2`,`EffectMultipleValue_3`,`EffectChainTargets_1`,`EffectChainTargets_2`,`EffectChainTargets_3`,`EffectItemType_1`,`EffectItemType_2`,`EffectItemType_3`,`EffectMiscValue_1`,`EffectMiscValue_2`,`EffectMiscValue_3`,`EffectMiscValueB_1`,`EffectMiscValueB_2`,`EffectMiscValueB_3`,`EffectTriggerSpell_1`,`EffectTriggerSpell_2`,`EffectTriggerSpell_3`,`EffectPointsPerCombo_1`,`EffectPointsPerCombo_2`,`EffectPointsPerCombo_3`,`EffectSpellClassMaskA_1`,`EffectSpellClassMaskA_2`,`EffectSpellClassMaskA_3`,`EffectSpellClassMaskB_1`,`EffectSpellClassMaskB_2`,`EffectSpellClassMaskB_3`,`EffectSpellClassMaskC_1`,`EffectSpellClassMaskC_2`,`EffectSpellClassMaskC_3`,`SpellVisualID_1`,`SpellVisualID_2`,`SpellIconID`,`ActiveIconID`,`SpellPriority`,`Name_Lang_enUS`,`Name_Lang_enGB`,`Name_Lang_koKR`,`Name_Lang_frFR`,`Name_Lang_deDE`,`Name_Lang_enCN`,`Name_Lang_zhCN`,`Name_Lang_enTW`,`Name_Lang_zhTW`,`Name_Lang_esES`,`Name_Lang_esMX`,`Name_Lang_ruRU`,`Name_Lang_ptPT`,`Name_Lang_ptBR`,`Name_Lang_itIT`,`Name_Lang_Unk`,`Name_Lang_Mask`,`NameSubtext_Lang_enUS`,`NameSubtext_Lang_enGB`,`NameSubtext_Lang_koKR`,`NameSubtext_Lang_frFR`,`NameSubtext_Lang_deDE`,`NameSubtext_Lang_enCN`,`NameSubtext_Lang_zhCN`,`NameSubtext_Lang_enTW`,`NameSubtext_Lang_zhTW`,`NameSubtext_Lang_esES`,`NameSubtext_Lang_esMX`,`NameSubtext_Lang_ruRU`,`NameSubtext_Lang_ptPT`,`NameSubtext_Lang_ptBR`,`NameSubtext_Lang_itIT`,`NameSubtext_Lang_Unk`,`NameSubtext_Lang_Mask`,`Description_Lang_enUS`,`Description_Lang_enGB`,`Description_Lang_koKR`,`Description_Lang_frFR`,`Description_Lang_deDE`,`Description_Lang_enCN`,`Description_Lang_zhCN`,`Description_Lang_enTW`,`Description_Lang_zhTW`,`Description_Lang_esES`,`Description_Lang_esMX`,`Description_Lang_ruRU`,`Description_Lang_ptPT`,`Description_Lang_ptBR`,`Description_Lang_itIT`,`Description_Lang_Unk`,`Description_Lang_Mask`,`AuraDescription_Lang_enUS`,`AuraDescription_Lang_enGB`,`AuraDescription_Lang_koKR`,`AuraDescription_Lang_frFR`,`AuraDescription_Lang_deDE`,`AuraDescription_Lang_enCN`,`AuraDescription_Lang_zhCN`,`AuraDescription_Lang_enTW`,`AuraDescription_Lang_zhTW`,`AuraDescription_Lang_esES`,`AuraDescription_Lang_esMX`,`AuraDescription_Lang_ruRU`,`AuraDescription_Lang_ptPT`,`AuraDescription_Lang_ptBR`,`AuraDescription_Lang_itIT`,`AuraDescription_Lang_Unk`,`AuraDescription_Lang_Mask`,`ManaCostPct`,`StartRecoveryCategory`,`StartRecoveryTime`,`MaxTargetLevel`,`SpellClassSet`,`SpellClassMask_1`,`SpellClassMask_2`,`SpellClassMask_3`,`MaxTargets`,`DefenseType`,`PreventionType`,`StanceBarOrder`,`EffectChainAmplitude_1`,`EffectChainAmplitude_2`,`EffectChainAmplitude_3`,`MinFactionID`,`MinReputation`,`RequiredAuraVision`,`RequiredTotemCategoryID_1`,`RequiredTotemCategoryID_2`,`RequiredAreasID`,`SchoolMask`,`RuneCostID`,`SpellMissileID`,`PowerDisplayID`,`EffectBonusMultiplier_1`,`EffectBonusMultiplier_2`,`EffectBonusMultiplier_3`,`SpellDescriptionVariableID`,`SpellDifficultyID`)
VALUES (30758,0,0,0,696254720,132128,268976133,269680640,8388736,393224,4100,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,13,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-1,0,0,77,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,52,0,0,0,0,10,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,'aedm','','','','','','','','','','','','','','','',16712190,'','','','','','','','','','','','','','','','',16712172,'','','','','','','','','','','','','','','','',16712188,'','','','','','','','','','','','','','','','',16712188,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0);

View File

@@ -472,10 +472,6 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
} }
} }
} }
if (sRandomPlayerbotMgr->IsRandomBot(player))
{
engine->ChangeStrategy(sPlayerbotAIConfig->randomBotCombatStrategies);
}
else else
{ {
engine->ChangeStrategy(sPlayerbotAIConfig->combatStrategies); engine->ChangeStrategy(sPlayerbotAIConfig->combatStrategies);

View File

@@ -46,14 +46,6 @@ public:
if (melee && botAI->IsRanged(bot)) if (melee && botAI->IsRanged(bot))
return ""; return "";
bool rangeddps = message.find("@rangeddps") == 0;
if (rangeddps && (!botAI->IsRanged(bot) || botAI->IsTank(bot) || botAI->IsHeal(bot)))
return "";
bool meleedps = message.find("@meleedps") == 0;
if (meleedps && (!botAI->IsMelee(bot) || botAI->IsTank(bot) || botAI->IsHeal(bot)))
return "";
if (tank || dps || heal || ranged || melee) if (tank || dps || heal || ranged || melee)
return ChatFilter::Filter(message); return ChatFilter::Filter(message);
@@ -254,41 +246,21 @@ public:
if (message.find("@group") == 0) if (message.find("@group") == 0)
{ {
size_t spacePos = message.find(" "); std::string const pnum = message.substr(6, message.find(" "));
if (spacePos == std::string::npos) uint32 from = atoi(pnum.c_str());
return message; uint32 to = from;
if (pnum.find("-") != std::string::npos)
std::string pnum = message.substr(6, spacePos - 6);
std::string actualMessage = message.substr(spacePos + 1);
std::set<uint32> targets;
std::istringstream ss(pnum);
std::string token;
while (std::getline(ss, token, ','))
{ {
size_t dashPos = token.find("-"); from = atoi(pnum.substr(pnum.find("@") + 1, pnum.find("-")).c_str());
if (dashPos != std::string::npos) to = atoi(pnum.substr(pnum.find("-") + 1, pnum.find(" ")).c_str());
{
uint32 from = atoi(token.substr(0, dashPos).c_str());
uint32 to = atoi(token.substr(dashPos + 1).c_str());
if (from > to) std::swap(from, to);
for (uint32 i = from; i <= to; ++i)
targets.insert(i);
}
else
{
uint32 index = atoi(token.c_str());
targets.insert(index);
}
} }
if (!bot->GetGroup()) if (!bot->GetGroup())
return message; return message;
uint32 sg = bot->GetSubGroup() + 1; uint32 sg = bot->GetSubGroup() + 1;
if (targets.find(sg) != targets.end()) if (sg >= from && sg <= to)
return ChatFilter::Filter(actualMessage); return ChatFilter::Filter(message);
} }
return message; return message;

View File

@@ -299,10 +299,9 @@ bool LootObject::IsLootPossible(Player* bot)
return false; return false;
} }
// Prevent bot from running to chests that are unlootable (e.g. Gunship Armory before completing the event) or on // Prevent bot from running to chests that are unlootable (e.g. Gunship Armory before completing the event)
// respawn time
GameObject* go = botAI->GetGameObject(guid); GameObject* go = botAI->GetGameObject(guid);
if (go && (go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_INTERACT_COND | GO_FLAG_NOT_SELECTABLE) || !go->isSpawned())) if (go && go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_INTERACT_COND | GO_FLAG_NOT_SELECTABLE))
return false; return false;
if (skillId == SKILL_NONE) if (skillId == SKILL_NONE)
@@ -408,4 +407,4 @@ LootObject LootObjectStack::GetNearest(float maxDistance)
} }
return nearest; return nearest;
} }

View File

@@ -722,8 +722,7 @@ void PlayerbotAI::HandleTeleportAck()
// SetNextCheckDelay(urand(2000, 5000)); // SetNextCheckDelay(urand(2000, 5000));
if (sPlayerbotAIConfig->applyInstanceStrategies) if (sPlayerbotAIConfig->applyInstanceStrategies)
ApplyInstanceStrategies(bot->GetMapId(), true); ApplyInstanceStrategies(bot->GetMapId(), true);
if (sPlayerbotAIConfig->restrictHealerDPS) EvaluateHealerDpsStrategy();
EvaluateHealerDpsStrategy();
Reset(true); Reset(true);
} }
@@ -1766,7 +1765,7 @@ bool PlayerbotAI::IsRangedDps(Player* player, bool bySpec) { return IsRanged(pla
bool PlayerbotAI::IsHealAssistantOfIndex(Player* player, int index) bool PlayerbotAI::IsHealAssistantOfIndex(Player* player, int index)
{ {
Group* group = player->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
{ {
return false; return false;
@@ -1778,11 +1777,6 @@ bool PlayerbotAI::IsHealAssistantOfIndex(Player* player, int index)
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (!member)
{
continue;
}
if (IsHeal(member)) // Check if the member is a healer if (IsHeal(member)) // Check if the member is a healer
{ {
bool isAssistant = group->IsAssistant(member->GetGUID()); bool isAssistant = group->IsAssistant(member->GetGUID());
@@ -1802,7 +1796,7 @@ bool PlayerbotAI::IsHealAssistantOfIndex(Player* player, int index)
bool PlayerbotAI::IsRangedDpsAssistantOfIndex(Player* player, int index) bool PlayerbotAI::IsRangedDpsAssistantOfIndex(Player* player, int index)
{ {
Group* group = player->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
{ {
return false; return false;
@@ -1814,11 +1808,6 @@ bool PlayerbotAI::IsRangedDpsAssistantOfIndex(Player* player, int index)
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (!member)
{
continue;
}
if (IsRangedDps(member)) // Check if the member is a ranged DPS if (IsRangedDps(member)) // Check if the member is a ranged DPS
{ {
bool isAssistant = group->IsAssistant(member->GetGUID()); bool isAssistant = group->IsAssistant(member->GetGUID());
@@ -1851,35 +1840,6 @@ bool PlayerbotAI::HasAggro(Unit* unit)
return false; return false;
} }
int32 PlayerbotAI::GetAssistTankIndex(Player* player)
{
Group* group = player->GetGroup();
if (!group)
{
return -1;
}
int counter = 0;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member)
{
continue;
}
if (player == member)
{
return counter;
}
if (IsTank(member, true) && group->IsAssistant(member->GetGUID()))
{
counter++;
}
}
return 0;
}
int32 PlayerbotAI::GetGroupSlotIndex(Player* player) int32 PlayerbotAI::GetGroupSlotIndex(Player* player)
{ {
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
@@ -1891,12 +1851,6 @@ int32 PlayerbotAI::GetGroupSlotIndex(Player* player)
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (!member)
{
continue;
}
if (player == member) if (player == member)
{ {
return counter; return counter;
@@ -1921,12 +1875,6 @@ int32 PlayerbotAI::GetRangedIndex(Player* player)
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (!member)
{
continue;
}
if (player == member) if (player == member)
{ {
return counter; return counter;
@@ -1954,12 +1902,6 @@ int32 PlayerbotAI::GetClassIndex(Player* player, uint8 cls)
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (!member)
{
continue;
}
if (player == member) if (player == member)
{ {
return counter; return counter;
@@ -1986,12 +1928,6 @@ int32 PlayerbotAI::GetRangedDpsIndex(Player* player)
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (!member)
{
continue;
}
if (player == member) if (player == member)
{ {
return counter; return counter;
@@ -2019,12 +1955,6 @@ int32 PlayerbotAI::GetMeleeIndex(Player* player)
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (!member)
{
continue;
}
if (player == member) if (player == member)
{ {
return counter; return counter;
@@ -2196,12 +2126,6 @@ bool PlayerbotAI::IsMainTank(Player* player)
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (!member)
{
continue;
}
if (IsTank(member) && member->IsAlive()) if (IsTank(member) && member->IsAlive())
{ {
return player->GetGUID() == member->GetGUID(); return player->GetGUID() == member->GetGUID();
@@ -2210,62 +2134,6 @@ bool PlayerbotAI::IsMainTank(Player* player)
return false; return false;
} }
bool PlayerbotAI::IsBotMainTank(Player* player)
{
if (!player->GetSession()->IsBot() || !IsTank(player))
{
return false;
}
if (IsMainTank(player))
{
return true;
}
Group* group = player->GetGroup();
if (!group)
{
return true; // If no group, consider the bot as main tank
}
uint32 botAssistTankIndex = GetAssistTankIndex(player);
if (botAssistTankIndex == -1)
{
return false;
}
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (!member)
{
continue;
}
uint32 memberAssistTankIndex = GetAssistTankIndex(member);
if (memberAssistTankIndex == -1)
{
continue;
}
if (memberAssistTankIndex == botAssistTankIndex && player == member)
{
return true;
}
if (memberAssistTankIndex < botAssistTankIndex && member->GetSession()->IsBot())
{
return false;
}
return false;
}
return false;
}
uint32 PlayerbotAI::GetGroupTankNum(Player* player) uint32 PlayerbotAI::GetGroupTankNum(Player* player)
{ {
Group* group = player->GetGroup(); Group* group = player->GetGroup();
@@ -2277,12 +2145,6 @@ uint32 PlayerbotAI::GetGroupTankNum(Player* player)
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (!member)
{
continue;
}
if (IsTank(member) && member->IsAlive()) if (IsTank(member) && member->IsAlive())
{ {
result++; result++;
@@ -2295,7 +2157,7 @@ bool PlayerbotAI::IsAssistTank(Player* player) { return IsTank(player) && !IsMai
bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index) bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index)
{ {
Group* group = player->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
{ {
return false; return false;
@@ -2304,12 +2166,6 @@ bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index)
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (!member)
{
continue;
}
if (group->IsAssistant(member->GetGUID()) && IsAssistTank(member)) if (group->IsAssistant(member->GetGUID()) && IsAssistTank(member))
{ {
if (index == counter) if (index == counter)
@@ -2323,12 +2179,6 @@ bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index)
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (!member)
{
continue;
}
if (!group->IsAssistant(member->GetGUID()) && IsAssistTank(member)) if (!group->IsAssistant(member->GetGUID()) && IsAssistTank(member))
{ {
if (index == counter) if (index == counter)
@@ -2536,11 +2386,6 @@ std::vector<Player*> PlayerbotAI::GetPlayersInGroup()
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (!member)
{
continue;
}
if (GET_PLAYERBOT_AI(member) && !GET_PLAYERBOT_AI(member)->IsRealPlayer()) if (GET_PLAYERBOT_AI(member) && !GET_PLAYERBOT_AI(member)->IsRealPlayer())
continue; continue;
@@ -4125,15 +3970,37 @@ bool IsAlliance(uint8 race)
bool PlayerbotAI::HasRealPlayerMaster() bool PlayerbotAI::HasRealPlayerMaster()
{ {
if (master) // if (master)
// {
// PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(master);
// return !masterBotAI || masterBotAI->IsRealPlayer();
// }
//
// return false;
// Removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
/* 1) The "master" pointer can be null if the bot was created
without a master player or if the master was just removed. */
if (!master)
return false;
/* 2) Is the master player still present in the world?
If FindPlayer fails, we invalidate "master" and stop here. */
if (!ObjectAccessor::FindPlayer(master->GetGUID()))
{ {
PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(master); master = nullptr; // avoids repeating the check on the next tick
return !masterBotAI || masterBotAI->IsRealPlayer(); return false;
} }
return false; /* 3) If the master is a bot, we check that it is itself controlled
by a real player. Otherwise, it's already a real player → true. */
if (PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(master))
return masterBotAI->IsRealPlayer(); // bot controlled by a player?
return true; // master = real player
} }
bool PlayerbotAI::HasActivePlayerMaster() { return master && !GET_PLAYERBOT_AI(master); } bool PlayerbotAI::HasActivePlayerMaster() { return master && !GET_PLAYERBOT_AI(master); }
bool PlayerbotAI::IsAlt() { return HasRealPlayerMaster() && !sRandomPlayerbotMgr->IsRandomBot(bot); } bool PlayerbotAI::IsAlt() { return HasRealPlayerMaster() && !sRandomPlayerbotMgr->IsRandomBot(bot); }

View File

@@ -423,15 +423,13 @@ public:
static bool IsCaster(Player* player, bool bySpec = false); static bool IsCaster(Player* player, bool bySpec = false);
static bool IsRangedDps(Player* player, bool bySpec = false); static bool IsRangedDps(Player* player, bool bySpec = false);
static bool IsCombo(Player* player); static bool IsCombo(Player* player);
static bool IsBotMainTank(Player* player);
static bool IsMainTank(Player* player); static bool IsMainTank(Player* player);
static uint32 GetGroupTankNum(Player* player); static uint32 GetGroupTankNum(Player* player);
static bool IsAssistTank(Player* player); bool IsAssistTank(Player* player);
static bool IsAssistTankOfIndex(Player* player, int index); bool IsAssistTankOfIndex(Player* player, int index);
static bool IsHealAssistantOfIndex(Player* player, int index); bool IsHealAssistantOfIndex(Player* player, int index);
static bool IsRangedDpsAssistantOfIndex(Player* player, int index); bool IsRangedDpsAssistantOfIndex(Player* player, int index);
bool HasAggro(Unit* unit); bool HasAggro(Unit* unit);
static int32 GetAssistTankIndex(Player* player);
int32 GetGroupSlotIndex(Player* player); int32 GetGroupSlotIndex(Player* player);
int32 GetRangedIndex(Player* player); int32 GetRangedIndex(Player* player);
int32 GetClassIndex(Player* player, uint8 cls); int32 GetClassIndex(Player* player, uint8 cls);

View File

@@ -139,17 +139,6 @@ bool PlayerbotAIConfig::Initialize()
randomBotMapsAsString = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotMaps", "0,1,530,571"); randomBotMapsAsString = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotMaps", "0,1,530,571");
LoadList<std::vector<uint32>>(randomBotMapsAsString, randomBotMaps); LoadList<std::vector<uint32>>(randomBotMapsAsString, randomBotMaps);
probTeleToBankers = sConfigMgr->GetOption<float>("AiPlayerbot.ProbTeleToBankers", 0.25f); probTeleToBankers = sConfigMgr->GetOption<float>("AiPlayerbot.ProbTeleToBankers", 0.25f);
enableWeightTeleToCityBankers = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableWeightTeleToCityBankers", false);
weightTeleToStormwind = sConfigMgr->GetOption<int>("AiPlayerbot.TeleToStormwindWeight", 1);
weightTeleToIronforge = sConfigMgr->GetOption<int>("AiPlayerbot.TeleToIronforgeWeight", 1);
weightTeleToDarnassus = sConfigMgr->GetOption<int>("AiPlayerbot.TeleToDarnassusWeight", 1);
weightTeleToExodar = sConfigMgr->GetOption<int>("AiPlayerbot.TeleToExodarWeight", 1);
weightTeleToOrgrimmar = sConfigMgr->GetOption<int>("AiPlayerbot.TeleToOrgrimmarWeight", 1);
weightTeleToUndercity = sConfigMgr->GetOption<int>("AiPlayerbot.TeleToUndercityWeight", 1);
weightTeleToThunderBluff = sConfigMgr->GetOption<int>("AiPlayerbot.TeleToThunderBluffWeight", 1);
weightTeleToSilvermoonCity = sConfigMgr->GetOption<int>("AiPlayerbot.TeleToSilvermoonCityWeight", 1);
weightTeleToShattrathCity = sConfigMgr->GetOption<int>("AiPlayerbot.TeleToShattrathCityWeight", 1);
weightTeleToDalaran = sConfigMgr->GetOption<int>("AiPlayerbot.TeleToDalaranWeight", 1);
LoadList<std::vector<uint32>>( LoadList<std::vector<uint32>>(
sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotQuestItems", sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotQuestItems",
"6948,5175,5176,5177,5178,16309,12382,13704,11000"), "6948,5175,5176,5177,5178,16309,12382,13704,11000"),
@@ -161,21 +150,15 @@ bool PlayerbotAIConfig::Initialize()
"2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565," "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"), "3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,3951"),
pvpProhibitedZoneIds); pvpProhibitedZoneIds);
LoadList<std::vector<uint32>>( LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.PvpProhibitedAreaIds", "976,35"),
sConfigMgr->GetOption<std::string>("AiPlayerbot.PvpProhibitedAreaIds", pvpProhibitedAreaIds);
"976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754"),
pvpProhibitedAreaIds);
fastReactInBG = sConfigMgr->GetOption<bool>("AiPlayerbot.FastReactInBG", true); fastReactInBG = sConfigMgr->GetOption<bool>("AiPlayerbot.FastReactInBG", true);
LoadList<std::vector<uint32>>( LoadList<std::vector<uint32>>(
sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotQuestIds", "7848,3802,5505,6502,7761"), sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotQuestIds", "7848,3802,5505,6502,7761"),
randomBotQuestIds); randomBotQuestIds);
LoadSet<std::set<uint32>>( LoadSet<std::set<uint32>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.DisallowedGameObjects", "176213,17155"),
sConfigMgr->GetOption<std::string>("AiPlayerbot.DisallowedGameObjects", disallowedGameObjects);
"176213,17155,2656,74448,19020,3719,3658,3705,3706,105579,75293,2857,"
"179490,141596,160836,160845,179516,176224,181085,176112,128308,128403,"
"165739,165738,175245,175970,176325,176327,123329"),
disallowedGameObjects);
botAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.BotAutologin", false); botAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.BotAutologin", false);
randomBotAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotAutologin", true); randomBotAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotAutologin", true);
minRandomBots = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBots", 50); minRandomBots = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBots", 50);
@@ -215,7 +198,7 @@ bool PlayerbotAIConfig::Initialize()
"575,576,578,595,599,600,601,602,604,608,619,632,650,658,668,409,469,509," "575,576,578,595,599,600,601,602,604,608,619,632,650,658,668,409,469,509,"
"531,532,534,544,548,550,564,565,580,249,533,603,615,616,624,631,649,724"), "531,532,534,544,548,550,564,565,580,249,533,603,615,616,624,631,649,724"),
restrictedHealerDPSMaps); restrictedHealerDPSMaps);
//////////////////////////// ICC //////////////////////////// ICC
EnableICCBuffs = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableICCBuffs", true); EnableICCBuffs = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableICCBuffs", true);
@@ -386,7 +369,7 @@ bool PlayerbotAIConfig::Initialize()
randomChangeMultiplier = sConfigMgr->GetOption<float>("AiPlayerbot.RandomChangeMultiplier", 1.0); randomChangeMultiplier = sConfigMgr->GetOption<float>("AiPlayerbot.RandomChangeMultiplier", 1.0);
randomBotCombatStrategies = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotCombatStrategies", ""); randomBotCombatStrategies = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotCombatStrategies", "-threat");
randomBotNonCombatStrategies = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotNonCombatStrategies", ""); randomBotNonCombatStrategies = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotNonCombatStrategies", "");
combatStrategies = sConfigMgr->GetOption<std::string>("AiPlayerbot.CombatStrategies", "+custom::say"); combatStrategies = sConfigMgr->GetOption<std::string>("AiPlayerbot.CombatStrategies", "+custom::say");
nonCombatStrategies = sConfigMgr->GetOption<std::string>("AiPlayerbot.NonCombatStrategies", "+custom::say,+return"); nonCombatStrategies = sConfigMgr->GetOption<std::string>("AiPlayerbot.NonCombatStrategies", "+custom::say,+return");
@@ -473,13 +456,11 @@ bool PlayerbotAIConfig::Initialize()
} }
botCheats.clear(); botCheats.clear();
LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.BotCheats", "food,taxi,raid"), LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.BotCheats", "taxi,raid"),
botCheats); botCheats);
botCheatMask = 0; botCheatMask = 0;
if (std::find(botCheats.begin(), botCheats.end(), "food") != botCheats.end())
botCheatMask |= (uint32)BotCheatMask::food;
if (std::find(botCheats.begin(), botCheats.end(), "taxi") != botCheats.end()) if (std::find(botCheats.begin(), botCheats.end(), "taxi") != botCheats.end())
botCheatMask |= (uint32)BotCheatMask::taxi; botCheatMask |= (uint32)BotCheatMask::taxi;
if (std::find(botCheats.begin(), botCheats.end(), "gold") != botCheats.end()) if (std::find(botCheats.begin(), botCheats.end(), "gold") != botCheats.end())
@@ -594,8 +575,6 @@ bool PlayerbotAIConfig::Initialize()
autoPickTalents = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoPickTalents", true); autoPickTalents = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoPickTalents", true);
autoUpgradeEquip = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoUpgradeEquip", false); autoUpgradeEquip = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoUpgradeEquip", false);
hunterWolfPet = sConfigMgr->GetOption<int32>("AiPlayerbot.HunterWolfPet", 0); hunterWolfPet = sConfigMgr->GetOption<int32>("AiPlayerbot.HunterWolfPet", 0);
defaultPetStance = sConfigMgr->GetOption<int32>("AiPlayerbot.DefaultPetStance", 1);
petChatCommandDebug = sConfigMgr->GetOption<bool>("AiPlayerbot.PetChatCommandDebug", 0);
autoLearnTrainerSpells = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoLearnTrainerSpells", true); autoLearnTrainerSpells = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoLearnTrainerSpells", true);
autoLearnQuestSpells = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoLearnQuestSpells", false); autoLearnQuestSpells = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoLearnQuestSpells", false);
autoTeleportForLevel = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoTeleportForLevel", false); autoTeleportForLevel = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoTeleportForLevel", false);
@@ -611,6 +590,7 @@ bool PlayerbotAIConfig::Initialize()
RpgStatusProbWeight[RPG_REST] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.Rest", 5); RpgStatusProbWeight[RPG_REST] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.Rest", 5);
syncLevelWithPlayers = sConfigMgr->GetOption<bool>("AiPlayerbot.SyncLevelWithPlayers", false); syncLevelWithPlayers = sConfigMgr->GetOption<bool>("AiPlayerbot.SyncLevelWithPlayers", false);
freeFood = sConfigMgr->GetOption<bool>("AiPlayerbot.FreeFood", true);
randomBotGroupNearby = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotGroupNearby", true); randomBotGroupNearby = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotGroupNearby", true);
// arena // arena

View File

@@ -23,8 +23,7 @@ enum class BotCheatMask : uint32
mana = 8, mana = 8,
power = 16, power = 16,
raid = 32, raid = 32,
food = 64, maxMask = 64
maxMask = 128
}; };
enum class HealingManaEfficiency : uint8 enum class HealingManaEfficiency : uint8
@@ -102,17 +101,6 @@ public:
bool botAutologin; bool botAutologin;
std::string randomBotMapsAsString; std::string randomBotMapsAsString;
float probTeleToBankers; float probTeleToBankers;
bool enableWeightTeleToCityBankers;
int weightTeleToStormwind;
int weightTeleToIronforge;
int weightTeleToDarnassus;
int weightTeleToExodar;
int weightTeleToOrgrimmar;
int weightTeleToUndercity;
int weightTeleToThunderBluff;
int weightTeleToSilvermoonCity;
int weightTeleToShattrathCity;
int weightTeleToDalaran;
std::vector<uint32> randomBotMaps; std::vector<uint32> randomBotMaps;
std::vector<uint32> randomBotQuestItems; std::vector<uint32> randomBotQuestItems;
std::vector<uint32> randomBotAccounts; std::vector<uint32> randomBotAccounts;
@@ -345,13 +333,12 @@ public:
bool autoPickTalents; bool autoPickTalents;
bool autoUpgradeEquip; bool autoUpgradeEquip;
int32 hunterWolfPet; int32 hunterWolfPet;
int32 defaultPetStance;
int32 petChatCommandDebug;
bool autoLearnTrainerSpells; bool autoLearnTrainerSpells;
bool autoDoQuests; bool autoDoQuests;
bool enableNewRpgStrategy; bool enableNewRpgStrategy;
std::unordered_map<NewRpgStatus, uint32> RpgStatusProbWeight; std::unordered_map<NewRpgStatus, uint32> RpgStatusProbWeight;
bool syncLevelWithPlayers; bool syncLevelWithPlayers;
bool freeFood;
bool autoLearnQuestSpells; bool autoLearnQuestSpells;
bool autoTeleportForLevel; bool autoTeleportForLevel;
bool randomBotGroupNearby; bool randomBotGroupNearby;

View File

@@ -38,6 +38,8 @@
#include "WorldSessionMgr.h" #include "WorldSessionMgr.h"
#include "DatabaseEnv.h" // Added for gender choice #include "DatabaseEnv.h" // Added for gender choice
#include <algorithm> // Added for gender choice #include <algorithm> // Added for gender choice
#include "Log.h" // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
#include <shared_mutex> // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
class BotInitGuard class BotInitGuard
{ {
@@ -1726,23 +1728,72 @@ void PlayerbotsMgr::RemovePlayerBotData(ObjectGuid const& guid, bool is_AI)
PlayerbotAI* PlayerbotsMgr::GetPlayerbotAI(Player* player) PlayerbotAI* PlayerbotsMgr::GetPlayerbotAI(Player* player)
{ {
if (!(sPlayerbotAIConfig->enabled) || !player) // if (!(sPlayerbotAIConfig->enabled) || !player)
{ // {
return nullptr;
}
// if (player->GetSession()->isLogingOut() || player->IsDuringRemoveFromWorld()) {
// return nullptr; // return nullptr;
// } // }
auto itr = _playerbotsAIMap.find(player->GetGUID()); // // if (player->GetSession()->isLogingOut() || player->IsDuringRemoveFromWorld()) {
if (itr != _playerbotsAIMap.end()) // // return nullptr;
{ // // }
if (itr->second->IsBotAI()) // auto itr = _playerbotsAIMap.find(player->GetGUID());
return reinterpret_cast<PlayerbotAI*>(itr->second); // if (itr != _playerbotsAIMap.end())
// {
// if (itr->second->IsBotAI())
// return reinterpret_cast<PlayerbotAI*>(itr->second);
// }
//
// return nullptr;
// removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
if (!player || !sPlayerbotAIConfig->enabled)
return nullptr;
// First read the GUID into a local variable, but ONLY after the check!
ObjectGuid guid = player->GetGUID(); // <-- OK here, we know that player != nullptr
{
std::shared_lock rlock(_aiMutex);
auto it = _playerbotsAIMap.find(guid);
if (it != _playerbotsAIMap.end() && it->second->IsBotAI())
return static_cast<PlayerbotAI*>(it->second);
} }
// Transient state: NEVER break the master ⇄ bots relationship here.
if (!ObjectAccessor::FindPlayer(guid))
{
RemovePlayerbotAI(guid, /*removeMgrEntry=*/false);
}
return nullptr; return nullptr;
} }
// removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
PlayerbotAI* PlayerbotsMgr::GetPlayerbotAIByGuid(ObjectGuid guid)
{
if (!sPlayerbotAIConfig->enabled)
return nullptr;
std::shared_lock rlock(_aiMutex);
auto it = _playerbotsAIMap.find(guid);
if (it != _playerbotsAIMap.end() && it->second->IsBotAI())
return static_cast<PlayerbotAI*>(it->second);
return nullptr;
}
void PlayerbotsMgr::RemovePlayerbotAI(ObjectGuid const& guid, bool removeMgrEntry /*= true*/)
{
std::unique_lock wlock(_aiMutex);
if (auto it = _playerbotsAIMap.find(guid); it != _playerbotsAIMap.end())
{
delete it->second;
_playerbotsAIMap.erase(it);
LOG_DEBUG("playerbots", "Removed stale AI for GUID {}",
static_cast<uint64>(guid.GetRawValue()));
}
if (removeMgrEntry)
_playerbotsMgrMap.erase(guid); // we NO longer touch the relation in a "soft" purge
}
PlayerbotMgr* PlayerbotsMgr::GetPlayerbotMgr(Player* player) PlayerbotMgr* PlayerbotsMgr::GetPlayerbotMgr(Player* player)
{ {
if (!(sPlayerbotAIConfig->enabled) || !player) if (!(sPlayerbotAIConfig->enabled) || !player)

View File

@@ -12,6 +12,7 @@
#include "PlayerbotAIBase.h" #include "PlayerbotAIBase.h"
#include "QueryHolder.h" #include "QueryHolder.h"
#include "QueryResult.h" #include "QueryResult.h"
#include <shared_mutex> // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
class ChatHandler; class ChatHandler;
class PlayerbotAI; class PlayerbotAI;
@@ -114,13 +115,38 @@ public:
void RemovePlayerBotData(ObjectGuid const& guid, bool is_AI); void RemovePlayerBotData(ObjectGuid const& guid, bool is_AI);
PlayerbotAI* GetPlayerbotAI(Player* player); PlayerbotAI* GetPlayerbotAI(Player* player);
PlayerbotAI* GetPlayerbotAIByGuid(ObjectGuid guid); // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
// void RemovePlayerbotAI(ObjectGuid const& guid); // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
// removeMgrEntry = true => "hard" purge (AI + manager relation), for real logouts
// removeMgrEntry = false => "soft" purge (AI only), for detected "stale" cases
void RemovePlayerbotAI(ObjectGuid const& guid, bool removeMgrEntry = true);
PlayerbotMgr* GetPlayerbotMgr(Player* player); PlayerbotMgr* GetPlayerbotMgr(Player* player);
private: private:
std::unordered_map<ObjectGuid, PlayerbotAIBase*> _playerbotsAIMap; std::unordered_map<ObjectGuid, PlayerbotAIBase*> _playerbotsAIMap;
std::unordered_map<ObjectGuid, PlayerbotAIBase*> _playerbotsMgrMap; std::unordered_map<ObjectGuid, PlayerbotAIBase*> _playerbotsMgrMap;
mutable std::shared_mutex _aiMutex; // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
}; };
#define sPlayerbotsMgr PlayerbotsMgr::instance() #define sPlayerbotsMgr PlayerbotsMgr::instance()
// Temporary addition If it keeps crashing, we will use them.
// Like
// BEFORE : PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
// AFTER (safe) : PlayerbotAI* botAI = GET_PLAYERBOT_AI_SAFE(bot);
// BEFORE : if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(player)) { ... }
// AFTER (safe) : if (PlayerbotAI* botAI = GET_PLAYERBOT_AI_SAFE(player)) { ... }
// --- SAFE helpers (append to PlayerbotMgr.h) ---
inline PlayerbotAI* GET_PLAYERBOT_AI_SAFE(Player* p)
{
// Avoid any dereference during transient states (nullptr, teleport, flight, etc.)
return p ? sPlayerbotsMgr->GetPlayerbotAI(p) : nullptr;
}
inline PlayerbotMgr* GET_PLAYERBOT_MGR_SAFE(Player* p)
{
return p ? sPlayerbotsMgr->GetPlayerbotMgr(p) : nullptr;
}
// --- end SAFE helpers ---
#endif #endif

View File

@@ -377,6 +377,10 @@ public:
void OnPlayerbotLogout(Player* player) override void OnPlayerbotLogout(Player* player) override
{ {
// immediate purge of the bot's AI upon disconnection
if (player && player->GetSession()->IsBot())
sPlayerbotsMgr->RemovePlayerbotAI(player->GetGUID()); // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player)) if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player))
{ {
PlayerbotAI* botAI = GET_PLAYERBOT_AI(player); PlayerbotAI* botAI = GET_PLAYERBOT_AI(player);

View File

@@ -60,48 +60,6 @@ struct GuidClassRaceInfo
uint32 rRace; uint32 rRace;
}; };
enum class CityId : uint8 {
STORMWIND, IRONFORGE, DARNASSUS, EXODAR,
ORGRIMMAR, UNDERCITY, THUNDER_BLUFF, SILVERMOON_CITY,
SHATTRATH_CITY, DALARAN
};
enum class FactionId : uint8 { ALLIANCE, HORDE, NEUTRAL };
// Map of banker entry → city + faction
static const std::unordered_map<uint16, std::pair<CityId, FactionId>> bankerToCity = {
{2455, {CityId::STORMWIND, FactionId::ALLIANCE}}, {2456, {CityId::STORMWIND, FactionId::ALLIANCE}}, {2457, {CityId::STORMWIND, FactionId::ALLIANCE}},
{2460, {CityId::IRONFORGE, FactionId::ALLIANCE}}, {2461, {CityId::IRONFORGE, FactionId::ALLIANCE}}, {5099, {CityId::IRONFORGE, FactionId::ALLIANCE}},
{4155, {CityId::DARNASSUS, FactionId::ALLIANCE}}, {4208, {CityId::DARNASSUS, FactionId::ALLIANCE}}, {4209, {CityId::DARNASSUS, FactionId::ALLIANCE}},
{17773, {CityId::EXODAR, FactionId::ALLIANCE}}, {18350, {CityId::EXODAR, FactionId::ALLIANCE}}, {16710, {CityId::EXODAR, FactionId::ALLIANCE}},
{3320, {CityId::ORGRIMMAR, FactionId::HORDE}}, {3309, {CityId::ORGRIMMAR, FactionId::HORDE}}, {3318, {CityId::ORGRIMMAR, FactionId::HORDE}},
{4549, {CityId::UNDERCITY, FactionId::HORDE}}, {2459, {CityId::UNDERCITY, FactionId::HORDE}}, {2458, {CityId::UNDERCITY, FactionId::HORDE}}, {4550, {CityId::UNDERCITY, FactionId::HORDE}},
{2996, {CityId::THUNDER_BLUFF, FactionId::HORDE}}, {8356, {CityId::THUNDER_BLUFF, FactionId::HORDE}}, {8357, {CityId::THUNDER_BLUFF, FactionId::HORDE}},
{17631, {CityId::SILVERMOON_CITY, FactionId::HORDE}}, {17632, {CityId::SILVERMOON_CITY, FactionId::HORDE}}, {17633, {CityId::SILVERMOON_CITY, FactionId::HORDE}},
{16615, {CityId::SILVERMOON_CITY, FactionId::HORDE}}, {16616, {CityId::SILVERMOON_CITY, FactionId::HORDE}}, {16617, {CityId::SILVERMOON_CITY, FactionId::HORDE}},
{19246, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}}, {19338, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}},
{19034, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}}, {19318, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}},
{30604, {CityId::DALARAN, FactionId::NEUTRAL}}, {30605, {CityId::DALARAN, FactionId::NEUTRAL}}, {30607, {CityId::DALARAN, FactionId::NEUTRAL}},
{28675, {CityId::DALARAN, FactionId::NEUTRAL}}, {28676, {CityId::DALARAN, FactionId::NEUTRAL}}, {28677, {CityId::DALARAN, FactionId::NEUTRAL}}
};
// Map of city → available banker entries
static const std::unordered_map<CityId, std::vector<uint16>> cityToBankers = {
{CityId::STORMWIND, {2455, 2456, 2457}},
{CityId::IRONFORGE, {2460, 2461, 5099}},
{CityId::DARNASSUS, {4155, 4208, 4209}},
{CityId::EXODAR, {17773, 18350, 16710}},
{CityId::ORGRIMMAR, {3320, 3309, 3318}},
{CityId::UNDERCITY, {4549, 2459, 2458, 4550}},
{CityId::THUNDER_BLUFF, {2996, 8356, 8357}},
{CityId::SILVERMOON_CITY, {17631, 17632, 17633, 16615, 16616, 16617}},
{CityId::SHATTRATH_CITY, {19246, 19338, 19034, 19318}},
{CityId::DALARAN, {30604, 30605, 30607, 28675, 28676, 28677, 29530}}
};
// Quick lookup map: banker entry → location
static std::unordered_map<uint32, WorldLocation> bankerEntryToLocation;
void PrintStatsThread() { sRandomPlayerbotMgr->PrintStats(); } void PrintStatsThread() { sRandomPlayerbotMgr->PrintStats(); }
void activatePrintStatsThread() void activatePrintStatsThread()
@@ -1702,6 +1660,11 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector<WorldLocation>&
return; return;
} }
if (tlocs.empty())
{
LOG_DEBUG("playerbots", "Cannot teleport bot {} - no locations available", bot->GetName().c_str());
return;
}
PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(PERF_MON_RNDBOT, "RandomTeleportByLocations"); PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(PERF_MON_RNDBOT, "RandomTeleportByLocations");
@@ -2058,8 +2021,7 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
"position_y, " "position_y, "
"position_z, " "position_z, "
"orientation, " "orientation, "
"t.minlevel, " "t.minlevel "
"t.entry "
"FROM " "FROM "
"creature c " "creature c "
"INNER JOIN creature_template t on c.id1 = t.entry " "INNER JOIN creature_template t on c.id1 = t.entry "
@@ -2086,30 +2048,27 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
float z = fields[3].Get<float>(); float z = fields[3].Get<float>();
float orient = fields[4].Get<float>(); float orient = fields[4].Get<float>();
uint32 level = fields[5].Get<uint32>(); uint32 level = fields[5].Get<uint32>();
uint32 entry = fields[6].Get<uint32>(); WorldLocation loc(mapId, x + cos(orient) * 6.0f, y + sin(orient) * 6.0f, z + 2.0f, orient + M_PI);
BankerLocation bLoc;
bLoc.loc = WorldLocation(mapId, x + cos(orient) * 6.0f, y + sin(orient) * 6.0f, z + 2.0f, orient + M_PI);
bLoc.entry = entry;
collected_locs++; collected_locs++;
for (int32 l = 1; l <= maxLevel; l++) for (int32 l = 1; l <= maxLevel; l++)
{ {
// Bots 1-60 go to base game bankers (all have minlevel 30 or 45) if (l <= 60 && level >= 60)
if (l <=60 && level > 45)
{ {
continue; continue;
} }
// Bots 61-70 go to Shattrath bankers (all have minlevel 60 or 70) if (l <= 70 && level >= 70)
if ((l >=61 && l <=70) && (level < 60 || level > 70))
{ {
continue; continue;
} }
// Bots 71+ go to Dalaran bankers (all have minlevel 75) if (l >= 70 && level >= 60 && level <= 70)
if ((l >=71) && level != 75)
{ {
continue; continue;
} }
bankerLocsPerLevelCache[(uint8)l].push_back(bLoc); if (l >= 30 && level <= 30)
bankerEntryToLocation[bLoc.entry] = bLoc.loc; {
continue;
}
bankerLocsPerLevelCache[(uint8)l].push_back(loc);
} }
} while (results->NextRow()); } while (results->NextRow());
} }
@@ -2179,92 +2138,11 @@ void RandomPlayerbotMgr::RandomTeleportForLevel(Player* bot)
locs = IsAlliance(race) ? &allianceStarterPerLevelCache[level] : &hordeStarterPerLevelCache[level]; locs = IsAlliance(race) ? &allianceStarterPerLevelCache[level] : &hordeStarterPerLevelCache[level];
else else
locs = &locsPerLevelCache[level]; locs = &locsPerLevelCache[level];
LOG_DEBUG("playerbots", "Random teleporting bot {} for level {} ({} locations available)", bot->GetName().c_str(),
bot->GetLevel(), locs->size());
if (level >= 10 && urand(0, 100) < sPlayerbotAIConfig->probTeleToBankers * 100) if (level >= 10 && urand(0, 100) < sPlayerbotAIConfig->probTeleToBankers * 100)
{ {
std::vector<WorldLocation> fallbackLocs; RandomTeleport(bot, bankerLocsPerLevelCache[level], true);
for (auto& bLoc : bankerLocsPerLevelCache[level])
fallbackLocs.push_back(bLoc.loc);
if (!sPlayerbotAIConfig->enableWeightTeleToCityBankers)
{
RandomTeleport(bot, fallbackLocs, true);
return;
}
// Collect valid cities based on bot faction.
std::unordered_set<CityId> validBankerCities;
for (auto& loc : bankerLocsPerLevelCache[level])
{
auto cityIt = bankerToCity.find(loc.entry);
if (cityIt == bankerToCity.end()) continue;
CityId cityId = cityIt->second.first;
FactionId cityFactionId = cityIt->second.second;
if ((IsAlliance(bot->getRace()) && cityFactionId == FactionId::ALLIANCE) ||
(!IsAlliance(bot->getRace()) && cityFactionId == FactionId::HORDE) ||
(cityFactionId == FactionId::NEUTRAL))
{
validBankerCities.insert(cityId);
}
}
// Fallback if no valid cities
if (validBankerCities.empty())
{
RandomTeleport(bot, fallbackLocs, true);
return;
}
// Apply weights to valid cities
std::vector<CityId> weightedCities;
for (CityId city : validBankerCities)
{
int weight = 0;
switch (city)
{
case CityId::STORMWIND: weight = sPlayerbotAIConfig->weightTeleToStormwind; break;
case CityId::IRONFORGE: weight = sPlayerbotAIConfig->weightTeleToIronforge; break;
case CityId::DARNASSUS: weight = sPlayerbotAIConfig->weightTeleToDarnassus; break;
case CityId::EXODAR: weight = sPlayerbotAIConfig->weightTeleToExodar; break;
case CityId::ORGRIMMAR: weight = sPlayerbotAIConfig->weightTeleToOrgrimmar; break;
case CityId::UNDERCITY: weight = sPlayerbotAIConfig->weightTeleToUndercity; break;
case CityId::THUNDER_BLUFF: weight = sPlayerbotAIConfig->weightTeleToThunderBluff; break;
case CityId::SILVERMOON_CITY: weight = sPlayerbotAIConfig->weightTeleToSilvermoonCity; break;
case CityId::SHATTRATH_CITY: weight = sPlayerbotAIConfig->weightTeleToShattrathCity; break;
case CityId::DALARAN: weight = sPlayerbotAIConfig->weightTeleToDalaran; break;
default: weight = 0; break;
}
if (weight <= 0) continue;
for (int i = 0; i < weight; ++i)
{
weightedCities.push_back(city);
}
}
// Fallback if no valid cities
if (weightedCities.empty())
{
RandomTeleport(bot, fallbackLocs, true);
return;
}
// Pick a weighted city randomly, then a random banker in that city
// then teleport to that banker
CityId selectedCity = weightedCities[urand(0, weightedCities.size() - 1)];
const auto& bankers = cityToBankers.at(selectedCity);
uint32 selectedBankerEntry = bankers[urand(0, bankers.size() - 1)];
auto locIt = bankerEntryToLocation.find(selectedBankerEntry);
if (locIt != bankerEntryToLocation.end())
{
std::vector<WorldLocation> teleportTarget = { locIt->second };
RandomTeleport(bot, teleportTarget, true);
return;
}
// Fallback if something went wrong
RandomTeleport(bot, *locs);
} }
else else
{ {

View File

@@ -183,11 +183,7 @@ public:
bool InsideBracket(uint32 val) { return val >= low && val <= high; } bool InsideBracket(uint32 val) { return val >= low && val <= high; }
}; };
std::map<uint32, LevelBracket> zone2LevelBracket; std::map<uint32, LevelBracket> zone2LevelBracket;
struct BankerLocation { std::map<uint8, std::vector<WorldLocation>> bankerLocsPerLevelCache;
WorldLocation loc;
uint32 entry;
};
std::map<uint8, std::vector<BankerLocation>> bankerLocsPerLevelCache;
// Account type management // Account type management
void AssignAccountTypes(); void AssignAccountTypes();

View File

@@ -40,7 +40,6 @@
#include "StatsWeightCalculator.h" #include "StatsWeightCalculator.h"
#include "World.h" #include "World.h"
#include "AiObjectContext.h" #include "AiObjectContext.h"
#include "ItemPackets.h"
const uint64 diveMask = (1LL << 7) | (1LL << 44) | (1LL << 37) | (1LL << 38) | (1LL << 26) | (1LL << 30) | (1LL << 27) | const uint64 diveMask = (1LL << 7) | (1LL << 44) | (1LL << 37) | (1LL << 38) | (1LL << 26) | (1LL << 30) | (1LL << 27) |
(1LL << 33) | (1LL << 24) | (1LL << 34); (1LL << 33) | (1LL << 24) | (1LL << 34);
@@ -124,10 +123,7 @@ void PlayerbotFactory::Init()
if (id == 47181 || id == 50358 || id == 47242 || id == 52639 || id == 47147 || id == 7218) // Test Enchant if (id == 47181 || id == 50358 || id == 47242 || id == 52639 || id == 47147 || id == 7218) // Test Enchant
continue; continue;
if (id == 15463 || id == 15490) // Legendary Arcane Amalgamation if (id == 15463) // Legendary Arcane Amalgamation
continue;
if (id == 29467 || id == 29475 || id == 29480 || id == 29483) // Naxx40 Sapphiron Shoulder Enchants
continue; continue;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(id); SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(id);
@@ -168,33 +164,22 @@ void PlayerbotFactory::Init()
{ {
continue; continue;
} }
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(gemId); ItemTemplate const* proto = sObjectMgr->GetItemTemplate(gemId);
if (!proto)
{
continue;
}
if (proto->ItemLevel < 60) if (proto->ItemLevel < 60)
{
continue; continue;
}
if (proto->Flags & ITEM_FLAG_UNIQUE_EQUIPPABLE) if (proto->Flags & ITEM_FLAG_UNIQUE_EQUIPPABLE)
{ {
continue; continue;
} }
if (sRandomItemMgr->IsTestItem(gemId)) if (sRandomItemMgr->IsTestItem(gemId))
{ continue;
continue;
} if (!proto || !sGemPropertiesStore.LookupEntry(proto->GemProperties))
if (!sGemPropertiesStore.LookupEntry(proto->GemProperties))
{ {
continue; continue;
} }
// LOG_INFO("playerbots", "Add {} to enchantment gems", gemId); // LOG_INFO("playerbots", "Add {} to enchantment gems", gemId);
enchantGemIdCache.push_back(gemId); enchantGemIdCache.push_back(gemId);
} }
@@ -1032,18 +1017,15 @@ void PlayerbotFactory::ClearSkills()
} }
bot->SetUInt32Value(PLAYER_SKILL_INDEX(0), 0); bot->SetUInt32Value(PLAYER_SKILL_INDEX(0), 0);
bot->SetUInt32Value(PLAYER_SKILL_INDEX(1), 0); bot->SetUInt32Value(PLAYER_SKILL_INDEX(1), 0);
// unlearn default race/class skills // unlearn default race/class skills
if (PlayerInfo const* info = sObjectMgr->GetPlayerInfo(bot->getRace(), bot->getClass())) PlayerInfo const* info = sObjectMgr->GetPlayerInfo(bot->getRace(), bot->getClass());
{ for (PlayerCreateInfoSkills::const_iterator itr = info->skills.begin(); itr != info->skills.end(); ++itr)
for (PlayerCreateInfoSkills::const_iterator itr = info->skills.begin(); itr != info->skills.end(); ++itr) {
{ uint32 skillId = itr->SkillId;
uint32 skillId = itr->SkillId; if (!bot->HasSkill(skillId))
if (!bot->HasSkill(skillId)) continue;
continue; bot->SetSkill(skillId, 0, 0, 0);
bot->SetSkill(skillId, 0, 0, 0); }
}
}
} }
void PlayerbotFactory::ClearEverything() void PlayerbotFactory::ClearEverything()
@@ -1900,9 +1882,7 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
WorldPacket packet(CMSG_AUTOSTORE_BAG_ITEM, 3); WorldPacket packet(CMSG_AUTOSTORE_BAG_ITEM, 3);
packet << bagIndex << slot << dstBag; packet << bagIndex << slot << dstBag;
WorldPackets::Item::AutoStoreBagItem nicePacket(std::move(packet)); bot->GetSession()->HandleAutoStoreBagItemOpcode(packet);
nicePacket.Read();
bot->GetSession()->HandleAutoStoreBagItemOpcode(nicePacket);
} }
oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
@@ -3250,7 +3230,7 @@ std::vector<uint32> PlayerbotFactory::GetCurrentGemsCount()
void PlayerbotFactory::InitFood() void PlayerbotFactory::InitFood()
{ {
if (botAI && botAI->HasCheat(BotCheatMask::food)) if (sPlayerbotAIConfig->freeFood)
{ {
return; return;
} }
@@ -4368,10 +4348,10 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld)
} }
// disable next expansion enchantments // disable next expansion enchantments
if (sPlayerbotAIConfig->limitEnchantExpansion && bot->GetLevel() <= 60 && enchantSpell >= 27899) if (sPlayerbotAIConfig->limitEnchantExpansion && bot->GetLevel() <= 60 && enchantSpell >= 25072)
continue; continue;
if (sPlayerbotAIConfig->limitEnchantExpansion && bot->GetLevel() <= 70 && enchantSpell >= 44483) if (sPlayerbotAIConfig->limitEnchantExpansion && bot->GetLevel() <= 70 && enchantSpell > 48557)
continue; continue;
for (uint8 j = 0; j < MAX_SPELL_EFFECTS; ++j) for (uint8 j = 0; j < MAX_SPELL_EFFECTS; ++j)

View File

@@ -14,10 +14,10 @@ void Qualified::Qualify(int qual)
qualifier = out.str(); qualifier = out.str();
} }
std::string const Qualified::MultiQualify(const std::vector<std::string>& qualifiers, const std::string& separator, const std::string_view brackets) std::string const Qualified::MultiQualify(std::vector<std::string> qualifiers, const std::string& separator, const std::string_view brackets)
{ {
std::stringstream out; std::stringstream out;
for (uint8 i = 0; i < qualifiers.size(); ++i) for (uint8 i = 0; i < qualifiers.size(); i++)
{ {
const std::string& qualifier = qualifiers[i]; const std::string& qualifier = qualifiers[i];
if (i == qualifiers.size() - 1) if (i == qualifiers.size() - 1)
@@ -40,13 +40,13 @@ std::string const Qualified::MultiQualify(const std::vector<std::string>& qualif
} }
} }
std::vector<std::string> Qualified::getMultiQualifiers(const std::string& qualifier1) std::vector<std::string> Qualified::getMultiQualifiers(std::string const qualifier1)
{ {
std::istringstream iss(qualifier1); std::istringstream iss(qualifier1);
return {std::istream_iterator<std::string>{iss}, std::istream_iterator<std::string>{}}; return {std::istream_iterator<std::string>{iss}, std::istream_iterator<std::string>{}};
} }
int32 Qualified::getMultiQualifier(const std::string& qualifier1, uint32 pos) int32 Qualified::getMultiQualifier(std::string const qualifier1, uint32 pos)
{ {
return std::stoi(getMultiQualifiers(qualifier1)[pos]); return std::stoi(getMultiQualifiers(qualifier1)[pos]);
} }

View File

@@ -11,7 +11,6 @@
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
#include <functional>
#include "Common.h" #include "Common.h"
@@ -30,10 +29,10 @@ public:
std::string const getQualifier() { return qualifier; } std::string const getQualifier() { return qualifier; }
static std::string const MultiQualify(const std::vector<std::string>& qualifiers, const std::string& separator, static std::string const MultiQualify(std::vector<std::string> qualifiers, const std::string& separator,
const std::string_view brackets = "{}"); const std::string_view brackets = "{}");
static std::vector<std::string> getMultiQualifiers(const std::string& qualifier1); static std::vector<std::string> getMultiQualifiers(std::string const qualifier1);
static int32 getMultiQualifier(const std::string& qualifier1, uint32 pos); static int32 getMultiQualifier(std::string const qualifier1, uint32 pos);
protected: protected:
std::string qualifier; std::string qualifier;
@@ -43,11 +42,11 @@ template <class T>
class NamedObjectFactory class NamedObjectFactory
{ {
public: public:
using ObjectCreator = std::function<T*(PlayerbotAI* ai)>; typedef T* (*ObjectCreator)(PlayerbotAI* botAI);
std::unordered_map<std::string, ObjectCreator> creators; std::unordered_map<std::string, ObjectCreator> creators;
public: public:
virtual T* create(std::string name, PlayerbotAI* botAI) T* create(std::string name, PlayerbotAI* botAI)
{ {
size_t found = name.find("::"); size_t found = name.find("::");
std::string qualifier; std::string qualifier;
@@ -60,9 +59,11 @@ public:
if (creators.find(name) == creators.end()) if (creators.find(name) == creators.end())
return nullptr; return nullptr;
ObjectCreator& creator = creators[name]; ObjectCreator creator = creators[name];
if (!creator)
return nullptr;
T* object = creator(botAI); T* object = (*creator)(botAI);
Qualified* q = dynamic_cast<Qualified*>(object); Qualified* q = dynamic_cast<Qualified*>(object);
if (q && found != std::string::npos) if (q && found != std::string::npos)
q->Qualify(qualifier); q->Qualify(qualifier);
@@ -73,7 +74,7 @@ public:
std::set<std::string> supports() std::set<std::string> supports()
{ {
std::set<std::string> keys; std::set<std::string> keys;
for (typename std::unordered_map<std::string, ObjectCreator>::const_iterator it = creators.begin(); for (typename std::unordered_map<std::string, ObjectCreator>::iterator it = creators.begin();
it != creators.end(); it++) it != creators.end(); it++)
keys.insert(it->first); keys.insert(it->first);
@@ -92,7 +93,7 @@ public:
virtual ~NamedObjectContext() { Clear(); } virtual ~NamedObjectContext() { Clear(); }
virtual T* create(std::string name, PlayerbotAI* botAI) override T* create(std::string const name, PlayerbotAI* botAI)
{ {
if (created.find(name) == created.end()) if (created.find(name) == created.end())
return created[name] = NamedObjectFactory<T>::create(name, botAI); return created[name] = NamedObjectFactory<T>::create(name, botAI);
@@ -102,7 +103,7 @@ public:
void Clear() void Clear()
{ {
for (typename std::unordered_map<std::string, T*>::const_iterator i = created.begin(); i != created.end(); i++) for (typename std::unordered_map<std::string, T*>::iterator i = created.begin(); i != created.end(); i++)
{ {
if (i->second) if (i->second)
delete i->second; delete i->second;
@@ -133,13 +134,13 @@ template <class T>
class SharedNamedObjectContextList class SharedNamedObjectContextList
{ {
public: public:
using ObjectCreator = std::function<T*(PlayerbotAI* ai)>; typedef T* (*ObjectCreator)(PlayerbotAI* botAI);
std::unordered_map<std::string, ObjectCreator> creators; std::unordered_map<std::string, ObjectCreator> creators;
std::vector<NamedObjectContext<T>*> contexts; std::vector<NamedObjectContext<T>*> contexts;
~SharedNamedObjectContextList() ~SharedNamedObjectContextList()
{ {
for (typename std::vector<NamedObjectContext<T>*>::const_iterator i = contexts.begin(); i != contexts.end(); i++) for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
delete *i; delete *i;
} }
@@ -157,7 +158,7 @@ template <class T>
class NamedObjectContextList class NamedObjectContextList
{ {
public: public:
using ObjectCreator = std::function<T*(PlayerbotAI* ai)>; typedef T* (*ObjectCreator)(PlayerbotAI* botAI);
const std::unordered_map<std::string, ObjectCreator>& creators; const std::unordered_map<std::string, ObjectCreator>& creators;
const std::vector<NamedObjectContext<T>*>& contexts; const std::vector<NamedObjectContext<T>*>& contexts;
std::unordered_map<std::string, T*> created; std::unordered_map<std::string, T*> created;
@@ -169,7 +170,7 @@ public:
~NamedObjectContextList() ~NamedObjectContextList()
{ {
for (typename std::unordered_map<std::string, T*>::const_iterator i = created.begin(); i != created.end(); i++) for (typename std::unordered_map<std::string, T*>::iterator i = created.begin(); i != created.end(); i++)
{ {
if (i->second) if (i->second)
delete i->second; delete i->second;
@@ -191,9 +192,11 @@ public:
if (creators.find(name) == creators.end()) if (creators.find(name) == creators.end())
return nullptr; return nullptr;
const ObjectCreator& creator = creators.at(name); ObjectCreator creator = creators.at(name);
if (!creator)
return nullptr;
T* object = creator(botAI); T* object = (*creator)(botAI);
Qualified* q = dynamic_cast<Qualified*>(object); Qualified* q = dynamic_cast<Qualified*>(object);
if (q && found != std::string::npos) if (q && found != std::string::npos)
q->Qualify(qualifier); q->Qualify(qualifier);
@@ -201,7 +204,7 @@ public:
return object; return object;
} }
T* GetContextObject(const std::string& name, PlayerbotAI* botAI) T* GetContextObject(std::string const name, PlayerbotAI* botAI)
{ {
if (created.find(name) == created.end()) if (created.find(name) == created.end())
{ {
@@ -211,7 +214,7 @@ public:
return created[name]; return created[name];
} }
std::set<std::string> GetSiblings(const std::string& name) std::set<std::string> GetSiblings(std::string const name)
{ {
for (auto i = contexts.begin(); i != contexts.end(); i++) for (auto i = contexts.begin(); i != contexts.end(); i++)
{ {
@@ -237,7 +240,7 @@ public:
{ {
std::set<std::string> supported = (*i)->supports(); std::set<std::string> supported = (*i)->supports();
for (std::set<std::string>::const_iterator j = supported.begin(); j != supported.end(); ++j) for (std::set<std::string>::iterator j = supported.begin(); j != supported.end(); j++)
result.insert(*j); result.insert(*j);
} }
@@ -247,7 +250,7 @@ public:
std::set<std::string> GetCreated() std::set<std::string> GetCreated()
{ {
std::set<std::string> result; std::set<std::string> result;
for (typename std::unordered_map<std::string, T*>::const_iterator i = created.begin(); i != created.end(); i++) for (typename std::unordered_map<std::string, T*>::iterator i = created.begin(); i != created.end(); i++)
{ {
result.insert(i->first); result.insert(i->first);
} }
@@ -260,13 +263,13 @@ template <class T>
class NamedObjectFactoryList class NamedObjectFactoryList
{ {
public: public:
using ObjectCreator = std::function<T*(PlayerbotAI* ai)>; typedef T* (*ObjectCreator)(PlayerbotAI* botAI);
std::vector<NamedObjectFactory<T>*> factories; std::vector<NamedObjectFactory<T>*> factories;
std::unordered_map<std::string, ObjectCreator> creators; std::unordered_map<std::string, ObjectCreator> creators;
virtual ~NamedObjectFactoryList() virtual ~NamedObjectFactoryList()
{ {
for (typename std::vector<NamedObjectFactory<T>*>::const_iterator i = factories.begin(); i != factories.end(); i++) for (typename std::vector<NamedObjectFactory<T>*>::iterator i = factories.begin(); i != factories.end(); i++)
delete *i; delete *i;
} }
@@ -283,9 +286,11 @@ public:
if (creators.find(name) == creators.end()) if (creators.find(name) == creators.end())
return nullptr; return nullptr;
const ObjectCreator& creator = creators[name]; ObjectCreator creator = creators[name];
if (!creator)
return nullptr;
T* object = creator(botAI); T* object = (*creator)(botAI);
Qualified* q = dynamic_cast<Qualified*>(object); Qualified* q = dynamic_cast<Qualified*>(object);
if (q && found != std::string::npos) if (q && found != std::string::npos)
q->Qualify(qualifier); q->Qualify(qualifier);
@@ -302,7 +307,7 @@ public:
} }
} }
T* GetContextObject(const std::string& name, PlayerbotAI* botAI) T* GetContextObject(std::string const name, PlayerbotAI* botAI)
{ {
if (T* object = create(name, botAI)) if (T* object = create(name, botAI))
return object; return object;

View File

@@ -246,8 +246,7 @@ public:
creators["rpg mount anim"] = &ActionContext::rpg_mount_anim; creators["rpg mount anim"] = &ActionContext::rpg_mount_anim;
creators["toggle pet spell"] = &ActionContext::toggle_pet_spell; creators["toggle pet spell"] = &ActionContext::toggle_pet_spell;
creators["pet attack"] = &ActionContext::pet_attack; creators["pet attack"] = &ActionContext::pet_attack;
creators["set pet stance"] = &ActionContext::set_pet_stance;
creators["new rpg status update"] = &ActionContext::new_rpg_status_update; creators["new rpg status update"] = &ActionContext::new_rpg_status_update;
creators["new rpg go grind"] = &ActionContext::new_rpg_go_grind; creators["new rpg go grind"] = &ActionContext::new_rpg_go_grind;
@@ -435,7 +434,6 @@ private:
static Action* toggle_pet_spell(PlayerbotAI* ai) { return new TogglePetSpellAutoCastAction(ai); } static Action* toggle_pet_spell(PlayerbotAI* ai) { return new TogglePetSpellAutoCastAction(ai); }
static Action* pet_attack(PlayerbotAI* ai) { return new PetAttackAction(ai); } static Action* pet_attack(PlayerbotAI* ai) { return new PetAttackAction(ai); }
static Action* set_pet_stance(PlayerbotAI* ai) { return new SetPetStanceAction(ai); }
static Action* new_rpg_status_update(PlayerbotAI* ai) { return new NewRpgStatusUpdateAction(ai); } static Action* new_rpg_status_update(PlayerbotAI* ai) { return new NewRpgStatusUpdateAction(ai); }
static Action* new_rpg_go_grind(PlayerbotAI* ai) { return new NewRpgGoGrindAction(ai); } static Action* new_rpg_go_grind(PlayerbotAI* ai) { return new NewRpgGoGrindAction(ai); }

View File

@@ -22,7 +22,6 @@ bool BossFireResistanceAction::Execute(Event event)
{ {
PaladinFireResistanceStrategy paladinFireResistanceStrategy(botAI); PaladinFireResistanceStrategy paladinFireResistanceStrategy(botAI);
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinFireResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT); botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinFireResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT);
botAI->DoSpecificAction("fire resistance aura", Event(), true);
return true; return true;
} }
@@ -36,7 +35,6 @@ bool BossFrostResistanceAction::Execute(Event event)
{ {
PaladinFrostResistanceStrategy paladinFrostResistanceStrategy(botAI); PaladinFrostResistanceStrategy paladinFrostResistanceStrategy(botAI);
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinFrostResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT); botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinFrostResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT);
botAI->DoSpecificAction("frost resistance aura", Event(), true);
return true; return true;
} }
@@ -50,7 +48,6 @@ bool BossNatureResistanceAction::Execute(Event event)
{ {
HunterNatureResistanceStrategy hunterNatureResistanceStrategy(botAI); HunterNatureResistanceStrategy hunterNatureResistanceStrategy(botAI);
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + hunterNatureResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT); botAI->ChangeStrategy(ADD_STRATEGY_CHAR + hunterNatureResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT);
botAI->DoSpecificAction("aspect of the wild", Event(), true);
return true; return true;
} }
@@ -64,6 +61,5 @@ bool BossShadowResistanceAction::Execute(Event event)
{ {
PaladinShadowResistanceStrategy paladinShadowResistanceStrategy(botAI); PaladinShadowResistanceStrategy paladinShadowResistanceStrategy(botAI);
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinShadowResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT); botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinShadowResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT);
botAI->DoSpecificAction("shadow resistance aura", Event(), true);
return true; return true;
} }

View File

@@ -79,10 +79,9 @@
#include "OpenItemAction.h" #include "OpenItemAction.h"
#include "UnlockItemAction.h" #include "UnlockItemAction.h"
#include "UnlockTradedItemAction.h" #include "UnlockTradedItemAction.h"
#include "TameAction.h" #include "PetAction.h"
#include "TellGlyphsAction.h" #include "TellGlyphsAction.h"
#include "EquipGlyphsAction.h" #include "EquipGlyphsAction.h"
#include "PetAction.h"
class ChatActionContext : public NamedObjectContext<Action> class ChatActionContext : public NamedObjectContext<Action>
{ {
@@ -192,11 +191,9 @@ public:
creators["lfg"] = &ChatActionContext::lfg; creators["lfg"] = &ChatActionContext::lfg;
creators["calc"] = &ChatActionContext::calc; creators["calc"] = &ChatActionContext::calc;
creators["wipe"] = &ChatActionContext::wipe; creators["wipe"] = &ChatActionContext::wipe;
creators["tame"] = &ChatActionContext::tame; creators["pet"] = &ChatActionContext::pet;
creators["glyphs"] = &ChatActionContext::glyphs; // Added for custom Glyphs creators["glyphs"] = &ChatActionContext::glyphs; // Added for custom Glyphs
creators["glyph equip"] = &ChatActionContext::glyph_equip; // Added for custom Glyphs creators["glyph equip"] = &ChatActionContext::glyph_equip; // Added for custom Glyphs
creators["pet"] = &ChatActionContext::pet;
creators["pet attack"] = &ChatActionContext::pet_attack;
creators["roll"] = &ChatActionContext::roll_action; creators["roll"] = &ChatActionContext::roll_action;
} }
@@ -304,11 +301,9 @@ private:
static Action* join(PlayerbotAI* ai) { return new JoinGroupAction(ai); } static Action* join(PlayerbotAI* ai) { return new JoinGroupAction(ai); }
static Action* calc(PlayerbotAI* ai) { return new TellCalculateItemAction(ai); } static Action* calc(PlayerbotAI* ai) { return new TellCalculateItemAction(ai); }
static Action* wipe(PlayerbotAI* ai) { return new WipeAction(ai); } static Action* wipe(PlayerbotAI* ai) { return new WipeAction(ai); }
static Action* tame(PlayerbotAI* botAI) { return new TameAction(botAI); } static Action* pet(PlayerbotAI* botAI) { return new PetAction(botAI); }
static Action* glyphs(PlayerbotAI* botAI) { return new TellGlyphsAction(botAI); } // Added for custom Glyphs static Action* glyphs(PlayerbotAI* botAI) { return new TellGlyphsAction(botAI); } // Added for custom Glyphs
static Action* glyph_equip(PlayerbotAI* ai) { return new EquipGlyphsAction(ai); } // Added for custom Glyphs static Action* glyph_equip(PlayerbotAI* ai) { return new EquipGlyphsAction(ai); } // Added for custom Glyphs
static Action* pet(PlayerbotAI* botAI) { return new PetAction(botAI); }
static Action* pet_attack(PlayerbotAI* botAI) { return new PetAction(botAI, "attack"); }
static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); } static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); }
}; };

View File

@@ -4,7 +4,6 @@
*/ */
#include "EquipAction.h" #include "EquipAction.h"
#include <utility>
#include "Event.h" #include "Event.h"
#include "ItemCountValue.h" #include "ItemCountValue.h"
@@ -12,7 +11,6 @@
#include "ItemVisitors.h" #include "ItemVisitors.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "StatsWeightCalculator.h" #include "StatsWeightCalculator.h"
#include "ItemPackets.h"
bool EquipAction::Execute(Event event) bool EquipAction::Execute(Event event)
{ {
@@ -106,10 +104,7 @@ void EquipAction::EquipItem(Item* item)
WorldPacket packet(CMSG_AUTOEQUIP_ITEM_SLOT, 2); WorldPacket packet(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
ObjectGuid itemguid = item->GetGUID(); ObjectGuid itemguid = item->GetGUID();
packet << itemguid << uint8(EQUIPMENT_SLOT_RANGED); packet << itemguid << uint8(EQUIPMENT_SLOT_RANGED);
bot->GetSession()->HandleAutoEquipItemSlotOpcode(packet);
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(packet));
nicePacket.Read();
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
std::ostringstream out; std::ostringstream out;
out << "Equipping " << chat->FormatItem(itemProto) << " in ranged slot"; out << "Equipping " << chat->FormatItem(itemProto) << " in ranged slot";
@@ -204,9 +199,7 @@ void EquipAction::EquipItem(Item* item)
WorldPacket eqPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2); WorldPacket eqPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
ObjectGuid newItemGuid = item->GetGUID(); ObjectGuid newItemGuid = item->GetGUID();
eqPacket << newItemGuid << uint8(EQUIPMENT_SLOT_MAINHAND); eqPacket << newItemGuid << uint8(EQUIPMENT_SLOT_MAINHAND);
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(eqPacket)); bot->GetSession()->HandleAutoEquipItemSlotOpcode(eqPacket);
nicePacket.Read();
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
} }
// Try moving old main hand weapon to offhand if beneficial // Try moving old main hand weapon to offhand if beneficial
@@ -217,9 +210,7 @@ void EquipAction::EquipItem(Item* item)
WorldPacket offhandPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2); WorldPacket offhandPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
ObjectGuid oldMHGuid = mainHandItem->GetGUID(); ObjectGuid oldMHGuid = mainHandItem->GetGUID();
offhandPacket << oldMHGuid << uint8(EQUIPMENT_SLOT_OFFHAND); offhandPacket << oldMHGuid << uint8(EQUIPMENT_SLOT_OFFHAND);
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(offhandPacket)); bot->GetSession()->HandleAutoEquipItemSlotOpcode(offhandPacket);
nicePacket.Read();
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
std::ostringstream moveMsg; std::ostringstream moveMsg;
moveMsg << "Main hand upgrade found. Moving " << chat->FormatItem(oldMHProto) << " to offhand"; moveMsg << "Main hand upgrade found. Moving " << chat->FormatItem(oldMHProto) << " to offhand";
@@ -239,9 +230,7 @@ void EquipAction::EquipItem(Item* item)
WorldPacket eqPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2); WorldPacket eqPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
ObjectGuid newItemGuid = item->GetGUID(); ObjectGuid newItemGuid = item->GetGUID();
eqPacket << newItemGuid << uint8(EQUIPMENT_SLOT_OFFHAND); eqPacket << newItemGuid << uint8(EQUIPMENT_SLOT_OFFHAND);
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(eqPacket)); bot->GetSession()->HandleAutoEquipItemSlotOpcode(eqPacket);
nicePacket.Read();
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
std::ostringstream out; std::ostringstream out;
out << "Equipping " << chat->FormatItem(itemProto) << " in offhand"; out << "Equipping " << chat->FormatItem(itemProto) << " in offhand";
@@ -298,9 +287,7 @@ void EquipAction::EquipItem(Item* item)
WorldPacket packet(CMSG_AUTOEQUIP_ITEM_SLOT, 2); WorldPacket packet(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
ObjectGuid itemguid = item->GetGUID(); ObjectGuid itemguid = item->GetGUID();
packet << itemguid << dstSlot; packet << itemguid << dstSlot;
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(packet)); bot->GetSession()->HandleAutoEquipItemSlotOpcode(packet);
nicePacket.Read();
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
} }
} }

View File

@@ -36,13 +36,10 @@ bool FollowAction::Execute(Event event)
true, priority, true); true, priority, true);
} }
// This section has been commented out because it was forcing the pet to if (Pet* pet = bot->GetPet())
// follow the bot on every "follow" action tick, overriding any attack or {
// stay commands that might have been issued by the player. botAI->PetFollow();
// if (Pet* pet = bot->GetPet()) }
// {
// botAI->PetFollow();
// }
// if (moved) // if (moved)
// botAI->SetNextCheckDelay(sPlayerbotAIConfig->reactDelay); // botAI->SetNextCheckDelay(sPlayerbotAIConfig->reactDelay);

View File

@@ -4,19 +4,9 @@
*/ */
#include "GenericActions.h" #include "GenericActions.h"
#include "PlayerbotAI.h"
#include "Player.h"
#include "Pet.h"
#include "PlayerbotAIConfig.h"
#include "CreatureAI.h" #include "CreatureAI.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "CharmInfo.h"
#include "SharedDefines.h"
#include "ObjectGuid.h"
#include "SpellMgr.h"
#include "SpellInfo.h"
#include <vector>
#include <algorithm>
enum PetSpells enum PetSpells
{ {
@@ -33,8 +23,7 @@ enum PetSpells
PET_DEVOUR_MAGIC_4 = 19736, PET_DEVOUR_MAGIC_4 = 19736,
PET_DEVOUR_MAGIC_5 = 27276, PET_DEVOUR_MAGIC_5 = 27276,
PET_DEVOUR_MAGIC_6 = 27277, PET_DEVOUR_MAGIC_6 = 27277,
PET_DEVOUR_MAGIC_7 = 48011, PET_DEVOUR_MAGIC_7 = 48011
PET_SPIRIT_WOLF_LEAP = 58867
}; };
static std::vector<uint32> disabledPetSpells = { static std::vector<uint32> disabledPetSpells = {
@@ -42,7 +31,7 @@ static std::vector<uint32> disabledPetSpells = {
PET_COWER, PET_LEAP, PET_COWER, PET_LEAP,
PET_SPELL_LOCK_1, PET_SPELL_LOCK_2, PET_SPELL_LOCK_1, PET_SPELL_LOCK_2,
PET_DEVOUR_MAGIC_1, PET_DEVOUR_MAGIC_2, PET_DEVOUR_MAGIC_3, PET_DEVOUR_MAGIC_1, PET_DEVOUR_MAGIC_2, PET_DEVOUR_MAGIC_3,
PET_DEVOUR_MAGIC_4, PET_DEVOUR_MAGIC_5, PET_DEVOUR_MAGIC_6, PET_DEVOUR_MAGIC_7, PET_SPIRIT_WOLF_LEAP PET_DEVOUR_MAGIC_4, PET_DEVOUR_MAGIC_5, PET_DEVOUR_MAGIC_6, PET_DEVOUR_MAGIC_7
}; };
bool MeleeAction::isUseful() bool MeleeAction::isUseful()
@@ -111,11 +100,6 @@ bool TogglePetSpellAutoCastAction::Execute(Event event)
toggled = true; toggled = true;
} }
} }
// Debug message if pet spells have been toggled and debug is enabled
if (toggled && sPlayerbotAIConfig->petChatCommandDebug == 1)
botAI->TellMaster("Pet autocast spells have been toggled.");
return toggled; return toggled;
} }
@@ -123,23 +107,22 @@ bool PetAttackAction::Execute(Event event)
{ {
Guardian* pet = bot->GetGuardianPet(); Guardian* pet = bot->GetGuardianPet();
if (!pet) if (!pet)
{
return false; return false;
}
// Do not attack if the pet's stance is set to "passive".
if (pet->GetReactState() == REACT_PASSIVE)
return false;
Unit* target = AI_VALUE(Unit*, "current target"); Unit* target = AI_VALUE(Unit*, "current target");
if (!target) if (!target)
{
return false; return false;
}
if (!bot->IsValidAttackTarget(target)) if (!bot->IsValidAttackTarget(target))
{
return false; return false;
}
// This section has been commented because it was overriding the pet->SetReactState(REACT_PASSIVE);
// pet's stance to "passive" every time the attack action was executed.
// pet->SetReactState(REACT_PASSIVE);
pet->ClearUnitState(UNIT_STATE_FOLLOW); pet->ClearUnitState(UNIT_STATE_FOLLOW);
pet->AttackStop(); pet->AttackStop();
pet->SetTarget(target->GetGUID()); pet->SetTarget(target->GetGUID());
@@ -153,76 +136,3 @@ bool PetAttackAction::Execute(Event event)
pet->ToCreature()->AI()->AttackStart(target); pet->ToCreature()->AI()->AttackStart(target);
return true; return true;
} }
bool SetPetStanceAction::Execute(Event /*event*/)
{
// Prepare a list to hold all controlled pet and guardian creatures
std::vector<Creature*> targets;
// Add the bot's main pet (if it exists) to the target list
Pet* pet = bot->GetPet();
if (pet)
targets.push_back(pet);
// Loop through all units controlled by the bot (could be pets, guardians, etc.)
for (Unit::ControlSet::const_iterator itr = bot->m_Controlled.begin(); itr != bot->m_Controlled.end(); ++itr)
{
// Only add creatures (skip players, vehicles, etc.)
Creature* creature = dynamic_cast<Creature*>(*itr);
if (!creature)
continue;
// Avoid adding the main pet twice
if (pet && creature == pet)
continue;
targets.push_back(creature);
}
// If there are no controlled pets or guardians, notify the player and exit
if (targets.empty())
{
botAI->TellError("You have no pet or guardian pet.");
return false;
}
// Get the default pet stance from the configuration
int32 stance = sPlayerbotAIConfig->defaultPetStance;
ReactStates react = REACT_DEFENSIVE;
std::string stanceText = "defensive (from config, fallback)";
// Map the config stance integer to a ReactStates value and a message
switch (stance)
{
case 0:
react = REACT_PASSIVE;
stanceText = "passive (from config)";
break;
case 1:
react = REACT_DEFENSIVE;
stanceText = "defensive (from config)";
break;
case 2:
react = REACT_AGGRESSIVE;
stanceText = "aggressive (from config)";
break;
default:
react = REACT_DEFENSIVE;
stanceText = "defensive (from config, fallback)";
break;
}
// Apply the stance to all target creatures (pets/guardians)
for (Creature* target : targets)
{
target->SetReactState(react);
CharmInfo* charmInfo = target->GetCharmInfo();
// If the creature has a CharmInfo, set the player-visible stance as well
if (charmInfo)
charmInfo->SetPlayerReactState(react);
}
// If debug is enabled in config, inform the master of the new stance
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
botAI->TellMaster("Pet stance set to " + stanceText + " (applied to all pets/guardians).");
return true;
}

View File

@@ -7,8 +7,6 @@
#define _PLAYERBOT_GENERICACTIONS_H #define _PLAYERBOT_GENERICACTIONS_H
#include "AttackAction.h" #include "AttackAction.h"
#include "Action.h"
#include "PlayerbotAI.h"
class PlayerbotAI; class PlayerbotAI;
@@ -35,12 +33,4 @@ public:
virtual bool Execute(Event event) override; virtual bool Execute(Event event) override;
}; };
class SetPetStanceAction : public Action
{
public:
SetPetStanceAction(PlayerbotAI* botAI) : Action(botAI, "set pet stance") {}
bool Execute(Event event) override;
};
#endif #endif

View File

@@ -76,7 +76,7 @@ bool GiveFoodAction::isUseful()
bool isRandomBot = GetTarget()->IsPlayer() && sRandomPlayerbotMgr->IsRandomBot((Player*)GetTarget()); bool isRandomBot = GetTarget()->IsPlayer() && sRandomPlayerbotMgr->IsRandomBot((Player*)GetTarget());
return !isRandomBot || (isRandomBot && !botAI->HasCheat(BotCheatMask::food)); return !isRandomBot || (isRandomBot && !sPlayerbotAIConfig->freeFood);
} }
Unit* GiveWaterAction::GetTarget() { return AI_VALUE(Unit*, "party member without water"); } Unit* GiveWaterAction::GetTarget() { return AI_VALUE(Unit*, "party member without water"); }
@@ -88,5 +88,5 @@ bool GiveWaterAction::isUseful()
bool isRandomBot = GetTarget()->IsPlayer() && sRandomPlayerbotMgr->IsRandomBot((Player*)GetTarget()); bool isRandomBot = GetTarget()->IsPlayer() && sRandomPlayerbotMgr->IsRandomBot((Player*)GetTarget());
return !isRandomBot || (isRandomBot && !botAI->HasCheat(BotCheatMask::food)); return !isRandomBot || (isRandomBot && !sPlayerbotAIConfig->freeFood);
} }

View File

@@ -2767,177 +2767,3 @@ bool MoveFromGroupAction::Execute(Event event)
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); return MoveFromGroup(distance);
} }
bool MoveAwayFromCreatureAction::Execute(Event event)
{
GuidVector targets = AI_VALUE(GuidVector, "nearest npcs");
Creature* nearestCreature = bot->FindNearestCreature(creatureId, range, alive);
// Find all creatures with the specified Id
std::vector<Unit*> creatures;
for (const auto& guid : targets)
{
Unit* unit = botAI->GetUnit(guid);
if (unit && (alive && unit->IsAlive()) && unit->GetEntry() == creatureId)
{
creatures.push_back(unit);
}
}
if (creatures.empty())
{
return false;
}
// Search for a safe position
const int directions = 8;
const float increment = 3.0f;
float bestX = bot->GetPositionX();
float bestY = bot->GetPositionY();
float bestZ = bot->GetPositionZ();
float maxSafetyScore = -1.0f;
for (int i = 0; i < directions; ++i)
{
float angle = (i * 2 * M_PI) / directions;
for (float distance = increment; distance <= 30.0f; distance += increment)
{
float moveX = bot->GetPositionX() + distance * cos(angle);
float moveY = bot->GetPositionY() + distance * sin(angle);
float moveZ = bot->GetPositionZ();
// Check creature distance constraints
bool isSafeFromCreatures = true;
float minCreatureDist = std::numeric_limits<float>::max();
for (Unit* creature : creatures)
{
float dist = creature->GetExactDist2d(moveX, moveY);
if (dist < range)
{
isSafeFromCreatures = false;
break;
}
if (dist < minCreatureDist)
{
minCreatureDist = dist;
}
}
if (isSafeFromCreatures && bot->IsWithinLOS(moveX, moveY, moveZ))
{
// A simple safety score: the minimum distance to any creature. Higher is better.
if (minCreatureDist > maxSafetyScore)
{
maxSafetyScore = minCreatureDist;
bestX = moveX;
bestY = moveY;
bestZ = moveZ;
}
}
}
}
// Move to the best position found
if (maxSafetyScore > 0.0f)
{
return MoveTo(bot->GetMapId(), bestX, bestY, bestZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT);
}
return false;
}
bool MoveAwayFromCreatureAction::isPossible()
{
return bot->CanFreeMove();
}
bool MoveAwayFromPlayerWithDebuffAction::Execute(Event event)
{
Player* closestPlayer = nullptr;
float minDistance = 0.0f;
Group* group = bot->GetGroup();
if (!group)
{
return false;
}
std::vector<Player*> debuffedPlayers;
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* player = gref->GetSource();
if (player && player->IsAlive() && player->HasAura(spellId))
{
debuffedPlayers.push_back(player);
}
}
if (debuffedPlayers.empty())
{
return false;
}
// Search for a safe position
const int directions = 8;
const float increment = 3.0f;
float bestX = bot->GetPositionX();
float bestY = bot->GetPositionY();
float bestZ = bot->GetPositionZ();
float maxSafetyScore = -1.0f;
for (int i = 0; i < directions; ++i)
{
float angle = (i * 2 * M_PI) / directions;
for (float distance = increment; distance <= (range + 5.0f); distance += increment)
{
float moveX = bot->GetPositionX() + distance * cos(angle);
float moveY = bot->GetPositionY() + distance * sin(angle);
float moveZ = bot->GetPositionZ();
// Check creature distance constraints
bool isSafeFromDebuffedPlayer = true;
float minDebuffedPlayerDistance = std::numeric_limits<float>::max();
for (Unit* debuffedPlayer : debuffedPlayers)
{
float dist = debuffedPlayer->GetExactDist2d(moveX, moveY);
if (dist < range)
{
isSafeFromDebuffedPlayer = false;
break;
}
if (dist < minDebuffedPlayerDistance)
{
minDebuffedPlayerDistance = dist;
}
}
if (isSafeFromDebuffedPlayer && bot->IsWithinLOS(moveX, moveY, moveZ))
{
// A simple safety score: the minimum distance to any debuffed player. Higher is better.
if (minDebuffedPlayerDistance > maxSafetyScore)
{
maxSafetyScore = minDebuffedPlayerDistance;
bestX = moveX;
bestY = moveY;
bestZ = moveZ;
}
}
}
}
// Move to the best position found
if (maxSafetyScore > 0.0f)
{
return MoveTo(bot->GetMapId(), bestX, bestY, bestZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true);
}
return false;
}
bool MoveAwayFromPlayerWithDebuffAction::isPossible()
{
return bot->CanFreeMove();
}

View File

@@ -294,34 +294,4 @@ public:
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
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) {}
bool Execute(Event event) override;
bool isPossible() override;
private:
uint32 creatureId;
float range;
bool alive;
};
class MoveAwayFromPlayerWithDebuffAction : public MovementAction
{
public:
MoveAwayFromPlayerWithDebuffAction(PlayerbotAI* botAI, std::string name, uint32 spellId, float range)
: MovementAction(botAI, name), spellId(spellId), range(range) {}
bool Execute(Event event) override;
bool isPossible() override;
private:
uint32 spellId;
float range;
bool alive;
};
#endif #endif

View File

@@ -17,7 +17,7 @@ bool DrinkAction::Execute(Event event)
if (!hasMana) if (!hasMana)
return false; return false;
if (botAI->HasCheat(BotCheatMask::food)) if (sPlayerbotAIConfig->freeFood)
{ {
// if (bot->IsNonMeleeSpellCast(true)) // if (bot->IsNonMeleeSpellCast(true))
// return false; // return false;
@@ -54,11 +54,11 @@ bool DrinkAction::Execute(Event event)
return UseItemAction::Execute(event); return UseItemAction::Execute(event);
} }
bool DrinkAction::isUseful() { return UseItemAction::isUseful() && AI_VALUE2(uint8, "mana", "self target") < 100; } bool DrinkAction::isUseful() { return UseItemAction::isUseful() && AI_VALUE2(uint8, "mana", "self target") < 85; }
bool DrinkAction::isPossible() bool DrinkAction::isPossible()
{ {
return !bot->IsInCombat() && (botAI->HasCheat(BotCheatMask::food) || UseItemAction::isPossible()); return !bot->IsInCombat() && (sPlayerbotAIConfig->freeFood || UseItemAction::isPossible());
} }
bool EatAction::Execute(Event event) bool EatAction::Execute(Event event)
@@ -66,7 +66,7 @@ bool EatAction::Execute(Event event)
if (bot->IsInCombat()) if (bot->IsInCombat())
return false; return false;
if (botAI->HasCheat(BotCheatMask::food)) if (sPlayerbotAIConfig->freeFood)
{ {
// if (bot->IsNonMeleeSpellCast(true)) // if (bot->IsNonMeleeSpellCast(true))
// return false; // return false;
@@ -107,5 +107,5 @@ bool EatAction::isUseful() { return UseItemAction::isUseful() && AI_VALUE2(uint8
bool EatAction::isPossible() bool EatAction::isPossible()
{ {
return !bot->IsInCombat() && (botAI->HasCheat(BotCheatMask::food) || UseItemAction::isPossible()); return !bot->IsInCombat() && (sPlayerbotAIConfig->freeFood || UseItemAction::isPossible());
} }

View File

@@ -8,7 +8,6 @@
#include "Event.h" #include "Event.h"
#include "ItemVisitors.h" #include "ItemVisitors.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "ItemPackets.h"
bool OutfitAction::Execute(Event event) bool OutfitAction::Execute(Event event)
{ {
@@ -71,9 +70,7 @@ bool OutfitAction::Execute(Event event)
WorldPacket packet(CMSG_AUTOSTORE_BAG_ITEM, 3); WorldPacket packet(CMSG_AUTOSTORE_BAG_ITEM, 3);
packet << bagIndex << slot << dstBag; packet << bagIndex << slot << dstBag;
WorldPackets::Item::AutoStoreBagItem nicePacket(std::move(packet)); bot->GetSession()->HandleAutoStoreBagItemOpcode(packet);
nicePacket.Read();
bot->GetSession()->HandleAutoStoreBagItemOpcode(nicePacket);
} }
EquipItems(outfit); EquipItems(outfit);

View File

@@ -4,254 +4,373 @@
*/ */
#include "PetAction.h" #include "PetAction.h"
#include <algorithm>
#include "CharmInfo.h" #include <iomanip>
#include "Creature.h" #include <sstream>
#include "CreatureAI.h"
#include "Pet.h" #include "Pet.h"
#include "SpellMgr.h"
#include "DBCStructure.h"
#include "Log.h"
#include "ObjectMgr.h"
#include "Player.h" #include "Player.h"
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
#include "SharedDefines.h" #include "PlayerbotFactory.h"
#include <random>
#include <cctype>
#include "WorldSession.h"
bool IsExoticPet(const CreatureTemplate* creature)
{
// Use the IsExotic() method from CreatureTemplate
return creature && creature->IsExotic();
}
bool HasBeastMastery(Player* bot)
{
// Beast Mastery talent aura ID for WotLK is 53270
return bot->HasAura(53270);
}
bool PetAction::Execute(Event event) bool PetAction::Execute(Event event)
{ {
// Extract the command parameter from the event (e.g., "aggressive", "defensive", "attack", etc.)
std::string param = event.getParam(); std::string param = event.getParam();
if (param.empty() && !defaultCmd.empty()) std::istringstream iss(param);
{ std::string mode, value;
param = defaultCmd; iss >> mode;
} std::getline(iss, value);
value.erase(0, value.find_first_not_of(" ")); // trim leading spaces
if (param.empty()) bool found = false;
{
// If no parameter is provided, show usage instructions and return.
botAI->TellError("Usage: pet <aggressive|defensive|passive|stance|attack|follow|stay>");
return false;
}
Player* bot = botAI->GetBot(); // Reset lastPetName/Id each time
lastPetName = "";
lastPetId = 0;
// Collect all controlled pets and guardians, except totems, into the targets vector. if (mode == "name" && !value.empty())
std::vector<Creature*> targets;
Pet* pet = bot->GetPet();
if (pet)
targets.push_back(pet);
for (Unit::ControlSet::const_iterator itr = bot->m_Controlled.begin(); itr != bot->m_Controlled.end(); ++itr)
{ {
Creature* creature = dynamic_cast<Creature*>(*itr); found = SetPetByName(value);
if (!creature)
continue;
if (pet && creature == pet)
continue;
if (creature->IsTotem())
continue;
targets.push_back(creature);
} }
else if (mode == "id" && !value.empty())
// If no pets or guardians are found, notify and return.
if (targets.empty())
{ {
botAI->TellError("You have no pet or guardian pet."); try
return false;
}
ReactStates react;
std::string stanceText;
// Handle stance commands: aggressive, defensive, or passive.
if (param == "aggressive")
{
react = REACT_AGGRESSIVE;
stanceText = "aggressive";
}
else if (param == "defensive")
{
react = REACT_DEFENSIVE;
stanceText = "defensive";
}
else if (param == "passive")
{
react = REACT_PASSIVE;
stanceText = "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"; uint32 id = std::stoul(value);
std::string name = target->GetName(); found = SetPetById(id);
std::string stance; }
switch (target->GetReactState()) catch (...)
{ {
case REACT_AGGRESSIVE: botAI->TellError("Invalid pet id.");
stance = "aggressive";
break;
case REACT_DEFENSIVE:
stance = "defensive";
break;
case REACT_PASSIVE:
stance = "passive";
break;
default:
stance = "unknown";
break;
}
botAI->TellMaster("Current stance of " + type + " \"" + name + "\": " + stance + ".");
} }
return true;
} }
// The "attack" command forces pets/guardians to attack the master's selected target. else if (mode == "family" && !value.empty())
else if (param == "attack")
{ {
// Try to get the master's selected target. found = SetPetByFamily(value);
Player* master = botAI->GetMaster();
Unit* targetUnit = nullptr;
if (master)
{
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.");
return false;
}
if (!targetUnit->IsAlive())
{
botAI->TellError("Target is not alive.");
return false;
}
if (!bot->IsValidAttackTarget(targetUnit))
{
botAI->TellError("Target is not a valid attack target for the bot.");
return false;
}
bool didAttack = false;
// For each controlled pet/guardian, command them to attack the selected target.
for (Creature* petCreature : targets)
{
CharmInfo* charmInfo = petCreature->GetCharmInfo();
if (!charmInfo)
continue;
petCreature->ClearUnitState(UNIT_STATE_FOLLOW);
// Only command attack if not already attacking the target, or if not currently under command attack.
if (petCreature->GetVictim() != targetUnit ||
(petCreature->GetVictim() == targetUnit && !charmInfo->IsCommandAttack()))
{
if (petCreature->GetVictim())
petCreature->AttackStop();
if (!petCreature->IsPlayer() && petCreature->ToCreature()->IsAIEnabled)
{
// For AI-enabled creatures (NPC pets/guardians): issue attack command and set flags.
charmInfo->SetIsCommandAttack(true);
charmInfo->SetIsAtStay(false);
charmInfo->SetIsFollowing(false);
charmInfo->SetIsCommandFollow(false);
charmInfo->SetIsReturning(false);
petCreature->ToCreature()->AI()->AttackStart(targetUnit);
didAttack = true;
}
else // For charmed player pets/guardians
{
if (petCreature->GetVictim() && petCreature->GetVictim() != targetUnit)
petCreature->AttackStop();
charmInfo->SetIsCommandAttack(true);
charmInfo->SetIsAtStay(false);
charmInfo->SetIsFollowing(false);
charmInfo->SetIsCommandFollow(false);
charmInfo->SetIsReturning(false);
petCreature->Attack(targetUnit, true);
didAttack = true;
}
}
}
// Inform the master if the command succeeded or failed.
if (didAttack && sPlayerbotAIConfig->petChatCommandDebug == 1)
botAI->TellMaster("Pet commanded to attack your target.");
else if (!didAttack)
botAI->TellError("Pet did not attack. (Already attacking or unable to attack target)");
return didAttack;
} }
// The "follow" command makes all pets/guardians follow the bot. else if (mode == "rename" && !value.empty())
else if (param == "follow")
{ {
botAI->PetFollow(); found = RenamePet(value);
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
botAI->TellMaster("Pet commanded to follow.");
return true;
} }
// The "stay" command causes all pets/guardians to stop and stay in place.
else if (param == "stay")
{
for (Creature* target : targets)
{
// If not already in controlled motion, stop movement and set to idle.
bool controlledMotion =
target->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE;
if (!controlledMotion)
{
target->StopMovingOnCurrentPos();
target->GetMotionMaster()->Clear(false);
target->GetMotionMaster()->MoveIdle();
}
CharmInfo* charmInfo = target->GetCharmInfo();
if (charmInfo)
{
// Set charm/pet state flags for "stay".
charmInfo->SetCommandState(COMMAND_STAY);
charmInfo->SetIsCommandAttack(false);
charmInfo->SetIsCommandFollow(false);
charmInfo->SetIsFollowing(false);
charmInfo->SetIsReturning(false);
charmInfo->SetIsAtStay(!controlledMotion);
charmInfo->SaveStayPosition(controlledMotion);
if (target->ToPet())
target->ToPet()->ClearCastWhenWillAvailable();
charmInfo->SetForcedSpell(0);
charmInfo->SetForcedTargetGUID();
}
}
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
botAI->TellMaster("Pet commanded to stay.");
return true;
}
// Unknown command: show usage instructions and return.
else else
{ {
botAI->TellError("Unknown pet command: " + param + botAI->TellError("Usage: pet name <name> | pet id <id> | pet family <family> | pet rename <new name> ");
". Use: pet <aggressive|defensive|passive|stance|attack|follow|stay>");
return false; return false;
} }
// For stance commands, apply the chosen stance to all targets. if (!found)
for (Creature* target : targets) return false;
{
target->SetReactState(react);
CharmInfo* charmInfo = target->GetCharmInfo();
if (charmInfo)
charmInfo->SetPlayerReactState(react);
}
// Inform the master of the new stance if debug is enabled. // For non-rename commands, initialize pet and give feedback
if (sPlayerbotAIConfig->petChatCommandDebug == 1) if (mode != "rename")
botAI->TellMaster("Pet stance set to " + stanceText + "."); {
Player* bot = botAI->GetBot();
PlayerbotFactory factory(bot, bot->GetLevel());
factory.InitPet();
factory.InitPetTalents();
if (!lastPetName.empty() && lastPetId != 0)
{
std::ostringstream oss;
oss << "Pet changed to " << lastPetName << ", ID: " << lastPetId << ".";
botAI->TellMaster(oss.str());
}
else
{
botAI->TellMaster("Pet changed and initialized!");
}
}
return true;
}
bool PetAction::SetPetByName(const std::string& name)
{
// Convert the input to lowercase for case-insensitive comparison
std::string lowerName = name;
std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower);
CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates();
Player* bot = botAI->GetBot();
for (auto itr = creatures->begin(); itr != creatures->end(); ++itr)
{
const CreatureTemplate& creature = itr->second;
std::string creatureName = creature.Name;
std::transform(creatureName.begin(), creatureName.end(), creatureName.begin(), ::tolower);
// Only match if names match (case-insensitive)
if (creatureName == lowerName)
{
// Check if the pet is tameable at all
if (!creature.IsTameable(true))
continue;
// Exotic pet check with talent requirement
if (IsExoticPet(&creature) && !HasBeastMastery(bot))
{
botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent.");
return false;
}
// Final tameable check based on hunter's actual ability
if (!creature.IsTameable(bot->CanTameExoticPets()))
continue;
lastPetName = creature.Name;
lastPetId = creature.Entry;
return CreateAndSetPet(creature.Entry);
}
}
botAI->TellError("No tameable pet found with name: " + name);
return false;
}
bool PetAction::SetPetById(uint32 id)
{
CreatureTemplate const* creature = sObjectMgr->GetCreatureTemplate(id);
Player* bot = botAI->GetBot();
if (creature)
{
// Check if the pet is tameable at all
if (!creature->IsTameable(true))
{
botAI->TellError("No tameable pet found with id: " + std::to_string(id));
return false;
}
// Exotic pet check with talent requirement
if (IsExoticPet(creature) && !HasBeastMastery(bot))
{
botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent.");
return false;
}
// Final tameable check based on hunter's actual ability
if (!creature->IsTameable(bot->CanTameExoticPets()))
{
botAI->TellError("No tameable pet found with id: " + std::to_string(id));
return false;
}
lastPetName = creature->Name;
lastPetId = creature->Entry;
return CreateAndSetPet(creature->Entry);
}
botAI->TellError("No tameable pet found with id: " + std::to_string(id));
return false;
}
bool PetAction::SetPetByFamily(const std::string& family)
{
std::string lowerFamily = family;
std::transform(lowerFamily.begin(), lowerFamily.end(), lowerFamily.begin(), ::tolower);
CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates();
Player* bot = botAI->GetBot();
std::vector<const CreatureTemplate*> candidates;
bool foundExotic = false;
for (auto itr = creatures->begin(); itr != creatures->end(); ++itr)
{
const CreatureTemplate& creature = itr->second;
if (!creature.IsTameable(true)) // allow exotics for search
continue;
CreatureFamilyEntry const* familyEntry = sCreatureFamilyStore.LookupEntry(creature.family);
if (!familyEntry)
continue;
std::string familyName = familyEntry->Name[0];
std::transform(familyName.begin(), familyName.end(), familyName.begin(), ::tolower);
if (familyName != lowerFamily)
continue;
// Exotic/BM check
if (IsExoticPet(&creature))
{
foundExotic = true;
if (!HasBeastMastery(bot))
continue;
}
if (!creature.IsTameable(bot->CanTameExoticPets()))
continue;
candidates.push_back(&creature);
}
if (candidates.empty())
{
if (foundExotic && !HasBeastMastery(bot))
botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent.");
else
botAI->TellError("No tameable pet found with family: " + family);
return false;
}
// Randomly select one from candidates
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, candidates.size() - 1);
const CreatureTemplate* selected = candidates[dis(gen)];
lastPetName = selected->Name;
lastPetId = selected->Entry;
return CreateAndSetPet(selected->Entry);
}
bool PetAction::RenamePet(const std::string& newName)
{
Player* bot = botAI->GetBot();
Pet* pet = bot->GetPet();
if (!pet)
{
botAI->TellError("You have no pet to rename.");
return false;
}
// Length check (WoW max pet name is 12 characters)
if (newName.empty() || newName.length() > 12)
{
botAI->TellError("Pet name must be between 1 and 12 alphabetic characters.");
return false;
}
// Alphabetic character check
for (char c : newName)
{
if (!std::isalpha(static_cast<unsigned char>(c)))
{
botAI->TellError("Pet name must only contain alphabetic characters (A-Z, a-z).");
return false;
}
}
// Normalize case: capitalize first letter, lower the rest
std::string normalized = newName;
normalized[0] = std::toupper(normalized[0]);
for (size_t i = 1; i < normalized.size(); ++i)
normalized[i] = std::tolower(normalized[i]);
// Forbidden name check
if (sObjectMgr->IsReservedName(normalized))
{
botAI->TellError("That pet name is forbidden. Please choose another name.");
return false;
}
// Set the pet's name, save to DB, and send instant client update
pet->SetName(normalized);
pet->SavePetToDB(PET_SAVE_AS_CURRENT);
bot->GetSession()->SendPetNameQuery(pet->GetGUID(), pet->GetEntry());
botAI->TellMaster("Your pet has been renamed to " + normalized + "!");
botAI->TellMaster("If you do not see the new name, please dismiss and recall your pet.");
// Dismiss pet
bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT, true);
// Recall pet using Hunter's Call Pet spell (spellId 883)
if (bot->getClass() == CLASS_HUNTER && bot->HasSpell(883))
{
bot->CastSpell(bot, 883, true);
}
return true;
}
bool PetAction::CreateAndSetPet(uint32 creatureEntry)
{
Player* bot = botAI->GetBot();
if (bot->getClass() != CLASS_HUNTER || bot->GetLevel() < 10)
{
botAI->TellError("Only level 10+ hunters can have pets.");
return false;
}
CreatureTemplate const* creature = sObjectMgr->GetCreatureTemplate(creatureEntry);
if (!creature)
{
botAI->TellError("Creature template not found.");
return false;
}
// Remove current pet(s)
if (bot->GetPetStable() && bot->GetPetStable()->CurrentPet)
{
bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT);
bot->RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT);
}
if (bot->GetPetStable() && bot->GetPetStable()->GetUnslottedHunterPet())
{
bot->GetPetStable()->UnslottedPets.clear();
bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT);
bot->RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT);
}
// Actually create the new pet
Pet* pet = bot->CreateTamedPetFrom(creatureEntry, 0);
if (!pet)
{
botAI->TellError("Failed to create pet.");
return false;
}
// Set pet level and add to world
pet->SetUInt32Value(UNIT_FIELD_LEVEL, bot->GetLevel() - 1);
pet->GetMap()->AddToMap(pet->ToCreature());
pet->SetUInt32Value(UNIT_FIELD_LEVEL, bot->GetLevel());
bot->SetMinion(pet, true);
pet->InitTalentForLevel();
pet->SavePetToDB(PET_SAVE_AS_CURRENT);
bot->PetSpellInitialize();
// Set stats
pet->InitStatsForLevel(bot->GetLevel());
pet->SetLevel(bot->GetLevel());
pet->SetPower(POWER_HAPPINESS, pet->GetMaxPower(Powers(POWER_HAPPINESS)));
pet->SetHealth(pet->GetMaxHealth());
// Enable autocast for active spells
for (PetSpellMap::const_iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr)
{
if (itr->second.state == PETSPELL_REMOVED)
continue;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first);
if (!spellInfo)
continue;
if (spellInfo->IsPassive())
continue;
pet->ToggleAutocast(spellInfo, true);
}
return true; return true;
} }

View File

@@ -10,20 +10,25 @@
#include "Action.h" #include "Action.h"
#include "PlayerbotFactory.h" #include "PlayerbotFactory.h"
#include "Unit.h"
class PlayerbotAI; class PlayerbotAI;
class PetAction : public Action class PetAction : public Action
{ {
public: public:
PetAction(PlayerbotAI* botAI, const std::string& defaultCmd = "") : Action(botAI, "pet"), defaultCmd(defaultCmd) {} PetAction(PlayerbotAI* botAI) : Action(botAI, "pet") {}
bool Execute(Event event) override; bool Execute(Event event) override;
private: private:
bool warningEnabled = true; bool SetPetByName(const std::string& name);
std::string defaultCmd; bool SetPetById(uint32 id);
}; bool SetPetByFamily(const std::string& family);
bool RenamePet(const std::string& newName);
bool CreateAndSetPet(uint32 creatureEntry);
std::string lastPetName;
uint32 lastPetId = 0;
};
#endif #endif

View File

@@ -9,7 +9,6 @@
#include "ItemUsageValue.h" #include "ItemUsageValue.h"
#include "ItemVisitors.h" #include "ItemVisitors.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "ItemPackets.h"
class SellItemsVisitor : public IterateItemsVisitor class SellItemsVisitor : public IterateItemsVisitor
{ {
@@ -115,12 +114,9 @@ void SellAction::Sell(Item* item)
uint32 botMoney = bot->GetMoney(); uint32 botMoney = bot->GetMoney();
WorldPacket p(CMSG_SELL_ITEM); WorldPacket p;
p << vendorguid << itemguid << count; p << vendorguid << itemguid << count;
bot->GetSession()->HandleSellItemOpcode(p);
WorldPackets::Item::SellItem nicePacket(std::move(p));
nicePacket.Read();
bot->GetSession()->HandleSellItemOpcode(nicePacket);
if (botAI->HasCheat(BotCheatMask::gold)) if (botAI->HasCheat(BotCheatMask::gold))
{ {

View File

@@ -1,500 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#include "TameAction.h"
#include <algorithm>
#include <cctype>
#include <iomanip>
#include <random>
#include <set>
#include <sstream>
#include "DBCStructure.h"
#include "Log.h"
#include "ObjectMgr.h"
#include "Pet.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "PlayerbotFactory.h"
#include "SpellMgr.h"
#include "WorldSession.h"
bool IsExoticPet(const CreatureTemplate* creature)
{
// Use the IsExotic() method from CreatureTemplate
return creature && creature->IsExotic();
}
bool HasBeastMastery(Player* bot)
{
// Beast Mastery talent aura ID for WotLK is 53270
return bot->HasAura(53270);
}
bool TameAction::Execute(Event event)
{
// Parse the user's input command into mode and value (e.g. "name wolf", "id 1234", etc.)
std::string param = event.getParam();
std::istringstream iss(param);
std::string mode, value;
iss >> mode;
std::getline(iss, value);
value.erase(0, value.find_first_not_of(" ")); // Remove leading spaces from value
bool found = false;
// Reset any previous pet name/id state
lastPetName = "";
lastPetId = 0;
// If the command is "family" with no value, list all available pet families
if (mode == "family" && value.empty())
{
std::set<std::string> normalFamilies;
std::set<std::string> exoticFamilies;
Player* bot = botAI->GetBot();
// Loop over all creature templates and collect tameable families
CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates();
for (auto itr = creatures->begin(); itr != creatures->end(); ++itr)
{
const CreatureTemplate& creature = itr->second;
if (!creature.IsTameable(true))
continue;
CreatureFamilyEntry const* familyEntry = sCreatureFamilyStore.LookupEntry(creature.family);
if (!familyEntry)
continue;
std::string familyName = familyEntry->Name[0];
if (familyName.empty())
continue;
if (creature.IsExotic())
exoticFamilies.insert(familyName);
else
normalFamilies.insert(familyName);
}
// Build the output message for the user
std::ostringstream oss;
oss << "Available pet families: ";
size_t count = 0;
for (const auto& name : normalFamilies)
{
if (count++ != 0)
oss << ", ";
oss << name;
}
if (!exoticFamilies.empty())
{
if (!normalFamilies.empty())
oss << " | ";
oss << "Exotic: ";
count = 0;
for (const auto& name : exoticFamilies)
{
if (count++ != 0)
oss << ", ";
oss << name;
}
}
botAI->TellError(oss.str());
return true;
}
// Handle "tame abandon" command to give up your current pet
if (mode == "abandon")
{
return AbandonPet();
}
// Try to process the command based on mode and value
if (mode == "name" && !value.empty())
{
found = SetPetByName(value);
}
else if (mode == "id" && !value.empty())
{
// Try to convert value to an integer and set pet by ID
try
{
uint32 id = std::stoul(value);
found = SetPetById(id);
}
catch (...)
{
botAI->TellError("Invalid tame id.");
}
}
else if (mode == "family" && !value.empty())
{
found = SetPetByFamily(value);
}
else if (mode == "rename" && !value.empty())
{
found = RenamePet(value);
}
else
{
// Unrecognized command or missing argument; show usage
botAI->TellError(
"Usage: tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon");
return false;
}
// If the requested tame/rename failed, return failure
if (!found)
return false;
// For all non-rename commands, initialize the new pet and talents, then notify the master
if (mode != "rename")
{
Player* bot = botAI->GetBot();
PlayerbotFactory factory(bot, bot->GetLevel());
factory.InitPet();
factory.InitPetTalents();
if (!lastPetName.empty() && lastPetId != 0)
{
std::ostringstream oss;
oss << "Pet changed to " << lastPetName << ", ID: " << lastPetId << ".";
botAI->TellMaster(oss.str());
}
else
{
botAI->TellMaster("Pet changed and initialized!");
}
}
return true;
}
bool TameAction::SetPetByName(const std::string& name)
{
// Make a lowercase copy of the input name for case-insensitive comparison
std::string lowerName = name;
std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower);
// Get the full list of creature templates from the object manager
CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates();
Player* bot = botAI->GetBot();
// Iterate through all creature templates
for (auto itr = creatures->begin(); itr != creatures->end(); ++itr)
{
const CreatureTemplate& creature = itr->second;
std::string creatureName = creature.Name;
// Convert creature's name to lowercase for comparison
std::transform(creatureName.begin(), creatureName.end(), creatureName.begin(), ::tolower);
// If the input name matches this creature's name
if (creatureName == lowerName)
{
// Skip if the creature isn't tameable at all
if (!creature.IsTameable(true))
continue;
// If the creature is exotic and the bot doesn't have Beast Mastery, show error and fail
if (IsExoticPet(&creature) && !HasBeastMastery(bot))
{
botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent.");
return false;
}
// Skip if the creature isn't tameable by this bot (respecting exotic pet rules)
if (!creature.IsTameable(bot->CanTameExoticPets()))
continue;
// Store the found pet's name and entry ID for later use/feedback
lastPetName = creature.Name;
lastPetId = creature.Entry;
// Create and set this pet for the bot
return CreateAndSetPet(creature.Entry);
}
}
// If no suitable pet found, show an error and return failure
botAI->TellError("No tameable pet found with name: " + name);
return false;
}
bool TameAction::SetPetById(uint32 id)
{
// Look up the creature template by its numeric entry/id
CreatureTemplate const* creature = sObjectMgr->GetCreatureTemplate(id);
Player* bot = botAI->GetBot();
// Proceed only if a valid creature was found
if (creature)
{
// Check if this creature is ever tameable (ignore bot's own restrictions for now)
if (!creature->IsTameable(true))
{
// If not tameable at all, show an error and fail
botAI->TellError("No tameable pet found with id: " + std::to_string(id));
return false;
}
// If it's an exotic pet, make sure the bot has the Beast Mastery talent
if (IsExoticPet(creature) && !HasBeastMastery(bot))
{
botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent.");
return false;
}
// Check if the bot is actually allowed to tame this pet (honoring exotic pet rules)
if (!creature->IsTameable(bot->CanTameExoticPets()))
{
botAI->TellError("No tameable pet found with id: " + std::to_string(id));
return false;
}
// Remember this pet's name and id for later feedback
lastPetName = creature->Name;
lastPetId = creature->Entry;
// Set and create the pet for the bot
return CreateAndSetPet(creature->Entry);
}
// If no valid creature was found by id, show an error
botAI->TellError("No tameable pet found with id: " + std::to_string(id));
return false;
}
bool TameAction::SetPetByFamily(const std::string& family)
{
// Convert the input family name to lowercase for case-insensitive comparison
std::string lowerFamily = family;
std::transform(lowerFamily.begin(), lowerFamily.end(), lowerFamily.begin(), ::tolower);
// Get all creature templates from the object manager
CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates();
Player* bot = botAI->GetBot();
// Prepare a list of candidate creatures and track if any exotic pet is found
std::vector<const CreatureTemplate*> candidates;
bool foundExotic = false;
// Iterate through all creature templates
for (auto itr = creatures->begin(); itr != creatures->end(); ++itr)
{
const CreatureTemplate& creature = itr->second;
// Skip if this creature is never tameable
if (!creature.IsTameable(true))
continue;
// Look up the family entry for this creature
CreatureFamilyEntry const* familyEntry = sCreatureFamilyStore.LookupEntry(creature.family);
if (!familyEntry)
continue;
// Compare the family name in a case-insensitive way
std::string familyName = familyEntry->Name[0];
std::transform(familyName.begin(), familyName.end(), familyName.begin(), ::tolower);
if (familyName != lowerFamily)
continue;
// If the creature is exotic, check Beast Mastery talent requirements
if (IsExoticPet(&creature))
{
foundExotic = true;
if (!HasBeastMastery(bot))
continue;
}
// Only add as candidate if this bot is allowed to tame it (including exotic rules)
if (!creature.IsTameable(bot->CanTameExoticPets()))
continue;
candidates.push_back(&creature);
}
// If no candidates found, inform the user of the reason and return false
if (candidates.empty())
{
if (foundExotic && !HasBeastMastery(bot))
botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent.");
else
botAI->TellError("No tameable pet found with family: " + family);
return false;
}
// Randomly select one candidate from the list to tame
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, candidates.size() - 1);
const CreatureTemplate* selected = candidates[dis(gen)];
// Save the selected pet's name and id for feedback
lastPetName = selected->Name;
lastPetId = selected->Entry;
// Attempt to create and set the new pet for the bot
return CreateAndSetPet(selected->Entry);
}
bool TameAction::RenamePet(const std::string& newName)
{
Player* bot = botAI->GetBot();
Pet* pet = bot->GetPet();
// Check if the bot currently has a pet
if (!pet)
{
botAI->TellError("You have no pet to rename.");
return false;
}
// Validate the new name: must not be empty and max 12 characters
if (newName.empty() || newName.length() > 12)
{
botAI->TellError("Pet name must be between 1 and 12 alphabetic characters.");
return false;
}
// Ensure all characters in the new name are alphabetic
for (char c : newName)
{
if (!std::isalpha(static_cast<unsigned char>(c)))
{
botAI->TellError("Pet name must only contain alphabetic characters (A-Z, a-z).");
return false;
}
}
// Normalize the name: capitalize the first letter, lowercase the rest
std::string normalized = newName;
normalized[0] = std::toupper(normalized[0]);
for (size_t i = 1; i < normalized.size(); ++i)
normalized[i] = std::tolower(normalized[i]);
// Check if the new name is reserved or forbidden
if (sObjectMgr->IsReservedName(normalized))
{
botAI->TellError("That pet name is forbidden. Please choose another name.");
return false;
}
// Set the pet's name and save it to the database
pet->SetName(normalized);
pet->SavePetToDB(PET_SAVE_AS_CURRENT);
bot->GetSession()->SendPetNameQuery(pet->GetGUID(), pet->GetEntry());
// Notify the master about the rename and give a tip to update the client name display
botAI->TellMaster("Your pet has been renamed to " + normalized + "!");
botAI->TellMaster("If you do not see the new name, please dismiss and recall your pet.");
// Remove the current pet and (re-)cast Call Pet spell if the bot is a hunter
bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT, true);
if (bot->getClass() == CLASS_HUNTER && bot->HasSpell(883))
{
bot->CastSpell(bot, 883, true);
}
return true;
}
bool TameAction::CreateAndSetPet(uint32 creatureEntry)
{
Player* bot = botAI->GetBot();
// Ensure the player is a hunter and at least level 10 (required for pets)
if (bot->getClass() != CLASS_HUNTER || bot->GetLevel() < 10)
{
botAI->TellError("Only level 10+ hunters can have pets.");
return false;
}
// Retrieve the creature template for the given entry (pet species info)
CreatureTemplate const* creature = sObjectMgr->GetCreatureTemplate(creatureEntry);
if (!creature)
{
botAI->TellError("Creature template not found.");
return false;
}
// If the bot already has a current pet or an unslotted pet, remove them to avoid conflicts
if (bot->GetPetStable() && bot->GetPetStable()->CurrentPet)
{
bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT);
bot->RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT);
}
if (bot->GetPetStable() && bot->GetPetStable()->GetUnslottedHunterPet())
{
bot->GetPetStable()->UnslottedPets.clear();
bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT);
bot->RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT);
}
// Create the new tamed pet from the specified creature entry
Pet* pet = bot->CreateTamedPetFrom(creatureEntry, 0);
if (!pet)
{
botAI->TellError("Failed to create pet.");
return false;
}
// Set the pet's level to one below the bot's current level, then add to the map and set to full level
pet->SetUInt32Value(UNIT_FIELD_LEVEL, bot->GetLevel() - 1);
pet->GetMap()->AddToMap(pet->ToCreature());
pet->SetUInt32Value(UNIT_FIELD_LEVEL, bot->GetLevel());
// Set the pet as the bot's active minion
bot->SetMinion(pet, true);
// Initialize talents appropriate for the pet's level
pet->InitTalentForLevel();
// Save pet to the database as the current pet
pet->SavePetToDB(PET_SAVE_AS_CURRENT);
// Initialize available pet spells
bot->PetSpellInitialize();
// Further initialize pet stats to match the bot's level
pet->InitStatsForLevel(bot->GetLevel());
pet->SetLevel(bot->GetLevel());
// Set happiness and health of the pet to maximum values
pet->SetPower(POWER_HAPPINESS, pet->GetMaxPower(Powers(POWER_HAPPINESS)));
pet->SetHealth(pet->GetMaxHealth());
// Enable autocast for all active (not removed) non-passive spells the pet knows
for (PetSpellMap::const_iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr)
{
if (itr->second.state == PETSPELL_REMOVED)
continue;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first);
if (!spellInfo)
continue;
if (spellInfo->IsPassive())
continue;
pet->ToggleAutocast(spellInfo, true);
}
return true;
}
bool TameAction::AbandonPet()
{
// Get the bot player and its current pet (if any)
Player* bot = botAI->GetBot();
Pet* pet = bot->GetPet();
// Check if the bot has a pet and that it is a hunter pet
if (pet && pet->getPetType() == HUNTER_PET)
{
// Remove the pet from the bot and mark it as deleted in the database
bot->RemovePet(pet, PET_SAVE_AS_DELETED);
// Inform the bot's master/player that the pet was abandoned
botAI->TellMaster("Your pet has been abandoned.");
return true;
}
else
{
// If there is no hunter pet, show an error message
botAI->TellError("You have no hunter pet to abandon.");
return false;
}
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_TAMEACTION_H
#define _PLAYERBOT_TAMEACTION_H
#include <string>
#include "Action.h"
#include "PlayerbotFactory.h"
class PlayerbotAI;
class TameAction : public Action
{
public:
TameAction(PlayerbotAI* botAI) : Action(botAI, "tame") {}
bool Execute(Event event) override;
private:
bool SetPetByName(const std::string& name);
bool SetPetById(uint32 id);
bool SetPetByFamily(const std::string& family);
bool RenamePet(const std::string& newName);
bool CreateAndSetPet(uint32 creatureEntry);
bool AbandonPet();
std::string lastPetName;
uint32 lastPetId = 0;
};
#endif

View File

@@ -8,8 +8,6 @@
#include "Event.h" #include "Event.h"
#include "ItemCountValue.h" #include "ItemCountValue.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "WorldSession.h"
#include "ItemPackets.h"
std::vector<std::string> split(std::string const s, char delim); std::vector<std::string> split(std::string const s, char delim);
@@ -72,9 +70,7 @@ void UnequipAction::UnequipItem(Item* item)
WorldPacket packet(CMSG_AUTOSTORE_BAG_ITEM, 3); WorldPacket packet(CMSG_AUTOSTORE_BAG_ITEM, 3);
packet << bagIndex << slot << dstBag; packet << bagIndex << slot << dstBag;
WorldPackets::Item::AutoStoreBagItem nicePacket(std::move(packet)); bot->GetSession()->HandleAutoStoreBagItemOpcode(packet);
nicePacket.Read();
bot->GetSession()->HandleAutoStoreBagItemOpcode(nicePacket);
std::ostringstream out; std::ostringstream out;
out << chat->FormatItem(item->GetTemplate()) << " unequipped"; out << chat->FormatItem(item->GetTemplate()) << " unequipped";

View File

@@ -9,7 +9,6 @@
#include "Event.h" #include "Event.h"
#include "ItemUsageValue.h" #include "ItemUsageValue.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "ItemPackets.h"
bool UseItemAction::Execute(Event event) bool UseItemAction::Execute(Event event)
{ {
@@ -325,8 +324,8 @@ void UseItemAction::TellConsumableUse(Item* item, std::string const action, floa
bool UseItemAction::SocketItem(Item* item, Item* gem, bool replace) bool UseItemAction::SocketItem(Item* item, Item* gem, bool replace)
{ {
WorldPacket packet(CMSG_SOCKET_GEMS); WorldPacket* const packet = new WorldPacket(CMSG_SOCKET_GEMS);
packet << item->GetGUID(); *packet << item->GetGUID();
bool fits = false; bool fits = false;
for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot < SOCK_ENCHANTMENT_SLOT + MAX_GEM_SOCKETS; for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot < SOCK_ENCHANTMENT_SLOT + MAX_GEM_SOCKETS;
@@ -338,14 +337,14 @@ bool UseItemAction::SocketItem(Item* item, Item* gem, bool replace)
{ {
if (fits) if (fits)
{ {
packet << ObjectGuid::Empty; *packet << ObjectGuid::Empty;
continue; continue;
} }
uint32 enchant_id = item->GetEnchantmentId(EnchantmentSlot(enchant_slot)); uint32 enchant_id = item->GetEnchantmentId(EnchantmentSlot(enchant_slot));
if (!enchant_id) if (!enchant_id)
{ {
packet << gem->GetGUID(); *packet << gem->GetGUID();
fits = true; fits = true;
continue; continue;
} }
@@ -353,20 +352,20 @@ bool UseItemAction::SocketItem(Item* item, Item* gem, bool replace)
SpellItemEnchantmentEntry const* enchantEntry = sSpellItemEnchantmentStore.LookupEntry(enchant_id); SpellItemEnchantmentEntry const* enchantEntry = sSpellItemEnchantmentStore.LookupEntry(enchant_id);
if (!enchantEntry || !enchantEntry->GemID) if (!enchantEntry || !enchantEntry->GemID)
{ {
packet << gem->GetGUID(); *packet << gem->GetGUID();
fits = true; fits = true;
continue; continue;
} }
if (replace && enchantEntry->GemID != gem->GetTemplate()->ItemId) if (replace && enchantEntry->GemID != gem->GetTemplate()->ItemId)
{ {
packet << gem->GetGUID(); *packet << gem->GetGUID();
fits = true; fits = true;
continue; continue;
} }
} }
packet << ObjectGuid::Empty; *packet << ObjectGuid::Empty;
} }
if (fits) if (fits)
@@ -376,9 +375,7 @@ bool UseItemAction::SocketItem(Item* item, Item* gem, bool replace)
out << " with " << chat->FormatItem(gem->GetTemplate()); out << " with " << chat->FormatItem(gem->GetTemplate());
botAI->TellMaster(out); botAI->TellMaster(out);
WorldPackets::Item::SocketGems nicePacket(std::move(packet)); bot->GetSession()->HandleSocketOpcode(*packet);
nicePacket.Read();
bot->GetSession()->HandleSocketOpcode(nicePacket);
} }
return fits; return fits;

View File

@@ -41,7 +41,6 @@
#include "UseMeetingStoneAction.h" #include "UseMeetingStoneAction.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "ReleaseSpiritAction.h" #include "ReleaseSpiritAction.h"
#include "PetAction.h"
class PlayerbotAI; class PlayerbotAI;
@@ -71,7 +70,6 @@ public:
creators["trade status extended"] = &WorldPacketActionContext::trade_status_extended; creators["trade status extended"] = &WorldPacketActionContext::trade_status_extended;
creators["store loot"] = &WorldPacketActionContext::store_loot; creators["store loot"] = &WorldPacketActionContext::store_loot;
creators["self resurrect"] = &WorldPacketActionContext::self_resurrect; creators["self resurrect"] = &WorldPacketActionContext::self_resurrect;
creators["pet"] = &WorldPacketActionContext::pet;
// quest // quest
creators["talk to quest giver"] = &WorldPacketActionContext::turn_in_quest; creators["talk to quest giver"] = &WorldPacketActionContext::turn_in_quest;
@@ -141,7 +139,6 @@ private:
static Action* tell_not_enough_reputation(PlayerbotAI* botAI) { return new TellMasterAction(botAI, "Not enough reputation"); } static Action* tell_not_enough_reputation(PlayerbotAI* botAI) { return new TellMasterAction(botAI, "Not enough reputation"); }
static Action* tell_cannot_equip(PlayerbotAI* botAI) { return new InventoryChangeFailureAction(botAI); } static Action* tell_cannot_equip(PlayerbotAI* botAI) { return new InventoryChangeFailureAction(botAI); }
static Action* self_resurrect(PlayerbotAI* botAI) { return new SelfResurrectAction(botAI); } static Action* self_resurrect(PlayerbotAI* botAI) { return new SelfResurrectAction(botAI); }
static Action* pet(PlayerbotAI* botAI) { return new PetAction(botAI); }
// quest // quest
static Action* quest_update_add_kill(PlayerbotAI* ai) { return new QuestUpdateAddKillAction(ai); } static Action* quest_update_add_kill(PlayerbotAI* ai) { return new QuestUpdateAddKillAction(ai); }

View File

@@ -50,9 +50,7 @@ void GenericDKNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
triggers.push_back( triggers.push_back(
new TriggerNode("bone shield", NextAction::array(0, new NextAction("bone shield", 21.0f), nullptr))); new TriggerNode("bone shield", NextAction::array(0, new NextAction("bone shield", 21.0f), nullptr)));
triggers.push_back( triggers.push_back(
new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), NULL))); new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 11.0f), NULL)));
triggers.push_back(
new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), NULL)));
} }
void DKBuffDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void DKBuffDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)

View File

@@ -171,10 +171,6 @@ void GenericDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
// NextAction::array(0, new NextAction("anti magic zone", ACTION_EMERGENCY + 1), nullptr))); // NextAction::array(0, new NextAction("anti magic zone", ACTION_EMERGENCY + 1), nullptr)));
triggers.push_back( triggers.push_back(
new TriggerNode("no pet", NextAction::array(0, new NextAction("raise dead", ACTION_NORMAL + 5), nullptr))); new TriggerNode("no pet", NextAction::array(0, new NextAction("raise dead", ACTION_NORMAL + 5), nullptr)));
triggers.push_back(
new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr)));
triggers.push_back(
new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr)));
triggers.push_back( triggers.push_back(
new TriggerNode("mind freeze", NextAction::array(0, new NextAction("mind freeze", ACTION_HIGH + 1), nullptr))); new TriggerNode("mind freeze", NextAction::array(0, new NextAction("mind freeze", ACTION_HIGH + 1), nullptr)));
triggers.push_back( triggers.push_back(

View File

@@ -119,42 +119,6 @@ void GenericDruidNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trig
// triggers.push_back(new TriggerNode("low mana", NextAction::array(0, new NextAction("innervate", ACTION_EMERGENCY // triggers.push_back(new TriggerNode("low mana", NextAction::array(0, new NextAction("innervate", ACTION_EMERGENCY
// + 5), nullptr))); triggers.push_back(new TriggerNode("swimming", NextAction::array(0, new NextAction("aquatic // + 5), nullptr))); triggers.push_back(new TriggerNode("swimming", NextAction::array(0, new NextAction("aquatic
// form", 1.0f), nullptr))); // form", 1.0f), nullptr)));
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply oil", 1.0f), nullptr)));
triggers.push_back(
new TriggerNode("party member critical health",
NextAction::array(0,
new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 7),
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 6),
new NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 5),
nullptr)));
triggers.push_back(
new TriggerNode("party member low health",
NextAction::array(0,
new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 5),
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 4),
new NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 3),
nullptr)));
triggers.push_back(
new TriggerNode("party member medium health",
NextAction::array(0, new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 3),
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 2),
new NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 1),
nullptr)));
triggers.push_back(
new TriggerNode("party member almost full health",
NextAction::array(0, new NextAction("wild growth on party", ACTION_LIGHT_HEAL + 3), new NextAction("rejuvenation on party", ACTION_LIGHT_HEAL + 2), NULL)));
triggers.push_back(
new TriggerNode("party member remove curse",
NextAction::array(0, new NextAction("remove curse on party", ACTION_DISPEL + 7), nullptr)));
triggers.push_back(
new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr)));
triggers.push_back(new TriggerNode("party member critical health", NextAction::array(0, triggers.push_back(new TriggerNode("party member critical health", NextAction::array(0,
new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 7), new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 7),
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 6), new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 6),
@@ -183,7 +147,6 @@ void GenericDruidNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trig
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply oil", 1.0f), nullptr))); triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply oil", 1.0f), nullptr)));
if (specTab == 1) // Feral if (specTab == 1) // Feral
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply stone", 1.0f), nullptr))); triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply stone", 1.0f), nullptr)));
} }
GenericDruidBuffStrategy::GenericDruidBuffStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI) GenericDruidBuffStrategy::GenericDruidBuffStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI)

View File

@@ -122,8 +122,7 @@ void GenericDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode("combat party member dead", triggers.push_back(new TriggerNode("combat party member dead",
NextAction::array(0, new NextAction("rebirth", ACTION_HIGH + 9), NULL))); NextAction::array(0, new NextAction("rebirth", ACTION_HIGH + 9), NULL)));
triggers.push_back(new TriggerNode("being attacked", triggers.push_back(new TriggerNode("being attacked",
NextAction::array(0, new NextAction("nature's grasp", ACTION_HIGH + 1), nullptr))); NextAction::array(0, new NextAction("nature's grasp", ACTION_HIGH + 1), nullptr)));
triggers.push_back(new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr)));
} }
void DruidCureStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void DruidCureStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)

View File

@@ -66,7 +66,7 @@ bool MountDrakeAction::Execute(Event event)
for (auto& member : members) for (auto& member : members)
{ {
Player* player = botAI->GetPlayer(member); Player* player = botAI->GetPlayer(member);
if (!player->GetSession()->IsBot()) { continue; } if (!player) { continue; }
for (int i = 0; i < composition.size(); i++) for (int i = 0; i < composition.size(); i++)
{ {

View File

@@ -102,11 +102,9 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
new TriggerNode("unlock traded item", NextAction::array(0, new NextAction("unlock traded item", relevance), nullptr))); new TriggerNode("unlock traded item", NextAction::array(0, new NextAction("unlock traded item", relevance), nullptr)));
triggers.push_back( triggers.push_back(
new TriggerNode("wipe", NextAction::array(0, new NextAction("wipe", relevance), nullptr))); new TriggerNode("wipe", NextAction::array(0, new NextAction("wipe", relevance), nullptr)));
triggers.push_back(new TriggerNode("tame", NextAction::array(0, new NextAction("tame", relevance), nullptr))); triggers.push_back(new TriggerNode("pet", NextAction::array(0, new NextAction("pet", relevance), nullptr)));
triggers.push_back(new TriggerNode("glyphs", NextAction::array(0, new NextAction("glyphs", relevance), nullptr))); // Added for custom Glyphs triggers.push_back(new TriggerNode("glyphs", NextAction::array(0, new NextAction("glyphs", relevance), nullptr))); // Added for custom Glyphs
triggers.push_back(new TriggerNode("glyph equip", NextAction::array(0, new NextAction("glyph equip", relevance), nullptr))); // Added for custom Glyphs triggers.push_back(new TriggerNode("glyph equip", NextAction::array(0, new NextAction("glyph equip", relevance), nullptr))); // Added for custom Glyphs
triggers.push_back(new TriggerNode("pet", NextAction::array(0, new NextAction("pet", relevance), nullptr)));
triggers.push_back(new TriggerNode("pet attack", NextAction::array(0, new NextAction("pet attack", relevance), nullptr)));
triggers.push_back(new TriggerNode("roll", NextAction::array(0, new NextAction("roll", relevance), nullptr))); triggers.push_back(new TriggerNode("roll", NextAction::array(0, new NextAction("roll", relevance), nullptr)));
} }
@@ -187,9 +185,7 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
supported.push_back("qi"); supported.push_back("qi");
supported.push_back("unlock items"); supported.push_back("unlock items");
supported.push_back("unlock traded item"); supported.push_back("unlock traded item");
supported.push_back("tame"); supported.push_back("pet");
supported.push_back("glyphs"); // Added for custom Glyphs supported.push_back("glyphs"); // Added for custom Glyphs
supported.push_back("glyph equip"); // Added for custom Glyphs supported.push_back("glyph equip"); // Added for custom Glyphs
supported.push_back("pet");
supported.push_back("pet attack");
} }

View File

@@ -22,10 +22,8 @@ void CombatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode("combat stuck", NextAction::array(0, new NextAction("reset", 1.0f), nullptr))); triggers.push_back(new TriggerNode("combat stuck", NextAction::array(0, new NextAction("reset", 1.0f), nullptr)));
triggers.push_back(new TriggerNode("not facing target", triggers.push_back(new TriggerNode("not facing target",
NextAction::array(0, new NextAction("set facing", ACTION_MOVE + 7), nullptr))); NextAction::array(0, new NextAction("set facing", ACTION_MOVE + 7), nullptr)));
// triggers.push_back(new TriggerNode("pet attack", NextAction::array(0, new NextAction("pet attack", 40.0f), nullptr))); triggers.push_back(
// The pet-attack trigger is commented out because it was forcing the bot's pet to attack, overriding stay and follow commands. new TriggerNode("pet attack", NextAction::array(0, new NextAction("pet attack", 40.0f), nullptr)));
// Pets will automatically attack the bot's enemy if they are in "defensive" or "aggressive"
// stance, or if the master issues an attack command.
// triggers.push_back(new TriggerNode("combat long stuck", NextAction::array(0, new NextAction("hearthstone", 0.9f), // triggers.push_back(new TriggerNode("combat long stuck", NextAction::array(0, new NextAction("hearthstone", 0.9f),
// new NextAction("repop", 0.8f), nullptr))); // new NextAction("repop", 0.8f), nullptr)));
} }

View File

@@ -11,14 +11,13 @@
void UseFoodStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void UseFoodStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
Strategy::InitTriggers(triggers); Strategy::InitTriggers(triggers);
if (botAI->HasCheat(BotCheatMask::food)) if (sPlayerbotAIConfig->freeFood)
{
triggers.push_back(new TriggerNode("medium health", NextAction::array(0, new NextAction("food", 3.0f), nullptr))); triggers.push_back(new TriggerNode("medium health", NextAction::array(0, new NextAction("food", 3.0f), nullptr)));
triggers.push_back(new TriggerNode("high mana", NextAction::array(0, new NextAction("drink", 3.0f), nullptr)));
}
else else
{
triggers.push_back(new TriggerNode("low health", NextAction::array(0, new NextAction("food", 3.0f), nullptr))); triggers.push_back(new TriggerNode("low health", NextAction::array(0, new NextAction("food", 3.0f), nullptr)));
if (sPlayerbotAIConfig->freeFood)
triggers.push_back(new TriggerNode("high mana", NextAction::array(0, new NextAction("drink", 3.0f), nullptr)));
else
triggers.push_back(new TriggerNode("low mana", NextAction::array(0, new NextAction("drink", 3.0f), nullptr))); triggers.push_back(new TriggerNode("low mana", NextAction::array(0, new NextAction("drink", 3.0f), nullptr)));
}
} }

View File

@@ -61,7 +61,6 @@ void HunterPetStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("call pet", 60.0f), nullptr))); triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("call pet", 60.0f), nullptr)));
triggers.push_back(new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr))); triggers.push_back(new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr)));
triggers.push_back(new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr)));
triggers.push_back(new TriggerNode("pet not happy", NextAction::array(0, new NextAction("feed pet", 60.0f), nullptr))); triggers.push_back(new TriggerNode("pet not happy", NextAction::array(0, new NextAction("feed pet", 60.0f), nullptr)));
triggers.push_back(new TriggerNode("hunters pet medium health", NextAction::array(0, new NextAction("mend pet", 60.0f), nullptr))); triggers.push_back(new TriggerNode("hunters pet medium health", NextAction::array(0, new NextAction("mend pet", 60.0f), nullptr)));
triggers.push_back(new TriggerNode("hunters pet dead", NextAction::array(0, new NextAction("revive pet", 60.0f), nullptr))); triggers.push_back(new TriggerNode("hunters pet dead", NextAction::array(0, new NextAction("revive pet", 60.0f), nullptr)));

View File

@@ -40,7 +40,7 @@ bool CastArcaneShotAction::isUseful()
return false; return false;
// Armor Penetration rating check - will not cast Arcane Shot above 435 ArP // Armor Penetration rating check - will not cast Arcane Shot above 435 ArP
int32 armorPenRating = bot->GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1) + bot->GetUInt32Value(CR_ARMOR_PENETRATION); int32 armorPenRating = bot->GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + CR_ARMOR_PENETRATION);
if (armorPenRating > 435) if (armorPenRating > 435)
return false; return false;

View File

@@ -61,8 +61,7 @@ void FrostMageStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
// Pet/Defensive triggers // Pet/Defensive triggers
triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon water elemental", 30.0f), nullptr))); triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon water elemental", 30.0f), nullptr)));
triggers.push_back(new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr))); triggers.push_back(new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 29.5f), nullptr)));
triggers.push_back(new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr)));
triggers.push_back(new TriggerNode("medium health", NextAction::array(0, new NextAction("ice barrier", 29.0f), nullptr))); triggers.push_back(new TriggerNode("medium health", NextAction::array(0, new NextAction("ice barrier", 29.0f), nullptr)));
triggers.push_back(new TriggerNode("being attacked", NextAction::array(0, new NextAction("ice barrier", 29.0f), nullptr))); triggers.push_back(new TriggerNode("being attacked", NextAction::array(0, new NextAction("ice barrier", 29.0f), nullptr)));

View File

@@ -58,7 +58,6 @@ void GenericPriestStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply oil", 1.0f), nullptr))); triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply oil", 1.0f), nullptr)));
triggers.push_back(new TriggerNode("being attacked", triggers.push_back(new TriggerNode("being attacked",
NextAction::array(0, new NextAction("power word: shield", ACTION_HIGH + 1), nullptr))); NextAction::array(0, new NextAction("power word: shield", ACTION_HIGH + 1), nullptr)));
triggers.push_back(new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr)));
} }
PriestCureStrategy::PriestCureStrategy(PlayerbotAI* botAI) : Strategy(botAI) PriestCureStrategy::PriestCureStrategy(PlayerbotAI* botAI) : Strategy(botAI)

View File

@@ -56,8 +56,6 @@ void PriestNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back( triggers.push_back(
new TriggerNode("group heal setting", NextAction::array(0, new NextAction("circle of healing on party", 27.0f), NULL))); new TriggerNode("group heal setting", NextAction::array(0, new NextAction("circle of healing on party", 27.0f), NULL)));
triggers.push_back(new TriggerNode("new pet",
NextAction::array(0, new NextAction("set pet stance", 10.0f), nullptr)));
} }
void PriestBuffStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void PriestBuffStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)

View File

@@ -65,24 +65,6 @@ public:
creators["vezax cheat action"] = &RaidUlduarActionContext::vezax_cheat_action; creators["vezax cheat action"] = &RaidUlduarActionContext::vezax_cheat_action;
creators["vezax shadow crash action"] = &RaidUlduarActionContext::vezax_shadow_crash_action; creators["vezax shadow crash action"] = &RaidUlduarActionContext::vezax_shadow_crash_action;
creators["vezax mark of the faceless action"] = &RaidUlduarActionContext::vezax_mark_of_the_faceless_action; creators["vezax mark of the faceless action"] = &RaidUlduarActionContext::vezax_mark_of_the_faceless_action;
creators["vezax shadow resistance action"] = &RaidUlduarActionContext::vezax_shadow_resistance_action;
creators["sara shadow resistance action"] = &RaidUlduarActionContext::sara_shadow_resistance_action;
creators["yogg-saron shadow resistance action"] = &RaidUlduarActionContext::yogg_saron_shadow_resistance_action;
creators["yogg-saron ominous cloud cheat action"] = &RaidUlduarActionContext::yogg_saron_ominous_cloud_cheat_action;
creators["yogg-saron guardian positioning action"] = &RaidUlduarActionContext::yogg_saron_guardian_positioning_action;
creators["yogg-saron sanity action"] = &RaidUlduarActionContext::yogg_saron_sanity_action;
creators["yogg-saron death orb action"] = &RaidUlduarActionContext::yogg_saron_death_orb_action;
creators["yogg-saron malady of the mind action"] = &RaidUlduarActionContext::yogg_saron_malady_of_the_mind_action;
creators["yogg-saron mark target action"] = &RaidUlduarActionContext::yogg_saron_mark_target_action;
creators["yogg-saron brain link action"] = &RaidUlduarActionContext::yogg_saron_brain_link_action;
creators["yogg-saron move to enter portal action"] = &RaidUlduarActionContext::yogg_saron_move_to_enter_portal_action;
creators["yogg-saron use portal action"] = &RaidUlduarActionContext::yogg_saron_use_portal_action;
creators["yogg-saron fall from floor action"] = &RaidUlduarActionContext::yogg_saron_fall_from_floor_action;
creators["yogg-saron boss room movement cheat action"] = &RaidUlduarActionContext::yogg_saron_boss_room_movement_cheat_action;
creators["yogg-saron illusion room action"] = &RaidUlduarActionContext::yogg_saron_illusion_room_action;
creators["yogg-saron move to exit portal action"] = &RaidUlduarActionContext::yogg_saron_move_to_exit_portal_action;
creators["yogg-saron lunatic gaze action"] = &RaidUlduarActionContext::yogg_saron_lunatic_gaze_action;
creators["yogg-saron phase 3 positioning action"] = &RaidUlduarActionContext::yogg_saron_phase_3_positioning_action;
} }
private: private:
@@ -135,24 +117,6 @@ private:
static Action* vezax_cheat_action(PlayerbotAI* ai) { return new VezaxCheatAction(ai); } static Action* vezax_cheat_action(PlayerbotAI* ai) { return new VezaxCheatAction(ai); }
static Action* vezax_shadow_crash_action(PlayerbotAI* ai) { return new VezaxShadowCrashAction(ai); } static Action* vezax_shadow_crash_action(PlayerbotAI* ai) { return new VezaxShadowCrashAction(ai); }
static Action* vezax_mark_of_the_faceless_action(PlayerbotAI* ai) { return new VezaxMarkOfTheFacelessAction(ai); } static Action* vezax_mark_of_the_faceless_action(PlayerbotAI* ai) { return new VezaxMarkOfTheFacelessAction(ai); }
static Action* vezax_shadow_resistance_action(PlayerbotAI* ai) { return new BossShadowResistanceAction(ai, "general vezax"); }
static Action* sara_shadow_resistance_action(PlayerbotAI* ai) { return new BossShadowResistanceAction(ai, "sara"); }
static Action* yogg_saron_shadow_resistance_action(PlayerbotAI* ai) { return new BossShadowResistanceAction(ai, "yogg-saron"); }
static Action* yogg_saron_ominous_cloud_cheat_action(PlayerbotAI* ai) { return new YoggSaronOminousCloudCheatAction(ai); }
static Action* yogg_saron_guardian_positioning_action(PlayerbotAI* ai) { return new YoggSaronGuardianPositioningAction(ai); }
static Action* yogg_saron_sanity_action(PlayerbotAI* ai) { return new YoggSaronSanityAction(ai); }
static Action* yogg_saron_death_orb_action(PlayerbotAI* ai) { return new YoggSaronDeathOrbAction(ai); }
static Action* yogg_saron_malady_of_the_mind_action(PlayerbotAI* ai) { return new YoggSaronMaladyOfTheMindAction(ai); }
static Action* yogg_saron_mark_target_action(PlayerbotAI* ai) { return new YoggSaronMarkTargetAction(ai); }
static Action* yogg_saron_brain_link_action(PlayerbotAI* ai) { return new YoggSaronBrainLinkAction(ai); }
static Action* yogg_saron_move_to_enter_portal_action(PlayerbotAI* ai) { return new YoggSaronMoveToEnterPortalAction(ai); }
static Action* yogg_saron_use_portal_action(PlayerbotAI* ai) { return new YoggSaronUsePortalAction(ai); }
static Action* yogg_saron_fall_from_floor_action(PlayerbotAI* ai) { return new YoggSaronFallFromFloorAction(ai); }
static Action* yogg_saron_boss_room_movement_cheat_action(PlayerbotAI* ai) { return new YoggSaronBossRoomMovementCheatAction(ai); }
static Action* yogg_saron_illusion_room_action(PlayerbotAI* ai) { return new YoggSaronIllusionRoomAction(ai); }
static Action* yogg_saron_move_to_exit_portal_action(PlayerbotAI* ai) { return new YoggSaronMoveToExitPortalAction(ai); }
static Action* yogg_saron_lunatic_gaze_action(PlayerbotAI* ai) { return new YoggSaronLunaticGazeAction(ai); }
static Action* yogg_saron_phase_3_positioning_action(PlayerbotAI* ai) { return new YoggSaronPhase3PositioningAction(ai); }
}; };
#endif #endif

View File

@@ -20,14 +20,13 @@
#include "RaidUlduarBossHelper.h" #include "RaidUlduarBossHelper.h"
#include "RaidUlduarScripts.h" #include "RaidUlduarScripts.h"
#include "RaidUlduarStrategy.h" #include "RaidUlduarStrategy.h"
#include "RaidUlduarTriggers.h"
#include "RtiValue.h" #include "RtiValue.h"
#include "ScriptedCreature.h" #include "ScriptedCreature.h"
#include "ServerFacade.h" #include "ServerFacade.h"
#include "SharedDefines.h" #include "SharedDefines.h"
#include "Unit.h" #include "Unit.h"
#include "Vehicle.h" #include "Vehicle.h"
#include <RtiTargetValue.h>
#include <TankAssistStrategy.h>
const std::string ADD_STRATEGY_CHAR = "+"; const std::string ADD_STRATEGY_CHAR = "+";
const std::string REMOVE_STRATEGY_CHAR = "-"; const std::string REMOVE_STRATEGY_CHAR = "-";
@@ -43,12 +42,6 @@ const Position ULDUAR_KOLOGARN_RESTORE_POSITION = Position(1764.3749f, -24.02903
const Position ULDUAR_KOLOGARN_EYEBEAM_LEFT_POSITION = Position(1781.2051f, 9.34402f, 449.0f, 0.00087690353f); const Position ULDUAR_KOLOGARN_EYEBEAM_LEFT_POSITION = Position(1781.2051f, 9.34402f, 449.0f, 0.00087690353f);
const Position ULDUAR_KOLOGARN_EYEBEAM_RIGHT_POSITION = Position(1763.2561f, -24.44305f, 449.0f, 0.00087690353f); const Position ULDUAR_KOLOGARN_EYEBEAM_RIGHT_POSITION = Position(1763.2561f, -24.44305f, 449.0f, 0.00087690353f);
const Position ULDUAR_THORIM_JUMP_START_POINT = Position(2137.137f, -291.19025f, 438.24753f, 1.7059844f); const Position ULDUAR_THORIM_JUMP_START_POINT = Position(2137.137f, -291.19025f, 438.24753f, 1.7059844f);
const Position ULDUAR_YOGG_SARON_BOSS_ROOM_RESTORE_POINT = Position(1928.8923f, -24.871964f, 324.88956f, 6.247805f);
const Position yoggPortalLoc[] = {
{1970.48f, -9.75f, 325.5f}, {1992.76f, -10.21f, 325.5f}, {1995.53f, -39.78f, 325.5f}, {1969.25f, -42.00f, 325.5f},
{1960.62f, -32.00f, 325.5f}, {1981.98f, -5.69f, 325.5f}, {1982.78f, -45.73f, 325.5f}, {2000.66f, -29.68f, 325.5f},
{1999.88f, -19.61f, 325.5f}, {1961.37f, -19.54f, 325.5f}};
bool FlameLeviathanVehicleAction::Execute(Event event) bool FlameLeviathanVehicleAction::Execute(Event event)
{ {
@@ -1824,24 +1817,24 @@ bool ThorimMarkDpsTargetAction::Execute(Event event)
if (!group) if (!group)
return false; return false;
ObjectGuid currentMoonTarget = group->GetTargetIcon(RtiTargetValue::moonIndex); ObjectGuid currentMoonTarget = group->GetTargetIcon(moonIndex);
Unit* currentMoonUnit = botAI->GetUnit(currentMoonTarget); Unit* currentMoonUnit = botAI->GetUnit(currentMoonTarget);
Unit* boss = AI_VALUE2(Unit*, "find target", "thorim"); Unit* boss = AI_VALUE2(Unit*, "find target", "thorim");
if (!currentMoonUnit && boss && boss->IsAlive() && boss->GetPositionZ() > ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD) if (!currentMoonUnit && boss && boss->IsAlive() && boss->GetPositionZ() > ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD)
{ {
group->SetTargetIcon(RtiTargetValue::moonIndex, bot->GetGUID(), boss->GetGUID()); group->SetTargetIcon(moonIndex, bot->GetGUID(), boss->GetGUID());
} }
if (currentMoonUnit && boss && currentMoonUnit->GetEntry() == boss->GetEntry() && if (currentMoonUnit && boss && currentMoonUnit->GetEntry() == boss->GetEntry() &&
boss->GetPositionZ() < ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD) boss->GetPositionZ() < ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD)
{ {
group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), boss->GetGUID()); group->SetTargetIcon(skullIndex, bot->GetGUID(), boss->GetGUID());
return true; return true;
} }
if (botAI->IsMainTank(bot)) if (botAI->IsMainTank(bot))
{ {
ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); ObjectGuid currentSkullTarget = group->GetTargetIcon(skullIndex);
Unit* currentSkullUnit = botAI->GetUnit(currentSkullTarget); Unit* currentSkullUnit = botAI->GetUnit(currentSkullTarget);
if (currentSkullUnit && !currentSkullUnit->IsAlive()) if (currentSkullUnit && !currentSkullUnit->IsAlive())
{ {
@@ -1862,7 +1855,7 @@ bool ThorimMarkDpsTargetAction::Execute(Event event)
} }
else if (botAI->IsAssistTankOfIndex(bot, 0)) else if (botAI->IsAssistTankOfIndex(bot, 0))
{ {
ObjectGuid currentCrossTarget = group->GetTargetIcon(RtiTargetValue::crossIndex); ObjectGuid currentCrossTarget = group->GetTargetIcon(crossIndex);
Unit* currentCrossUnit = botAI->GetUnit(currentCrossTarget); Unit* currentCrossUnit = botAI->GetUnit(currentCrossTarget);
if (currentCrossUnit && !currentCrossUnit->IsAlive()) if (currentCrossUnit && !currentCrossUnit->IsAlive())
{ {
@@ -1898,13 +1891,13 @@ bool ThorimMarkDpsTargetAction::Execute(Event event)
if (botAI->IsMainTank(bot)) if (botAI->IsMainTank(bot))
{ {
group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), targetToMark->GetGUID()); group->SetTargetIcon(skullIndex, bot->GetGUID(), targetToMark->GetGUID());
return true; return true;
} }
if (botAI->IsAssistTankOfIndex(bot, 0)) if (botAI->IsAssistTankOfIndex(bot, 0))
{ {
group->SetTargetIcon(RtiTargetValue::crossIndex, bot->GetGUID(), targetToMark->GetGUID()); group->SetTargetIcon(crossIndex, bot->GetGUID(), targetToMark->GetGUID());
return true; return true;
} }
@@ -2445,20 +2438,20 @@ bool MimironAerialCommandUnitAction::Execute(Event event)
if (bombBot) if (bombBot)
{ {
group->SetTargetIcon(RtiTargetValue::crossIndex, bot->GetGUID(), bombBot->GetGUID()); group->SetTargetIcon(crossIndex, bot->GetGUID(), bombBot->GetGUID());
} }
else if (boss) else if (boss)
{ {
group->SetTargetIcon(RtiTargetValue::crossIndex, bot->GetGUID(), boss->GetGUID()); group->SetTargetIcon(crossIndex, bot->GetGUID(), boss->GetGUID());
} }
if (assaultBot) if (assaultBot)
{ {
ObjectGuid skullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); ObjectGuid skullTarget = group->GetTargetIcon(skullIndex);
Unit* skullUnit = botAI->GetUnit(skullTarget); Unit* skullUnit = botAI->GetUnit(skullTarget);
if (!skullTarget || !skullUnit || !skullUnit->IsAlive()) if (!skullTarget || !skullUnit || !skullUnit->IsAlive())
{ {
group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), assaultBot->GetGUID()); group->SetTargetIcon(skullIndex, bot->GetGUID(), assaultBot->GetGUID());
} }
} }
@@ -2598,7 +2591,7 @@ bool MimironPhase4MarkDpsAction::Execute(Event event)
highestHealthUnit = aerialCommandUnit; highestHealthUnit = aerialCommandUnit;
} }
group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), highestHealthUnit->GetGUID()); group->SetTargetIcon(skullIndex, bot->GetGUID(), highestHealthUnit->GetGUID());
if (highestHealthUnit == leviathanMkII) if (highestHealthUnit == leviathanMkII)
{ {
if (AI_VALUE(std::string, "rti") == "skull") if (AI_VALUE(std::string, "rti") == "skull")
@@ -2608,7 +2601,7 @@ bool MimironPhase4MarkDpsAction::Execute(Event event)
} }
else else
{ {
group->SetTargetIcon(RtiTargetValue::crossIndex, bot->GetGUID(), leviathanMkII->GetGUID()); group->SetTargetIcon(crossIndex, bot->GetGUID(), leviathanMkII->GetGUID());
if (AI_VALUE(std::string, "rti") != "cross") if (AI_VALUE(std::string, "rti") != "cross")
{ {
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set("cross"); botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set("cross");
@@ -2714,575 +2707,3 @@ bool VezaxMarkOfTheFacelessAction::Execute(Event event)
ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT.GetPositionZ(), false, false, false, true, ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT.GetPositionZ(), false, false, false, true,
MovementPriority::MOVEMENT_FORCED, true, false); MovementPriority::MOVEMENT_FORCED, true, false);
} }
bool YoggSaronOminousCloudCheatAction::Execute(Event event)
{
YoggSaronTrigger yoggSaronTrigger(botAI);
Unit* boss = yoggSaronTrigger.GetSaraIfAlive();
if (!boss)
{
return false;
}
Creature* target = boss->FindNearestCreature(NPC_OMINOUS_CLOUD, 25.0f);
if (!target || !target->IsAlive())
{
return false;
}
target->Kill(bot, target);
return true;
}
bool YoggSaronGuardianPositioningAction::Execute(Event event)
{
return MoveTo(bot->GetMapId(), ULDUAR_YOGG_SARON_MIDDLE.GetPositionX(), ULDUAR_YOGG_SARON_MIDDLE.GetPositionY(),
ULDUAR_YOGG_SARON_MIDDLE.GetPositionZ(), false, false, false, true,
MovementPriority::MOVEMENT_FORCED, true, false);
}
bool YoggSaronSanityAction::Execute(Event event)
{
Creature* sanityWell = bot->FindNearestCreature(NPC_SANITY_WELL, 200.0f);
return MoveTo(bot->GetMapId(), sanityWell->GetPositionX(), sanityWell->GetPositionY(), sanityWell->GetPositionZ(),
false, false, false, true, MovementPriority::MOVEMENT_FORCED,
true, false);
}
bool YoggSaronMarkTargetAction::Execute(Event event)
{
Group* group = bot->GetGroup();
if (!group)
{
return false;
}
YoggSaronTrigger yoggSaronTrigger(botAI);
if (yoggSaronTrigger.IsPhase2())
{
if (botAI->HasCheat(BotCheatMask::raid))
{
Unit* crusherTentacle = bot->FindNearestCreature(NPC_CRUSHER_TENTACLE, 200.0f, true);
if (crusherTentacle)
{
crusherTentacle->Kill(bot, crusherTentacle);
}
}
ObjectGuid currentMoonTarget = group->GetTargetIcon(RtiTargetValue::moonIndex);
Creature* yogg_saron = bot->FindNearestCreature(NPC_YOGG_SARON, 200.0f, true);
if (!currentMoonTarget || currentMoonTarget != yogg_saron->GetGUID())
{
group->SetTargetIcon(RtiTargetValue::moonIndex, bot->GetGUID(), yogg_saron->GetGUID());
return true;
}
ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex);
Creature* nextPossibleTarget = bot->FindNearestCreature(NPC_CONSTRICTOR_TENTACLE, 200.0f, true);
if (!nextPossibleTarget)
{
nextPossibleTarget = bot->FindNearestCreature(NPC_CORRUPTOR_TENTACLE, 200.0f, true);
if (!nextPossibleTarget)
{
return false;
}
}
if (currentSkullTarget)
{
Unit* currentSkullUnit = botAI->GetUnit(currentSkullTarget);
if (currentSkullUnit && currentSkullUnit->IsAlive() &&
currentSkullUnit->GetGUID() == nextPossibleTarget->GetGUID())
{
return false;
}
}
group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), nextPossibleTarget->GetGUID());
}
else if (yoggSaronTrigger.IsPhase3())
{
TankFaceStrategy tankFaceStrategy(botAI);
if (botAI->HasStrategy(tankFaceStrategy.getName(), BotState::BOT_STATE_COMBAT))
{
botAI->ChangeStrategy(REMOVE_STRATEGY_CHAR + tankFaceStrategy.getName(), BotState::BOT_STATE_COMBAT);
}
TankAssistStrategy tankAssistStrategy(botAI);
if (!botAI->HasStrategy(tankAssistStrategy.getName(), BotState::BOT_STATE_COMBAT))
{
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + tankAssistStrategy.getName(), BotState::BOT_STATE_COMBAT);
}
GuidVector targets = AI_VALUE(GuidVector, "nearest npcs");
int lowestHealth = std::numeric_limits<int>::max();
Unit* lowestHealthUnit = nullptr;
for (const ObjectGuid& guid : targets)
{
Unit* unit = botAI->GetUnit(guid);
if (!unit || !unit->IsAlive())
{
continue;
}
if ((unit->GetEntry() == NPC_IMMORTAL_GUARDIAN || unit->GetEntry() == NPC_MARKED_IMMORTAL_GUARDIAN) &&
unit->GetHealthPct() > 10)
{
if (unit->GetHealth() < lowestHealth)
{
lowestHealth = unit->GetHealth();
lowestHealthUnit = unit;
}
}
}
if (lowestHealthUnit)
{
// Added because lunatic gaze freeze all bots and they can't attack
// If someone fix it then this cheat can be removed
if (botAI->HasCheat(BotCheatMask::raid))
{
lowestHealthUnit->Kill(bot, lowestHealthUnit);
}
else
{
group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), lowestHealthUnit->GetGUID());
}
return true;
}
ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex);
Unit* currentSkullUnit = nullptr;
if (currentSkullTarget)
{
currentSkullUnit = botAI->GetUnit(currentSkullTarget);
}
if (!currentSkullUnit || currentSkullUnit->GetEntry() != NPC_YOGG_SARON)
{
Unit* yoggsaron = AI_VALUE2(Unit*, "find target", "yogg-saron");
if (yoggsaron && yoggsaron->IsAlive())
{
group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), yoggsaron->GetGUID());
return true;
}
}
return false;
}
return false;
}
bool YoggSaronBrainLinkAction::Execute(Event event)
{
Group* group = bot->GetGroup();
if (!group)
{
return false;
}
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* player = gref->GetSource();
if (player && player->IsAlive() && player->HasAura(SPELL_BRAIN_LINK) && player->GetGUID() != bot->GetGUID())
{
return MoveNear(player, 10.0f, MovementPriority::MOVEMENT_FORCED);
}
}
return false;
}
bool YoggSaronMoveToEnterPortalAction::Execute(Event event)
{
Group* group = bot->GetGroup();
if (!group)
{
return false;
}
bool isInBrainRoomTeam = false;
int portalNumber = 0;
int brainRoomTeamCount = 10;
if (bot->GetRaidDifficulty() == Difficulty::RAID_DIFFICULTY_10MAN_NORMAL)
{
brainRoomTeamCount = 4;
}
Player* master = botAI->GetMaster();
if (master && !botAI->IsTank(master))
{
portalNumber++;
brainRoomTeamCount--;
}
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (!member || !member->IsAlive() || botAI->IsTank(member) || botAI->GetMaster()->GetGUID() == member->GetGUID())
{
continue;
}
portalNumber++;
if (member->GetGUID() == bot->GetGUID())
{
isInBrainRoomTeam = true;
break;
}
brainRoomTeamCount--;
if (brainRoomTeamCount == 0)
{
break;
}
}
if (!isInBrainRoomTeam)
{
return false;
}
Position assignedPortalPosition = yoggPortalLoc[portalNumber - 1];
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set("diamond");
if (botAI->HasCheat(BotCheatMask::raid))
{
return bot->TeleportTo(bot->GetMapId(), assignedPortalPosition.GetPositionX(),
assignedPortalPosition.GetPositionY(),
assignedPortalPosition.GetPositionZ(), bot->GetOrientation());
}
else
{
return MoveNear(bot->GetMapId(), assignedPortalPosition.GetPositionX(),
assignedPortalPosition.GetPositionY(),
assignedPortalPosition.GetPositionZ(), sPlayerbotAIConfig->contactDistance,
MovementPriority::MOVEMENT_FORCED);
}
}
bool YoggSaronFallFromFloorAction::Execute(Event event)
{
std::string rtiMark = AI_VALUE(std::string, "rti");
if (rtiMark == "skull")
{
return bot->TeleportTo(bot->GetMapId(), ULDUAR_YOGG_SARON_BOSS_ROOM_RESTORE_POINT.GetPositionX(),
ULDUAR_YOGG_SARON_BOSS_ROOM_RESTORE_POINT.GetPositionY(),
ULDUAR_YOGG_SARON_BOSS_ROOM_RESTORE_POINT.GetPositionZ(),
ULDUAR_YOGG_SARON_BOSS_ROOM_RESTORE_POINT.GetOrientation());
}
if (rtiMark == "cross")
{
return bot->TeleportTo(bot->GetMapId(), ULDUAR_YOGG_SARON_STORMWIND_KEEPER_MIDDLE.GetPositionX(),
ULDUAR_YOGG_SARON_STORMWIND_KEEPER_MIDDLE.GetPositionY(),
ULDUAR_YOGG_SARON_STORMWIND_KEEPER_MIDDLE.GetPositionZ(),
bot->GetOrientation());
}
if (rtiMark == "circle")
{
return bot->TeleportTo(bot->GetMapId(), ULDUAR_YOGG_SARON_ICECROWN_CITADEL_MIDDLE.GetPositionX(),
ULDUAR_YOGG_SARON_ICECROWN_CITADEL_MIDDLE.GetPositionY(),
ULDUAR_YOGG_SARON_ICECROWN_CITADEL_MIDDLE.GetPositionZ(), bot->GetOrientation());
}
if (rtiMark == "star")
{
return bot->TeleportTo(bot->GetMapId(), ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_MIDDLE.GetPositionX(),
ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_MIDDLE.GetPositionY(),
ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_MIDDLE.GetPositionZ(), bot->GetOrientation());
}
return false;
}
bool YoggSaronBossRoomMovementCheatAction::Execute(Event event)
{
FollowMasterStrategy followMasterStrategy(botAI);
if (botAI->HasStrategy(followMasterStrategy.getName(), BotState::BOT_STATE_NON_COMBAT))
{
botAI->ChangeStrategy(REMOVE_STRATEGY_CHAR + followMasterStrategy.getName(), BotState::BOT_STATE_NON_COMBAT);
}
if (!botAI->HasCheat(BotCheatMask::raid))
{
return false;
}
Group* group = bot->GetGroup();
if (!group)
{
return false;
}
ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex);
if (!currentSkullTarget)
{
return false;
}
Unit* currentSkullUnit = botAI->GetUnit(currentSkullTarget);
if (!currentSkullUnit || !currentSkullUnit->IsAlive())
{
return false;
}
return bot->TeleportTo(bot->GetMapId(), currentSkullUnit->GetPositionX(), currentSkullUnit->GetPositionY(),
currentSkullUnit->GetPositionZ(), bot->GetOrientation());
}
bool YoggSaronUsePortalAction::Execute(Event event)
{
Creature* assignedPortal = bot->FindNearestCreature(NPC_DESCEND_INTO_MADNESS, 2.0f, true);
if (!assignedPortal)
{
return false;
}
FollowMasterStrategy followMasterStrategy(botAI);
if (botAI->HasStrategy(followMasterStrategy.getName(), BotState::BOT_STATE_NON_COMBAT))
{
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + followMasterStrategy.getName(), BotState::BOT_STATE_NON_COMBAT);
}
return assignedPortal->HandleSpellClick(bot);
}
bool YoggSaronIllusionRoomAction::Execute(Event event)
{
YoggSaronTrigger yoggSaronTrigger(botAI);
bool resultSetRtiMark = SetRtiMark(yoggSaronTrigger);
bool resultSetIllusionRtiTarget = SetIllusionRtiTarget(yoggSaronTrigger);
bool resultSetBrainRtiTarget = SetBrainRtiTarget(yoggSaronTrigger);
return resultSetRtiMark || resultSetIllusionRtiTarget || resultSetBrainRtiTarget;
}
bool YoggSaronIllusionRoomAction::SetRtiMark(YoggSaronTrigger yoggSaronTrigger)
{
if (AI_VALUE(std::string, "rti") == "diamond")
{
if (yoggSaronTrigger.IsInStormwindKeeperIllusion())
{
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set("cross");
return true;
}
else if (yoggSaronTrigger.IsInIcecrownKeeperIllusion())
{
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set("circle");
return true;
}
else if (yoggSaronTrigger.IsInChamberOfTheAspectsIllusion())
{
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set("star");
return true;
}
}
return false;
}
bool YoggSaronIllusionRoomAction::SetIllusionRtiTarget(YoggSaronTrigger yoggSaronTrigger)
{
Unit* currentRtiTarget = yoggSaronTrigger.GetIllusionRoomRtiTarget();
if (currentRtiTarget)
{
return false;
}
Unit* nextRtiTarget = yoggSaronTrigger.GetNextIllusionRoomRtiTarget();
if (!nextRtiTarget)
{
return false;
}
// If proper adds handling in illusion room will be implemented, then this can be removed
if (botAI->HasCheat(BotCheatMask::raid))
{
bot->TeleportTo(bot->GetMapId(), nextRtiTarget->GetPositionX(), nextRtiTarget->GetPositionY(),
nextRtiTarget->GetPositionZ(), bot->GetOrientation());
Unit::DealDamage(bot->GetSession()->GetPlayer(), nextRtiTarget, nextRtiTarget->GetHealth(), nullptr,
DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, nullptr, false, true);
}
else
{
Group* group = bot->GetGroup();
if (!group)
{
return false;
}
uint8 rtiIndex = RtiTargetValue::GetRtiIndex(AI_VALUE(std::string, "rti"));
group->SetTargetIcon(rtiIndex, bot->GetGUID(), nextRtiTarget->GetGUID());
}
return true;
}
bool YoggSaronIllusionRoomAction::SetBrainRtiTarget(YoggSaronTrigger yoggSaronTrigger)
{
if (AI_VALUE(std::string, "rti") == "square" || !yoggSaronTrigger.IsMasterIsInBrainRoom())
{
return false;
}
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set("square");
Group* group = bot->GetGroup();
if (!group)
{
return false;
}
Creature* brain = bot->FindNearestCreature(NPC_BRAIN, 200.0f, true);
if (!brain)
{
return false;
}
group->SetTargetIcon(RtiTargetValue::squareIndex, bot->GetGUID(), brain->GetGUID());
Position entrancePosition = yoggSaronTrigger.GetIllusionRoomEntrancePosition();
if (botAI->HasCheat(BotCheatMask::raid))
{
if (Unit const* master = botAI->GetMaster())
{
Position masterPosition = master->GetPosition();
bot->TeleportTo(bot->GetMapId(), masterPosition.GetPositionX(), masterPosition.GetPositionY(),
masterPosition.GetPositionZ(), bot->GetOrientation());
}
else
{
bot->TeleportTo(bot->GetMapId(), entrancePosition.GetPositionX(), entrancePosition.GetPositionY(),
entrancePosition.GetPositionZ(), bot->GetOrientation());
}
}
else
{
MoveTo(bot->GetMapId(), entrancePosition.GetPositionX(), entrancePosition.GetPositionY(),
entrancePosition.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_FORCED, true,
false);
}
botAI->DoSpecificAction("attack rti target");
return true;
}
bool YoggSaronMoveToExitPortalAction::Execute(Event event)
{
GameObject* portal = bot->FindNearestGameObject(GO_FLEE_TO_THE_SURFACE_PORTAL, 100.0f);
if (!portal)
{
return false;
}
if (botAI->HasCheat(BotCheatMask::raid))
{
bot->TeleportTo(bot->GetMapId(), portal->GetPositionX(), portal->GetPositionY(), portal->GetPositionZ(),
bot->GetOrientation());
}
else
{
MoveTo(bot->GetMapId(), portal->GetPositionX(), portal->GetPositionY(), portal->GetPositionZ(), false,
false, false, true, MovementPriority::MOVEMENT_FORCED,
true, false);
}
if (bot->GetDistance2d(portal) > 2.0f)
{
return false;
}
portal->Use(bot);
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set("skull");
return true;
}
bool YoggSaronLunaticGazeAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "yogg-saron");
if (!boss || !boss->IsAlive())
{
return false;
}
float angle = bot->GetAngle(boss);
float newAngle = Position::NormalizeOrientation(angle + M_PI); // Add 180 degrees (PI radians)
bot->SetFacingTo(newAngle);
if (botAI->IsRangedDps(bot))
{
if (AI_VALUE(std::string, "rti") != "cross")
{
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set("cross");
}
}
return true;
}
bool YoggSaronPhase3PositioningAction::Execute(Event event)
{
if (botAI->IsRanged(bot))
{
if (botAI->HasCheat(BotCheatMask::raid))
{
return bot->TeleportTo(bot->GetMapId(), ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT.GetPositionX(),
ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT.GetPositionY(),
ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT.GetPositionZ(),
bot->GetOrientation());
}
else
{
return MoveTo(bot->GetMapId(), ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT.GetPositionX(),
ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT.GetPositionY(),
ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT.GetPositionZ(), false,
false, false, true, MovementPriority::MOVEMENT_FORCED, true, false);
}
}
if (botAI->IsMelee(bot) && !botAI->IsTank(bot))
{
if (botAI->HasCheat(BotCheatMask::raid))
{
return bot->TeleportTo(bot->GetMapId(), ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionX(),
ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionY(),
ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionZ(), bot->GetOrientation());
}
else
{
return MoveTo(bot->GetMapId(), ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionX(),
ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionY(),
ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionZ(), false, false, false, true,
MovementPriority::MOVEMENT_FORCED, true, false);
}
}
if (botAI->IsTank(bot))
{
if (bot->GetDistance(ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT) > 30.0f)
{
if (botAI->HasCheat(BotCheatMask::raid))
{
return bot->TeleportTo(bot->GetMapId(), ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionX(),
ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionY(),
ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionZ(), bot->GetOrientation());
}
}
return MoveTo(bot->GetMapId(), ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionX(),
ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionY(),
ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionZ(), false, false, false, true,
MovementPriority::MOVEMENT_FORCED, true, false);
}
return false;
}

View File

@@ -9,7 +9,6 @@
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "RaidUlduarBossHelper.h" #include "RaidUlduarBossHelper.h"
#include "RaidUlduarTriggers.h"
#include "Vehicle.h" #include "Vehicle.h"
// //
@@ -379,125 +378,4 @@ public:
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class YoggSaronOminousCloudCheatAction : public Action
{
public:
YoggSaronOminousCloudCheatAction(PlayerbotAI* ai) : Action(ai, "yogg-saron ominous cloud cheat action") {}
bool Execute(Event event) override;
};
class YoggSaronGuardianPositioningAction : public MovementAction
{
public:
YoggSaronGuardianPositioningAction(PlayerbotAI* ai) : MovementAction(ai, "yogg-saron guardian positioning action") {}
bool Execute(Event event) override;
};
class YoggSaronSanityAction : public MovementAction
{
public:
YoggSaronSanityAction(PlayerbotAI* ai) : MovementAction(ai, "yogg-saron sanity action") {}
bool Execute(Event event) override;
};
class YoggSaronDeathOrbAction : public MoveAwayFromCreatureAction
{
public:
YoggSaronDeathOrbAction(PlayerbotAI* ai) : MoveAwayFromCreatureAction(ai, "yogg-saron death orb action", NPC_DEATH_ORB, 10.0f) {}
};
class YoggSaronMaladyOfTheMindAction : public MoveAwayFromPlayerWithDebuffAction
{
public:
YoggSaronMaladyOfTheMindAction(PlayerbotAI* ai) : MoveAwayFromPlayerWithDebuffAction(ai, "yogg-saron malady of the mind action", SPELL_MALADY_OF_THE_MIND, 15.0f) {}
};
class YoggSaronMarkTargetAction : public Action
{
public:
YoggSaronMarkTargetAction(PlayerbotAI* ai) : Action(ai, "yogg-saron mark target action") {}
bool Execute(Event event) override;
};
class YoggSaronBrainLinkAction : public MovementAction
{
public:
YoggSaronBrainLinkAction(PlayerbotAI* ai) : MovementAction(ai, "yogg-saron brain link action") {}
bool Execute(Event event) override;
};
class YoggSaronMoveToEnterPortalAction : public MovementAction
{
public:
YoggSaronMoveToEnterPortalAction(PlayerbotAI* ai) : MovementAction(ai, "yogg-saron move to enter portal action") {}
bool Execute(Event event) override;
};
class YoggSaronFallFromFloorAction : public MovementAction
{
public:
YoggSaronFallFromFloorAction(PlayerbotAI* ai) : MovementAction(ai, "yogg-saron fall from floor action") {}
bool Execute(Event event) override;
};
class YoggSaronBossRoomMovementCheatAction : public MovementAction
{
public:
YoggSaronBossRoomMovementCheatAction(PlayerbotAI* ai) : MovementAction(ai, "yogg-saron boss room movement cheat action") {}
bool Execute(Event event) override;
};
class YoggSaronUsePortalAction : public Action
{
public:
YoggSaronUsePortalAction(PlayerbotAI* ai) : Action(ai, "yogg-saron use portal action") {}
bool Execute(Event event) override;
};
class YoggSaronIllusionRoomAction : public MovementAction
{
public:
YoggSaronIllusionRoomAction(PlayerbotAI* ai) : MovementAction(ai, "yogg-saron illusion room action") {}
bool Execute(Event event) override;
private:
bool SetRtiMark(YoggSaronTrigger yoggSaronTrigger);
bool SetIllusionRtiTarget(YoggSaronTrigger yoggSaronTrigger);
bool SetBrainRtiTarget(YoggSaronTrigger yoggSaronTrigger);
};
class YoggSaronMoveToExitPortalAction : public MovementAction
{
public:
YoggSaronMoveToExitPortalAction(PlayerbotAI* ai) : MovementAction(ai, "yogg-saron move to exit portal action") {}
bool Execute(Event event) override;
};
class YoggSaronLunaticGazeAction : public MovementAction
{
public:
YoggSaronLunaticGazeAction(PlayerbotAI* ai) : MovementAction(ai, "yogg-saron lunatic gaze action") {}
bool Execute(Event event) override;
};
class YoggSaronPhase3PositioningAction : public MovementAction
{
public:
YoggSaronPhase3PositioningAction(PlayerbotAI* ai) : MovementAction(ai, "yogg-saron phase 3 positioning action") {}
bool Execute(Event event) override;
};
#endif #endif

View File

@@ -255,66 +255,6 @@ void RaidUlduarStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode( triggers.push_back(new TriggerNode(
"yogg-saron shadow resistance trigger", "yogg-saron shadow resistance trigger",
NextAction::array(0, new NextAction("yogg-saron shadow resistance action", ACTION_RAID), nullptr))); NextAction::array(0, new NextAction("yogg-saron shadow resistance action", ACTION_RAID), nullptr)));
triggers.push_back(new TriggerNode(
"yogg-saron ominous cloud cheat trigger",
NextAction::array(0, new NextAction("yogg-saron ominous cloud cheat action", ACTION_RAID), nullptr)));
triggers.push_back(new TriggerNode(
"yogg-saron guardian positioning trigger",
NextAction::array(0, new NextAction("yogg-saron guardian positioning action", ACTION_RAID), nullptr)));
triggers.push_back(new TriggerNode(
"yogg-saron sanity trigger",
NextAction::array(0, new NextAction("yogg-saron sanity action", ACTION_RAID + 1), nullptr)));
triggers.push_back(new TriggerNode(
"yogg-saron death orb trigger",
NextAction::array(0, new NextAction("yogg-saron death orb action", ACTION_RAID), nullptr)));
triggers.push_back(new TriggerNode(
"yogg-saron malady of the mind trigger",
NextAction::array(0, new NextAction("yogg-saron malady of the mind action", ACTION_RAID), nullptr)));
triggers.push_back(new TriggerNode(
"yogg-saron mark target trigger",
NextAction::array(0, new NextAction("yogg-saron mark target action", ACTION_RAID), nullptr)));
triggers.push_back(new TriggerNode(
"yogg-saron brain link trigger",
NextAction::array(0, new NextAction("yogg-saron brain link action", ACTION_RAID), nullptr)));
triggers.push_back(new TriggerNode(
"yogg-saron move to enter portal trigger",
NextAction::array(0, new NextAction("yogg-saron move to enter portal action", ACTION_RAID), nullptr)));
triggers.push_back(new TriggerNode(
"yogg-saron use portal trigger",
NextAction::array(0, new NextAction("yogg-saron use portal action", ACTION_RAID), nullptr)));
triggers.push_back(new TriggerNode(
"yogg-saron fall from floor trigger",
NextAction::array(0, new NextAction("yogg-saron fall from floor action", ACTION_RAID), nullptr)));
triggers.push_back(new TriggerNode(
"yogg-saron boss room movement cheat trigger",
NextAction::array(0, new NextAction("yogg-saron boss room movement cheat action", ACTION_RAID), nullptr)));
triggers.push_back(new TriggerNode(
"yogg-saron illusion room trigger",
NextAction::array(0, new NextAction("yogg-saron illusion room action", ACTION_RAID), nullptr)));
triggers.push_back(new TriggerNode(
"yogg-saron move to exit portal trigger",
NextAction::array(0, new NextAction("yogg-saron move to exit portal action", ACTION_RAID), nullptr)));
triggers.push_back(new TriggerNode(
"yogg-saron lunatic gaze trigger",
NextAction::array(0, new NextAction("yogg-saron lunatic gaze action", ACTION_EMERGENCY), nullptr)));
triggers.push_back(new TriggerNode(
"yogg-saron phase 3 positioning trigger",
NextAction::array(0, new NextAction("yogg-saron phase 3 positioning action", ACTION_RAID), nullptr)));
} }
void RaidUlduarStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers) void RaidUlduarStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)

View File

@@ -67,24 +67,6 @@ public:
creators["vezax cheat trigger"] = &RaidUlduarTriggerContext::vezax_cheat_trigger; creators["vezax cheat trigger"] = &RaidUlduarTriggerContext::vezax_cheat_trigger;
creators["vezax shadow crash trigger"] = &RaidUlduarTriggerContext::vezax_shadow_crash_trigger; creators["vezax shadow crash trigger"] = &RaidUlduarTriggerContext::vezax_shadow_crash_trigger;
creators["vezax mark of the faceless trigger"] = &RaidUlduarTriggerContext::vezax_mark_of_the_faceless_trigger; creators["vezax mark of the faceless trigger"] = &RaidUlduarTriggerContext::vezax_mark_of_the_faceless_trigger;
creators["vezax shadow resistance trigger"] = &RaidUlduarTriggerContext::vezax_shadow_resistance_trigger;
creators["sara shadow resistance trigger"] = &RaidUlduarTriggerContext::sara_shadow_resistance_trigger;
creators["yogg-saron shadow resistance triggerr"] = &RaidUlduarTriggerContext::yogg_saron_shadow_resistance_trigger;
creators["yogg-saron ominous cloud cheat trigger"] = &RaidUlduarTriggerContext::yogg_saron_ominous_cloud_cheat_trigger;
creators["yogg-saron guardian positioning trigger"] = &RaidUlduarTriggerContext::yogg_saron_guardian_positioning_trigger;
creators["yogg-saron sanity trigger"] = &RaidUlduarTriggerContext::yogg_saron_sanity_trigger;
creators["yogg-saron death orb trigger"] = &RaidUlduarTriggerContext::yogg_saron_death_orb_trigger;
creators["yogg-saron malady of the mind trigger"] = &RaidUlduarTriggerContext::yogg_saron_malady_of_the_mind_trigger;
creators["yogg-saron mark target trigger"] = &RaidUlduarTriggerContext::yogg_saron_mark_target_trigger;
creators["yogg-saron brain link trigger"] = &RaidUlduarTriggerContext::yogg_saron_brain_link_trigger;
creators["yogg-saron move to enter portal trigger"] = &RaidUlduarTriggerContext::yogg_saron_move_to_enter_portal_trigger;
creators["yogg-saron use portal trigger"] = &RaidUlduarTriggerContext::yogg_saron_use_portal_trigger;
creators["yogg-saron fall from floor trigger"] = &RaidUlduarTriggerContext::yogg_saron_fall_from_floor_trigger;
creators["yogg-saron boss room movement cheat trigger"] = &RaidUlduarTriggerContext::yogg_saron_boss_room_movement_cheat_trigger;
creators["yogg-saron illusion room trigger"] = &RaidUlduarTriggerContext::yogg_saron_illusion_room_trigger;
creators["yogg-saron move to exit portal trigger"] = &RaidUlduarTriggerContext::yogg_saron_move_to_exit_portal_trigger;
creators["yogg-saron lunatic gaze trigger"] = &RaidUlduarTriggerContext::yogg_saron_lunatic_gaze_trigger;
creators["yogg-saron phase 3 positioning trigger"] = &RaidUlduarTriggerContext::yogg_saron_phase_3_positioning_trigger;
} }
private: private:
@@ -138,25 +120,7 @@ private:
static Trigger* mimiron_cheat_trigger(PlayerbotAI* ai) { return new MimironCheatTrigger(ai); } static Trigger* mimiron_cheat_trigger(PlayerbotAI* ai) { return new MimironCheatTrigger(ai); }
static Trigger* vezax_cheat_trigger(PlayerbotAI* ai) { return new VezaxCheatTrigger(ai); } static Trigger* vezax_cheat_trigger(PlayerbotAI* ai) { return new VezaxCheatTrigger(ai); }
static Trigger* vezax_shadow_crash_trigger(PlayerbotAI* ai) { return new VezaxShadowCrashTrigger(ai); } static Trigger* vezax_shadow_crash_trigger(PlayerbotAI* ai) { return new VezaxShadowCrashTrigger(ai); }
static Trigger* vezax_shadow_resistance_trigger(PlayerbotAI* ai) { return new BossShadowResistanceTrigger(ai, "general vezax"); }
static Trigger* sara_shadow_resistance_trigger(PlayerbotAI* ai) { return new BossShadowResistanceTrigger(ai, "sara"); }
static Trigger* yogg_saron_shadow_resistance_trigger(PlayerbotAI* ai) { return new BossShadowResistanceTrigger(ai, "yogg-saron"); }
static Trigger* vezax_mark_of_the_faceless_trigger(PlayerbotAI* ai) { return new VezaxMarkOfTheFacelessTrigger(ai); } static Trigger* vezax_mark_of_the_faceless_trigger(PlayerbotAI* ai) { return new VezaxMarkOfTheFacelessTrigger(ai); }
static Trigger* yogg_saron_ominous_cloud_cheat_trigger(PlayerbotAI* ai) { return new YoggSaronOminousCloudCheatTrigger(ai); }
static Trigger* yogg_saron_guardian_positioning_trigger(PlayerbotAI* ai) { return new YoggSaronGuardianPositioningTrigger(ai); }
static Trigger* yogg_saron_sanity_trigger(PlayerbotAI* ai) { return new YoggSaronSanityTrigger(ai); }
static Trigger* yogg_saron_death_orb_trigger(PlayerbotAI* ai) { return new YoggSaronDeathOrbTrigger(ai); }
static Trigger* yogg_saron_malady_of_the_mind_trigger(PlayerbotAI* ai) { return new YoggSaronMaladyOfTheMindTrigger(ai); }
static Trigger* yogg_saron_mark_target_trigger(PlayerbotAI* ai) { return new YoggSaronMarkTargetTrigger(ai); }
static Trigger* yogg_saron_brain_link_trigger(PlayerbotAI* ai) { return new YoggSaronBrainLinkTrigger(ai); }
static Trigger* yogg_saron_move_to_enter_portal_trigger(PlayerbotAI* ai) { return new YoggSaronMoveToEnterPortalTrigger(ai); }
static Trigger* yogg_saron_use_portal_trigger(PlayerbotAI* ai) { return new YoggSaronUsePortalTrigger(ai); }
static Trigger* yogg_saron_fall_from_floor_trigger(PlayerbotAI* ai) { return new YoggSaronFallFromFloorTrigger(ai); }
static Trigger* yogg_saron_boss_room_movement_cheat_trigger(PlayerbotAI* ai) { return new YoggSaronBossRoomMovementCheatTrigger(ai); }
static Trigger* yogg_saron_illusion_room_trigger(PlayerbotAI* ai) { return new YoggSaronIllusionRoomTrigger(ai); }
static Trigger* yogg_saron_move_to_exit_portal_trigger(PlayerbotAI* ai) { return new YoggSaronMoveToExitPortalTrigger(ai); }
static Trigger* yogg_saron_lunatic_gaze_trigger(PlayerbotAI* ai) { return new YoggSaronLunaticGazeTrigger(ai); }
static Trigger* yogg_saron_phase_3_positioning_trigger(PlayerbotAI* ai) { return new YoggSaronPhase3PositioningTrigger(ai); }
}; };
#endif #endif

View File

@@ -12,33 +12,11 @@
#include "Trigger.h" #include "Trigger.h"
#include "Vehicle.h" #include "Vehicle.h"
#include <MovementActions.h> #include <MovementActions.h>
#include <FollowMasterStrategy.h>
#include <RtiTargetValue.h>
const std::vector<uint32> availableVehicles = {NPC_VEHICLE_CHOPPER, NPC_SALVAGED_DEMOLISHER, const std::vector<uint32> availableVehicles = {NPC_VEHICLE_CHOPPER, NPC_SALVAGED_DEMOLISHER,
NPC_SALVAGED_DEMOLISHER_TURRET, NPC_SALVAGED_SIEGE_ENGINE, NPC_SALVAGED_DEMOLISHER_TURRET, NPC_SALVAGED_SIEGE_ENGINE,
NPC_SALVAGED_SIEGE_ENGINE_TURRET}; NPC_SALVAGED_SIEGE_ENGINE_TURRET};
const std::vector<uint32> illusionMobs =
{
NPC_INFLUENCE_TENTACLE,
NPC_RUBY_CONSORT,
NPC_AZURE_CONSORT,
NPC_BRONZE_CONSORT,
NPC_EMERALD_CONSORT,
NPC_OBSIDIAN_CONSORT,
NPC_ALEXTRASZA,
NPC_MALYGOS_ILLUSION,
NPC_NELTHARION,
NPC_YSERA,
NPC_DEATHSWORN_ZEALOT,
NPC_LICH_KING_ILLUSION,
NPC_IMMOLATED_CHAMPION,
NPC_SUIT_OF_ARMOR,
NPC_GARONA,
NPC_KING_LLANE
};
bool FlameLeviathanOnVehicleTrigger::IsActive() bool FlameLeviathanOnVehicleTrigger::IsActive()
{ {
Unit* vehicleBase = bot->GetVehicleBase(); Unit* vehicleBase = bot->GetVehicleBase();
@@ -470,8 +448,8 @@ bool KologarnAttackDpsTargetTrigger::IsActive()
if (!group) if (!group)
return false; return false;
ObjectGuid skullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); ObjectGuid skullTarget = group->GetTargetIcon(skullIndex);
ObjectGuid crossTarget = group->GetTargetIcon(RtiTargetValue::crossIndex); ObjectGuid crossTarget = group->GetTargetIcon(crossIndex);
if (crossTarget && (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0))) if (crossTarget && (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0)))
{ {
@@ -767,7 +745,7 @@ bool ThorimUnbalancingStrikeTrigger::IsActive()
Unit* boss = AI_VALUE2(Unit*, "find target", "thorim"); Unit* boss = AI_VALUE2(Unit*, "find target", "thorim");
// Check boss and it is alive // Check boss and it is alive
if (!boss || !boss->IsAlive() || !boss->IsHostileTo(bot)) if (!boss || !boss->IsAlive())
return false; return false;
return bot->HasAura(SPELL_UNBALANCING_STRIKE); return bot->HasAura(SPELL_UNBALANCING_STRIKE);
@@ -784,7 +762,7 @@ bool ThorimMarkDpsTargetTrigger::IsActive()
if (botAI->IsMainTank(bot)) if (botAI->IsMainTank(bot))
{ {
ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); ObjectGuid currentSkullTarget = group->GetTargetIcon(skullIndex);
Unit* currentSkullUnit = botAI->GetUnit(currentSkullTarget); Unit* currentSkullUnit = botAI->GetUnit(currentSkullTarget);
if (currentSkullUnit && !currentSkullUnit->IsAlive()) if (currentSkullUnit && !currentSkullUnit->IsAlive())
{ {
@@ -805,13 +783,13 @@ bool ThorimMarkDpsTargetTrigger::IsActive()
Unit* boss = AI_VALUE2(Unit*, "find target", "thorim"); Unit* boss = AI_VALUE2(Unit*, "find target", "thorim");
// Check boss and it is alive // Check boss and it is alive
if (!boss || !boss->IsAlive() || !boss->IsHostileTo(bot)) if (!boss || !boss->IsAlive())
return false; return false;
if (boss->GetPositionZ() < ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD && (!currentSkullUnit || !currentSkullUnit->IsAlive())) if (boss->GetPositionZ() < ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD && (!currentSkullUnit || !currentSkullUnit->IsAlive()))
{ {
group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), boss->GetGUID()); group->SetTargetIcon(skullIndex, bot->GetGUID(), boss->GetGUID());
return true; return true;
} }
@@ -833,7 +811,7 @@ bool ThorimMarkDpsTargetTrigger::IsActive()
if (mainTank && bot->GetDistance(mainTank) < 30.0f) if (mainTank && bot->GetDistance(mainTank) < 30.0f)
return false; return false;
ObjectGuid currentCrossTarget = group->GetTargetIcon(RtiTargetValue::crossIndex); ObjectGuid currentCrossTarget = group->GetTargetIcon(crossIndex);
Unit* currentCrossUnit = botAI->GetUnit(currentCrossTarget); Unit* currentCrossUnit = botAI->GetUnit(currentCrossTarget);
if (currentCrossUnit && !currentCrossUnit->IsAlive()) if (currentCrossUnit && !currentCrossUnit->IsAlive())
{ {
@@ -985,7 +963,7 @@ bool ThorimArenaPositioningTrigger::IsActive()
Unit* boss = AI_VALUE2(Unit*, "find target", "thorim"); Unit* boss = AI_VALUE2(Unit*, "find target", "thorim");
// Check boss and it is alive // Check boss and it is alive
if (!boss || !boss->IsAlive() || !boss->IsHostileTo(bot)) if (!boss || !boss->IsAlive())
return false; return false;
if (boss->GetPositionZ() < ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD) if (boss->GetPositionZ() < ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD)
@@ -1083,7 +1061,7 @@ bool ThorimPhase2PositioningTrigger::IsActive()
Unit* boss = AI_VALUE2(Unit*, "find target", "thorim"); Unit* boss = AI_VALUE2(Unit*, "find target", "thorim");
// Check boss and it is alive // Check boss and it is alive
if (!boss || !boss->IsAlive() || !boss->IsHostileTo(bot)) if (!boss || !boss->IsAlive())
return false; return false;
if (boss->GetPositionZ() > ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD) if (boss->GetPositionZ() > ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD)
@@ -1430,8 +1408,8 @@ bool MimironAerialCommandUnitTrigger::IsActive()
return false; return false;
} }
ObjectGuid skullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); ObjectGuid skullTarget = group->GetTargetIcon(skullIndex);
ObjectGuid crossTarget = group->GetTargetIcon(RtiTargetValue::crossIndex); ObjectGuid crossTarget = group->GetTargetIcon(crossIndex);
//if (bombBot && bombBot->GetGUID() != crossTarget) //if (bombBot && bombBot->GetGUID() != crossTarget)
//{ //{
@@ -1537,7 +1515,7 @@ bool MimironPhase4MarkDpsTrigger::IsActive()
highestHealthUnit = aerialCommandUnit; highestHealthUnit = aerialCommandUnit;
} }
ObjectGuid skullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); ObjectGuid skullTarget = group->GetTargetIcon(skullIndex);
if (!skullTarget) if (!skullTarget)
{ {
return true; return true;
@@ -1641,750 +1619,3 @@ bool VezaxMarkOfTheFacelessTrigger::IsActive()
return distance > 2.0f; return distance > 2.0f;
} }
Unit* YoggSaronTrigger::GetSaraIfAlive()
{
Unit* sara = AI_VALUE2(Unit*, "find target", "sara");
if (!sara || !sara->IsAlive())
{
return nullptr;
}
return sara;
}
bool YoggSaronTrigger::IsPhase2()
{
Creature* target = bot->FindNearestCreature(NPC_YOGG_SARON, 200.0f, true);
return target && target->IsAlive() && target->HasAura(SPELL_SHADOW_BARRIER);
}
bool YoggSaronTrigger::IsPhase3()
{
Creature* target = bot->FindNearestCreature(NPC_YOGG_SARON, 200.0f, true);
Creature* guardian = bot->FindNearestCreature(NPC_GUARDIAN_OF_YS, 200.0f, true);
return target && target->IsAlive() && !target->HasAura(SPELL_SHADOW_BARRIER) && !guardian;
}
bool YoggSaronTrigger::IsInBrainLevel()
{
return bot->GetPositionZ() > 230.0f && bot->GetPositionZ() < 250.0f;
}
bool YoggSaronTrigger::IsYoggSaronFight()
{
Unit* sara = AI_VALUE2(Unit*, "find target", "sara");
Unit* yoggsaron = AI_VALUE2(Unit*, "find target", "yogg-saron");
if ((sara && sara->IsAlive()) || (yoggsaron && yoggsaron->IsAlive()))
{
return true;
}
return false;
}
bool YoggSaronTrigger::IsInIllusionRoom()
{
if (!IsInBrainLevel())
{
return false;
}
if (IsInStormwindKeeperIllusion())
{
return true;
}
if (IsInIcecrownKeeperIllusion())
{
return true;
}
if (IsInChamberOfTheAspectsIllusion())
{
return true;
}
return false;
}
bool YoggSaronTrigger::IsInStormwindKeeperIllusion()
{
return bot->GetDistance2d(ULDUAR_YOGG_SARON_STORMWIND_KEEPER_MIDDLE.GetPositionX(),
ULDUAR_YOGG_SARON_STORMWIND_KEEPER_MIDDLE.GetPositionY()) <
ULDUAR_YOGG_SARON_STORMWIND_KEEPER_RADIUS;
}
bool YoggSaronTrigger::IsInIcecrownKeeperIllusion()
{
return bot->GetDistance2d(ULDUAR_YOGG_SARON_ICECROWN_CITADEL_MIDDLE.GetPositionX(),
ULDUAR_YOGG_SARON_ICECROWN_CITADEL_MIDDLE.GetPositionY()) <
ULDUAR_YOGG_SARON_ICECROWN_CITADEL_RADIUS;
}
bool YoggSaronTrigger::IsInChamberOfTheAspectsIllusion()
{
return bot->GetDistance2d(ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_MIDDLE.GetPositionX(),
ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_MIDDLE.GetPositionY()) <
ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_RADIUS;
}
bool YoggSaronTrigger::IsMasterIsInIllusionGroup()
{
Player* master = botAI->GetMaster();
return master && !botAI->IsTank(master);
}
bool YoggSaronTrigger::IsMasterIsInBrainRoom()
{
Player* master = botAI->GetMaster();
if (!master)
{
return false;
}
return master->GetDistance2d(ULDUAR_YOGG_SARON_BRAIN_ROOM_MIDDLE.GetPositionX(),
ULDUAR_YOGG_SARON_BRAIN_ROOM_MIDDLE.GetPositionY()) <
ULDUAR_YOGG_SARON_BRAIN_ROOM_RADIUS &&
master->GetPositionZ() > 230.0f && master->GetPositionZ() < 250.0f;
}
Position YoggSaronTrigger::GetIllusionRoomEntrancePosition()
{
if (IsInChamberOfTheAspectsIllusion())
{
return ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_ENTRANCE;
}
else if (IsInIcecrownKeeperIllusion())
{
return ULDUAR_YOGG_SARON_ICECROWN_CITADEL_ENTRANCE;
}
else if (IsInStormwindKeeperIllusion())
{
return ULDUAR_YOGG_SARON_STORMWIND_KEEPER_ENTRANCE;
}
else
{
return Position();
}
}
Unit* YoggSaronTrigger::GetIllusionRoomRtiTarget()
{
Group* group = bot->GetGroup();
if (!group)
{
return nullptr;
}
uint8 rtiIndex = RtiTargetValue::GetRtiIndex(AI_VALUE(std::string, "rti"));
if (rtiIndex == -1)
{
return nullptr; // Invalid RTI mark
}
ObjectGuid currentRtiTarget = group->GetTargetIcon(rtiIndex);
Unit* currentRtiTargetUnit = botAI->GetUnit(currentRtiTarget);
if (!currentRtiTargetUnit || !currentRtiTargetUnit->IsAlive())
{
currentRtiTargetUnit = nullptr;
}
return currentRtiTargetUnit;
}
Unit* YoggSaronTrigger::GetNextIllusionRoomRtiTarget()
{
float detectionRadius = 0.0f;
if (IsInStormwindKeeperIllusion())
{
detectionRadius = ULDUAR_YOGG_SARON_STORMWIND_KEEPER_RADIUS;
}
else if (IsInIcecrownKeeperIllusion())
{
detectionRadius = ULDUAR_YOGG_SARON_ICECROWN_CITADEL_RADIUS;
}
else if (IsInChamberOfTheAspectsIllusion())
{
detectionRadius = ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_RADIUS;
}
else
{
return nullptr;
}
GuidVector targets = AI_VALUE(GuidVector, "nearest npcs");
if (botAI->HasCheat(BotCheatMask::raid))
{
for (const ObjectGuid& guid : targets)
{
Unit* unit = botAI->GetUnit(guid);
if (unit && unit->IsAlive() && unit->GetEntry() == NPC_LAUGHING_SKULL)
{
return unit;
}
}
}
float nearestDistance = std::numeric_limits<float>::max();
Unit* nextIllusionRoomRtiTarget = nullptr;
for (const uint32& creatureId : illusionMobs)
{
for (const ObjectGuid& guid : targets)
{
Unit* unit = botAI->GetUnit(guid);
if (unit && unit->IsAlive() && unit->GetEntry() == creatureId)
{
float distance = bot->GetDistance(unit);
if (distance < nearestDistance)
{
nextIllusionRoomRtiTarget = unit;
nearestDistance = distance;
}
}
}
}
if (nextIllusionRoomRtiTarget)
{
return nextIllusionRoomRtiTarget;
}
if (IsInStormwindKeeperIllusion())
{
Creature* target = bot->FindNearestCreature(NPC_SUIT_OF_ARMOR, detectionRadius, true);
if (target)
{
return target;
}
}
return nullptr;
}
bool YoggSaronOminousCloudCheatTrigger::IsActive()
{
if (!botAI->HasCheat(BotCheatMask::raid))
{
return false;
}
Unit* boss = GetSaraIfAlive();
if (!boss)
{
return false;
}
if (!botAI->IsBotMainTank(bot))
{
return false;
}
if (bot->GetDistance2d(boss->GetPositionX(), boss->GetPositionY()) > 50.0f)
{
return false;
}
Creature* target = boss->FindNearestCreature(NPC_OMINOUS_CLOUD, 25.0f, true);
return target;
}
bool YoggSaronGuardianPositioningTrigger::IsActive()
{
if (!GetSaraIfAlive())
{
return false;
}
if (!botAI->IsTank(bot))
{
return false;
}
GuidVector targets = AI_VALUE(GuidVector, "nearest npcs");
bool thereIsAnyGuardian = false;
for (const ObjectGuid& guid : targets)
{
Unit* unit = botAI->GetUnit(guid);
if (!unit || !unit->IsAlive())
{
continue;
}
if (unit->GetEntry() == NPC_GUARDIAN_OF_YS)
{
thereIsAnyGuardian = true;
ObjectGuid unitTargetGuid = unit->GetTarget();
Player* targetedPlayer = botAI->GetPlayer(unitTargetGuid);
if (!targetedPlayer || !botAI->IsTank(targetedPlayer))
{
return false;
}
}
}
return thereIsAnyGuardian &&
bot->GetDistance2d(ULDUAR_YOGG_SARON_MIDDLE.GetPositionX(), ULDUAR_YOGG_SARON_MIDDLE.GetPositionY()) > 1.0f;
}
bool YoggSaronSanityTrigger::IsActive()
{
Aura* sanityAura = bot->GetAura(SPELL_SANITY);
if (!sanityAura)
{
return false;
}
int sanityAuraStacks = sanityAura->GetStackAmount();
Creature* sanityWell = bot->FindNearestCreature(NPC_SANITY_WELL, 200.0f);
if (!sanityWell)
{
return false;
}
float distanceToSanityWell = bot->GetDistance(sanityWell);
return (distanceToSanityWell >= 1.0f && sanityAuraStacks < 40) ||
(distanceToSanityWell < 1.0f && sanityAuraStacks < 100);
}
bool YoggSaronDeathOrbTrigger::IsActive()
{
TooCloseToCreatureTrigger tooCloseToDeathOrbTrigger(botAI);
return IsPhase2() && tooCloseToDeathOrbTrigger.TooCloseToCreature(NPC_DEATH_ORB, 10.0f);
}
bool YoggSaronMaladyOfTheMindTrigger::IsActive()
{
TooCloseToPlayerWithDebuffTrigger tooCloseToPlayerWithDebuffTrigger(botAI);
return IsPhase2() && tooCloseToPlayerWithDebuffTrigger.TooCloseToPlayerWithDebuff(SPELL_MALADY_OF_THE_MIND, 15.0f) && botAI->CanMove();
}
bool YoggSaronMarkTargetTrigger::IsActive()
{
if (!IsYoggSaronFight())
{
return false;
}
if (!botAI->IsBotMainTank(bot))
{
return false;
}
Group* group = bot->GetGroup();
if (!group)
{
return false;
}
if (IsPhase2())
{
ObjectGuid currentMoonTarget = group->GetTargetIcon(RtiTargetValue::moonIndex);
Creature* yogg_saron = bot->FindNearestCreature(NPC_YOGG_SARON, 200.0f, true);
if (!currentMoonTarget || currentMoonTarget != yogg_saron->GetGUID())
{
return true;
}
ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex);
Creature* nextPossibleTarget = bot->FindNearestCreature(NPC_CONSTRICTOR_TENTACLE, 200.0f, true);
if (!nextPossibleTarget)
{
nextPossibleTarget = bot->FindNearestCreature(NPC_CORRUPTOR_TENTACLE, 200.0f, true);
if (!nextPossibleTarget)
{
return false;
}
}
if (currentSkullTarget)
{
Unit* currentSkullUnit = botAI->GetUnit(currentSkullTarget);
if (!currentSkullUnit)
{
return true;
}
if (currentSkullUnit->IsAlive() && currentSkullUnit->GetGUID() == nextPossibleTarget->GetGUID())
{
return false;
}
}
return true;
}
else if (IsPhase3())
{
ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex);
Unit* currentSkullUnit = nullptr;
if (currentSkullTarget)
{
currentSkullUnit = botAI->GetUnit(currentSkullTarget);
}
if (currentSkullUnit &&
(currentSkullUnit->GetEntry() == NPC_IMMORTAL_GUARDIAN ||
currentSkullUnit->GetEntry() == NPC_MARKED_IMMORTAL_GUARDIAN) &&
currentSkullUnit->GetHealthPct() > 10)
{
return false;
}
GuidVector targets = AI_VALUE(GuidVector, "nearest npcs");
for (const ObjectGuid& guid : targets)
{
Unit* unit = botAI->GetUnit(guid);
if (!unit || !unit->IsAlive())
{
continue;
}
if ((unit->GetEntry() == NPC_IMMORTAL_GUARDIAN || unit->GetEntry() == NPC_MARKED_IMMORTAL_GUARDIAN) &&
unit->GetHealthPct() > 10)
{
return true;
}
}
if (!currentSkullUnit || currentSkullUnit->GetEntry() != NPC_YOGG_SARON)
{
return true;
}
return false;
}
return false;
}
bool YoggSaronBrainLinkTrigger::IsActive()
{
TooFarFromPlayerWithAuraTrigger tooFarFromPlayerWithAuraTrigger(botAI);
return IsPhase2() && bot->HasAura(SPELL_BRAIN_LINK) &&
tooFarFromPlayerWithAuraTrigger.TooFarFromPlayerWithAura(SPELL_BRAIN_LINK, 20.0f, false);
}
bool YoggSaronMoveToEnterPortalTrigger::IsActive()
{
if (!IsPhase2())
{
return false;
}
Creature* portal = bot->FindNearestCreature(NPC_DESCEND_INTO_MADNESS, 100.0f, true);
if (!portal)
{
return false;
}
if (bot->GetDistance2d(portal->GetPositionX(), portal->GetPositionY()) < 2.0f)
{
return false;
}
if (AI_VALUE(std::string, "rti") != "skull")
{
return false;
}
Group* group = bot->GetGroup();
if (!group)
{
return false;
}
int brainRoomTeamCount = 10;
if (bot->GetRaidDifficulty() == Difficulty::RAID_DIFFICULTY_10MAN_NORMAL)
{
brainRoomTeamCount = 4;
}
if (IsMasterIsInIllusionGroup())
{
brainRoomTeamCount--;
}
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (!member || !member->IsAlive() || botAI->IsTank(member))
{
continue;
}
if (member->GetGUID() == bot->GetGUID())
{
return true;
}
brainRoomTeamCount--;
if (brainRoomTeamCount == 0)
{
break;
}
}
return false;
}
bool YoggSaronFallFromFloorTrigger::IsActive()
{
if (!IsYoggSaronFight())
{
return false;
}
std::string rtiMark = AI_VALUE(std::string, "rti");
if (rtiMark == "skull" && bot->GetPositionZ() < ULDUAR_YOGG_SARON_BOSS_ROOM_AXIS_Z_PATHING_ISSUE_DETECT)
{
return true;
}
if ((rtiMark == "cross" || rtiMark == "circle" || rtiMark == "star") && bot->GetPositionZ() < ULDUAR_YOGG_SARON_BRAIN_ROOM_AXIS_Z_PATHING_ISSUE_DETECT)
{
return true;
}
return false;
}
bool YoggSaronBossRoomMovementCheatTrigger::IsActive()
{
if (!IsYoggSaronFight() || !IsPhase2())
{
return false;
}
FollowMasterStrategy followMasterStrategy(botAI);
if (botAI->HasStrategy(followMasterStrategy.getName(), BotState::BOT_STATE_NON_COMBAT))
{
return true;
}
if (!botAI->HasCheat(BotCheatMask::raid))
{
return false;
}
if (AI_VALUE(std::string, "rti") != "skull")
{
return false;
}
Group* group = bot->GetGroup();
if (!group)
{
return false;
}
ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex);
if (!currentSkullTarget)
{
return false;
}
Unit* currentSkullUnit = botAI->GetUnit(currentSkullTarget);
if (!currentSkullUnit || !currentSkullUnit->IsAlive() || bot->GetDistance2d(currentSkullUnit->GetPositionX(), currentSkullUnit->GetPositionY()) < 40.0f)
{
return false;
}
return true;
}
bool YoggSaronUsePortalTrigger::IsActive()
{
if (!IsPhase2())
{
return false;
}
if (AI_VALUE(std::string, "rti") != "diamond")
{
return false;
}
return bot->FindNearestCreature(NPC_DESCEND_INTO_MADNESS, 2.0f, true) != nullptr;
}
bool YoggSaronIllusionRoomTrigger::IsActive()
{
if (!IsYoggSaronFight() || !IsInIllusionRoom() || AI_VALUE(std::string, "rti") == "square")
{
return false;
}
if (SetRtiMarkRequired())
{
return true;
}
if (SetRtiTargetRequired())
{
return true;
}
if (GoToBrainRoomRequired())
{
return true;
}
return false;
}
bool YoggSaronIllusionRoomTrigger::GoToBrainRoomRequired()
{
if (AI_VALUE(std::string, "rti") == "square")
{
return false;
}
return IsMasterIsInBrainRoom();
}
bool YoggSaronIllusionRoomTrigger::SetRtiMarkRequired()
{
return AI_VALUE(std::string, "rti") == "diamond";
}
bool YoggSaronIllusionRoomTrigger::SetRtiTargetRequired()
{
Unit const* currentRtiTarget = GetIllusionRoomRtiTarget();
if (currentRtiTarget != nullptr)
{
return false;
}
return GetNextIllusionRoomRtiTarget() != nullptr;
}
bool YoggSaronMoveToExitPortalTrigger::IsActive()
{
if (!IsYoggSaronFight() || !IsInBrainLevel())
{
return false;
}
Creature const* brain = bot->FindNearestCreature(NPC_BRAIN, 60.0f, true);
if (!brain || !brain->IsAlive())
{
return false;
}
if (brain->HasUnitState(UNIT_STATE_CASTING))
{
Spell* induceMadnessSpell = brain->GetCurrentSpell(CURRENT_GENERIC_SPELL);
if (induceMadnessSpell && induceMadnessSpell->m_spellInfo->Id == SPELL_INDUCE_MADNESS)
{
uint32 castingTimeLeft = induceMadnessSpell->GetCastTimeRemaining();
if ((botAI->HasCheat(BotCheatMask::raid) && castingTimeLeft < 6000) ||
(!botAI->HasCheat(BotCheatMask::raid) && castingTimeLeft < 8000))
{
return true;
}
}
}
else if (brain->GetHealth() < brain->GetMaxHealth() * 0.3f)
{
return true;
}
return false;
}
bool YoggSaronLunaticGazeTrigger::IsActive()
{
Unit* yoggsaron = AI_VALUE2(Unit*, "find target", "yogg-saron");
if (yoggsaron && yoggsaron->IsAlive() && yoggsaron->HasUnitState(UNIT_STATE_CASTING))
{
Spell* currentSpell = yoggsaron->GetCurrentSpell(CURRENT_CHANNELED_SPELL);
if (currentSpell && currentSpell->m_spellInfo->Id == SPELL_LUNATIC_GAZE_YS)
{
return true;
}
}
return false;
}
bool YoggSaronPhase3PositioningTrigger::IsActive()
{
if (!IsYoggSaronFight() || !IsPhase3())
{
return false;
}
YoggSaronSanityTrigger sanityTrigger(botAI);
if (sanityTrigger.IsActive())
{
return false;
}
if (botAI->IsRanged(bot) && bot->GetDistance2d(ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT.GetPositionX(),
ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT.GetPositionY()) > 15.0f)
{
return true;
}
if (botAI->IsMelee(bot) && !botAI->IsTank(bot) &&
bot->GetDistance2d(ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionX(),
ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionY()) > 15.0f)
{
return true;
}
if (botAI->IsTank(bot))
{
if (bot->GetDistance(ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT) > 30.0f)
{
return true;
}
GuidVector targets = AI_VALUE(GuidVector, "nearest npcs");
bool thereIsAnyGuardian = false;
for (const ObjectGuid& guid : targets)
{
Unit* unit = botAI->GetUnit(guid);
if (!unit || !unit->IsAlive())
{
continue;
}
if (unit->GetEntry() == NPC_IMMORTAL_GUARDIAN || unit->GetEntry() == NPC_MARKED_IMMORTAL_GUARDIAN)
{
thereIsAnyGuardian = true;
ObjectGuid unitTargetGuid = unit->GetTarget();
Player* targetedPlayer = botAI->GetPlayer(unitTargetGuid);
if (!targetedPlayer || !botAI->IsTank(targetedPlayer))
{
return false;
}
}
}
if (thereIsAnyGuardian && bot->GetDistance2d(ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionX(),
ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionY()) > 3.0f)
{
return true;
}
}
return false;
}

View File

@@ -18,7 +18,7 @@ enum UlduarIDs
SPELL_OVERLOAD_25_MAN_2 = 61886, SPELL_OVERLOAD_25_MAN_2 = 61886,
SPELL_RUNE_OF_POWER = 64320, SPELL_RUNE_OF_POWER = 64320,
// Kologarn //Kologarn
NPC_RIGHT_ARM = 32934, NPC_RIGHT_ARM = 32934,
NPC_RUBBLE = 33768, NPC_RUBBLE = 33768,
SPELL_CRUNCH_ARMOR = 64002, SPELL_CRUNCH_ARMOR = 64002,
@@ -27,13 +27,13 @@ enum UlduarIDs
SPELL_FOCUSED_EYEBEAM_10 = 63347, SPELL_FOCUSED_EYEBEAM_10 = 63347,
SPELL_FOCUSED_EYEBEAM_25_2 = 63976, SPELL_FOCUSED_EYEBEAM_25_2 = 63976,
SPELL_FOCUSED_EYEBEAM_25 = 63977, SPELL_FOCUSED_EYEBEAM_25 = 63977,
// Hodir // Hodir
NPC_SNOWPACKED_ICICLE = 33174, NPC_SNOWPACKED_ICICLE = 33174,
NPC_TOASTY_FIRE = 33342, NPC_TOASTY_FIRE = 33342,
SPELL_FLASH_FREEZE = 61968, SPELL_FLASH_FREEZE = 61968,
SPELL_BITING_COLD_PLAYER_AURA = 62039, SPELL_BITING_COLD_PLAYER_AURA = 62039,
// Freya // Freya
NPC_SNAPLASHER = 32916, NPC_SNAPLASHER = 32916,
NPC_STORM_LASHER = 32919, NPC_STORM_LASHER = 32919,
@@ -44,7 +44,7 @@ enum UlduarIDs
NPC_EONARS_GIFT = 33228, NPC_EONARS_GIFT = 33228,
GOBJECT_NATURE_BOMB = 194902, GOBJECT_NATURE_BOMB = 194902,
// Thorim //Thorim
NPC_DARK_RUNE_ACOLYTE_I = 32886, NPC_DARK_RUNE_ACOLYTE_I = 32886,
NPC_CAPTURED_MERCENARY_SOLDIER_ALLY = 32885, NPC_CAPTURED_MERCENARY_SOLDIER_ALLY = 32885,
NPC_CAPTURED_MERCENARY_SOLDIER_HORDE = 32883, NPC_CAPTURED_MERCENARY_SOLDIER_HORDE = 32883,
@@ -62,7 +62,7 @@ enum UlduarIDs
NPC_IRON_HONOR_GUARD = 32875, NPC_IRON_HONOR_GUARD = 32875,
SPELL_UNBALANCING_STRIKE = 62130, SPELL_UNBALANCING_STRIKE = 62130,
// Mimiron //Mimiron
NPC_LEVIATHAN_MKII = 33432, NPC_LEVIATHAN_MKII = 33432,
NPC_VX001 = 33651, NPC_VX001 = 33651,
NPC_AERIAL_COMMAND_UNIT = 33670, NPC_AERIAL_COMMAND_UNIT = 33670,
@@ -78,73 +78,23 @@ enum UlduarIDs
SPELL_P3WX2_LASER_BARRAGE_AURA_1 = 63274, SPELL_P3WX2_LASER_BARRAGE_AURA_1 = 63274,
SPELL_P3WX2_LASER_BARRAGE_AURA_2 = 63300, SPELL_P3WX2_LASER_BARRAGE_AURA_2 = 63300,
// General Vezax //General Vezax
SPELL_MARK_OF_THE_FACELESS = 63276, SPELL_MARK_OF_THE_FACELESS = 63276,
SPELL_SHADOW_CRASH = 63277, SPELL_SHADOW_CRASH = 63277,
// Yogg-Saron
ACTION_ILLUSION_DRAGONS = 1,
ACTION_ILLUSION_ICECROWN = 2,
ACTION_ILLUSION_STORMWIND = 3,
NPC_GUARDIAN_OF_YS = 33136,
NPC_YOGG_SARON = 33288,
NPC_OMINOUS_CLOUD = 33292,
NPC_RUBY_CONSORT = 33716,
NPC_AZURE_CONSORT = 33717,
NPC_BRONZE_CONSORT = 33718,
NPC_EMERALD_CONSORT = 33719,
NPC_OBSIDIAN_CONSORT = 33720,
NPC_ALEXTRASZA = 33536,
NPC_MALYGOS_ILLUSION = 33535,
NPC_NELTHARION = 33523,
NPC_YSERA = 33495,
GO_DRAGON_SOUL = 194462,
NPC_SARA_PHASE_1 = 33134,
NPC_LICH_KING_ILLUSION = 33441,
NPC_IMMOLATED_CHAMPION = 33442,
NPC_SUIT_OF_ARMOR = 33433,
NPC_GARONA = 33436,
NPC_KING_LLANE = 33437,
NPC_DEATHSWORN_ZEALOT = 33567,
NPC_INFLUENCE_TENTACLE = 33943,
NPC_DEATH_ORB = 33882,
NPC_BRAIN = 33890,
NPC_CRUSHER_TENTACLE = 33966,
NPC_CONSTRICTOR_TENTACLE = 33983,
NPC_CORRUPTOR_TENTACLE = 33985,
NPC_IMMORTAL_GUARDIAN = 33988,
NPC_LAUGHING_SKULL = 33990,
NPC_SANITY_WELL = 33991,
NPC_DESCEND_INTO_MADNESS = 34072,
NPC_MARKED_IMMORTAL_GUARDIAN = 36064,
SPELL_SANITY = 63050,
SPELL_BRAIN_LINK = 63802,
SPELL_MALADY_OF_THE_MIND = 63830,
SPELL_SHADOW_BARRIER = 63894,
SPELL_TELEPORT_TO_CHAMBER = 63997,
SPELL_TELEPORT_TO_ICECROWN = 63998,
SPELL_TELEPORT_TO_STORMWIND = 63989,
SPELL_TELEPORT_BACK = 63992,
SPELL_CANCEL_ILLUSION_AURA = 63993,
SPELL_INDUCE_MADNESS = 64059,
SPELL_LUNATIC_GAZE_YS = 64163,
GO_FLEE_TO_THE_SURFACE_PORTAL = 194625,
// Buffs // Buffs
SPELL_FROST_TRAP = 13809 SPELL_FROST_TRAP = 13809
}; };
const int8 skullIndex = 7; // Skull
const int8 crossIndex = 6; // Cross
const int8 moonIndex = 4; // Moon
const float ULDUAR_KOLOGARN_AXIS_Z_PATHING_ISSUE_DETECT = 420.0f; const float ULDUAR_KOLOGARN_AXIS_Z_PATHING_ISSUE_DETECT = 420.0f;
const float ULDUAR_KOLOGARN_EYEBEAM_RADIUS = 3.0f; const float ULDUAR_KOLOGARN_EYEBEAM_RADIUS = 3.0f;
const float ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD = 429.6094f; const float ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD = 429.6094f;
const float ULDUAR_THORIM_AXIS_Z_PATHING_ISSUE_DETECT = 410.0f; const float ULDUAR_THORIM_AXIS_Z_PATHING_ISSUE_DETECT = 410.0f;
const float ULDUAR_AURIAYA_AXIS_Z_PATHING_ISSUE_DETECT = 410.0f; const float ULDUAR_AURIAYA_AXIS_Z_PATHING_ISSUE_DETECT = 410.0f;
const float ULDUAR_YOGG_SARON_BOSS_ROOM_AXIS_Z_PATHING_ISSUE_DETECT = 300.0f;
const float ULDUAR_YOGG_SARON_BRAIN_ROOM_AXIS_Z_PATHING_ISSUE_DETECT = 200.0f;
const float ULDUAR_YOGG_SARON_STORMWIND_KEEPER_RADIUS = 150.0f;
const float ULDUAR_YOGG_SARON_ICECROWN_CITADEL_RADIUS = 150.0f;
const float ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_RADIUS = 150.0f;
const float ULDUAR_YOGG_SARON_BRAIN_ROOM_RADIUS = 50.0f;
const Position ULDUAR_THORIM_NEAR_ARENA_CENTER = Position(2134.9854f, -263.11853f, 419.8465f); const Position ULDUAR_THORIM_NEAR_ARENA_CENTER = Position(2134.9854f, -263.11853f, 419.8465f);
const Position ULDUAR_THORIM_NEAR_ENTRANCE_POSITION = Position(2172.4355f, -258.27957f, 418.47162f); const Position ULDUAR_THORIM_NEAR_ENTRANCE_POSITION = Position(2172.4355f, -258.27957f, 418.47162f);
@@ -173,16 +123,6 @@ const Position ULDUAR_MIMIRON_PHASE2_SIDE3RANGE_SPOT = Position(2754.1294f, 2553
const Position ULDUAR_MIMIRON_PHASE2_SIDE3MELEE_SPOT = Position(2746.8513f, 2565.4263f, 364.31357f); const Position ULDUAR_MIMIRON_PHASE2_SIDE3MELEE_SPOT = Position(2746.8513f, 2565.4263f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE4_TANK_SPOT = Position(2744.5754f, 2570.8657f, 364.3138f); const Position ULDUAR_MIMIRON_PHASE4_TANK_SPOT = Position(2744.5754f, 2570.8657f, 364.3138f);
const Position ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT = Position(1913.6501f, 122.93989f, 342.38083f); const Position ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT = Position(1913.6501f, 122.93989f, 342.38083f);
const Position ULDUAR_YOGG_SARON_MIDDLE = Position(1980.28f, -25.5868f, 329.397f);
const Position ULDUAR_YOGG_SARON_STORMWIND_KEEPER_MIDDLE = Position(1927.1511f, 68.507256f, 242.37657f);
const Position ULDUAR_YOGG_SARON_ICECROWN_CITADEL_MIDDLE = Position(1925.6553f, -121.59296f, 239.98965f);
const Position ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_MIDDLE = Position(2104.5667f, -25.509348f, 242.64679f);
const Position ULDUAR_YOGG_SARON_BRAIN_ROOM_MIDDLE = Position(1980.1971f, -27.854689f, 236.06789f);
const Position ULDUAR_YOGG_SARON_STORMWIND_KEEPER_ENTRANCE = Position(1954.06f, 21.66f, 239.71f);
const Position ULDUAR_YOGG_SARON_ICECROWN_CITADEL_ENTRANCE = Position(1950.11f, -79.284f, 239.98982f);
const Position ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_ENTRANCE = Position(2048.63f, -25.5f, 239.72f);
const Position ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT = Position(1998.5377f, -22.90317f, 324.8895f);
const Position ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT = Position(2018.7628f, -18.896868f, 327.07245f);
// //
// Flame Levi // Flame Levi
@@ -508,139 +448,4 @@ public:
bool IsActive() override; bool IsActive() override;
}; };
//
// Yogg-Saron
//
class YoggSaronTrigger : public Trigger
{
public:
YoggSaronTrigger(PlayerbotAI* ai, std::string const name = "yogg saron trigger", int32 checkInteval = 1)
: Trigger(ai, name, checkInteval) {}
bool IsYoggSaronFight();
bool IsPhase2();
bool IsPhase3();
bool IsInBrainLevel();
bool IsInIllusionRoom();
bool IsInStormwindKeeperIllusion();
bool IsInIcecrownKeeperIllusion();
bool IsInChamberOfTheAspectsIllusion();
bool IsMasterIsInIllusionGroup();
bool IsMasterIsInBrainRoom();
Position GetIllusionRoomEntrancePosition();
Unit* GetIllusionRoomRtiTarget();
Unit* GetNextIllusionRoomRtiTarget();
Unit* GetSaraIfAlive();
};
class YoggSaronOminousCloudCheatTrigger : public YoggSaronTrigger
{
public:
YoggSaronOminousCloudCheatTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron ominous cloud cheat trigger") {}
bool IsActive() override;
};
class YoggSaronGuardianPositioningTrigger : public YoggSaronTrigger
{
public:
YoggSaronGuardianPositioningTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron guardian positioning trigger") {}
bool IsActive() override;
};
class YoggSaronSanityTrigger : public YoggSaronTrigger
{
public:
YoggSaronSanityTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron sanity trigger") {}
bool IsActive() override;
};
class YoggSaronDeathOrbTrigger : public YoggSaronTrigger
{
public:
YoggSaronDeathOrbTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron death orb trigger") {}
bool IsActive() override;
};
class YoggSaronMaladyOfTheMindTrigger : public YoggSaronTrigger
{
public:
YoggSaronMaladyOfTheMindTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron malady of the mind trigger") {}
bool IsActive() override;
};
class YoggSaronMarkTargetTrigger : public YoggSaronTrigger
{
public:
YoggSaronMarkTargetTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron mark target trigger") {}
bool IsActive() override;
};
class YoggSaronBrainLinkTrigger : public YoggSaronTrigger
{
public:
YoggSaronBrainLinkTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron brain link trigger") {}
bool IsActive() override;
};
class YoggSaronMoveToEnterPortalTrigger : public YoggSaronTrigger
{
public:
YoggSaronMoveToEnterPortalTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron move to enter portal trigger") {}
bool IsActive() override;
};
class YoggSaronFallFromFloorTrigger : public YoggSaronTrigger
{
public:
YoggSaronFallFromFloorTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron fall from floor trigger") {}
bool IsActive() override;
};
class YoggSaronBossRoomMovementCheatTrigger : public YoggSaronTrigger
{
public:
YoggSaronBossRoomMovementCheatTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron boss room movement cheat trigger") {}
bool IsActive() override;
};
class YoggSaronUsePortalTrigger : public YoggSaronTrigger
{
public:
YoggSaronUsePortalTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron use portal trigger") {}
bool IsActive() override;
};
class YoggSaronIllusionRoomTrigger : public YoggSaronTrigger
{
public:
YoggSaronIllusionRoomTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron illusion room trigger") {}
bool IsActive() override;
private:
bool GoToBrainRoomRequired();
bool SetRtiMarkRequired();
bool SetRtiTargetRequired();
};
class YoggSaronMoveToExitPortalTrigger : public YoggSaronTrigger
{
public:
YoggSaronMoveToExitPortalTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron move to exit portal trigger") {}
bool IsActive() override;
};
class YoggSaronLunaticGazeTrigger : public YoggSaronTrigger
{
public:
YoggSaronLunaticGazeTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron lunatic gaze trigger") {}
bool IsActive() override;
};
class YoggSaronPhase3PositioningTrigger : public YoggSaronTrigger
{
public:
YoggSaronPhase3PositioningTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron phase 3 positioning trigger") {}
bool IsActive() override;
};
#endif #endif

View File

@@ -20,8 +20,6 @@ public:
creators["emalon lighting nova action"] = &RaidVoAActionContext::emalon_lighting_nova_action; creators["emalon lighting nova action"] = &RaidVoAActionContext::emalon_lighting_nova_action;
creators["emalon overcharge action"] = &RaidVoAActionContext::emalon_overcharge_action; creators["emalon overcharge action"] = &RaidVoAActionContext::emalon_overcharge_action;
creators["emalon fall from floor action"] = &RaidVoAActionContext::emalon_fall_from_floor_action; creators["emalon fall from floor action"] = &RaidVoAActionContext::emalon_fall_from_floor_action;
creators["emalon nature resistance action"] = &RaidVoAActionContext::emalon_nature_resistance_action;
creators["koralon fire resistance action"] = &RaidVoAActionContext::koralon_fire_resistance_action;
} }
private: private:
@@ -29,8 +27,6 @@ private:
static Action* emalon_lighting_nova_action(PlayerbotAI* ai) { return new EmalonLightingNovaAction(ai); } static Action* emalon_lighting_nova_action(PlayerbotAI* ai) { return new EmalonLightingNovaAction(ai); }
static Action* emalon_overcharge_action(PlayerbotAI* ai) { return new EmalonOverchargeAction(ai); } static Action* emalon_overcharge_action(PlayerbotAI* ai) { return new EmalonOverchargeAction(ai); }
static Action* emalon_fall_from_floor_action(PlayerbotAI* ai) { return new EmalonFallFromFloorAction(ai); } static Action* emalon_fall_from_floor_action(PlayerbotAI* ai) { return new EmalonFallFromFloorAction(ai); }
static Action* emalon_nature_resistance_action(PlayerbotAI* ai) { return new BossNatureResistanceAction(ai, "emalon the storm watcher"); }
static Action* koralon_fire_resistance_action(PlayerbotAI* ai) { return new BossFireResistanceAction(ai, "koralon the flame watcher"); }
}; };
#endif #endif

View File

@@ -24,16 +24,4 @@ void RaidVoAStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode( triggers.push_back(new TriggerNode(
"emalon fall from floor trigger", "emalon fall from floor trigger",
NextAction::array(0, new NextAction("emalon fall from floor action", ACTION_RAID), nullptr))); NextAction::array(0, new NextAction("emalon fall from floor action", ACTION_RAID), nullptr)));
triggers.push_back(new TriggerNode(
"emalon nature resistance trigger",
NextAction::array(0, new NextAction("emalon nature resistance action", ACTION_RAID), nullptr)));
//
// Koralon the Flame Watcher
//
triggers.push_back(new TriggerNode(
"koralon fire resistance trigger",
NextAction::array(0, new NextAction("koralon fire resistance action", ACTION_RAID), nullptr)));
} }

View File

@@ -19,8 +19,6 @@ public:
creators["emalon lighting nova trigger"] = &RaidVoATriggerContext::emalon_lighting_nova_trigger; creators["emalon lighting nova trigger"] = &RaidVoATriggerContext::emalon_lighting_nova_trigger;
creators["emalon overcharge trigger"] = &RaidVoATriggerContext::emalon_overcharge_trigger; creators["emalon overcharge trigger"] = &RaidVoATriggerContext::emalon_overcharge_trigger;
creators["emalon fall from floor trigger"] = &RaidVoATriggerContext::emalon_fall_from_floor_trigger; creators["emalon fall from floor trigger"] = &RaidVoATriggerContext::emalon_fall_from_floor_trigger;
creators["emalon nature resistance trigger"] = &RaidVoATriggerContext::emalon_nature_resistance_trigger;
creators["koralon fire resistance trigger"] = &RaidVoATriggerContext::koralon_fire_resistance_trigger;
} }
private: private:
@@ -28,8 +26,6 @@ private:
static Trigger* emalon_lighting_nova_trigger(PlayerbotAI* ai) { return new EmalonLightingNovaTrigger(ai); } static Trigger* emalon_lighting_nova_trigger(PlayerbotAI* ai) { return new EmalonLightingNovaTrigger(ai); }
static Trigger* emalon_overcharge_trigger(PlayerbotAI* ai) { return new EmalonOverchargeTrigger(ai); } static Trigger* emalon_overcharge_trigger(PlayerbotAI* ai) { return new EmalonOverchargeTrigger(ai); }
static Trigger* emalon_fall_from_floor_trigger(PlayerbotAI* ai) { return new EmalonFallFromFloorTrigger(ai); } static Trigger* emalon_fall_from_floor_trigger(PlayerbotAI* ai) { return new EmalonFallFromFloorTrigger(ai); }
static Trigger* emalon_nature_resistance_trigger(PlayerbotAI* ai) { return new BossNatureResistanceTrigger(ai, "emalon the storm watcher"); }
static Trigger* koralon_fire_resistance_trigger(PlayerbotAI* ai) { return new BossFireResistanceTrigger(ai, "koralon the flame watcher"); }
}; };
#endif #endif

View File

@@ -1060,13 +1060,6 @@ bool NewRpgBaseAction::RandomChangeStatus(std::vector<NewRpgStatus> candidateSta
probSum += sPlayerbotAIConfig->RpgStatusProbWeight[status]; probSum += sPlayerbotAIConfig->RpgStatusProbWeight[status];
} }
} }
// Safety check. Default to "rest" if all RPG weights = 0
if (availableStatus.empty() || probSum == 0)
{
botAI->rpgInfo.ChangeToRest();
bot->SetStandState(UNIT_STAND_STATE_SIT);
return true;
}
uint32 rand = urand(1, probSum); uint32 rand = urand(1, probSum);
uint32 accumulate = 0; uint32 accumulate = 0;
NewRpgStatus chosenStatus = RPG_STATUS_END; NewRpgStatus chosenStatus = RPG_STATUS_END;
@@ -1225,4 +1218,4 @@ bool NewRpgBaseAction::CheckRpgStatusAvailable(NewRpgStatus status)
return false; return false;
} }
return false; return false;
} }

View File

@@ -133,8 +133,6 @@ void GenericShamanStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
// NextAction("riptide", 26.0f), nullptr))); // NextAction("riptide", 26.0f), nullptr)));
triggers.push_back(new TriggerNode("heroism", NextAction::array(0, new NextAction("heroism", 31.0f), nullptr))); triggers.push_back(new TriggerNode("heroism", NextAction::array(0, new NextAction("heroism", 31.0f), nullptr)));
triggers.push_back(new TriggerNode("bloodlust", NextAction::array(0, new NextAction("bloodlust", 30.0f), nullptr))); triggers.push_back(new TriggerNode("bloodlust", NextAction::array(0, new NextAction("bloodlust", 30.0f), nullptr)));
triggers.push_back(new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr)));
triggers.push_back(new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 65.0f), nullptr)));
} }
void ShamanBuffDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void ShamanBuffDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
@@ -189,4 +187,4 @@ void ShamanHealerDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
new TriggerNode("medium aoe and healer should attack", new TriggerNode("medium aoe and healer should attack",
NextAction::array(0, NextAction::array(0,
new NextAction("chain lightning", ACTION_DEFAULT + 0.3f), nullptr))); new NextAction("chain lightning", ACTION_DEFAULT + 0.3f), nullptr)));
} }

View File

@@ -49,10 +49,6 @@ void ShamanNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
new TriggerNode("cure disease", NextAction::array(0, new NextAction("cure disease", 31.0f), nullptr))); new TriggerNode("cure disease", NextAction::array(0, new NextAction("cure disease", 31.0f), nullptr)));
triggers.push_back(new TriggerNode("party member cure disease", triggers.push_back(new TriggerNode("party member cure disease",
NextAction::array(0, new NextAction("cure disease on party", 30.0f), nullptr))); NextAction::array(0, new NextAction("cure disease on party", 30.0f), nullptr)));
triggers.push_back(
new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr)));
triggers.push_back(
new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 65.0f), nullptr)));
} }
void ShamanNonCombatStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers) void ShamanNonCombatStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)

View File

@@ -15,7 +15,7 @@ bool BossFireResistanceTrigger::IsActive()
{ {
// Check boss and it is alive // Check boss and it is alive
Unit* boss = AI_VALUE2(Unit*, "find target", bossName); Unit* boss = AI_VALUE2(Unit*, "find target", bossName);
if (!boss || !boss->IsAlive() || boss->IsFriendlyTo(bot)) if (!boss || !boss->IsAlive())
return false; return false;
// Check if bot is paladin // Check if bot is paladin
@@ -68,7 +68,7 @@ bool BossFrostResistanceTrigger::IsActive()
{ {
// Check boss and it is alive // Check boss and it is alive
Unit* boss = AI_VALUE2(Unit*, "find target", bossName); Unit* boss = AI_VALUE2(Unit*, "find target", bossName);
if (!boss || !boss->IsAlive() || boss->IsFriendlyTo(bot)) if (!boss || !boss->IsAlive())
return false; return false;
// Check if bot is paladin // Check if bot is paladin
@@ -121,7 +121,7 @@ bool BossNatureResistanceTrigger::IsActive()
{ {
// Check boss and it is alive // Check boss and it is alive
Unit* boss = AI_VALUE2(Unit*, "find target", bossName); Unit* boss = AI_VALUE2(Unit*, "find target", bossName);
if (!boss || !boss->IsAlive() || boss->IsFriendlyTo(bot)) if (!boss || !boss->IsAlive())
return false; return false;
// Check if bot is alive // Check if bot is alive
@@ -176,7 +176,7 @@ bool BossShadowResistanceTrigger::IsActive()
{ {
// Check boss and it is alive // Check boss and it is alive
Unit* boss = AI_VALUE2(Unit*, "find target", bossName); Unit* boss = AI_VALUE2(Unit*, "find target", bossName);
if (!boss || !boss->IsAlive() || boss->IsFriendlyTo(bot)) if (!boss || !boss->IsAlive())
return false; return false;
// Check if bot is paladin // Check if bot is paladin

View File

@@ -133,11 +133,9 @@ public:
creators["calc"] = &ChatTriggerContext::calc; creators["calc"] = &ChatTriggerContext::calc;
creators["qi"] = &ChatTriggerContext::qi; creators["qi"] = &ChatTriggerContext::qi;
creators["wipe"] = &ChatTriggerContext::wipe; creators["wipe"] = &ChatTriggerContext::wipe;
creators["tame"] = &ChatTriggerContext::tame; creators["pet"] = &ChatTriggerContext::pet;
creators["glyphs"] = &ChatTriggerContext::glyphs; // Added for custom Glyphs creators["glyphs"] = &ChatTriggerContext::glyphs; // Added for custom Glyphs
creators["glyph equip"] = &ChatTriggerContext::glyph_equip; // Added for custom Glyphs creators["glyph equip"] = &ChatTriggerContext::glyph_equip; // Added for custom Glyphs
creators["pet"] = &ChatTriggerContext::pet;
creators["pet attack"] = &ChatTriggerContext::pet_attack;
creators["roll"] = &ChatTriggerContext::roll_action; creators["roll"] = &ChatTriggerContext::roll_action;
} }
@@ -251,11 +249,9 @@ private:
static Trigger* calc(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "calc"); } static Trigger* calc(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "calc"); }
static Trigger* qi(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "qi"); } static Trigger* qi(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "qi"); }
static Trigger* wipe(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "wipe"); } static Trigger* wipe(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "wipe"); }
static Trigger* tame(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "tame"); } static Trigger* pet(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pet"); }
static Trigger* glyphs(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "glyphs"); } // Added for custom Glyphs static Trigger* glyphs(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "glyphs"); } // Added for custom Glyphs
static Trigger* glyph_equip(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "glyph equip"); } // Added for custom Glyphs static Trigger* glyph_equip(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "glyph equip"); } // Added for custom Glyphs
static Trigger* pet(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pet"); }
static Trigger* pet_attack(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pet attack"); }
static Trigger* roll_action(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "roll"); } static Trigger* roll_action(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "roll"); }
}; };

View File

@@ -20,8 +20,6 @@
#include "TemporarySummon.h" #include "TemporarySummon.h"
#include "ThreatMgr.h" #include "ThreatMgr.h"
#include "Timer.h" #include "Timer.h"
#include "PlayerbotAI.h"
#include "Player.h"
bool LowManaTrigger::IsActive() bool LowManaTrigger::IsActive()
{ {
@@ -249,7 +247,7 @@ bool AoeTrigger::IsActive()
bool NoFoodTrigger::IsActive() bool NoFoodTrigger::IsActive()
{ {
bool isRandomBot = sRandomPlayerbotMgr->IsRandomBot(bot); bool isRandomBot = sRandomPlayerbotMgr->IsRandomBot(bot);
if (isRandomBot && botAI->HasCheat(BotCheatMask::food)) if (isRandomBot && sPlayerbotAIConfig->freeFood)
return false; return false;
return AI_VALUE2(std::vector<Item*>, "inventory items", "conjured food").empty(); return AI_VALUE2(std::vector<Item*>, "inventory items", "conjured food").empty();
@@ -258,7 +256,7 @@ bool NoFoodTrigger::IsActive()
bool NoDrinkTrigger::IsActive() bool NoDrinkTrigger::IsActive()
{ {
bool isRandomBot = sRandomPlayerbotMgr->IsRandomBot(bot); bool isRandomBot = sRandomPlayerbotMgr->IsRandomBot(bot);
if (isRandomBot && botAI->HasCheat(BotCheatMask::food)) if (isRandomBot && sPlayerbotAIConfig->freeFood)
return false; return false;
return AI_VALUE2(std::vector<Item*>, "inventory items", "conjured water").empty(); return AI_VALUE2(std::vector<Item*>, "inventory items", "conjured water").empty();
@@ -687,46 +685,3 @@ bool AmmoCountTrigger::IsActive()
return ItemCountTrigger::IsActive(); return ItemCountTrigger::IsActive();
} }
bool NewPetTrigger::IsActive()
{
// Get the bot player object from the AI
Player* bot = botAI->GetBot();
if (!bot)
return false;
// Try to get the current pet; initialize guardian and GUID to null/empty
Pet* pet = bot->GetPet();
Guardian* guardian = nullptr;
ObjectGuid currentPetGuid = ObjectGuid::Empty;
// If bot has a pet, get its GUID
if (pet)
{
currentPetGuid = pet->GetGUID();
}
else
{
// If no pet, try to get a guardian pet and its GUID
guardian = bot->GetGuardianPet();
if (guardian)
currentPetGuid = guardian->GetGUID();
}
// If the current pet or guardian GUID has changed (including becoming empty), reset the trigger state
if (currentPetGuid != lastPetGuid)
{
triggered = false;
lastPetGuid = currentPetGuid;
}
// If there's a valid current pet/guardian (non-empty GUID) and we haven't triggered yet, activate trigger
if (currentPetGuid != ObjectGuid::Empty && !triggered)
{
triggered = true;
return true;
}
// Otherwise, do not activate
return false;
}

View File

@@ -943,16 +943,4 @@ public:
bool IsActive() override { return !bot->IsAlive() && bot->GetUInt32Value(PLAYER_SELF_RES_SPELL); } bool IsActive() override { return !bot->IsAlive() && bot->GetUInt32Value(PLAYER_SELF_RES_SPELL); }
}; };
class NewPetTrigger : public Trigger
{
public:
NewPetTrigger(PlayerbotAI* ai) : Trigger(ai, "new pet"), lastPetGuid(ObjectGuid::Empty), triggered(false) {}
bool IsActive() override;
private:
ObjectGuid lastPetGuid;
bool triggered;
};
#endif #endif

View File

@@ -214,84 +214,3 @@ bool FarFromMasterTrigger::IsActive()
{ {
return sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "master target"), distance); return sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "master target"), distance);
} }
bool TooCloseToCreatureTrigger::TooCloseToCreature(uint32 creatureId, float range, bool alive)
{
Creature* nearestCreature = bot->FindNearestCreature(creatureId, range, alive);
return nearestCreature != nullptr;
}
bool TooCloseToPlayerWithDebuffTrigger::TooCloseToPlayerWithDebuff(uint32 spellId, float range)
{
Group* group = bot->GetGroup();
if (!group)
{
return false;
}
std::vector<Player*> debuffedPlayers;
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* player = gref->GetSource();
if (player && player->IsAlive() && player->HasAura(spellId))
{
debuffedPlayers.push_back(player);
}
}
if (debuffedPlayers.empty())
{
return false;
}
for (Unit* debuffedPlayer : debuffedPlayers)
{
float dist = debuffedPlayer->GetExactDist2d(bot->GetPositionX(), bot->GetPositionY());
if (dist < range)
{
return true;
}
}
return false;
}
bool TooFarFromPlayerWithAuraTrigger::TooFarFromPlayerWithAura(uint32 spellId, float range, bool selfInclude)
{
Group* group = bot->GetGroup();
if (!group)
{
return false;
}
std::vector<Player*> debuffedPlayers;
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* player = gref->GetSource();
if (player && player->IsAlive() && player->HasAura(spellId) &&
(selfInclude || (!selfInclude && player->GetGUID() != bot->GetGUID())))
{
debuffedPlayers.push_back(player);
}
}
return !debuffedPlayers.empty();
if (debuffedPlayers.empty())
{
return false;
}
for (Unit* debuffedPlayer : debuffedPlayers)
{
float dist = debuffedPlayer->GetExactDist2d(bot->GetPositionX(), bot->GetPositionY());
if (dist > range)
{
return true;
}
}
return false;
}

View File

@@ -121,28 +121,4 @@ public:
OutOfReactRangeTrigger(PlayerbotAI* botAI) : FarFromMasterTrigger(botAI, "out of react range", 50.0f, 5) {} OutOfReactRangeTrigger(PlayerbotAI* botAI) : FarFromMasterTrigger(botAI, "out of react range", 50.0f, 5) {}
}; };
class TooCloseToCreatureTrigger : public Trigger
{
public:
TooCloseToCreatureTrigger(PlayerbotAI* ai) : Trigger(ai, "too close to creature trigger") {}
bool TooCloseToCreature(uint32 creatureId, float range, bool alive = true);
};
class TooCloseToPlayerWithDebuffTrigger : public Trigger
{
public:
TooCloseToPlayerWithDebuffTrigger(PlayerbotAI* ai) : Trigger(ai, "too cloose to player with debuff trigger") {}
bool TooCloseToPlayerWithDebuff(uint32 spellId, float range);
};
class TooFarFromPlayerWithAuraTrigger : public Trigger
{
public:
TooFarFromPlayerWithAuraTrigger(PlayerbotAI* ai) : Trigger(ai, "too far from player with aura trigger") {}
bool TooFarFromPlayerWithAura(uint32 spellId, float range, bool selfInclude = false);
};
#endif #endif

View File

@@ -227,7 +227,6 @@ public:
creators["do quest status"] = &TriggerContext::do_quest_status; creators["do quest status"] = &TriggerContext::do_quest_status;
creators["travel flight status"] = &TriggerContext::travel_flight_status; creators["travel flight status"] = &TriggerContext::travel_flight_status;
creators["can self resurrect"] = &TriggerContext::can_self_resurrect; creators["can self resurrect"] = &TriggerContext::can_self_resurrect;
creators["new pet"] = &TriggerContext::new_pet;
} }
private: private:
@@ -426,7 +425,6 @@ private:
static Trigger* do_quest_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_DO_QUEST); } static Trigger* do_quest_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_DO_QUEST); }
static Trigger* travel_flight_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_TRAVEL_FLIGHT); } static Trigger* travel_flight_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_TRAVEL_FLIGHT); }
static Trigger* can_self_resurrect(PlayerbotAI* ai) { return new SelfResurrectTrigger(ai); } static Trigger* can_self_resurrect(PlayerbotAI* ai) { return new SelfResurrectTrigger(ai); }
static Trigger* new_pet(PlayerbotAI* ai) { return new NewPetTrigger(ai); }
}; };
#endif #endif

View File

@@ -21,14 +21,6 @@ public:
static int32 GetRtiIndex(std::string const rti); static int32 GetRtiIndex(std::string const rti);
Unit* Calculate() override; Unit* Calculate() override;
static const int8 starIndex = 0;
static const int8 circleIndex = 1;
static const int8 diamondIndex = 2;
static const int8 triangleIndex = 3;
static const int8 moonIndex = 4;
static const int8 squareIndex = 5;
static const int8 crossIndex = 6;
static const int8 skullIndex = 7;
private: private:
std::string const type; std::string const type;

View File

@@ -556,7 +556,7 @@ private:
static UntypedValue* last_flee_timestamp(PlayerbotAI* ai) { return new LastFleeTimestampValue(ai); } static UntypedValue* last_flee_timestamp(PlayerbotAI* ai) { return new LastFleeTimestampValue(ai); }
static UntypedValue* recently_flee_info(PlayerbotAI* ai) { return new RecentlyFleeInfo(ai); } static UntypedValue* recently_flee_info(PlayerbotAI* ai) { return new RecentlyFleeInfo(ai); }
// ------------------------------------------------------- // -------------------------------------------------------
// Flag for cutom glyphs : true when /w bot glyph equip // Flag for cutom glyphs : true when /w bot glyph equip
// ------------------------------------------------------- // -------------------------------------------------------
static UntypedValue* custom_glyphs(PlayerbotAI* ai) static UntypedValue* custom_glyphs(PlayerbotAI* ai)
{ {

View File

@@ -79,7 +79,6 @@ void GenericWarlockNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& tr
{ {
NonCombatStrategy::InitTriggers(triggers); NonCombatStrategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr))); triggers.push_back(new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr)));
triggers.push_back(new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr)));
triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("fel domination", 30.0f), nullptr))); triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("fel domination", 30.0f), nullptr)));
triggers.push_back(new TriggerNode("no soul shard", NextAction::array(0, new NextAction("create soul shard", 60.0f), nullptr))); triggers.push_back(new TriggerNode("no soul shard", NextAction::array(0, new NextAction("create soul shard", 60.0f), nullptr)));
triggers.push_back(new TriggerNode("too many soul shards", NextAction::array(0, new NextAction("destroy soul shard", 60.0f), nullptr))); triggers.push_back(new TriggerNode("too many soul shards", NextAction::array(0, new NextAction("destroy soul shard", 60.0f), nullptr)));