Compare commits

..

44 Commits

Author SHA1 Message Date
Yunfan Li
f5f4f32799 Sell item packet opcode 2025-09-11 20:43:57 +08:00
Yunfan Li
11b96b51b7 Core update item packets (#1624) 2025-09-11 13:43:13 +08:00
kadeshar
a41c1912ac Merge pull request #1611 from brighton-chi/fix-expansion-limits-for-enchants
Fix thresholds for LimitEnchantExpansion
2025-09-06 17:33:23 +02:00
Vanna White
21d8f32d24 Fix bots in Oculus not using Drake Mount (#1613)
* Fix bots sometimes not using drake mount

* change bot check

---------

Co-authored-by: wetbrownsauce <you@example.com>
2025-09-06 16:14:52 +02:00
zeb139
bf56154eee Add weighted bot to banker teleport logic and config (#1615)
* add weighted bot to banker teleport logic and config

* moved banker location lookup tables to top of file
2025-09-06 16:10:56 +02:00
kadeshar
e46269920a - Fixed update sql script name (#1619) 2025-09-05 21:38:26 +02:00
crow
1881ef1fe0 fix thresholds for LimitEnchantExpansion
And disallow Naxx40 shoulder enchants
2025-09-05 10:21:20 -05:00
kadeshar
3c442a6b71 - Excluded additional Legendary Arcane Amalgamation from obtainable for bot enchantments (#1600) 2025-09-02 19:24:55 +02:00
kadeshar
750d557e6a Merge pull request #1604 from kadeshar/koralon-strategy
Added automatic resistance switch on Emalon and Koralon
2025-09-02 17:02:59 +02:00
kadeshar
8057a5d7ac Merge pull request #1593 from kadeshar/rtsc-despawn-bugfix
Fixed bug with RTSC despawning objects
2025-09-02 17:01:46 +02:00
Spargel
c218dbe653 Fix uses of restrictHealerDPS and randomBotCombatStrategies. (#1570) 2025-09-01 19:05:07 +02:00
kadeshar
6588ca5878 - Added automatic resistance switch on Emalon and Koralon 2025-08-30 15:25:47 +02:00
kadeshar
df6b1490b1 - Fixed world sql scripts naming 2025-08-28 18:41:08 +02:00
kadeshar
179c34e3a9 Food cheat fixes (#1594)
* - Fixed bug with InitFood and food cheat
- Fixed food cheat description in config

* - Fixed bug with initself command
2025-08-28 18:25:13 +02:00
kadeshar
45d046f427 - Fixed bug where bot looting gameobject which is on respawn time (#1596) 2025-08-28 18:24:40 +02:00
kadeshar
31f2c6a20d - Fixed sql structure 2025-08-27 19:09:51 +02:00
kadeshar
37458f0dc5 -Fixed module sql structure 2025-08-27 19:04:01 +02:00
kadeshar
bc737ecc68 - Changed standalone config on cheat (#1585)
- Changed drink condition
2025-08-26 18:28:42 +02:00
Crow
704e02e9cc Add SMV area IDs to PvP Prohibited Areas (#1589)
* Add SMV area IDs to PvP Prohibited Areas

Sanctum of the Stars and the Altar of Sha’tar

* Update source default pvpProhibitedAreaIDs

* Update source default pvpProhibitedAreaIDs

Now with Sanctum of the Stars & Altar of Sha'tar as well
2025-08-26 18:27:57 +02:00
NoxMax
5f00b9bbd5 Crash fix for RPG weights 0 (#1590) 2025-08-26 18:25:19 +02:00
kadeshar
5469333465 - Fixed bug with RTSC despawning objects 2025-08-26 15:39:34 +02:00
Revision
2dad8bf01d Fixed a compiler warning (#1586) 2025-08-24 18:18:00 +02:00
kadeshar
78116fe37e Merge pull request #1510 from brighton-chi/bot-chat-filters
increase flexibility of multiple bot chatfiltering
2025-08-21 21:58:17 +02:00
bash
c9b4cfa184 [Revert] Threading leftover which belonged to other related PRs's (once green needs be merged) (#1583)
* Revert "Correct side effects of  merge f5ef5bd1c2 (#1512)"

This reverts commit 966bf1d6af.

* Revert "Fix ACCESS_VIOLATION in mod-playerbots: purge stale AIs, add thread-safety, and harden HasRealPlayerMaster (#1507)"

This reverts commit f5ef5bd1c2.
2025-08-20 20:13:45 +02:00
HennyWilly
957a60cd1d Ignore GameObject IDs from vanilla dungeons (#1518)
* DisallowedGameObjects for vanilla dungeons

* Bots ignore trap crates in Stratholme -> Remove

* Updated AiPlayerbot.DisallowedGameObjects default value in source code

* Removed duplicate

* Added 123329

* Added 123329 and removed duplicated

* Update playerbots.conf.dist

153464 - no reason to ignore it

* 153464 - no reason to ignore it

---------

Co-authored-by: bash <31279994+hermensbas@users.noreply.github.com>
2025-08-20 18:24:00 +02:00
mtm84
b661264c53 Update HunterActions.cpp (#1576)
Removes compiler warning
2025-08-18 12:05:53 +02:00
kadeshar
77c2354c3f Yogg-Saron strategy (#1565)
* - wip

* - Added Yogg-Saron strategy

* - Added Yogg-Saron sanity strategy

* - WIP

* - WIP

* - WIP

* - WIP

* - Added Yogg-Saron strategy

* - code refactoring

* - Code fix after pr
2025-08-18 12:02:19 +02:00
bash
b369b1f9ae MoveToTravelTargetAction prevent delay when in combat (#1558) 2025-08-18 02:38:06 +02:00
Vigerus
fa7b863035 NamedObjectContext improvement, remove unneccessary pass by copy, allow lambda ObjectCreators (#1561)
Co-authored-by: Viger <viger28@gmail.com>
2025-08-17 15:42:26 +02:00
bash
4f5f7d286e nullptr fix (#1555) 2025-08-16 15:29:44 +02:00
bash
6cb9f56c4e nullptr fix (#1557)
* nullptr fix

* Update PlayerbotFactory.cpp
2025-08-16 15:29:09 +02:00
Crow
3e0f23536d Update README.md (#1567) 2025-08-16 12:04:00 +02:00
kadeshar
12065a6ad5 Merge pull request #1486 from ThePenguinMan96/Tame-Chat-Action-/-Pet-Chat-Action-(stances/commands)
Tame Chat Action / Pet Chat Action (stances/commands)
2025-08-16 10:27:17 +02:00
ThePenguinMan96
c5010b3809 Merge branch 'liyunfan1223:master' into Tame-Chat-Action-/-Pet-Chat-Action-(stances/commands) 2025-08-10 09:59:34 -07:00
ThePenguinMan96
86dbf54584 Merge branch 'liyunfan1223:master' into Tame-Chat-Action-/-Pet-Chat-Action-(stances/commands) 2025-08-09 09:51:27 -07:00
crow
2144c95311 increase flexibility of multiple bot chatfiltering 2025-08-07 16:25:47 -05:00
ThePenguinMan96
28de238422 Merge branch 'liyunfan1223:master' into Tame-Chat-Action-/-Pet-Chat-Action-(stances/commands) 2025-08-05 20:49:00 -07:00
ThePenguinMan96
ab345b8847 Changes requested
I fixed what was requested of me. Built it, tested it - all functions are working.
2025-08-04 17:55:29 -07:00
ThePenguinMan96
683c6e39e4 Merge branch 'liyunfan1223:master' into Tame-Chat-Action-/-Pet-Chat-Action-(stances/commands) 2025-08-04 17:06:47 -07:00
ThePenguinMan96
548746c25f Revert "Update ChatActionContext.h"
This reverts commit f7e64589e8.
2025-08-01 23:55:25 -07:00
ThePenguinMan96
f7e64589e8 Update ChatActionContext.h 2025-08-01 23:54:12 -07:00
ThePenguinMan96
8545225923 Merge branch 'master' into Tame-Chat-Action-/-Pet-Chat-Action-(stances/commands) 2025-08-01 23:30:23 -07:00
ThePenguinMan96
ffc96b664e Turning off Spirit Wolf Leap
I had to add an exception to turn off autocasting for the Spirit Wolf Leap - they were still leaping into battle despite their stance.
2025-08-01 01:35:11 -07:00
ThePenguinMan96
aa6f8153a1 Tame Chat Action / Pet Chat Action (stances/commands)
Hello everyone,

I am on a quest to make bot's pets completely functional, focuses on solving issues #1351 , #1230 , and #1137 . This PR achieves the following:

1. Changes the current "pet" chat command to "tame", which is more intuitive that only hunters can use it. The modes are "tame name (name)", "tame id (id)", "tame family (family)", "tame rename (new name)", and "tame abandon". Tame abandon is new - it simply abandons the current pet. Also, now, if you type in "tame family" by itself, it will list the available families. See pictures below for examples.
2. Added new "pet" chat command, with the following modes: "pet passive", "pet aggressive", "pet defensive", "pet stance" (shows current pet stance), "pet attack", "pet follow", and "pet stay". Previously, the pet's stance was not changeable, and there were some less than desired effects from summonable pets - see the issues above.
3. New config option: AiPlayerbot.DefaultPetStance, which changes the stance that all bot's pets are summoned as. This makes sure when feral spirits or treants are summoned by shamans and druids, they are immediately set to this configured stance. Set as 1 as default, which is defensive. (0 = Passive, 1 = Defensive, 2 = Aggressive)
4. New config option: AiPlayerbot.PetChatCommandDebug, which enables debug messages for the "pet" chat command. By default it is set to 0, which is disabled, but if you would like to see when pet's stances are changed, or when you tell the pet to attack/follow/stay, and when pet spells are auto-toggled, this is an option. I made this for myself mainly to test the command - if anyone notices any wierd pet behavior, this will be an option to help them report it as well.
5. Modified FollowActions to not constantly execute the petfollow action, overriding any stay or attack commands issued.
6. Modified GenericActions to have TogglePetSpellAutoCastAction optionally log when spells are toggled based on AiPlayerbot.PetChatCommandDebug.
7. Modified PetAttackAction to not attack if the pet is set to passive, and not override the pet's stance to passive every time it was executed.
8. Modified CombatStrategy.cpp to not constantly issue the petattack command, respecting the "pet stay" and "pet follow" commands. Pets will still automatically attack the enemy if set to aggressive or defensive.
9. Warlocks, Priests, Hunters, Shamans, Mages, Druids, and DKs (all classes that have summons): Added a "new pet" trigger that executes the "set pet stance" action. The "new pet" trigger happens only once, upon summoning a pet. It sets the pet's stance from AiPlayerbot.DefaultPetStance's value.
2025-08-01 01:18:16 -07:00
89 changed files with 3805 additions and 692 deletions

View File

@@ -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

View File

@@ -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.
#
####################################################################################################

View File

View 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);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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); }

View File

@@ -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);

View File

@@ -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

View File

@@ -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;

View File

@@ -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)

View File

@@ -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

View File

@@ -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);

View File

@@ -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
{

View File

@@ -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();

View File

@@ -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)

View File

@@ -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]);
}

View File

@@ -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;

View File

@@ -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); }

View File

@@ -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;
}

View File

@@ -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); }
};

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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));
}

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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());
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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))
{

View 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;
}
}

View 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

View File

@@ -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";

View File

@@ -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;

View File

@@ -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); }

View File

@@ -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)

View File

@@ -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(

View File

@@ -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)

View File

@@ -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)

View File

@@ -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++)
{

View File

@@ -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");
}

View File

@@ -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)));
}

View File

@@ -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)));
}
}

View File

@@ -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)));

View File

@@ -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;

View File

@@ -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)));

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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)));
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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)));
}
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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"); }
};

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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)));