mirror of
https://github.com/mod-playerbots/mod-playerbots
synced 2025-11-29 15:58:20 +08:00
Compare commits
44 Commits
hermensbas
...
opcode-cra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5f4f32799 | ||
|
|
11b96b51b7 | ||
|
|
a41c1912ac | ||
|
|
21d8f32d24 | ||
|
|
bf56154eee | ||
|
|
e46269920a | ||
|
|
1881ef1fe0 | ||
|
|
3c442a6b71 | ||
|
|
750d557e6a | ||
|
|
8057a5d7ac | ||
|
|
c218dbe653 | ||
|
|
6588ca5878 | ||
|
|
df6b1490b1 | ||
|
|
179c34e3a9 | ||
|
|
45d046f427 | ||
|
|
31f2c6a20d | ||
|
|
37458f0dc5 | ||
|
|
bc737ecc68 | ||
|
|
704e02e9cc | ||
|
|
5f00b9bbd5 | ||
|
|
5469333465 | ||
|
|
2dad8bf01d | ||
|
|
78116fe37e | ||
|
|
c9b4cfa184 | ||
|
|
957a60cd1d | ||
|
|
b661264c53 | ||
|
|
77c2354c3f | ||
|
|
b369b1f9ae | ||
|
|
fa7b863035 | ||
|
|
4f5f7d286e | ||
|
|
6cb9f56c4e | ||
|
|
3e0f23536d | ||
|
|
12065a6ad5 | ||
|
|
c5010b3809 | ||
|
|
86dbf54584 | ||
|
|
2144c95311 | ||
|
|
28de238422 | ||
|
|
ab345b8847 | ||
|
|
683c6e39e4 | ||
|
|
548746c25f | ||
|
|
f7e64589e8 | ||
|
|
8545225923 | ||
|
|
ffc96b664e | ||
|
|
aa6f8153a1 |
26
README.md
26
README.md
@@ -2,6 +2,8 @@
|
||||
<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/brighton-chi/mod-playerbots/blob/readme/README_ES.md">Español</a>
|
||||
</p>
|
||||
|
||||
|
||||
@@ -16,23 +18,25 @@
|
||||
</div>
|
||||
|
||||
# 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). Features include:
|
||||
`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).
|
||||
|
||||
- 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;
|
||||
- Bots capable of running raids and battlegrounds;
|
||||
Features include:
|
||||
|
||||
- The ability to log in alt characters as bots, allowing players to interact with their other characters, form parties, level up, and more;
|
||||
- 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;
|
||||
- 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.
|
||||
|
||||
**Playerbots Module** has a **[Discord server](https://discord.gg/NQm5QShwf9)** where you can discuss the project.
|
||||
`mod-playerbots` has a **[Discord server](https://discord.gg/NQm5QShwf9)** where you can discuss the project, ask questions, and get involved in the community!
|
||||
|
||||
## Installation
|
||||
|
||||
### Classic Installation
|
||||
|
||||
`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:
|
||||
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:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/liyunfan1223/azerothcore-wotlk.git --branch=Playerbot
|
||||
@@ -81,21 +85,21 @@ Use `docker compose up -d --build` to build and run the server. For more informa
|
||||
|
||||
## Documentation
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
- **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.
|
||||
- **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.
|
||||
- **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.
|
||||
|
||||
## 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:
|
||||
|
||||
- [Multibot](https://github.com/Macx-Lio/MultiBot) (by Macx-Lio)
|
||||
- [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)
|
||||
- [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)]
|
||||
- [Unbot Addon (zh)](https://github.com/liyunfan1223/unbot-addon) (Chinese version by Liyunfan) [note: no longer under active development]
|
||||
- [Unbot Addon (en)](https://github.com/noisiver/unbot-addon/tree/english) (English version translated by @Revision) [note: no longer under active development]
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
|
||||
@@ -174,10 +174,6 @@ AiPlayerbot.SummonWhenGroup = 1
|
||||
# Selfbot permission level (0 = disabled, 1 = GM only (default), 2 = all players, 3 = activate on login)
|
||||
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
|
||||
# Default: 0 (non-GM player can use any intialization commands)
|
||||
AiPlayerbot.AutoInitOnly = 0
|
||||
@@ -496,6 +492,7 @@ AiPlayerbot.AutoGearQualityLimit = 3
|
||||
AiPlayerbot.AutoGearScoreLimit = 0
|
||||
|
||||
# Enable/Disable cheats for bots
|
||||
# "food" (bots eat or drink without using food or drinks from their inventory)
|
||||
# "gold" (bots have infinite gold)
|
||||
# "health" (bots have infinite health)
|
||||
# "mana" (bots have infinite mana)
|
||||
@@ -504,7 +501,7 @@ AiPlayerbot.AutoGearScoreLimit = 0
|
||||
# "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")
|
||||
# Default: taxi and raid are enabled
|
||||
AiPlayerbot.BotCheats = "taxi,raid"
|
||||
AiPlayerbot.BotCheats = "food,taxi,raid"
|
||||
|
||||
#
|
||||
#
|
||||
@@ -662,7 +659,7 @@ AiPlayerbot.RandomGearScoreLimit = 0
|
||||
# Default: 1 (enabled)
|
||||
AiPlayerbot.IncrementalGearInit = 1
|
||||
|
||||
# Set minimum level of bots that will enchant their equipment (Maxlevel + 1 to disable)
|
||||
# Set minimum level of bots that will enchant their equipment (if greater than RandomBotMaxlevel, bots will not enchant equipment)
|
||||
# Default: 60
|
||||
AiPlayerbot.MinEnchantingBotLevel = 60
|
||||
|
||||
@@ -699,6 +696,16 @@ AiPlayerbot.AutoUpgradeEquip = 1
|
||||
# Default: 0 (disabled)
|
||||
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
|
||||
# See the creature_family database table for all pet families by ID (note: ID for spiders is 3)
|
||||
AiPlayerbot.ExcludedHunterPetFamilies = ""
|
||||
@@ -804,12 +811,13 @@ AiPlayerbot.OpenGoSpell = 6477
|
||||
#
|
||||
|
||||
# Additional randombot strategies
|
||||
# Strategies added here are applied to all randombots, in addition (or subtraction) to spec-based default strategies. These rules are processed after the defaults.
|
||||
AiPlayerbot.RandomBotCombatStrategies = "+dps,+dps assist,-threat"
|
||||
# Strategies added here are applied to all randombots, in addition (or subtraction) to spec/role-based default strategies. These rules are processed after the defaults.
|
||||
# Example: "+threat,-potions"
|
||||
AiPlayerbot.RandomBotCombatStrategies = ""
|
||||
AiPlayerbot.RandomBotNonCombatStrategies = ""
|
||||
|
||||
# Additional altbot strategies
|
||||
# Strategies added here are applied to all altbots, in addition (or subtraction) to spec-based default strategies. These rules are processed after the defaults.
|
||||
# Strategies added here are applied to all altbots, in addition (or subtraction) to spec/role-based default strategies. These rules are processed after the defaults.
|
||||
AiPlayerbot.CombatStrategies = ""
|
||||
AiPlayerbot.NonCombatStrategies = ""
|
||||
|
||||
@@ -1006,6 +1014,20 @@ AiPlayerbot.RandomBotMaps = 0,1,530,571
|
||||
# Default: 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
|
||||
AiPlayerbot.RandomBotTeleportDistance = 100
|
||||
|
||||
@@ -1110,7 +1132,7 @@ AiPlayerbot.DeleteRandomBotArenaTeams = 0
|
||||
AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,3951"
|
||||
|
||||
# PvP Restricted Areas (bots don't pvp)
|
||||
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080"
|
||||
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754"
|
||||
|
||||
# Improve reaction speeds in battlegrounds and arenas (may cause lag)
|
||||
AiPlayerbot.FastReactInBG = 1
|
||||
@@ -1975,13 +1997,15 @@ AiPlayerbot.AllowedLogFiles = ""
|
||||
####################################################################################################
|
||||
# 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,17155,2857,179490
|
||||
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
|
||||
#
|
||||
# List of GUID's:
|
||||
# QuestItems:
|
||||
# 176213 = Blood of Heroes, 17155 = Defias Gunpowder, 2656 = Waterlogged Envelope
|
||||
# 176213 = Blood of Heroes, 17155 = Defias Gunpowder, 2656 = Waterlogged Envelope, 123329 = Baelogs Chest
|
||||
# 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
|
||||
# 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
|
||||
# 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.
|
||||
#
|
||||
####################################################################################################
|
||||
|
||||
0
data/sql/characters/updates/.gitkeep
Normal file
0
data/sql/characters/updates/.gitkeep
Normal file
3
data/sql/world/updates/2025_08_27_03.sql
Normal file
3
data/sql/world/updates/2025_08_27_03.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
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);
|
||||
@@ -472,6 +472,10 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sRandomPlayerbotMgr->IsRandomBot(player))
|
||||
{
|
||||
engine->ChangeStrategy(sPlayerbotAIConfig->randomBotCombatStrategies);
|
||||
}
|
||||
else
|
||||
{
|
||||
engine->ChangeStrategy(sPlayerbotAIConfig->combatStrategies);
|
||||
|
||||
@@ -46,6 +46,14 @@ public:
|
||||
if (melee && botAI->IsRanged(bot))
|
||||
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)
|
||||
return ChatFilter::Filter(message);
|
||||
|
||||
@@ -246,21 +254,41 @@ public:
|
||||
|
||||
if (message.find("@group") == 0)
|
||||
{
|
||||
std::string const pnum = message.substr(6, message.find(" "));
|
||||
uint32 from = atoi(pnum.c_str());
|
||||
uint32 to = from;
|
||||
if (pnum.find("-") != std::string::npos)
|
||||
size_t spacePos = message.find(" ");
|
||||
if (spacePos == std::string::npos)
|
||||
return message;
|
||||
|
||||
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, ','))
|
||||
{
|
||||
from = atoi(pnum.substr(pnum.find("@") + 1, pnum.find("-")).c_str());
|
||||
to = atoi(pnum.substr(pnum.find("-") + 1, pnum.find(" ")).c_str());
|
||||
size_t dashPos = token.find("-");
|
||||
if (dashPos != std::string::npos)
|
||||
{
|
||||
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())
|
||||
return message;
|
||||
|
||||
uint32 sg = bot->GetSubGroup() + 1;
|
||||
if (sg >= from && sg <= to)
|
||||
return ChatFilter::Filter(message);
|
||||
if (targets.find(sg) != targets.end())
|
||||
return ChatFilter::Filter(actualMessage);
|
||||
}
|
||||
|
||||
return message;
|
||||
|
||||
@@ -299,9 +299,10 @@ bool LootObject::IsLootPossible(Player* bot)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent bot from running to chests that are unlootable (e.g. Gunship Armory before completing the event)
|
||||
// Prevent bot from running to chests that are unlootable (e.g. Gunship Armory before completing the event) or on
|
||||
// respawn time
|
||||
GameObject* go = botAI->GetGameObject(guid);
|
||||
if (go && go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_INTERACT_COND | GO_FLAG_NOT_SELECTABLE))
|
||||
if (go && (go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_INTERACT_COND | GO_FLAG_NOT_SELECTABLE) || !go->isSpawned()))
|
||||
return false;
|
||||
|
||||
if (skillId == SKILL_NONE)
|
||||
@@ -407,4 +408,4 @@ LootObject LootObjectStack::GetNearest(float maxDistance)
|
||||
}
|
||||
|
||||
return nearest;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -722,7 +722,8 @@ void PlayerbotAI::HandleTeleportAck()
|
||||
// SetNextCheckDelay(urand(2000, 5000));
|
||||
if (sPlayerbotAIConfig->applyInstanceStrategies)
|
||||
ApplyInstanceStrategies(bot->GetMapId(), true);
|
||||
EvaluateHealerDpsStrategy();
|
||||
if (sPlayerbotAIConfig->restrictHealerDPS)
|
||||
EvaluateHealerDpsStrategy();
|
||||
Reset(true);
|
||||
}
|
||||
|
||||
@@ -1765,7 +1766,7 @@ bool PlayerbotAI::IsRangedDps(Player* player, bool bySpec) { return IsRanged(pla
|
||||
|
||||
bool PlayerbotAI::IsHealAssistantOfIndex(Player* player, int index)
|
||||
{
|
||||
Group* group = bot->GetGroup();
|
||||
Group* group = player->GetGroup();
|
||||
if (!group)
|
||||
{
|
||||
return false;
|
||||
@@ -1777,6 +1778,11 @@ bool PlayerbotAI::IsHealAssistantOfIndex(Player* player, int index)
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsHeal(member)) // Check if the member is a healer
|
||||
{
|
||||
bool isAssistant = group->IsAssistant(member->GetGUID());
|
||||
@@ -1796,7 +1802,7 @@ bool PlayerbotAI::IsHealAssistantOfIndex(Player* player, int index)
|
||||
|
||||
bool PlayerbotAI::IsRangedDpsAssistantOfIndex(Player* player, int index)
|
||||
{
|
||||
Group* group = bot->GetGroup();
|
||||
Group* group = player->GetGroup();
|
||||
if (!group)
|
||||
{
|
||||
return false;
|
||||
@@ -1808,6 +1814,11 @@ bool PlayerbotAI::IsRangedDpsAssistantOfIndex(Player* player, int index)
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsRangedDps(member)) // Check if the member is a ranged DPS
|
||||
{
|
||||
bool isAssistant = group->IsAssistant(member->GetGUID());
|
||||
@@ -1840,6 +1851,35 @@ bool PlayerbotAI::HasAggro(Unit* unit)
|
||||
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)
|
||||
{
|
||||
Group* group = bot->GetGroup();
|
||||
@@ -1851,6 +1891,12 @@ int32 PlayerbotAI::GetGroupSlotIndex(Player* player)
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (player == member)
|
||||
{
|
||||
return counter;
|
||||
@@ -1875,6 +1921,12 @@ int32 PlayerbotAI::GetRangedIndex(Player* player)
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (player == member)
|
||||
{
|
||||
return counter;
|
||||
@@ -1902,6 +1954,12 @@ int32 PlayerbotAI::GetClassIndex(Player* player, uint8 cls)
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (player == member)
|
||||
{
|
||||
return counter;
|
||||
@@ -1928,6 +1986,12 @@ int32 PlayerbotAI::GetRangedDpsIndex(Player* player)
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (player == member)
|
||||
{
|
||||
return counter;
|
||||
@@ -1955,6 +2019,12 @@ int32 PlayerbotAI::GetMeleeIndex(Player* player)
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (player == member)
|
||||
{
|
||||
return counter;
|
||||
@@ -2126,6 +2196,12 @@ bool PlayerbotAI::IsMainTank(Player* player)
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsTank(member) && member->IsAlive())
|
||||
{
|
||||
return player->GetGUID() == member->GetGUID();
|
||||
@@ -2134,6 +2210,62 @@ bool PlayerbotAI::IsMainTank(Player* player)
|
||||
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)
|
||||
{
|
||||
Group* group = player->GetGroup();
|
||||
@@ -2145,6 +2277,12 @@ uint32 PlayerbotAI::GetGroupTankNum(Player* player)
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsTank(member) && member->IsAlive())
|
||||
{
|
||||
result++;
|
||||
@@ -2157,7 +2295,7 @@ bool PlayerbotAI::IsAssistTank(Player* player) { return IsTank(player) && !IsMai
|
||||
|
||||
bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index)
|
||||
{
|
||||
Group* group = bot->GetGroup();
|
||||
Group* group = player->GetGroup();
|
||||
if (!group)
|
||||
{
|
||||
return false;
|
||||
@@ -2166,6 +2304,12 @@ bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index)
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (group->IsAssistant(member->GetGUID()) && IsAssistTank(member))
|
||||
{
|
||||
if (index == counter)
|
||||
@@ -2179,6 +2323,12 @@ bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index)
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!group->IsAssistant(member->GetGUID()) && IsAssistTank(member))
|
||||
{
|
||||
if (index == counter)
|
||||
@@ -2386,6 +2536,11 @@ std::vector<Player*> PlayerbotAI::GetPlayersInGroup()
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (GET_PLAYERBOT_AI(member) && !GET_PLAYERBOT_AI(member)->IsRealPlayer())
|
||||
continue;
|
||||
|
||||
@@ -3970,37 +4125,15 @@ bool IsAlliance(uint8 race)
|
||||
|
||||
bool PlayerbotAI::HasRealPlayerMaster()
|
||||
{
|
||||
// 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()))
|
||||
if (master)
|
||||
{
|
||||
master = nullptr; // avoids repeating the check on the next tick
|
||||
return false;
|
||||
PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(master);
|
||||
return !masterBotAI || masterBotAI->IsRealPlayer();
|
||||
}
|
||||
|
||||
/* 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
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool PlayerbotAI::HasActivePlayerMaster() { return master && !GET_PLAYERBOT_AI(master); }
|
||||
|
||||
bool PlayerbotAI::IsAlt() { return HasRealPlayerMaster() && !sRandomPlayerbotMgr->IsRandomBot(bot); }
|
||||
|
||||
@@ -423,13 +423,15 @@ public:
|
||||
static bool IsCaster(Player* player, bool bySpec = false);
|
||||
static bool IsRangedDps(Player* player, bool bySpec = false);
|
||||
static bool IsCombo(Player* player);
|
||||
static bool IsBotMainTank(Player* player);
|
||||
static bool IsMainTank(Player* player);
|
||||
static uint32 GetGroupTankNum(Player* player);
|
||||
bool IsAssistTank(Player* player);
|
||||
bool IsAssistTankOfIndex(Player* player, int index);
|
||||
bool IsHealAssistantOfIndex(Player* player, int index);
|
||||
bool IsRangedDpsAssistantOfIndex(Player* player, int index);
|
||||
static bool IsAssistTank(Player* player);
|
||||
static bool IsAssistTankOfIndex(Player* player, int index);
|
||||
static bool IsHealAssistantOfIndex(Player* player, int index);
|
||||
static bool IsRangedDpsAssistantOfIndex(Player* player, int index);
|
||||
bool HasAggro(Unit* unit);
|
||||
static int32 GetAssistTankIndex(Player* player);
|
||||
int32 GetGroupSlotIndex(Player* player);
|
||||
int32 GetRangedIndex(Player* player);
|
||||
int32 GetClassIndex(Player* player, uint8 cls);
|
||||
|
||||
@@ -139,6 +139,17 @@ bool PlayerbotAIConfig::Initialize()
|
||||
randomBotMapsAsString = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotMaps", "0,1,530,571");
|
||||
LoadList<std::vector<uint32>>(randomBotMapsAsString, randomBotMaps);
|
||||
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>>(
|
||||
sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotQuestItems",
|
||||
"6948,5175,5176,5177,5178,16309,12382,13704,11000"),
|
||||
@@ -150,15 +161,21 @@ bool PlayerbotAIConfig::Initialize()
|
||||
"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"),
|
||||
pvpProhibitedZoneIds);
|
||||
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.PvpProhibitedAreaIds", "976,35"),
|
||||
pvpProhibitedAreaIds);
|
||||
LoadList<std::vector<uint32>>(
|
||||
sConfigMgr->GetOption<std::string>("AiPlayerbot.PvpProhibitedAreaIds",
|
||||
"976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754"),
|
||||
pvpProhibitedAreaIds);
|
||||
fastReactInBG = sConfigMgr->GetOption<bool>("AiPlayerbot.FastReactInBG", true);
|
||||
LoadList<std::vector<uint32>>(
|
||||
sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotQuestIds", "7848,3802,5505,6502,7761"),
|
||||
randomBotQuestIds);
|
||||
|
||||
LoadSet<std::set<uint32>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.DisallowedGameObjects", "176213,17155"),
|
||||
disallowedGameObjects);
|
||||
LoadSet<std::set<uint32>>(
|
||||
sConfigMgr->GetOption<std::string>("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"),
|
||||
disallowedGameObjects);
|
||||
botAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.BotAutologin", false);
|
||||
randomBotAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotAutologin", true);
|
||||
minRandomBots = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBots", 50);
|
||||
@@ -198,7 +215,7 @@ bool PlayerbotAIConfig::Initialize()
|
||||
"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"),
|
||||
restrictedHealerDPSMaps);
|
||||
|
||||
|
||||
//////////////////////////// ICC
|
||||
|
||||
EnableICCBuffs = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableICCBuffs", true);
|
||||
@@ -369,7 +386,7 @@ bool PlayerbotAIConfig::Initialize()
|
||||
|
||||
randomChangeMultiplier = sConfigMgr->GetOption<float>("AiPlayerbot.RandomChangeMultiplier", 1.0);
|
||||
|
||||
randomBotCombatStrategies = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotCombatStrategies", "-threat");
|
||||
randomBotCombatStrategies = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotCombatStrategies", "");
|
||||
randomBotNonCombatStrategies = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotNonCombatStrategies", "");
|
||||
combatStrategies = sConfigMgr->GetOption<std::string>("AiPlayerbot.CombatStrategies", "+custom::say");
|
||||
nonCombatStrategies = sConfigMgr->GetOption<std::string>("AiPlayerbot.NonCombatStrategies", "+custom::say,+return");
|
||||
@@ -456,11 +473,13 @@ bool PlayerbotAIConfig::Initialize()
|
||||
}
|
||||
|
||||
botCheats.clear();
|
||||
LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.BotCheats", "taxi,raid"),
|
||||
LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.BotCheats", "food,taxi,raid"),
|
||||
botCheats);
|
||||
|
||||
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())
|
||||
botCheatMask |= (uint32)BotCheatMask::taxi;
|
||||
if (std::find(botCheats.begin(), botCheats.end(), "gold") != botCheats.end())
|
||||
@@ -575,6 +594,8 @@ bool PlayerbotAIConfig::Initialize()
|
||||
autoPickTalents = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoPickTalents", true);
|
||||
autoUpgradeEquip = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoUpgradeEquip", false);
|
||||
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);
|
||||
autoLearnQuestSpells = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoLearnQuestSpells", false);
|
||||
autoTeleportForLevel = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoTeleportForLevel", false);
|
||||
@@ -590,7 +611,6 @@ bool PlayerbotAIConfig::Initialize()
|
||||
RpgStatusProbWeight[RPG_REST] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.Rest", 5);
|
||||
|
||||
syncLevelWithPlayers = sConfigMgr->GetOption<bool>("AiPlayerbot.SyncLevelWithPlayers", false);
|
||||
freeFood = sConfigMgr->GetOption<bool>("AiPlayerbot.FreeFood", true);
|
||||
randomBotGroupNearby = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotGroupNearby", true);
|
||||
|
||||
// arena
|
||||
|
||||
@@ -23,7 +23,8 @@ enum class BotCheatMask : uint32
|
||||
mana = 8,
|
||||
power = 16,
|
||||
raid = 32,
|
||||
maxMask = 64
|
||||
food = 64,
|
||||
maxMask = 128
|
||||
};
|
||||
|
||||
enum class HealingManaEfficiency : uint8
|
||||
@@ -101,6 +102,17 @@ public:
|
||||
bool botAutologin;
|
||||
std::string randomBotMapsAsString;
|
||||
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> randomBotQuestItems;
|
||||
std::vector<uint32> randomBotAccounts;
|
||||
@@ -333,12 +345,13 @@ public:
|
||||
bool autoPickTalents;
|
||||
bool autoUpgradeEquip;
|
||||
int32 hunterWolfPet;
|
||||
int32 defaultPetStance;
|
||||
int32 petChatCommandDebug;
|
||||
bool autoLearnTrainerSpells;
|
||||
bool autoDoQuests;
|
||||
bool enableNewRpgStrategy;
|
||||
std::unordered_map<NewRpgStatus, uint32> RpgStatusProbWeight;
|
||||
bool syncLevelWithPlayers;
|
||||
bool freeFood;
|
||||
bool autoLearnQuestSpells;
|
||||
bool autoTeleportForLevel;
|
||||
bool randomBotGroupNearby;
|
||||
|
||||
@@ -38,8 +38,6 @@
|
||||
#include "WorldSessionMgr.h"
|
||||
#include "DatabaseEnv.h" // 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
|
||||
{
|
||||
@@ -1728,70 +1726,21 @@ void PlayerbotsMgr::RemovePlayerBotData(ObjectGuid const& guid, bool is_AI)
|
||||
|
||||
PlayerbotAI* PlayerbotsMgr::GetPlayerbotAI(Player* player)
|
||||
{
|
||||
// if (!(sPlayerbotAIConfig->enabled) || !player)
|
||||
// {
|
||||
if (!(sPlayerbotAIConfig->enabled) || !player)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
// if (player->GetSession()->isLogingOut() || player->IsDuringRemoveFromWorld()) {
|
||||
// return nullptr;
|
||||
// }
|
||||
// // if (player->GetSession()->isLogingOut() || player->IsDuringRemoveFromWorld()) {
|
||||
// // return nullptr;
|
||||
// // }
|
||||
// auto itr = _playerbotsAIMap.find(player->GetGUID());
|
||||
// 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))
|
||||
auto itr = _playerbotsAIMap.find(player->GetGUID());
|
||||
if (itr != _playerbotsAIMap.end())
|
||||
{
|
||||
RemovePlayerbotAI(guid, /*removeMgrEntry=*/false);
|
||||
}
|
||||
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 (itr->second->IsBotAI())
|
||||
return reinterpret_cast<PlayerbotAI*>(itr->second);
|
||||
}
|
||||
|
||||
if (removeMgrEntry)
|
||||
_playerbotsMgrMap.erase(guid); // we NO longer touch the relation in a "soft" purge
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PlayerbotMgr* PlayerbotsMgr::GetPlayerbotMgr(Player* player)
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "PlayerbotAIBase.h"
|
||||
#include "QueryHolder.h"
|
||||
#include "QueryResult.h"
|
||||
#include <shared_mutex> // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
|
||||
|
||||
class ChatHandler;
|
||||
class PlayerbotAI;
|
||||
@@ -115,38 +114,13 @@ public:
|
||||
void RemovePlayerBotData(ObjectGuid const& guid, bool is_AI);
|
||||
|
||||
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);
|
||||
|
||||
private:
|
||||
std::unordered_map<ObjectGuid, PlayerbotAIBase*> _playerbotsAIMap;
|
||||
std::unordered_map<ObjectGuid, PlayerbotAIBase*> _playerbotsMgrMap;
|
||||
mutable std::shared_mutex _aiMutex; // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
|
||||
};
|
||||
|
||||
#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
|
||||
|
||||
@@ -377,10 +377,6 @@ public:
|
||||
|
||||
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))
|
||||
{
|
||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(player);
|
||||
|
||||
@@ -60,6 +60,48 @@ struct GuidClassRaceInfo
|
||||
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 activatePrintStatsThread()
|
||||
@@ -1660,11 +1702,6 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector<WorldLocation>&
|
||||
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");
|
||||
|
||||
@@ -2021,7 +2058,8 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
|
||||
"position_y, "
|
||||
"position_z, "
|
||||
"orientation, "
|
||||
"t.minlevel "
|
||||
"t.minlevel, "
|
||||
"t.entry "
|
||||
"FROM "
|
||||
"creature c "
|
||||
"INNER JOIN creature_template t on c.id1 = t.entry "
|
||||
@@ -2048,27 +2086,30 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
|
||||
float z = fields[3].Get<float>();
|
||||
float orient = fields[4].Get<float>();
|
||||
uint32 level = fields[5].Get<uint32>();
|
||||
WorldLocation loc(mapId, x + cos(orient) * 6.0f, y + sin(orient) * 6.0f, z + 2.0f, orient + M_PI);
|
||||
uint32 entry = fields[6].Get<uint32>();
|
||||
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++;
|
||||
for (int32 l = 1; l <= maxLevel; l++)
|
||||
{
|
||||
if (l <= 60 && level >= 60)
|
||||
// Bots 1-60 go to base game bankers (all have minlevel 30 or 45)
|
||||
if (l <=60 && level > 45)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (l <= 70 && level >= 70)
|
||||
// Bots 61-70 go to Shattrath bankers (all have minlevel 60 or 70)
|
||||
if ((l >=61 && l <=70) && (level < 60 || level > 70))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (l >= 70 && level >= 60 && level <= 70)
|
||||
// Bots 71+ go to Dalaran bankers (all have minlevel 75)
|
||||
if ((l >=71) && level != 75)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (l >= 30 && level <= 30)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
bankerLocsPerLevelCache[(uint8)l].push_back(loc);
|
||||
bankerLocsPerLevelCache[(uint8)l].push_back(bLoc);
|
||||
bankerEntryToLocation[bLoc.entry] = bLoc.loc;
|
||||
}
|
||||
} while (results->NextRow());
|
||||
}
|
||||
@@ -2138,11 +2179,92 @@ void RandomPlayerbotMgr::RandomTeleportForLevel(Player* bot)
|
||||
locs = IsAlliance(race) ? &allianceStarterPerLevelCache[level] : &hordeStarterPerLevelCache[level];
|
||||
else
|
||||
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)
|
||||
{
|
||||
RandomTeleport(bot, bankerLocsPerLevelCache[level], true);
|
||||
std::vector<WorldLocation> fallbackLocs;
|
||||
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
|
||||
{
|
||||
|
||||
@@ -183,7 +183,11 @@ public:
|
||||
bool InsideBracket(uint32 val) { return val >= low && val <= high; }
|
||||
};
|
||||
std::map<uint32, LevelBracket> zone2LevelBracket;
|
||||
std::map<uint8, std::vector<WorldLocation>> bankerLocsPerLevelCache;
|
||||
struct BankerLocation {
|
||||
WorldLocation loc;
|
||||
uint32 entry;
|
||||
};
|
||||
std::map<uint8, std::vector<BankerLocation>> bankerLocsPerLevelCache;
|
||||
|
||||
// Account type management
|
||||
void AssignAccountTypes();
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "StatsWeightCalculator.h"
|
||||
#include "World.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "ItemPackets.h"
|
||||
|
||||
const uint64 diveMask = (1LL << 7) | (1LL << 44) | (1LL << 37) | (1LL << 38) | (1LL << 26) | (1LL << 30) | (1LL << 27) |
|
||||
(1LL << 33) | (1LL << 24) | (1LL << 34);
|
||||
@@ -123,7 +124,10 @@ void PlayerbotFactory::Init()
|
||||
if (id == 47181 || id == 50358 || id == 47242 || id == 52639 || id == 47147 || id == 7218) // Test Enchant
|
||||
continue;
|
||||
|
||||
if (id == 15463) // Legendary Arcane Amalgamation
|
||||
if (id == 15463 || id == 15490) // Legendary Arcane Amalgamation
|
||||
continue;
|
||||
|
||||
if (id == 29467 || id == 29475 || id == 29480 || id == 29483) // Naxx40 Sapphiron Shoulder Enchants
|
||||
continue;
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(id);
|
||||
@@ -164,22 +168,33 @@ void PlayerbotFactory::Init()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(gemId);
|
||||
|
||||
if (proto->ItemLevel < 60)
|
||||
if (!proto)
|
||||
{
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
if (proto->ItemLevel < 60)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (proto->Flags & ITEM_FLAG_UNIQUE_EQUIPPABLE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sRandomItemMgr->IsTestItem(gemId))
|
||||
continue;
|
||||
|
||||
if (!proto || !sGemPropertiesStore.LookupEntry(proto->GemProperties))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!sGemPropertiesStore.LookupEntry(proto->GemProperties))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// LOG_INFO("playerbots", "Add {} to enchantment gems", gemId);
|
||||
enchantGemIdCache.push_back(gemId);
|
||||
}
|
||||
@@ -1017,15 +1032,18 @@ void PlayerbotFactory::ClearSkills()
|
||||
}
|
||||
bot->SetUInt32Value(PLAYER_SKILL_INDEX(0), 0);
|
||||
bot->SetUInt32Value(PLAYER_SKILL_INDEX(1), 0);
|
||||
|
||||
// unlearn default race/class skills
|
||||
PlayerInfo const* info = sObjectMgr->GetPlayerInfo(bot->getRace(), bot->getClass());
|
||||
for (PlayerCreateInfoSkills::const_iterator itr = info->skills.begin(); itr != info->skills.end(); ++itr)
|
||||
{
|
||||
uint32 skillId = itr->SkillId;
|
||||
if (!bot->HasSkill(skillId))
|
||||
continue;
|
||||
bot->SetSkill(skillId, 0, 0, 0);
|
||||
}
|
||||
if (PlayerInfo const* info = sObjectMgr->GetPlayerInfo(bot->getRace(), bot->getClass()))
|
||||
{
|
||||
for (PlayerCreateInfoSkills::const_iterator itr = info->skills.begin(); itr != info->skills.end(); ++itr)
|
||||
{
|
||||
uint32 skillId = itr->SkillId;
|
||||
if (!bot->HasSkill(skillId))
|
||||
continue;
|
||||
bot->SetSkill(skillId, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerbotFactory::ClearEverything()
|
||||
@@ -1882,7 +1900,9 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
|
||||
|
||||
WorldPacket packet(CMSG_AUTOSTORE_BAG_ITEM, 3);
|
||||
packet << bagIndex << slot << dstBag;
|
||||
bot->GetSession()->HandleAutoStoreBagItemOpcode(packet);
|
||||
WorldPackets::Item::AutoStoreBagItem nicePacket(std::move(packet));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoStoreBagItemOpcode(nicePacket);
|
||||
}
|
||||
|
||||
oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
|
||||
@@ -3230,7 +3250,7 @@ std::vector<uint32> PlayerbotFactory::GetCurrentGemsCount()
|
||||
|
||||
void PlayerbotFactory::InitFood()
|
||||
{
|
||||
if (sPlayerbotAIConfig->freeFood)
|
||||
if (botAI && botAI->HasCheat(BotCheatMask::food))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -4348,10 +4368,10 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld)
|
||||
}
|
||||
|
||||
// disable next expansion enchantments
|
||||
if (sPlayerbotAIConfig->limitEnchantExpansion && bot->GetLevel() <= 60 && enchantSpell >= 25072)
|
||||
if (sPlayerbotAIConfig->limitEnchantExpansion && bot->GetLevel() <= 60 && enchantSpell >= 27899)
|
||||
continue;
|
||||
|
||||
if (sPlayerbotAIConfig->limitEnchantExpansion && bot->GetLevel() <= 70 && enchantSpell > 48557)
|
||||
if (sPlayerbotAIConfig->limitEnchantExpansion && bot->GetLevel() <= 70 && enchantSpell >= 44483)
|
||||
continue;
|
||||
|
||||
for (uint8 j = 0; j < MAX_SPELL_EFFECTS; ++j)
|
||||
|
||||
@@ -14,10 +14,10 @@ void Qualified::Qualify(int qual)
|
||||
qualifier = out.str();
|
||||
}
|
||||
|
||||
std::string const Qualified::MultiQualify(std::vector<std::string> qualifiers, const std::string& separator, const std::string_view brackets)
|
||||
std::string const Qualified::MultiQualify(const std::vector<std::string>& qualifiers, const std::string& separator, const std::string_view brackets)
|
||||
{
|
||||
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];
|
||||
if (i == qualifiers.size() - 1)
|
||||
@@ -40,13 +40,13 @@ std::string const Qualified::MultiQualify(std::vector<std::string> qualifiers, c
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> Qualified::getMultiQualifiers(std::string const qualifier1)
|
||||
std::vector<std::string> Qualified::getMultiQualifiers(const std::string& qualifier1)
|
||||
{
|
||||
std::istringstream iss(qualifier1);
|
||||
return {std::istream_iterator<std::string>{iss}, std::istream_iterator<std::string>{}};
|
||||
}
|
||||
|
||||
int32 Qualified::getMultiQualifier(std::string const qualifier1, uint32 pos)
|
||||
int32 Qualified::getMultiQualifier(const std::string& qualifier1, uint32 pos)
|
||||
{
|
||||
return std::stoi(getMultiQualifiers(qualifier1)[pos]);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include "Common.h"
|
||||
|
||||
@@ -29,10 +30,10 @@ public:
|
||||
|
||||
std::string const getQualifier() { return qualifier; }
|
||||
|
||||
static std::string const MultiQualify(std::vector<std::string> qualifiers, const std::string& separator,
|
||||
static std::string const MultiQualify(const std::vector<std::string>& qualifiers, const std::string& separator,
|
||||
const std::string_view brackets = "{}");
|
||||
static std::vector<std::string> getMultiQualifiers(std::string const qualifier1);
|
||||
static int32 getMultiQualifier(std::string const qualifier1, uint32 pos);
|
||||
static std::vector<std::string> getMultiQualifiers(const std::string& qualifier1);
|
||||
static int32 getMultiQualifier(const std::string& qualifier1, uint32 pos);
|
||||
|
||||
protected:
|
||||
std::string qualifier;
|
||||
@@ -42,11 +43,11 @@ template <class T>
|
||||
class NamedObjectFactory
|
||||
{
|
||||
public:
|
||||
typedef T* (*ObjectCreator)(PlayerbotAI* botAI);
|
||||
using ObjectCreator = std::function<T*(PlayerbotAI* ai)>;
|
||||
std::unordered_map<std::string, ObjectCreator> creators;
|
||||
|
||||
public:
|
||||
T* create(std::string name, PlayerbotAI* botAI)
|
||||
virtual T* create(std::string name, PlayerbotAI* botAI)
|
||||
{
|
||||
size_t found = name.find("::");
|
||||
std::string qualifier;
|
||||
@@ -59,11 +60,9 @@ public:
|
||||
if (creators.find(name) == creators.end())
|
||||
return nullptr;
|
||||
|
||||
ObjectCreator creator = creators[name];
|
||||
if (!creator)
|
||||
return nullptr;
|
||||
ObjectCreator& creator = creators[name];
|
||||
|
||||
T* object = (*creator)(botAI);
|
||||
T* object = creator(botAI);
|
||||
Qualified* q = dynamic_cast<Qualified*>(object);
|
||||
if (q && found != std::string::npos)
|
||||
q->Qualify(qualifier);
|
||||
@@ -74,7 +73,7 @@ public:
|
||||
std::set<std::string> supports()
|
||||
{
|
||||
std::set<std::string> keys;
|
||||
for (typename std::unordered_map<std::string, ObjectCreator>::iterator it = creators.begin();
|
||||
for (typename std::unordered_map<std::string, ObjectCreator>::const_iterator it = creators.begin();
|
||||
it != creators.end(); it++)
|
||||
keys.insert(it->first);
|
||||
|
||||
@@ -93,7 +92,7 @@ public:
|
||||
|
||||
virtual ~NamedObjectContext() { Clear(); }
|
||||
|
||||
T* create(std::string const name, PlayerbotAI* botAI)
|
||||
virtual T* create(std::string name, PlayerbotAI* botAI) override
|
||||
{
|
||||
if (created.find(name) == created.end())
|
||||
return created[name] = NamedObjectFactory<T>::create(name, botAI);
|
||||
@@ -103,7 +102,7 @@ public:
|
||||
|
||||
void Clear()
|
||||
{
|
||||
for (typename std::unordered_map<std::string, T*>::iterator i = created.begin(); i != created.end(); i++)
|
||||
for (typename std::unordered_map<std::string, T*>::const_iterator i = created.begin(); i != created.end(); i++)
|
||||
{
|
||||
if (i->second)
|
||||
delete i->second;
|
||||
@@ -134,13 +133,13 @@ template <class T>
|
||||
class SharedNamedObjectContextList
|
||||
{
|
||||
public:
|
||||
typedef T* (*ObjectCreator)(PlayerbotAI* botAI);
|
||||
using ObjectCreator = std::function<T*(PlayerbotAI* ai)>;
|
||||
std::unordered_map<std::string, ObjectCreator> creators;
|
||||
std::vector<NamedObjectContext<T>*> contexts;
|
||||
|
||||
~SharedNamedObjectContextList()
|
||||
{
|
||||
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
|
||||
for (typename std::vector<NamedObjectContext<T>*>::const_iterator i = contexts.begin(); i != contexts.end(); i++)
|
||||
delete *i;
|
||||
}
|
||||
|
||||
@@ -158,7 +157,7 @@ template <class T>
|
||||
class NamedObjectContextList
|
||||
{
|
||||
public:
|
||||
typedef T* (*ObjectCreator)(PlayerbotAI* botAI);
|
||||
using ObjectCreator = std::function<T*(PlayerbotAI* ai)>;
|
||||
const std::unordered_map<std::string, ObjectCreator>& creators;
|
||||
const std::vector<NamedObjectContext<T>*>& contexts;
|
||||
std::unordered_map<std::string, T*> created;
|
||||
@@ -170,7 +169,7 @@ public:
|
||||
|
||||
~NamedObjectContextList()
|
||||
{
|
||||
for (typename std::unordered_map<std::string, T*>::iterator i = created.begin(); i != created.end(); i++)
|
||||
for (typename std::unordered_map<std::string, T*>::const_iterator i = created.begin(); i != created.end(); i++)
|
||||
{
|
||||
if (i->second)
|
||||
delete i->second;
|
||||
@@ -192,11 +191,9 @@ public:
|
||||
if (creators.find(name) == creators.end())
|
||||
return nullptr;
|
||||
|
||||
ObjectCreator creator = creators.at(name);
|
||||
if (!creator)
|
||||
return nullptr;
|
||||
const ObjectCreator& creator = creators.at(name);
|
||||
|
||||
T* object = (*creator)(botAI);
|
||||
T* object = creator(botAI);
|
||||
Qualified* q = dynamic_cast<Qualified*>(object);
|
||||
if (q && found != std::string::npos)
|
||||
q->Qualify(qualifier);
|
||||
@@ -204,7 +201,7 @@ public:
|
||||
return object;
|
||||
}
|
||||
|
||||
T* GetContextObject(std::string const name, PlayerbotAI* botAI)
|
||||
T* GetContextObject(const std::string& name, PlayerbotAI* botAI)
|
||||
{
|
||||
if (created.find(name) == created.end())
|
||||
{
|
||||
@@ -214,7 +211,7 @@ public:
|
||||
return created[name];
|
||||
}
|
||||
|
||||
std::set<std::string> GetSiblings(std::string const name)
|
||||
std::set<std::string> GetSiblings(const std::string& name)
|
||||
{
|
||||
for (auto i = contexts.begin(); i != contexts.end(); i++)
|
||||
{
|
||||
@@ -240,7 +237,7 @@ public:
|
||||
{
|
||||
std::set<std::string> supported = (*i)->supports();
|
||||
|
||||
for (std::set<std::string>::iterator j = supported.begin(); j != supported.end(); j++)
|
||||
for (std::set<std::string>::const_iterator j = supported.begin(); j != supported.end(); ++j)
|
||||
result.insert(*j);
|
||||
}
|
||||
|
||||
@@ -250,7 +247,7 @@ public:
|
||||
std::set<std::string> GetCreated()
|
||||
{
|
||||
std::set<std::string> result;
|
||||
for (typename std::unordered_map<std::string, T*>::iterator i = created.begin(); i != created.end(); i++)
|
||||
for (typename std::unordered_map<std::string, T*>::const_iterator i = created.begin(); i != created.end(); i++)
|
||||
{
|
||||
result.insert(i->first);
|
||||
}
|
||||
@@ -263,13 +260,13 @@ template <class T>
|
||||
class NamedObjectFactoryList
|
||||
{
|
||||
public:
|
||||
typedef T* (*ObjectCreator)(PlayerbotAI* botAI);
|
||||
using ObjectCreator = std::function<T*(PlayerbotAI* ai)>;
|
||||
std::vector<NamedObjectFactory<T>*> factories;
|
||||
std::unordered_map<std::string, ObjectCreator> creators;
|
||||
|
||||
virtual ~NamedObjectFactoryList()
|
||||
{
|
||||
for (typename std::vector<NamedObjectFactory<T>*>::iterator i = factories.begin(); i != factories.end(); i++)
|
||||
for (typename std::vector<NamedObjectFactory<T>*>::const_iterator i = factories.begin(); i != factories.end(); i++)
|
||||
delete *i;
|
||||
}
|
||||
|
||||
@@ -286,11 +283,9 @@ public:
|
||||
if (creators.find(name) == creators.end())
|
||||
return nullptr;
|
||||
|
||||
ObjectCreator creator = creators[name];
|
||||
if (!creator)
|
||||
return nullptr;
|
||||
const ObjectCreator& creator = creators[name];
|
||||
|
||||
T* object = (*creator)(botAI);
|
||||
T* object = creator(botAI);
|
||||
Qualified* q = dynamic_cast<Qualified*>(object);
|
||||
if (q && found != std::string::npos)
|
||||
q->Qualify(qualifier);
|
||||
@@ -307,7 +302,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
T* GetContextObject(std::string const name, PlayerbotAI* botAI)
|
||||
T* GetContextObject(const std::string& name, PlayerbotAI* botAI)
|
||||
{
|
||||
if (T* object = create(name, botAI))
|
||||
return object;
|
||||
|
||||
@@ -246,7 +246,8 @@ public:
|
||||
creators["rpg mount anim"] = &ActionContext::rpg_mount_anim;
|
||||
|
||||
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 go grind"] = &ActionContext::new_rpg_go_grind;
|
||||
@@ -434,6 +435,7 @@ private:
|
||||
|
||||
static Action* toggle_pet_spell(PlayerbotAI* ai) { return new TogglePetSpellAutoCastAction(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_go_grind(PlayerbotAI* ai) { return new NewRpgGoGrindAction(ai); }
|
||||
|
||||
@@ -22,6 +22,7 @@ bool BossFireResistanceAction::Execute(Event event)
|
||||
{
|
||||
PaladinFireResistanceStrategy paladinFireResistanceStrategy(botAI);
|
||||
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinFireResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT);
|
||||
botAI->DoSpecificAction("fire resistance aura", Event(), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -35,6 +36,7 @@ bool BossFrostResistanceAction::Execute(Event event)
|
||||
{
|
||||
PaladinFrostResistanceStrategy paladinFrostResistanceStrategy(botAI);
|
||||
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinFrostResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT);
|
||||
botAI->DoSpecificAction("frost resistance aura", Event(), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -48,6 +50,7 @@ bool BossNatureResistanceAction::Execute(Event event)
|
||||
{
|
||||
HunterNatureResistanceStrategy hunterNatureResistanceStrategy(botAI);
|
||||
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + hunterNatureResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT);
|
||||
botAI->DoSpecificAction("aspect of the wild", Event(), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -61,5 +64,6 @@ bool BossShadowResistanceAction::Execute(Event event)
|
||||
{
|
||||
PaladinShadowResistanceStrategy paladinShadowResistanceStrategy(botAI);
|
||||
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinShadowResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT);
|
||||
botAI->DoSpecificAction("shadow resistance aura", Event(), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -79,9 +79,10 @@
|
||||
#include "OpenItemAction.h"
|
||||
#include "UnlockItemAction.h"
|
||||
#include "UnlockTradedItemAction.h"
|
||||
#include "PetAction.h"
|
||||
#include "TameAction.h"
|
||||
#include "TellGlyphsAction.h"
|
||||
#include "EquipGlyphsAction.h"
|
||||
#include "PetAction.h"
|
||||
|
||||
class ChatActionContext : public NamedObjectContext<Action>
|
||||
{
|
||||
@@ -191,9 +192,11 @@ public:
|
||||
creators["lfg"] = &ChatActionContext::lfg;
|
||||
creators["calc"] = &ChatActionContext::calc;
|
||||
creators["wipe"] = &ChatActionContext::wipe;
|
||||
creators["pet"] = &ChatActionContext::pet;
|
||||
creators["tame"] = &ChatActionContext::tame;
|
||||
creators["glyphs"] = &ChatActionContext::glyphs; // 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;
|
||||
}
|
||||
|
||||
@@ -301,9 +304,11 @@ private:
|
||||
static Action* join(PlayerbotAI* ai) { return new JoinGroupAction(ai); }
|
||||
static Action* calc(PlayerbotAI* ai) { return new TellCalculateItemAction(ai); }
|
||||
static Action* wipe(PlayerbotAI* ai) { return new WipeAction(ai); }
|
||||
static Action* pet(PlayerbotAI* botAI) { return new PetAction(botAI); }
|
||||
static Action* tame(PlayerbotAI* botAI) { return new TameAction(botAI); }
|
||||
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* 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); }
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
#include "EquipAction.h"
|
||||
#include <utility>
|
||||
|
||||
#include "Event.h"
|
||||
#include "ItemCountValue.h"
|
||||
@@ -11,6 +12,7 @@
|
||||
#include "ItemVisitors.h"
|
||||
#include "Playerbots.h"
|
||||
#include "StatsWeightCalculator.h"
|
||||
#include "ItemPackets.h"
|
||||
|
||||
bool EquipAction::Execute(Event event)
|
||||
{
|
||||
@@ -104,7 +106,10 @@ void EquipAction::EquipItem(Item* item)
|
||||
WorldPacket packet(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
|
||||
ObjectGuid itemguid = item->GetGUID();
|
||||
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;
|
||||
out << "Equipping " << chat->FormatItem(itemProto) << " in ranged slot";
|
||||
@@ -199,7 +204,9 @@ void EquipAction::EquipItem(Item* item)
|
||||
WorldPacket eqPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
|
||||
ObjectGuid newItemGuid = item->GetGUID();
|
||||
eqPacket << newItemGuid << uint8(EQUIPMENT_SLOT_MAINHAND);
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(eqPacket);
|
||||
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(eqPacket));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
|
||||
}
|
||||
|
||||
// Try moving old main hand weapon to offhand if beneficial
|
||||
@@ -210,7 +217,9 @@ void EquipAction::EquipItem(Item* item)
|
||||
WorldPacket offhandPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
|
||||
ObjectGuid oldMHGuid = mainHandItem->GetGUID();
|
||||
offhandPacket << oldMHGuid << uint8(EQUIPMENT_SLOT_OFFHAND);
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(offhandPacket);
|
||||
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(offhandPacket));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
|
||||
|
||||
std::ostringstream moveMsg;
|
||||
moveMsg << "Main hand upgrade found. Moving " << chat->FormatItem(oldMHProto) << " to offhand";
|
||||
@@ -230,7 +239,9 @@ void EquipAction::EquipItem(Item* item)
|
||||
WorldPacket eqPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
|
||||
ObjectGuid newItemGuid = item->GetGUID();
|
||||
eqPacket << newItemGuid << uint8(EQUIPMENT_SLOT_OFFHAND);
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(eqPacket);
|
||||
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(eqPacket));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
|
||||
|
||||
std::ostringstream out;
|
||||
out << "Equipping " << chat->FormatItem(itemProto) << " in offhand";
|
||||
@@ -287,7 +298,9 @@ void EquipAction::EquipItem(Item* item)
|
||||
WorldPacket packet(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
|
||||
ObjectGuid itemguid = item->GetGUID();
|
||||
packet << itemguid << dstSlot;
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(packet);
|
||||
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(packet));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,10 +36,13 @@ bool FollowAction::Execute(Event event)
|
||||
true, priority, true);
|
||||
}
|
||||
|
||||
if (Pet* pet = bot->GetPet())
|
||||
{
|
||||
botAI->PetFollow();
|
||||
}
|
||||
// This section has been commented out because it was forcing the pet to
|
||||
// follow the bot on every "follow" action tick, overriding any attack or
|
||||
// stay commands that might have been issued by the player.
|
||||
// if (Pet* pet = bot->GetPet())
|
||||
// {
|
||||
// botAI->PetFollow();
|
||||
// }
|
||||
// if (moved)
|
||||
// botAI->SetNextCheckDelay(sPlayerbotAIConfig->reactDelay);
|
||||
|
||||
|
||||
@@ -4,9 +4,19 @@
|
||||
*/
|
||||
|
||||
#include "GenericActions.h"
|
||||
|
||||
#include "PlayerbotAI.h"
|
||||
#include "Player.h"
|
||||
#include "Pet.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "CreatureAI.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
|
||||
{
|
||||
@@ -23,7 +33,8 @@ enum PetSpells
|
||||
PET_DEVOUR_MAGIC_4 = 19736,
|
||||
PET_DEVOUR_MAGIC_5 = 27276,
|
||||
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 = {
|
||||
@@ -31,7 +42,7 @@ static std::vector<uint32> disabledPetSpells = {
|
||||
PET_COWER, PET_LEAP,
|
||||
PET_SPELL_LOCK_1, PET_SPELL_LOCK_2,
|
||||
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_DEVOUR_MAGIC_4, PET_DEVOUR_MAGIC_5, PET_DEVOUR_MAGIC_6, PET_DEVOUR_MAGIC_7, PET_SPIRIT_WOLF_LEAP
|
||||
};
|
||||
|
||||
bool MeleeAction::isUseful()
|
||||
@@ -100,6 +111,11 @@ bool TogglePetSpellAutoCastAction::Execute(Event event)
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -107,22 +123,23 @@ bool PetAttackAction::Execute(Event event)
|
||||
{
|
||||
Guardian* pet = bot->GetGuardianPet();
|
||||
if (!pet)
|
||||
{
|
||||
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");
|
||||
if (!target)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bot->IsValidAttackTarget(target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
pet->SetReactState(REACT_PASSIVE);
|
||||
// This section has been commented because it was overriding the
|
||||
// pet's stance to "passive" every time the attack action was executed.
|
||||
// pet->SetReactState(REACT_PASSIVE);
|
||||
|
||||
pet->ClearUnitState(UNIT_STATE_FOLLOW);
|
||||
pet->AttackStop();
|
||||
pet->SetTarget(target->GetGUID());
|
||||
@@ -136,3 +153,76 @@ bool PetAttackAction::Execute(Event event)
|
||||
pet->ToCreature()->AI()->AttackStart(target);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#define _PLAYERBOT_GENERICACTIONS_H
|
||||
|
||||
#include "AttackAction.h"
|
||||
#include "Action.h"
|
||||
#include "PlayerbotAI.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
@@ -33,4 +35,12 @@ public:
|
||||
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
|
||||
|
||||
@@ -76,7 +76,7 @@ bool GiveFoodAction::isUseful()
|
||||
|
||||
bool isRandomBot = GetTarget()->IsPlayer() && sRandomPlayerbotMgr->IsRandomBot((Player*)GetTarget());
|
||||
|
||||
return !isRandomBot || (isRandomBot && !sPlayerbotAIConfig->freeFood);
|
||||
return !isRandomBot || (isRandomBot && !botAI->HasCheat(BotCheatMask::food));
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
return !isRandomBot || (isRandomBot && !sPlayerbotAIConfig->freeFood);
|
||||
return !isRandomBot || (isRandomBot && !botAI->HasCheat(BotCheatMask::food));
|
||||
}
|
||||
|
||||
@@ -2767,3 +2767,177 @@ bool MoveFromGroupAction::Execute(Event event)
|
||||
distance = 20.0f; // flee distance from config is too small for this
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -294,4 +294,34 @@ public:
|
||||
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
|
||||
|
||||
@@ -17,7 +17,7 @@ bool DrinkAction::Execute(Event event)
|
||||
if (!hasMana)
|
||||
return false;
|
||||
|
||||
if (sPlayerbotAIConfig->freeFood)
|
||||
if (botAI->HasCheat(BotCheatMask::food))
|
||||
{
|
||||
// if (bot->IsNonMeleeSpellCast(true))
|
||||
// return false;
|
||||
@@ -54,11 +54,11 @@ bool DrinkAction::Execute(Event event)
|
||||
return UseItemAction::Execute(event);
|
||||
}
|
||||
|
||||
bool DrinkAction::isUseful() { return UseItemAction::isUseful() && AI_VALUE2(uint8, "mana", "self target") < 85; }
|
||||
bool DrinkAction::isUseful() { return UseItemAction::isUseful() && AI_VALUE2(uint8, "mana", "self target") < 100; }
|
||||
|
||||
bool DrinkAction::isPossible()
|
||||
{
|
||||
return !bot->IsInCombat() && (sPlayerbotAIConfig->freeFood || UseItemAction::isPossible());
|
||||
return !bot->IsInCombat() && (botAI->HasCheat(BotCheatMask::food) || UseItemAction::isPossible());
|
||||
}
|
||||
|
||||
bool EatAction::Execute(Event event)
|
||||
@@ -66,7 +66,7 @@ bool EatAction::Execute(Event event)
|
||||
if (bot->IsInCombat())
|
||||
return false;
|
||||
|
||||
if (sPlayerbotAIConfig->freeFood)
|
||||
if (botAI->HasCheat(BotCheatMask::food))
|
||||
{
|
||||
// if (bot->IsNonMeleeSpellCast(true))
|
||||
// return false;
|
||||
@@ -107,5 +107,5 @@ bool EatAction::isUseful() { return UseItemAction::isUseful() && AI_VALUE2(uint8
|
||||
|
||||
bool EatAction::isPossible()
|
||||
{
|
||||
return !bot->IsInCombat() && (sPlayerbotAIConfig->freeFood || UseItemAction::isPossible());
|
||||
return !bot->IsInCombat() && (botAI->HasCheat(BotCheatMask::food) || UseItemAction::isPossible());
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "Event.h"
|
||||
#include "ItemVisitors.h"
|
||||
#include "Playerbots.h"
|
||||
#include "ItemPackets.h"
|
||||
|
||||
bool OutfitAction::Execute(Event event)
|
||||
{
|
||||
@@ -70,7 +71,9 @@ bool OutfitAction::Execute(Event event)
|
||||
|
||||
WorldPacket packet(CMSG_AUTOSTORE_BAG_ITEM, 3);
|
||||
packet << bagIndex << slot << dstBag;
|
||||
bot->GetSession()->HandleAutoStoreBagItemOpcode(packet);
|
||||
WorldPackets::Item::AutoStoreBagItem nicePacket(std::move(packet));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoStoreBagItemOpcode(nicePacket);
|
||||
}
|
||||
|
||||
EquipItems(outfit);
|
||||
|
||||
@@ -4,373 +4,254 @@
|
||||
*/
|
||||
|
||||
#include "PetAction.h"
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#include "CharmInfo.h"
|
||||
#include "Creature.h"
|
||||
#include "CreatureAI.h"
|
||||
#include "Pet.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "DBCStructure.h"
|
||||
#include "Log.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "Player.h"
|
||||
#include "PlayerbotAI.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);
|
||||
}
|
||||
#include "SharedDefines.h"
|
||||
|
||||
bool PetAction::Execute(Event event)
|
||||
{
|
||||
// Extract the command parameter from the event (e.g., "aggressive", "defensive", "attack", 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(" ")); // trim leading spaces
|
||||
if (param.empty() && !defaultCmd.empty())
|
||||
{
|
||||
param = defaultCmd;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
|
||||
// Reset lastPetName/Id each time
|
||||
lastPetName = "";
|
||||
lastPetId = 0;
|
||||
|
||||
if (mode == "name" && !value.empty())
|
||||
if (param.empty())
|
||||
{
|
||||
found = SetPetByName(value);
|
||||
}
|
||||
else if (mode == "id" && !value.empty())
|
||||
{
|
||||
try
|
||||
{
|
||||
uint32 id = std::stoul(value);
|
||||
found = SetPetById(id);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
botAI->TellError("Invalid pet id.");
|
||||
}
|
||||
}
|
||||
else if (mode == "family" && !value.empty())
|
||||
{
|
||||
found = SetPetByFamily(value);
|
||||
}
|
||||
else if (mode == "rename" && !value.empty())
|
||||
{
|
||||
found = RenamePet(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
botAI->TellError("Usage: pet name <name> | pet id <id> | pet family <family> | pet rename <new name> ");
|
||||
// If no parameter is provided, show usage instructions and return.
|
||||
botAI->TellError("Usage: pet <aggressive|defensive|passive|stance|attack|follow|stay>");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
return false;
|
||||
|
||||
// For non-rename commands, initialize pet and give feedback
|
||||
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 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);
|
||||
// Collect all controlled pets and guardians, except totems, into the targets vector.
|
||||
std::vector<Creature*> targets;
|
||||
Pet* pet = bot->GetPet();
|
||||
if (pet)
|
||||
targets.push_back(pet);
|
||||
|
||||
// Only match if names match (case-insensitive)
|
||||
if (creatureName == lowerName)
|
||||
for (Unit::ControlSet::const_iterator itr = bot->m_Controlled.begin(); itr != bot->m_Controlled.end(); ++itr)
|
||||
{
|
||||
Creature* creature = dynamic_cast<Creature*>(*itr);
|
||||
if (!creature)
|
||||
continue;
|
||||
if (pet && creature == pet)
|
||||
continue;
|
||||
if (creature->IsTotem())
|
||||
continue;
|
||||
targets.push_back(creature);
|
||||
}
|
||||
|
||||
// If no pets or guardians are found, notify and return.
|
||||
if (targets.empty())
|
||||
{
|
||||
botAI->TellError("You have no pet or guardian pet.");
|
||||
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)
|
||||
{
|
||||
// Check if the pet is tameable at all
|
||||
if (!creature.IsTameable(true))
|
||||
std::string type = target->IsPet() ? "pet" : "guardian";
|
||||
std::string name = target->GetName();
|
||||
std::string stance;
|
||||
switch (target->GetReactState())
|
||||
{
|
||||
case REACT_AGGRESSIVE:
|
||||
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 (param == "attack")
|
||||
{
|
||||
// Try to get the master's selected target.
|
||||
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;
|
||||
|
||||
// Exotic pet check with talent requirement
|
||||
if (IsExoticPet(&creature) && !HasBeastMastery(bot))
|
||||
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()))
|
||||
{
|
||||
botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent.");
|
||||
return false;
|
||||
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 (param == "follow")
|
||||
{
|
||||
botAI->PetFollow();
|
||||
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();
|
||||
}
|
||||
|
||||
// Final tameable check based on hunter's actual ability
|
||||
if (!creature.IsTameable(bot->CanTameExoticPets()))
|
||||
continue;
|
||||
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();
|
||||
|
||||
lastPetName = creature.Name;
|
||||
lastPetId = creature.Entry;
|
||||
return CreateAndSetPet(creature.Entry);
|
||||
charmInfo->SetForcedSpell(0);
|
||||
charmInfo->SetForcedTargetGUID();
|
||||
}
|
||||
}
|
||||
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
botAI->TellMaster("Pet commanded to stay.");
|
||||
return true;
|
||||
}
|
||||
|
||||
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)
|
||||
// Unknown command: show usage instructions and return.
|
||||
else
|
||||
{
|
||||
// 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);
|
||||
botAI->TellError("Unknown pet command: " + param +
|
||||
". Use: pet <aggressive|defensive|passive|stance|attack|follow|stay>");
|
||||
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)
|
||||
// For stance commands, apply the chosen stance to all targets.
|
||||
for (Creature* target : targets)
|
||||
{
|
||||
botAI->TellError("You have no pet to rename.");
|
||||
return false;
|
||||
target->SetReactState(react);
|
||||
CharmInfo* charmInfo = target->GetCharmInfo();
|
||||
if (charmInfo)
|
||||
charmInfo->SetPlayerReactState(react);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
// Inform the master of the new stance if debug is enabled.
|
||||
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
botAI->TellMaster("Pet stance set to " + stanceText + ".");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -10,25 +10,20 @@
|
||||
|
||||
#include "Action.h"
|
||||
#include "PlayerbotFactory.h"
|
||||
#include "Unit.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class PetAction : public Action
|
||||
{
|
||||
public:
|
||||
PetAction(PlayerbotAI* botAI) : Action(botAI, "pet") {}
|
||||
PetAction(PlayerbotAI* botAI, const std::string& defaultCmd = "") : Action(botAI, "pet"), defaultCmd(defaultCmd) {}
|
||||
|
||||
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);
|
||||
|
||||
std::string lastPetName;
|
||||
uint32 lastPetId = 0;
|
||||
bool warningEnabled = true;
|
||||
std::string defaultCmd;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "ItemUsageValue.h"
|
||||
#include "ItemVisitors.h"
|
||||
#include "Playerbots.h"
|
||||
#include "ItemPackets.h"
|
||||
|
||||
class SellItemsVisitor : public IterateItemsVisitor
|
||||
{
|
||||
@@ -114,9 +115,12 @@ void SellAction::Sell(Item* item)
|
||||
|
||||
uint32 botMoney = bot->GetMoney();
|
||||
|
||||
WorldPacket p;
|
||||
WorldPacket p(CMSG_SELL_ITEM);
|
||||
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))
|
||||
{
|
||||
|
||||
500
src/strategy/actions/TameAction.cpp
Normal file
500
src/strategy/actions/TameAction.cpp
Normal file
@@ -0,0 +1,500 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
34
src/strategy/actions/TameAction.h
Normal file
34
src/strategy/actions/TameAction.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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
|
||||
@@ -8,6 +8,8 @@
|
||||
#include "Event.h"
|
||||
#include "ItemCountValue.h"
|
||||
#include "Playerbots.h"
|
||||
#include "WorldSession.h"
|
||||
#include "ItemPackets.h"
|
||||
|
||||
std::vector<std::string> split(std::string const s, char delim);
|
||||
|
||||
@@ -70,7 +72,9 @@ void UnequipAction::UnequipItem(Item* item)
|
||||
|
||||
WorldPacket packet(CMSG_AUTOSTORE_BAG_ITEM, 3);
|
||||
packet << bagIndex << slot << dstBag;
|
||||
bot->GetSession()->HandleAutoStoreBagItemOpcode(packet);
|
||||
WorldPackets::Item::AutoStoreBagItem nicePacket(std::move(packet));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoStoreBagItemOpcode(nicePacket);
|
||||
|
||||
std::ostringstream out;
|
||||
out << chat->FormatItem(item->GetTemplate()) << " unequipped";
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "Event.h"
|
||||
#include "ItemUsageValue.h"
|
||||
#include "Playerbots.h"
|
||||
#include "ItemPackets.h"
|
||||
|
||||
bool UseItemAction::Execute(Event event)
|
||||
{
|
||||
@@ -324,8 +325,8 @@ void UseItemAction::TellConsumableUse(Item* item, std::string const action, floa
|
||||
|
||||
bool UseItemAction::SocketItem(Item* item, Item* gem, bool replace)
|
||||
{
|
||||
WorldPacket* const packet = new WorldPacket(CMSG_SOCKET_GEMS);
|
||||
*packet << item->GetGUID();
|
||||
WorldPacket packet(CMSG_SOCKET_GEMS);
|
||||
packet << item->GetGUID();
|
||||
|
||||
bool fits = false;
|
||||
for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot < SOCK_ENCHANTMENT_SLOT + MAX_GEM_SOCKETS;
|
||||
@@ -337,14 +338,14 @@ bool UseItemAction::SocketItem(Item* item, Item* gem, bool replace)
|
||||
{
|
||||
if (fits)
|
||||
{
|
||||
*packet << ObjectGuid::Empty;
|
||||
packet << ObjectGuid::Empty;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32 enchant_id = item->GetEnchantmentId(EnchantmentSlot(enchant_slot));
|
||||
if (!enchant_id)
|
||||
{
|
||||
*packet << gem->GetGUID();
|
||||
packet << gem->GetGUID();
|
||||
fits = true;
|
||||
continue;
|
||||
}
|
||||
@@ -352,20 +353,20 @@ bool UseItemAction::SocketItem(Item* item, Item* gem, bool replace)
|
||||
SpellItemEnchantmentEntry const* enchantEntry = sSpellItemEnchantmentStore.LookupEntry(enchant_id);
|
||||
if (!enchantEntry || !enchantEntry->GemID)
|
||||
{
|
||||
*packet << gem->GetGUID();
|
||||
packet << gem->GetGUID();
|
||||
fits = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (replace && enchantEntry->GemID != gem->GetTemplate()->ItemId)
|
||||
{
|
||||
*packet << gem->GetGUID();
|
||||
packet << gem->GetGUID();
|
||||
fits = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
*packet << ObjectGuid::Empty;
|
||||
packet << ObjectGuid::Empty;
|
||||
}
|
||||
|
||||
if (fits)
|
||||
@@ -375,7 +376,9 @@ bool UseItemAction::SocketItem(Item* item, Item* gem, bool replace)
|
||||
out << " with " << chat->FormatItem(gem->GetTemplate());
|
||||
botAI->TellMaster(out);
|
||||
|
||||
bot->GetSession()->HandleSocketOpcode(*packet);
|
||||
WorldPackets::Item::SocketGems nicePacket(std::move(packet));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleSocketOpcode(nicePacket);
|
||||
}
|
||||
|
||||
return fits;
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include "UseMeetingStoneAction.h"
|
||||
#include "NamedObjectContext.h"
|
||||
#include "ReleaseSpiritAction.h"
|
||||
#include "PetAction.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
@@ -70,6 +71,7 @@ public:
|
||||
creators["trade status extended"] = &WorldPacketActionContext::trade_status_extended;
|
||||
creators["store loot"] = &WorldPacketActionContext::store_loot;
|
||||
creators["self resurrect"] = &WorldPacketActionContext::self_resurrect;
|
||||
creators["pet"] = &WorldPacketActionContext::pet;
|
||||
|
||||
// quest
|
||||
creators["talk to quest giver"] = &WorldPacketActionContext::turn_in_quest;
|
||||
@@ -139,6 +141,7 @@ private:
|
||||
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* self_resurrect(PlayerbotAI* botAI) { return new SelfResurrectAction(botAI); }
|
||||
static Action* pet(PlayerbotAI* botAI) { return new PetAction(botAI); }
|
||||
|
||||
// quest
|
||||
static Action* quest_update_add_kill(PlayerbotAI* ai) { return new QuestUpdateAddKillAction(ai); }
|
||||
|
||||
@@ -50,7 +50,9 @@ void GenericDKNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
|
||||
triggers.push_back(
|
||||
new TriggerNode("bone shield", NextAction::array(0, new NextAction("bone shield", 21.0f), nullptr)));
|
||||
triggers.push_back(
|
||||
new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 11.0f), NULL)));
|
||||
new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.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)
|
||||
|
||||
@@ -171,6 +171,10 @@ void GenericDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
// NextAction::array(0, new NextAction("anti magic zone", ACTION_EMERGENCY + 1), nullptr)));
|
||||
triggers.push_back(
|
||||
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(
|
||||
new TriggerNode("mind freeze", NextAction::array(0, new NextAction("mind freeze", ACTION_HIGH + 1), nullptr)));
|
||||
triggers.push_back(
|
||||
|
||||
@@ -119,6 +119,42 @@ void GenericDruidNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trig
|
||||
// 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
|
||||
// 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,
|
||||
new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 7),
|
||||
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 6),
|
||||
@@ -147,6 +183,7 @@ void GenericDruidNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trig
|
||||
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply oil", 1.0f), nullptr)));
|
||||
if (specTab == 1) // Feral
|
||||
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply stone", 1.0f), nullptr)));
|
||||
|
||||
}
|
||||
|
||||
GenericDruidBuffStrategy::GenericDruidBuffStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI)
|
||||
|
||||
@@ -122,7 +122,8 @@ void GenericDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
triggers.push_back(new TriggerNode("combat party member dead",
|
||||
NextAction::array(0, new NextAction("rebirth", ACTION_HIGH + 9), NULL)));
|
||||
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)
|
||||
|
||||
@@ -66,7 +66,7 @@ bool MountDrakeAction::Execute(Event event)
|
||||
for (auto& member : members)
|
||||
{
|
||||
Player* player = botAI->GetPlayer(member);
|
||||
if (!player) { continue; }
|
||||
if (!player->GetSession()->IsBot()) { continue; }
|
||||
|
||||
for (int i = 0; i < composition.size(); i++)
|
||||
{
|
||||
|
||||
@@ -102,9 +102,11 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
|
||||
new TriggerNode("unlock traded item", NextAction::array(0, new NextAction("unlock traded item", relevance), nullptr)));
|
||||
triggers.push_back(
|
||||
new TriggerNode("wipe", NextAction::array(0, new NextAction("wipe", relevance), nullptr)));
|
||||
triggers.push_back(new TriggerNode("pet", NextAction::array(0, new NextAction("pet", relevance), nullptr)));
|
||||
triggers.push_back(new TriggerNode("tame", NextAction::array(0, new NextAction("tame", 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("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)));
|
||||
}
|
||||
|
||||
@@ -185,7 +187,9 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
|
||||
supported.push_back("qi");
|
||||
supported.push_back("unlock items");
|
||||
supported.push_back("unlock traded item");
|
||||
supported.push_back("pet");
|
||||
supported.push_back("tame");
|
||||
supported.push_back("glyphs"); // Added for custom Glyphs
|
||||
supported.push_back("glyph equip"); // Added for custom Glyphs
|
||||
supported.push_back("pet");
|
||||
supported.push_back("pet attack");
|
||||
}
|
||||
|
||||
@@ -22,8 +22,10 @@ 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("not facing target",
|
||||
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(new TriggerNode("pet attack", NextAction::array(0, new NextAction("pet attack", 40.0f), nullptr)));
|
||||
// The pet-attack trigger is commented out because it was forcing the bot's pet to attack, overriding stay and follow commands.
|
||||
// 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),
|
||||
// new NextAction("repop", 0.8f), nullptr)));
|
||||
}
|
||||
|
||||
@@ -11,13 +11,14 @@
|
||||
void UseFoodStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
Strategy::InitTriggers(triggers);
|
||||
if (sPlayerbotAIConfig->freeFood)
|
||||
if (botAI->HasCheat(BotCheatMask::food))
|
||||
{
|
||||
triggers.push_back(new TriggerNode("medium health", NextAction::array(0, new NextAction("food", 3.0f), nullptr)));
|
||||
else
|
||||
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 health", NextAction::array(0, new NextAction("food", 3.0f), nullptr)));
|
||||
triggers.push_back(new TriggerNode("low mana", NextAction::array(0, new NextAction("drink", 3.0f), nullptr)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ 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("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("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)));
|
||||
|
||||
@@ -40,7 +40,7 @@ bool CastArcaneShotAction::isUseful()
|
||||
return false;
|
||||
|
||||
// Armor Penetration rating check - will not cast Arcane Shot above 435 ArP
|
||||
int32 armorPenRating = bot->GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + CR_ARMOR_PENETRATION);
|
||||
int32 armorPenRating = bot->GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1) + bot->GetUInt32Value(CR_ARMOR_PENETRATION);
|
||||
if (armorPenRating > 435)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -61,7 +61,8 @@ void FrostMageStrategy::InitTriggers(std::vector<TriggerNode*>& 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("has pet", NextAction::array(0, new NextAction("toggle pet spell", 29.5f), 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("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)));
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ 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("being attacked",
|
||||
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)
|
||||
|
||||
@@ -56,6 +56,8 @@ void PriestNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
|
||||
triggers.push_back(
|
||||
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)
|
||||
|
||||
@@ -65,6 +65,24 @@ public:
|
||||
creators["vezax cheat action"] = &RaidUlduarActionContext::vezax_cheat_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 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:
|
||||
@@ -117,6 +135,24 @@ private:
|
||||
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_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
|
||||
|
||||
@@ -20,13 +20,14 @@
|
||||
#include "RaidUlduarBossHelper.h"
|
||||
#include "RaidUlduarScripts.h"
|
||||
#include "RaidUlduarStrategy.h"
|
||||
#include "RaidUlduarTriggers.h"
|
||||
#include "RtiValue.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "ServerFacade.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "Unit.h"
|
||||
#include "Vehicle.h"
|
||||
#include <RtiTargetValue.h>
|
||||
#include <TankAssistStrategy.h>
|
||||
|
||||
const std::string ADD_STRATEGY_CHAR = "+";
|
||||
const std::string REMOVE_STRATEGY_CHAR = "-";
|
||||
@@ -42,6 +43,12 @@ 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_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_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)
|
||||
{
|
||||
@@ -1817,24 +1824,24 @@ bool ThorimMarkDpsTargetAction::Execute(Event event)
|
||||
if (!group)
|
||||
return false;
|
||||
|
||||
ObjectGuid currentMoonTarget = group->GetTargetIcon(moonIndex);
|
||||
ObjectGuid currentMoonTarget = group->GetTargetIcon(RtiTargetValue::moonIndex);
|
||||
Unit* currentMoonUnit = botAI->GetUnit(currentMoonTarget);
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "thorim");
|
||||
if (!currentMoonUnit && boss && boss->IsAlive() && boss->GetPositionZ() > ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD)
|
||||
{
|
||||
group->SetTargetIcon(moonIndex, bot->GetGUID(), boss->GetGUID());
|
||||
group->SetTargetIcon(RtiTargetValue::moonIndex, bot->GetGUID(), boss->GetGUID());
|
||||
}
|
||||
|
||||
if (currentMoonUnit && boss && currentMoonUnit->GetEntry() == boss->GetEntry() &&
|
||||
boss->GetPositionZ() < ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD)
|
||||
{
|
||||
group->SetTargetIcon(skullIndex, bot->GetGUID(), boss->GetGUID());
|
||||
group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), boss->GetGUID());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (botAI->IsMainTank(bot))
|
||||
{
|
||||
ObjectGuid currentSkullTarget = group->GetTargetIcon(skullIndex);
|
||||
ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex);
|
||||
Unit* currentSkullUnit = botAI->GetUnit(currentSkullTarget);
|
||||
if (currentSkullUnit && !currentSkullUnit->IsAlive())
|
||||
{
|
||||
@@ -1855,7 +1862,7 @@ bool ThorimMarkDpsTargetAction::Execute(Event event)
|
||||
}
|
||||
else if (botAI->IsAssistTankOfIndex(bot, 0))
|
||||
{
|
||||
ObjectGuid currentCrossTarget = group->GetTargetIcon(crossIndex);
|
||||
ObjectGuid currentCrossTarget = group->GetTargetIcon(RtiTargetValue::crossIndex);
|
||||
Unit* currentCrossUnit = botAI->GetUnit(currentCrossTarget);
|
||||
if (currentCrossUnit && !currentCrossUnit->IsAlive())
|
||||
{
|
||||
@@ -1891,13 +1898,13 @@ bool ThorimMarkDpsTargetAction::Execute(Event event)
|
||||
|
||||
if (botAI->IsMainTank(bot))
|
||||
{
|
||||
group->SetTargetIcon(skullIndex, bot->GetGUID(), targetToMark->GetGUID());
|
||||
group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), targetToMark->GetGUID());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (botAI->IsAssistTankOfIndex(bot, 0))
|
||||
{
|
||||
group->SetTargetIcon(crossIndex, bot->GetGUID(), targetToMark->GetGUID());
|
||||
group->SetTargetIcon(RtiTargetValue::crossIndex, bot->GetGUID(), targetToMark->GetGUID());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2438,20 +2445,20 @@ bool MimironAerialCommandUnitAction::Execute(Event event)
|
||||
|
||||
if (bombBot)
|
||||
{
|
||||
group->SetTargetIcon(crossIndex, bot->GetGUID(), bombBot->GetGUID());
|
||||
group->SetTargetIcon(RtiTargetValue::crossIndex, bot->GetGUID(), bombBot->GetGUID());
|
||||
}
|
||||
else if (boss)
|
||||
{
|
||||
group->SetTargetIcon(crossIndex, bot->GetGUID(), boss->GetGUID());
|
||||
group->SetTargetIcon(RtiTargetValue::crossIndex, bot->GetGUID(), boss->GetGUID());
|
||||
}
|
||||
|
||||
if (assaultBot)
|
||||
{
|
||||
ObjectGuid skullTarget = group->GetTargetIcon(skullIndex);
|
||||
ObjectGuid skullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex);
|
||||
Unit* skullUnit = botAI->GetUnit(skullTarget);
|
||||
if (!skullTarget || !skullUnit || !skullUnit->IsAlive())
|
||||
{
|
||||
group->SetTargetIcon(skullIndex, bot->GetGUID(), assaultBot->GetGUID());
|
||||
group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), assaultBot->GetGUID());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2591,7 +2598,7 @@ bool MimironPhase4MarkDpsAction::Execute(Event event)
|
||||
highestHealthUnit = aerialCommandUnit;
|
||||
}
|
||||
|
||||
group->SetTargetIcon(skullIndex, bot->GetGUID(), highestHealthUnit->GetGUID());
|
||||
group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), highestHealthUnit->GetGUID());
|
||||
if (highestHealthUnit == leviathanMkII)
|
||||
{
|
||||
if (AI_VALUE(std::string, "rti") == "skull")
|
||||
@@ -2601,7 +2608,7 @@ bool MimironPhase4MarkDpsAction::Execute(Event event)
|
||||
}
|
||||
else
|
||||
{
|
||||
group->SetTargetIcon(crossIndex, bot->GetGUID(), leviathanMkII->GetGUID());
|
||||
group->SetTargetIcon(RtiTargetValue::crossIndex, bot->GetGUID(), leviathanMkII->GetGUID());
|
||||
if (AI_VALUE(std::string, "rti") != "cross")
|
||||
{
|
||||
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set("cross");
|
||||
@@ -2707,3 +2714,575 @@ bool VezaxMarkOfTheFacelessAction::Execute(Event event)
|
||||
ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT.GetPositionZ(), false, false, false, true,
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "PlayerbotAI.h"
|
||||
#include "Playerbots.h"
|
||||
#include "RaidUlduarBossHelper.h"
|
||||
#include "RaidUlduarTriggers.h"
|
||||
#include "Vehicle.h"
|
||||
|
||||
//
|
||||
@@ -378,4 +379,125 @@ public:
|
||||
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
|
||||
|
||||
@@ -255,6 +255,66 @@ void RaidUlduarStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
triggers.push_back(new TriggerNode(
|
||||
"yogg-saron shadow resistance trigger",
|
||||
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)
|
||||
|
||||
@@ -67,6 +67,24 @@ public:
|
||||
creators["vezax cheat trigger"] = &RaidUlduarTriggerContext::vezax_cheat_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 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:
|
||||
@@ -120,7 +138,25 @@ private:
|
||||
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_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* 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
|
||||
|
||||
@@ -12,11 +12,33 @@
|
||||
#include "Trigger.h"
|
||||
#include "Vehicle.h"
|
||||
#include <MovementActions.h>
|
||||
#include <FollowMasterStrategy.h>
|
||||
#include <RtiTargetValue.h>
|
||||
|
||||
const std::vector<uint32> availableVehicles = {NPC_VEHICLE_CHOPPER, NPC_SALVAGED_DEMOLISHER,
|
||||
NPC_SALVAGED_DEMOLISHER_TURRET, NPC_SALVAGED_SIEGE_ENGINE,
|
||||
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()
|
||||
{
|
||||
Unit* vehicleBase = bot->GetVehicleBase();
|
||||
@@ -448,8 +470,8 @@ bool KologarnAttackDpsTargetTrigger::IsActive()
|
||||
if (!group)
|
||||
return false;
|
||||
|
||||
ObjectGuid skullTarget = group->GetTargetIcon(skullIndex);
|
||||
ObjectGuid crossTarget = group->GetTargetIcon(crossIndex);
|
||||
ObjectGuid skullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex);
|
||||
ObjectGuid crossTarget = group->GetTargetIcon(RtiTargetValue::crossIndex);
|
||||
|
||||
if (crossTarget && (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0)))
|
||||
{
|
||||
@@ -745,7 +767,7 @@ bool ThorimUnbalancingStrikeTrigger::IsActive()
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "thorim");
|
||||
|
||||
// Check boss and it is alive
|
||||
if (!boss || !boss->IsAlive())
|
||||
if (!boss || !boss->IsAlive() || !boss->IsHostileTo(bot))
|
||||
return false;
|
||||
|
||||
return bot->HasAura(SPELL_UNBALANCING_STRIKE);
|
||||
@@ -762,7 +784,7 @@ bool ThorimMarkDpsTargetTrigger::IsActive()
|
||||
|
||||
if (botAI->IsMainTank(bot))
|
||||
{
|
||||
ObjectGuid currentSkullTarget = group->GetTargetIcon(skullIndex);
|
||||
ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex);
|
||||
Unit* currentSkullUnit = botAI->GetUnit(currentSkullTarget);
|
||||
if (currentSkullUnit && !currentSkullUnit->IsAlive())
|
||||
{
|
||||
@@ -783,13 +805,13 @@ bool ThorimMarkDpsTargetTrigger::IsActive()
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "thorim");
|
||||
|
||||
// Check boss and it is alive
|
||||
if (!boss || !boss->IsAlive())
|
||||
if (!boss || !boss->IsAlive() || !boss->IsHostileTo(bot))
|
||||
return false;
|
||||
|
||||
|
||||
if (boss->GetPositionZ() < ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD && (!currentSkullUnit || !currentSkullUnit->IsAlive()))
|
||||
{
|
||||
group->SetTargetIcon(skullIndex, bot->GetGUID(), boss->GetGUID());
|
||||
group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), boss->GetGUID());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -811,7 +833,7 @@ bool ThorimMarkDpsTargetTrigger::IsActive()
|
||||
if (mainTank && bot->GetDistance(mainTank) < 30.0f)
|
||||
return false;
|
||||
|
||||
ObjectGuid currentCrossTarget = group->GetTargetIcon(crossIndex);
|
||||
ObjectGuid currentCrossTarget = group->GetTargetIcon(RtiTargetValue::crossIndex);
|
||||
Unit* currentCrossUnit = botAI->GetUnit(currentCrossTarget);
|
||||
if (currentCrossUnit && !currentCrossUnit->IsAlive())
|
||||
{
|
||||
@@ -963,7 +985,7 @@ bool ThorimArenaPositioningTrigger::IsActive()
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "thorim");
|
||||
|
||||
// Check boss and it is alive
|
||||
if (!boss || !boss->IsAlive())
|
||||
if (!boss || !boss->IsAlive() || !boss->IsHostileTo(bot))
|
||||
return false;
|
||||
|
||||
if (boss->GetPositionZ() < ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD)
|
||||
@@ -1061,7 +1083,7 @@ bool ThorimPhase2PositioningTrigger::IsActive()
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "thorim");
|
||||
|
||||
// Check boss and it is alive
|
||||
if (!boss || !boss->IsAlive())
|
||||
if (!boss || !boss->IsAlive() || !boss->IsHostileTo(bot))
|
||||
return false;
|
||||
|
||||
if (boss->GetPositionZ() > ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD)
|
||||
@@ -1408,8 +1430,8 @@ bool MimironAerialCommandUnitTrigger::IsActive()
|
||||
return false;
|
||||
}
|
||||
|
||||
ObjectGuid skullTarget = group->GetTargetIcon(skullIndex);
|
||||
ObjectGuid crossTarget = group->GetTargetIcon(crossIndex);
|
||||
ObjectGuid skullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex);
|
||||
ObjectGuid crossTarget = group->GetTargetIcon(RtiTargetValue::crossIndex);
|
||||
|
||||
//if (bombBot && bombBot->GetGUID() != crossTarget)
|
||||
//{
|
||||
@@ -1515,7 +1537,7 @@ bool MimironPhase4MarkDpsTrigger::IsActive()
|
||||
highestHealthUnit = aerialCommandUnit;
|
||||
}
|
||||
|
||||
ObjectGuid skullTarget = group->GetTargetIcon(skullIndex);
|
||||
ObjectGuid skullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex);
|
||||
if (!skullTarget)
|
||||
{
|
||||
return true;
|
||||
@@ -1619,3 +1641,750 @@ bool VezaxMarkOfTheFacelessTrigger::IsActive()
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ enum UlduarIDs
|
||||
SPELL_OVERLOAD_25_MAN_2 = 61886,
|
||||
SPELL_RUNE_OF_POWER = 64320,
|
||||
|
||||
//Kologarn
|
||||
// Kologarn
|
||||
NPC_RIGHT_ARM = 32934,
|
||||
NPC_RUBBLE = 33768,
|
||||
SPELL_CRUNCH_ARMOR = 64002,
|
||||
@@ -27,13 +27,13 @@ enum UlduarIDs
|
||||
SPELL_FOCUSED_EYEBEAM_10 = 63347,
|
||||
SPELL_FOCUSED_EYEBEAM_25_2 = 63976,
|
||||
SPELL_FOCUSED_EYEBEAM_25 = 63977,
|
||||
|
||||
|
||||
// Hodir
|
||||
NPC_SNOWPACKED_ICICLE = 33174,
|
||||
NPC_TOASTY_FIRE = 33342,
|
||||
SPELL_FLASH_FREEZE = 61968,
|
||||
SPELL_BITING_COLD_PLAYER_AURA = 62039,
|
||||
|
||||
|
||||
// Freya
|
||||
NPC_SNAPLASHER = 32916,
|
||||
NPC_STORM_LASHER = 32919,
|
||||
@@ -44,7 +44,7 @@ enum UlduarIDs
|
||||
NPC_EONARS_GIFT = 33228,
|
||||
GOBJECT_NATURE_BOMB = 194902,
|
||||
|
||||
//Thorim
|
||||
// Thorim
|
||||
NPC_DARK_RUNE_ACOLYTE_I = 32886,
|
||||
NPC_CAPTURED_MERCENARY_SOLDIER_ALLY = 32885,
|
||||
NPC_CAPTURED_MERCENARY_SOLDIER_HORDE = 32883,
|
||||
@@ -62,7 +62,7 @@ enum UlduarIDs
|
||||
NPC_IRON_HONOR_GUARD = 32875,
|
||||
SPELL_UNBALANCING_STRIKE = 62130,
|
||||
|
||||
//Mimiron
|
||||
// Mimiron
|
||||
NPC_LEVIATHAN_MKII = 33432,
|
||||
NPC_VX001 = 33651,
|
||||
NPC_AERIAL_COMMAND_UNIT = 33670,
|
||||
@@ -78,23 +78,73 @@ enum UlduarIDs
|
||||
SPELL_P3WX2_LASER_BARRAGE_AURA_1 = 63274,
|
||||
SPELL_P3WX2_LASER_BARRAGE_AURA_2 = 63300,
|
||||
|
||||
//General Vezax
|
||||
// General Vezax
|
||||
SPELL_MARK_OF_THE_FACELESS = 63276,
|
||||
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
|
||||
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_EYEBEAM_RADIUS = 3.0f;
|
||||
const float ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD = 429.6094f;
|
||||
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_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_ENTRANCE_POSITION = Position(2172.4355f, -258.27957f, 418.47162f);
|
||||
@@ -123,6 +173,16 @@ 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_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_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
|
||||
@@ -448,4 +508,139 @@ public:
|
||||
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
|
||||
|
||||
@@ -20,6 +20,8 @@ public:
|
||||
creators["emalon lighting nova action"] = &RaidVoAActionContext::emalon_lighting_nova_action;
|
||||
creators["emalon overcharge action"] = &RaidVoAActionContext::emalon_overcharge_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:
|
||||
@@ -27,6 +29,8 @@ private:
|
||||
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_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
|
||||
|
||||
@@ -24,4 +24,16 @@ void RaidVoAStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
triggers.push_back(new TriggerNode(
|
||||
"emalon fall from floor trigger",
|
||||
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)));
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ public:
|
||||
creators["emalon lighting nova trigger"] = &RaidVoATriggerContext::emalon_lighting_nova_trigger;
|
||||
creators["emalon overcharge trigger"] = &RaidVoATriggerContext::emalon_overcharge_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:
|
||||
@@ -26,6 +28,8 @@ private:
|
||||
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_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
|
||||
|
||||
@@ -1060,6 +1060,13 @@ bool NewRpgBaseAction::RandomChangeStatus(std::vector<NewRpgStatus> candidateSta
|
||||
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 accumulate = 0;
|
||||
NewRpgStatus chosenStatus = RPG_STATUS_END;
|
||||
@@ -1218,4 +1225,4 @@ bool NewRpgBaseAction::CheckRpgStatusAvailable(NewRpgStatus status)
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +133,8 @@ void GenericShamanStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
// 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("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)
|
||||
@@ -187,4 +189,4 @@ void ShamanHealerDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
new TriggerNode("medium aoe and healer should attack",
|
||||
NextAction::array(0,
|
||||
new NextAction("chain lightning", ACTION_DEFAULT + 0.3f), nullptr)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,10 @@ void ShamanNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
new TriggerNode("cure disease", NextAction::array(0, new NextAction("cure disease", 31.0f), nullptr)));
|
||||
triggers.push_back(new TriggerNode("party member cure disease",
|
||||
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)
|
||||
|
||||
@@ -15,7 +15,7 @@ bool BossFireResistanceTrigger::IsActive()
|
||||
{
|
||||
// Check boss and it is alive
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", bossName);
|
||||
if (!boss || !boss->IsAlive())
|
||||
if (!boss || !boss->IsAlive() || boss->IsFriendlyTo(bot))
|
||||
return false;
|
||||
|
||||
// Check if bot is paladin
|
||||
@@ -68,7 +68,7 @@ bool BossFrostResistanceTrigger::IsActive()
|
||||
{
|
||||
// Check boss and it is alive
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", bossName);
|
||||
if (!boss || !boss->IsAlive())
|
||||
if (!boss || !boss->IsAlive() || boss->IsFriendlyTo(bot))
|
||||
return false;
|
||||
|
||||
// Check if bot is paladin
|
||||
@@ -121,7 +121,7 @@ bool BossNatureResistanceTrigger::IsActive()
|
||||
{
|
||||
// Check boss and it is alive
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", bossName);
|
||||
if (!boss || !boss->IsAlive())
|
||||
if (!boss || !boss->IsAlive() || boss->IsFriendlyTo(bot))
|
||||
return false;
|
||||
|
||||
// Check if bot is alive
|
||||
@@ -176,7 +176,7 @@ bool BossShadowResistanceTrigger::IsActive()
|
||||
{
|
||||
// Check boss and it is alive
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", bossName);
|
||||
if (!boss || !boss->IsAlive())
|
||||
if (!boss || !boss->IsAlive() || boss->IsFriendlyTo(bot))
|
||||
return false;
|
||||
|
||||
// Check if bot is paladin
|
||||
|
||||
@@ -133,9 +133,11 @@ public:
|
||||
creators["calc"] = &ChatTriggerContext::calc;
|
||||
creators["qi"] = &ChatTriggerContext::qi;
|
||||
creators["wipe"] = &ChatTriggerContext::wipe;
|
||||
creators["pet"] = &ChatTriggerContext::pet;
|
||||
creators["tame"] = &ChatTriggerContext::tame;
|
||||
creators["glyphs"] = &ChatTriggerContext::glyphs; // 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;
|
||||
}
|
||||
|
||||
@@ -249,9 +251,11 @@ private:
|
||||
static Trigger* calc(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "calc"); }
|
||||
static Trigger* qi(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "qi"); }
|
||||
static Trigger* wipe(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "wipe"); }
|
||||
static Trigger* pet(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pet"); }
|
||||
static Trigger* tame(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "tame"); }
|
||||
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* 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"); }
|
||||
};
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#include "TemporarySummon.h"
|
||||
#include "ThreatMgr.h"
|
||||
#include "Timer.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "Player.h"
|
||||
|
||||
bool LowManaTrigger::IsActive()
|
||||
{
|
||||
@@ -247,7 +249,7 @@ bool AoeTrigger::IsActive()
|
||||
bool NoFoodTrigger::IsActive()
|
||||
{
|
||||
bool isRandomBot = sRandomPlayerbotMgr->IsRandomBot(bot);
|
||||
if (isRandomBot && sPlayerbotAIConfig->freeFood)
|
||||
if (isRandomBot && botAI->HasCheat(BotCheatMask::food))
|
||||
return false;
|
||||
|
||||
return AI_VALUE2(std::vector<Item*>, "inventory items", "conjured food").empty();
|
||||
@@ -256,7 +258,7 @@ bool NoFoodTrigger::IsActive()
|
||||
bool NoDrinkTrigger::IsActive()
|
||||
{
|
||||
bool isRandomBot = sRandomPlayerbotMgr->IsRandomBot(bot);
|
||||
if (isRandomBot && sPlayerbotAIConfig->freeFood)
|
||||
if (isRandomBot && botAI->HasCheat(BotCheatMask::food))
|
||||
return false;
|
||||
|
||||
return AI_VALUE2(std::vector<Item*>, "inventory items", "conjured water").empty();
|
||||
@@ -685,3 +687,46 @@ bool AmmoCountTrigger::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;
|
||||
}
|
||||
|
||||
@@ -943,4 +943,16 @@ public:
|
||||
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
|
||||
|
||||
@@ -214,3 +214,84 @@ bool FarFromMasterTrigger::IsActive()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -121,4 +121,28 @@ public:
|
||||
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
|
||||
|
||||
@@ -227,6 +227,7 @@ public:
|
||||
creators["do quest status"] = &TriggerContext::do_quest_status;
|
||||
creators["travel flight status"] = &TriggerContext::travel_flight_status;
|
||||
creators["can self resurrect"] = &TriggerContext::can_self_resurrect;
|
||||
creators["new pet"] = &TriggerContext::new_pet;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -425,6 +426,7 @@ private:
|
||||
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* can_self_resurrect(PlayerbotAI* ai) { return new SelfResurrectTrigger(ai); }
|
||||
static Trigger* new_pet(PlayerbotAI* ai) { return new NewPetTrigger(ai); }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -21,6 +21,14 @@ public:
|
||||
|
||||
static int32 GetRtiIndex(std::string const rti);
|
||||
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:
|
||||
std::string const type;
|
||||
|
||||
@@ -556,7 +556,7 @@ private:
|
||||
static UntypedValue* last_flee_timestamp(PlayerbotAI* ai) { return new LastFleeTimestampValue(ai); }
|
||||
static UntypedValue* recently_flee_info(PlayerbotAI* ai) { return new RecentlyFleeInfo(ai); }
|
||||
// -------------------------------------------------------
|
||||
// Flag for cutom glyphs : true when /w bot glyph equip <20>
|
||||
// Flag for cutom glyphs : true when /w bot glyph equip
|
||||
// -------------------------------------------------------
|
||||
static UntypedValue* custom_glyphs(PlayerbotAI* ai)
|
||||
{
|
||||
|
||||
@@ -79,6 +79,7 @@ void GenericWarlockNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& tr
|
||||
{
|
||||
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("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 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)));
|
||||
|
||||
Reference in New Issue
Block a user