Compare commits

..

185 Commits

Author SHA1 Message Date
bash
c191edf280 MoveToTravelTargetAction prevent delay when in combat 2025-08-13 00:54:46 +02:00
bash
8d51092d42 As requested revert for threadfixes last few days (#1552)
* Revert "[Large server fix] #1537 Serialize playerBots/botLoading with a mutex and use snapshot-based loops to fix concurrency crashes (#1540)"

This reverts commit 3fff58df1a.

* Revert "[Fix] teleport to invalid map or invalid coordinates (x , y , z  200000, o ) given when teleporting player (g UI d full type player low , name , map , x , y , z , o )  (#1538)"

This reverts commit ca2e2ef0db.

* Revert "Fix: prevent MoveSplineInitArgs::Validate velocity asserts (velocity > 0.01f) for bots, pets, and charmed units (#1534)"

This reverts commit 4e3ac609bd.

* Revert "[Fix issue #1527] : startup crash in tank target selection — add TOCTOU & null-safety guards (#1532)"

This reverts commit c6b0424c29.

* Revert "[Fix issue #1528] Close small window where the “in a BG/arena” state can change between the check (InBattleground() / InArena()) and grabbing the pointer (GetBattleground()), which leads to a null dereference. (#1530)"

This reverts commit 2e0a161623.

* Revert "Harden playerbot logout & packet dispatch; add null-safety in chat hooks and RPG checks (#1529)"

This reverts commit e4ea8e2694.

* Revert "Dont wait to travel when in combat. (#1524)"

This reverts commit ddfa919154.

* Revert "nullptr fix (#1523)"

This reverts commit 380312ffd2.

* Revert "Playerbots/LFG: fix false not eligible & dungeon 0/type 0, add clear diagnostics (#1521)"

This reverts commit 872e417613.

* Revert "nullptr exception (#1520)"

This reverts commit 3d28a81508.

* Revert "Removed bot freezing at startup and system message, not relevant anymore (#1519)"

This reverts commit bcd6f5bc06.
2025-08-12 22:10:47 +02:00
Alex Dcnh
3fff58df1a [Large server fix] #1537 Serialize playerBots/botLoading with a mutex and use snapshot-based loops to fix concurrency crashes (#1540)
* MoveSplineInitArgs::Validate: expression 'velocity > 0.01f' failed for GUID Full

* Update BotMovementUtils.h

* Playerbots: guard against invalid-Z teleports

* Update PlayerbotMgr.cpp
2025-08-12 08:15:22 +02:00
Alex Dcnh
ca2e2ef0db [Fix] teleport to invalid map or invalid coordinates (x , y , z 200000, o ) given when teleporting player (g UI d full type player low , name , map , x , y , z , o ) (#1538)
* MoveSplineInitArgs::Validate: expression 'velocity > 0.01f' failed for GUID Full

* Update BotMovementUtils.h

* Playerbots: guard against invalid-Z teleports
2025-08-12 01:54:17 +02:00
Alex Dcnh
4e3ac609bd Fix: prevent MoveSplineInitArgs::Validate velocity asserts (velocity > 0.01f) for bots, pets, and charmed units (#1534)
* MoveSplineInitArgs::Validate: expression 'velocity > 0.01f' failed for GUID Full

* Update BotMovementUtils.h
2025-08-12 01:53:48 +02:00
Alex Dcnh
c6b0424c29 [Fix issue #1527] : startup crash in tank target selection — add TOCTOU & null-safety guards (#1532)
* Harden playerbot logout & packet dispatch; add null-safety in chat hooks and RPG checks

* Fix Issue 1528

* Fix Issue #1527
2025-08-11 17:00:31 +02:00
Alex Dcnh
2e0a161623 [Fix issue #1528] Close small window where the “in a BG/arena” state can change between the check (InBattleground() / InArena()) and grabbing the pointer (GetBattleground()), which leads to a null dereference. (#1530)
* Harden playerbot logout & packet dispatch; add null-safety in chat hooks and RPG checks

* Fix Issue 1528
2025-08-11 16:27:38 +02:00
Alex Dcnh
e4ea8e2694 Harden playerbot logout & packet dispatch; add null-safety in chat hooks and RPG checks (#1529) 2025-08-11 16:27:25 +02:00
bash
ddfa919154 Dont wait to travel when in combat. (#1524)
Prevents bot adding a travel delay when in combat
2025-08-11 01:11:54 +02:00
bash
380312ffd2 nullptr fix (#1523) 2025-08-10 22:59:34 +02:00
Alex Dcnh
872e417613 Playerbots/LFG: fix false not eligible & dungeon 0/type 0, add clear diagnostics (#1521)
Tested
2025-08-10 21:23:02 +02:00
bash
3d28a81508 nullptr exception (#1520) 2025-08-10 19:31:10 +02:00
bash
bcd6f5bc06 Removed bot freezing at startup and system message, not relevant anymore (#1519) 2025-08-10 19:11:39 +02:00
bash
15f138aab0 Don't apply XPRate multiplier when bot is in group with real player (#1495)
* dont apply XPRate if bot is in group with real player

https://github.com/liyunfan1223/mod-playerbots/issues/1490

* Optimize code

* Oops minor correction

* Defense check on the player itself

* Safer way to check the leader is real player.

* Added abit more defense programming, should be needed still ..why not
2025-08-10 18:28:39 +02:00
ThePenguinMan96
5759a98d5a Warlock Soul Shard Cap Increase / Firestone and Spellstone Modes (#1514)
Hello everyone,

This is a small change to warlocks that accomplishes 2 things:

1. Changes the firestone and spellstone weapon enchants so only one of them can be active - players reported to me that both strategies could be present before, resulting in a bug where the bot repeatedly applied the enchant.
2. Changes the soul shard deletion cap from 6 or more to 26 or more - players will now be able to stockpile soul shards up to 25 in a bot's inventory before the bot starts deleting them one at a time back down 25. I chose 25 because if it was higher, drain soul would get multiple shards above the 32 unique cap, and spam the player "I can't carry any more of those". It was super annoying, and with testing, I have not seen this error at 25. This aims to address issue #1502 .
2025-08-10 11:13:01 +02:00
brighton-chi
13fca4398d Remove EPL from pvp prohibited zones (#1511)
* Remove EPL from pvp prohibited zones

* fixed unrelated error in comments

* Update default value
2025-08-09 20:59:55 +02:00
Yunfan Li
a307eb2f08 VisitAllObjects to VisitObjects (sync with acore) (#1513) 2025-08-09 19:17:33 +08:00
Alex Dcnh
966bf1d6af Correct side effects of merge f5ef5bd1c2 (#1512)
* Update PlayerbotMgr.h

* Update PlayerbotMgr.cpp

* Update PlayerbotMgr.cpp

* Update PlayerbotMgr.cpp

* Update PlayerbotMgr.cpp

* Update PlayerbotMgr.cpp

* Update PlayerbotMgr.cpp

* Update PlayerbotMgr.h

* Update PlayerbotMgr.cpp

* Update PlayerbotMgr.h

* Update PlayerbotMgr.cpp

* Update PlayerbotMgr.h

* Update PlayerbotMgr.h
2025-08-08 20:36:03 +02:00
Alex Dcnh
f5ef5bd1c2 Fix ACCESS_VIOLATION in mod-playerbots: purge stale AIs, add thread-safety, and harden HasRealPlayerMaster (#1507) 2025-08-07 00:31:00 +02:00
ThePenguinMan96
0afcf29490 Warlock Dismount Pet Fix (#1501)
Hello everyone,

This PR is to address #1489, where the warlock summons a pet when they dismount.

A tester found that the cause was the "wrong pet" triggering "summon (pet)". I looked into the "wrong pet" trigger, and noticed that there was not a clause if there was no active pet. It was inadvertently casting "summon (pet)" because for a brief second after dismounting, the warlock didn't technically have a pet.

I was able to recreate the issue based on tester feedback (dismounting with a warlock bot that has a pet). I tested this fix locally - it seems to work as intended. The warlock no longer attempts to summon a pet when dismounting. I tested it with nc +debug, and noticed that the "wrong pet" trigger was no longer firing. I also checked the logs - nothing.

Thank y'all for the testing and feedback!
2025-08-05 09:10:06 +02:00
brighton-chi
a6c07ca16d Improve attackaction failure message system (#1498) 2025-08-03 23:12:33 +02:00
Yunfan Li
ee99b66d04 Sync with azerothcore (#1492) 2025-08-02 16:10:49 +08:00
Revision
ede7697784 Changed OnPlayerBeforeAchievementComplete to allow random bots to unlock achievements (#1487) 2025-08-01 21:48:01 +02:00
Alex Dcnh
ba9cb5a256 Add optional gender parameter to .playerbot bot addclass command (#1491) 2025-08-01 21:45:29 +02:00
Keleborn
a1dd6f6fc5 New roll for item action (#1482)
* New roll for item action

* Add general roll command as well.

* Update ChatCommandHandlerStrategy.cpp

Add accidental removal of glyph equip

---------

Co-authored-by: bash <31279994+hermensbas@users.noreply.github.com>
2025-08-01 21:44:52 +02:00
brighton-chi
e950f65a83 Add config to disable hunters from generating specified pet families (#1485)
* Add config to disable hunters from generating specified pet families

* Fixed unrelated typo in config
2025-08-01 19:29:51 +02:00
brighton-chi
938872564a Revise bot logic for initializing and using consumables (#1483)
Bots will now add level- and spec-appropriate oils and stones when maintaining and, with respect to randombots, leveling. All bots (other than those with class-specific temporary weapon enchants) will apply oils and stones to their weapons. General clean-ups to associated code were made.
2025-08-01 19:28:13 +02:00
Yunfan Li
baa1aa9e9d Fix initself crash (#1488) 2025-08-01 19:15:59 +02:00
kadeshar
65a3bf481c - Added generic boss shadow aura trigger and action (#1480)
- Added automatic aura for General Vezax and Yogg-Saron
- Added support for all rank for boss aura triggers
2025-08-01 21:31:44 +08:00
kadeshar
989b48f491 Merge pull request #1477 from ThePenguinMan96/Channel-Cancel-checks-for-Channeled-AOE-DPS-Spells
Channel Cancel checks for Channeled AOE DPS Spells
2025-07-31 21:25:53 +02:00
kadeshar
40874624a8 Merge pull request #1475 from EricksOliveira/patch-33
Improve Arena Bot Behavior When Target is Behind Obstacles
2025-07-31 17:25:42 +02:00
kadeshar
f76435b4c4 Merge pull request #1473 from Tierisch/master
Add option to remove 'healer dps' strategy based on specified map.
2025-07-31 16:50:06 +02:00
kadeshar
df3c44419d Merge pull request #1474 from ThePenguinMan96/Druid-Flight-Form-Bug-Fix
Druid Flight Form Bug Fix
2025-07-28 18:23:01 +02:00
EricksOliveira
b7b8c60d17 Fix 2025-07-28 12:12:53 -03:00
EricksOliveira
e5f1446b9f . 2025-07-28 12:05:22 -03:00
EricksOliveira
e92029dd6e The bot immediately moves to a new position if the target is lost by LoS even during the cast. 2025-07-28 11:48:15 -03:00
EricksOliveira
b8c0a54f92 .. 2025-07-28 09:03:20 -03:00
EricksOliveira
18d1821dab Fix 2025-07-28 08:57:19 -03:00
EricksOliveira
8a8571c54f . 2025-07-28 08:45:20 -03:00
EricksOliveira
21bcbece7a Improve Bot Repositioning Based on Line of Sight and Vertical Distance
This update enhances bot behavior by adding a check for both line of sight (LoS) and significant vertical height differences between the bot and its target. If the bot cannot see its target or if the height difference exceeds 5.0f, it calculates a valid path and moves closer to regain visibility and combat effectiveness. This resolves cases where bots would previously stand still when enemies jumped from elevated terrain or were behind obstacles.
2025-07-28 08:36:28 -03:00
ThePenguinMan96
e40c2b21f2 Channel Cancel checks for Channeled AOE DPS Spells
Hello everyone,

I liked the channel cancel I did on mage recently, where they cancel blizzard if there is only 1 enemy left. I decided to do the same for the following classes:

Druid: Hurricane
Hunter: Volley
Priest: Mind Sear
Warlock: Rain of Fire

I moved the "ChannelCancel" action to it's own file, so other classes could benefit from it. I removed it from the mageactions.
2025-07-28 01:35:56 -07:00
EricksOliveira
8a9a833c98 fix 2025-07-27 19:31:54 -03:00
EricksOliveira
101c7f3046 . 2025-07-27 18:45:04 -03:00
EricksOliveira
d8d94f33ee Improve Arena Bot Behavior When Target is Behind Obstacles
This update enhances bot behavior in arena scenarios by addressing the issue where bots remain idle if their target moves behind line-of-sight (LoS obstacles). The bot now attempts to reposition near the target to regain LoS instead of standing still, preventing situations where enemies can recover without pressure.

Could someone test it?
2025-07-27 18:35:43 -03:00
kadeshar
eef2e8c1ef Merge pull request #1456 from brighton-chi/worldbuff
Make world buff strategy conditions configurable
2025-07-27 12:32:26 +02:00
kadeshar
a675e74d97 Merge pull request #1466 from Wishmaster117/Add-glyphs-and-glyph-equip-commands
Add /w botname "glyphs" and  "glyph equip" commands
2025-07-27 12:30:54 +02:00
ThePenguinMan96
de9f8fbbea Druid Flight Form Bug Fix
Hello everyone,

This is a fix for issue #1233

I have added a clause in the CanCastSpell check that checks if the bot is in flight form/swift flight form & not in combat. It will no longer attempt to shift into a caster form  and repetitively try and heal/buff party members while flying. Tested on my PC with the changes and druid now successfully flies as fast as the player with no stopping.

This was causing the issue:

static ActionNode* thorns([[maybe_unused]] PlayerbotAI* botAI)
{
    return new ActionNode("thorns",
                          /*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
                          /*A*/ nullptr,
                          /*C*/ nullptr);
}

static ActionNode* thorns_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
    return new ActionNode("thorns on party",
                          /*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
                          /*A*/ nullptr,
                          /*C*/ nullptr);
}

static ActionNode* mark_of_the_wild([[maybe_unused]] PlayerbotAI* botAI)
{
    return new ActionNode("mark of the wild",
                          /*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
                          /*A*/ nullptr,
                          /*C*/ nullptr);
}

static ActionNode* mark_of_the_wild_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
    return new ActionNode("mark of the wild on party",
                          /*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
                          /*A*/ nullptr,
                          /*C*/ nullptr);
}
static ActionNode* regrowth_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
    return new ActionNode("regrowth on party",
                          /*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
                          /*A*/ NULL,
                          /*C*/ NULL);
}
static ActionNode* rejuvenation_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
    return new ActionNode("rejuvenation on party",
                          /*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
                          /*A*/ NULL,
                          /*C*/ NULL);
}
static ActionNode* remove_curse_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
    return new ActionNode("remove curse on party",
                          /*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
                          /*A*/ NULL,
                          /*C*/ NULL);
}
static ActionNode* abolish_poison_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
    return new ActionNode("abolish poison on party",
                          /*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
                          /*A*/ NULL,
                          /*C*/ NULL);
}
static ActionNode* revive([[maybe_unused]] PlayerbotAI* botAI)
{
    return new ActionNode("revive",
                          /*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
                          /*A*/ NULL,
                          /*C*/ NULL);
}

The *P* stands for prior, aka before this action is taken, do this other action "caster form". This is the Caster Form action:

class CastCasterFormAction : public CastBuffSpellAction
{
public:
    CastCasterFormAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "caster form") {}

    bool isUseful() override;
    bool isPossible() override { return true; }
    bool Execute(Event event) override;
};

And this:

bool CastCasterFormAction::isUseful()
{
    return botAI->HasAnyAuraOf(GetTarget(), "dire bear form", "bear form", "cat form", "travel form", "aquatic form",
                               "flight form", "swift flight form", "moonkin form", nullptr) &&
           AI_VALUE2(uint8, "mana", "self target") > sPlayerbotAIConfig->mediumHealth;
}

bool CastCasterFormAction::Execute(Event event)
{
    botAI->RemoveShapeshift();
    return true;
}

So basically, the druid was triggering to heal/buff the party, and before it could do that, it had to switch into "caster form" which executed RemoveShapeshift(). After it did that, mounting back up into flight form/swift flight form had the highest priority, so it would loop the triggers/actions.
2025-07-27 01:10:31 -07:00
Spargel
66b326d79e Merge branch 'master' into master 2025-07-27 02:57:03 -05:00
Spargel
0d8e8fbd61 Second pass for adding 'healer dps' check based on map ID. Now works when teleporting in addition to on init. 2025-07-27 02:29:07 -05:00
kadeshar
66c88d4815 Merge pull request #1472 from liyunfan1223/drink_aura
Change drink aura (free food) to speed up
2025-07-27 08:53:09 +02:00
kadeshar
4c3906c243 Merge pull request #1468 from ThePenguinMan96/Mage-Overhaul
Mage Overhaul
2025-07-27 08:46:23 +02:00
Spargel
64b09fd3ca First pass for adding 'healer dps' check based on map ID. 2025-07-27 01:20:57 -05:00
NoxMax
db7a17ffde Fix: Properly track RNDbot and AddClass accounts, and login faction balance issue (#1434)
* AssignAccountTypes & AddRandomBots

Fix: Properly track RNDbot and AddClass accounts, and login faction balance issue

* code style edits

* fix addclass init on first build of playerbots_account_type
2025-07-27 14:13:20 +08:00
kadeshar
1e33b28abe - Added raid cheat to configuration to add posibility to turn off (#1465)
- Added General Vezax strategy
2025-07-27 13:51:45 +08:00
Yunfan Li
e59bad26c4 Change drink aura for free food 2025-07-27 11:36:43 +08:00
ThePenguinMan96
a632fa2194 Migrate important cooldowns over to BoostStrategy
This commit focuses on the changes requested - Migrate important cooldowns over to BoostStrategy.

Just tested it, and it is working fine - The player has more control over when they can boost. Also, added Mirror Image to the Boost Strategy, after other cooldowns are used - this way the mirror images get the buffs.
2025-07-26 13:06:41 -07:00
Alex Dcnh
c6005449e0 Update EquipGlyphsAction.cpp 2025-07-26 21:56:53 +02:00
Alex Dcnh
2aca50c1c7 Update ChatTriggerContext.h 2025-07-26 19:28:58 +02:00
Alex Dcnh
179e3bbf71 Update ChatCommandHandlerStrategy.cpp 2025-07-26 19:28:31 +02:00
Alex Dcnh
00b03bd29d Update ChatTriggerContext.h 2025-07-26 19:28:11 +02:00
Alex Dcnh
e62da73706 Update ChatCommandHandlerStrategy.cpp 2025-07-26 19:27:48 +02:00
Alex Dcnh
5108f709c7 Update ChatTriggerContext.h 2025-07-26 19:27:25 +02:00
Alex Dcnh
2beee4aec9 Update ChatCommandHandlerStrategy.cpp 2025-07-26 19:27:01 +02:00
Alex Dcnh
1e128ea24f Update ChatTriggerContext.h 2025-07-26 19:26:38 +02:00
Alex Dcnh
e64da42f87 Update ChatCommandHandlerStrategy.cpp 2025-07-26 19:26:07 +02:00
Alex Dcnh
e0ef04e1b9 Update ChatTriggerContext.h 2025-07-26 19:25:31 +02:00
Alex Dcnh
c5c1274d3c Update ChatCommandHandlerStrategy.cpp 2025-07-26 19:24:39 +02:00
Alex Dcnh
849b21f916 Update ChatCommandHandlerStrategy.cpp
Fix indentation: replaced tabs with spaces
2025-07-26 19:23:39 +02:00
Alex Dcnh
4c9e4e7b0f Update ChatTriggerContext.h 2025-07-26 19:18:09 +02:00
Alex Dcnh
961629f4ce Update EquipGlyphsAction.cpp 2025-07-26 18:58:16 +02:00
crow
1801d7c314 Merge remote-tracking branch 'upstream/master' into worldbuff 2025-07-26 11:16:33 -05:00
kadeshar
237c0cffc4 Merge pull request #1467 from ThePenguinMan96/Warlock-Pet-Re-Summon-on-Strategy-Change
Warlock Pet Re-Summon on Strategy Change
2025-07-26 18:05:38 +02:00
ThePenguinMan96
ee245f73b5 Mage Overhaul
Hello everyone,

Back again with another class overhaul. Here is a list of what changes have been made:
1. Consolidated the AoE strategies into "aoe". For light aoe (2+ enemies) the mage will use Cone of Cold (frost)/Arcane Explosion (Arcane)/Multi-Dot with Living Bomb (Fire/Frostfire). For medium aoe (3+ enemies) they will use Flamestrike -> Blizzard. Also, the mage will automatically cancel channeling their blizzard if there is less than 2 enemies around. This is huge, since the mage would often stand there and finish their entire channel during a boss fight after the adds died.
2. Organized actions, triggers, and the aiobjectcontext
3. Enabled Deep Freeze to be casted on bosses regardless of their immune status. Big benefit for frost dps on boss fights.
4. Slight tweaks in the conf so Arcane gets Arcane Barrage and Frostfire gets 2/2 Firestarter
5. Streamlined Arcane DPS to use Missile Barrage proc when at 4 stacks of Arcane Blast
5. Streamlined Fire/Frostfire DPS to keep Improved Scorch active (5% spell crit) unless there is a debuff of equal type
6. Added "firestarter" strategy, that utilizes the Fire talent Firestarter better. The mage will multi-dot Living Bomb while running towards melee, and cast Dragon's Breath -> instant cast Flamestrike -> Blast Wave -> instant cast Flamestrike -> Blizzard for bonkers damage. Disabled by default - not everyone wants their mages running into melee. Enable by typing "co +firestarter" on fire and frostfire mages.
7. Streamlined Frost DPS by finally adding support for Cold Snap for mages. It will proc when both Icy Veins and Deep Freeze are on cooldown. There is an exception to this - if the mage is level 30-59, it will not check for Deep Freeze - only Icy Veins.
8. Added Conjure Mana Gem support in the generic non-combat strategy and Use Mana Gem support in the generic combat strategy. This might be the biggest benefit of the overhaul - the gem has a 90 second cooldown, not shared with mana potions. It really prevents the mage from gassing out in longer fights. And the mana gem has 3 charges!
9. Added Mana Shield ability, which triggers on low health.
10. Changed Mirror Image from a boost ability to an anti-threat tool. Not many people know this, but it's best use in PvE is it's anti-threat modifier: "Mod Total Threat - Temporary Value: -90000000". It also doesn't do good damage, and is essentially used best as a pre-pull spell. But until the mages know how to react to a pull-timer, it's going to be used to reduce threat.

Let me know what y'all think!
2025-07-26 01:49:49 -07:00
ThePenguinMan96
c04477b54d Warlock Pet Re-Summon on Strategy Change
Hello everyone,

This PR addresses issue #1464 - Now, when you change a strategy, the new pet will be summoned accordingly - as long as the warlock knows the pet. I tested this myself with level 80 warlocks, then moved to lower level warlocks. It was nice to see that when my affliction warlock went from level 29 to 30, it automatically summoned the felhunter (while having the succubus out prior).

List of changes per file:
src\AiFactory.cpp: Set default level 1 spec to demonology for altbots - it was affliction prior. This will allow them to use Curse of agony, corruption, and immolate between levels 1-10.

src\strategy\warlock\GenericWarlockNonCombatStrategy.cpp: Added the "wrong pet" trigger to each of the 5 pet strategies, coupled with the appropriate summon action.

src\strategy\warlock\WarlockAiObjectContext.cpp: Registered the "wrong pet" trigger and moved the unstable affliction static trigger all to 1 line, cleaning it up a bit.

src\strategy\warlock\WarlockTriggers.cpp: Added the logic for the wrong pet trigger here. I tried to leave detailed comments as much as possible explaining it's thought process.

src\strategy\warlock\WarlockTriggers.h: Added the WrongPetTrigger.
2025-07-25 19:00:37 -07:00
kadeshar
b65646170c Merge pull request #1463 from ThePenguinMan96/Warlock-Soul-Shard/Soulstone-ID-conversion
Warlock Soul Shard/Soulstone ID conversion
2025-07-26 00:53:23 +02:00
Wishmaster117
55a37c48eb Add /w botname "glyphs" and "glyph equip" commands 2025-07-26 00:15:46 +02:00
ThePenguinMan96
a33bb3b51e Changes requested
Updating based on changes requested for commit
2025-07-25 14:02:20 -07:00
Yunfan Li
feda619066 Engine optimization for better performance and mem usage (#1462)
* Optimize loot

* World channel talk

* General improvement

* Engine rebuild for performance and memory usage

* Fix crash with AutoDoQuest = 0
2025-07-25 12:11:03 +02:00
crow
564bb198fb Merge remote-tracking branch 'upstream/master' into worldbuff 2025-07-24 00:48:57 -05:00
ThePenguinMan96
5ac6e8751c Bagspace checks
This commit is adding checks to the createsoulshard and createsoulstone actions, to check and see if there is enough bagspace to create an item.
2025-07-23 12:45:02 -07:00
ThePenguinMan96
7d7edbd961 Warlock Soul Shard/Soulstone ID conversion
Hello everyone,

I'm still working on fixing issue #1439 , and I have a theory - it could be because the clients that are having the issue are not english, and the code currently checks for strings "soul shard" and "soulstone". This PR converts the check over to the item IDs, so it should work regardless of what client is being used. I tested it myself with the english client and there is functionally no difference than before - but I hope it solves the issue #1439 for the non-english clients and community.
2025-07-23 11:26:55 -07:00
Yunfan Li
4a00c954ed RPG update travel flight status (#1445) 2025-07-23 23:37:41 +08:00
kadeshar
e150b8281b - Replaced custom protection of soul shard by trigger check interval (#1453) 2025-07-23 00:15:22 +02:00
crow
0b03b277c2 Make world buff strategy conditions configurable 2025-07-20 21:37:54 -05:00
ThePenguinMan96
d6b7693b8b Warlock Soulstone/Soulshard hotfix (#1452)
Hello everybody,

This PR is to address issues #1439 and #1451.

I added a 1 second cooldown to the createsoulshard action, as the warlock wouldn't ever use more than 1 soul shard per second.

I also added a cooldown check to the soulstone trigger, so it doesn't simply try to use the ss healer, ss self, ss tank, or ss master if the soulstone is present in the inventory.  I checked the logs, and was able to recreate the issue of #1451 - the bot was shifting targets over and over again, and that was because previously the trigger was just checking to see if a soulstone was present at all. Now it won't fire if it's on cooldown.
2025-07-19 18:25:56 +02:00
ThePenguinMan96
551c698e37 Hunter Pet Chat Command (#1448)
Hello everyone,

I was working with hunter bots and I feel like I didn't have enough control over their pets. So I started working on a chat command for the hunter pets, and it's been a blast. Below is a description of what the commands do:

pet name <name>
Summons a tameable pet by its creature name (case-insensitive).
The bot checks if the pet is exotic and requires the Beast Mastery talent if so.
If successful, the bot announces the pet's new name and creature ID.
If not found or not tameable, an error message is given.

pet id <id>
Summons a tameable pet by its database creature ID.
The same exotic pet checks apply.
Success is announced with the pet's name and ID.
Errors are given for invalid IDs or untameable pets.

pet family <family>
Randomly selects and summons a tameable pet from the specified family (e.g., "cat", "wolf").
Only families with tameable pets are considered.
Exotic pet checks and talent requirements apply.
Success is announced with the pet's name and ID.
Errors are given if no suitable pet is found.

pet rename <new name>
Renames the hunter's current summoned pet to a new name.
The name must be 1-12 alphabetic characters (A-Z, a-z) and is automatically formatted (first letter capitalized, rest lowercased).
Forbidden or reserved names are disallowed.
After renaming, the bot sets the new name, saves the pet, and updates the client.
The bot dismisses and attempts to recall the pet using "Call Pet" (if the hunter knows it) to update the UI.
If the new name isn't visible immediately, the bot instructs the player to dismiss and recall the pet manually.
Confirmation and guidance messages are provided.

Additional Details:
All commands display errors if requirements are not met (wrong name/id/family, forbidden name, lack of Beast Mastery, or hunter below level 10).
After changing or summoning a pet (except renaming), the bot initializes the pet and its talents, then announces the result.

TLDR:
pet name <name>	Summon a tameable pet by name
pet id <id>	Summon a tameable pet by database creature ID
pet family <family>	Randomly summon a tameable pet of the given family
pet rename <new name>	Rename the current pet and refresh its name in the client UI

Description of files changed:

src\strategy\actions\ChatActionContext.h: Added the "pet" action for the whisper command.

src\strategy\actions\PetAction.cpp: New chat actions for all things related to hunter pets.

src\strategy\actions\PetAction.h: New header for the PetAction.cpp.

src\strategy\generic\ChatCommandHandlerStrategy.cpp: Linked the trigger and action in the chatcommandhandlerstrategy, for the bot to take action when whispered "pet" (trigger).

src\strategy\triggers\ChatTriggerContext.h: Added the "pet" trigger.
2025-07-17 13:51:06 +02:00
ThePenguinMan96
45694ad6e6 Warlock Soul Shard Hotfix (#1442)
Hello everyone,

This PR is to address an issue that was posted recently - a player has shown that soul shards are being created in excess, spamming the player's chat log. I am adding an isuseful() check to the createsoulshard action, so it will never be executed if they have more than 5 soul shards.

Also, out of an abundance of caution, I am lowering the cap for CastDrainSoulAction::isUseful() to 20 from 32. That way, if for some reason the warlock has 20+ shards, it won't attempt to collect any more / use drain soul.
2025-07-15 15:54:04 +02:00
kadeshar
761ef634da - Fixed loot trigger to respect stay strategy (#1437) 2025-07-14 19:27:16 +02:00
ThePenguinMan96
96cc0daea6 Hunter/Warlock AoE Fix (#1440)
Hello everyone,

This fixes these two classes so they respond to the command "co -aoe" and "co +aoe". This also fixes the survival hunter so that trap weave is not a default strategy - they will not walk into melee anymore.
2025-07-14 10:15:11 +02:00
kadeshar
5202ac8db3 Merge pull request #1435 from ThePenguinMan96/Hunter-Overhaul
Hunter Overhaul
2025-07-13 21:56:51 +02:00
kadeshar
fa79fff4f4 Merge pull request #1436 from ThePenguinMan96/Firestone-Error-Fix/Excess-Soul-Shard-Fix
Firestone Error Fix/Excess Soul Shard Fix
2025-07-13 21:56:36 +02:00
ThePenguinMan96
6d07d6febe Firestone Error Fix/Excess Soul Shard Fix
Hello everyone,

This PR addresses two errors that players have been getting with the new warlock changes:

Firestone - Fel Firestone, which is rank 6 of create firestone, learned at level 74, is creating an error in the worldserver that is quite annoying. That is because the database has an incorrect enchant effect of a chance on hit for that rank. This PR changes the CreateFirestoneAction to skip that spell rank entirely, thus never having that error. Note: You might need to wait a little while after the new change for the errors to go away - that is because there still be pre-existing fel firestones  on the warlocks, as well as enchanted on their weapons. Once those disappear, the error will not be there anymore.

Excess soul shards - There is an error that currently exists where if a Warlock uses Drain Soul while they have 32 soul shards, it will spam in the chat log "I can't carry anymore of those". This PR will automatically delete soul shards if they have 6 or more. This PR also will reduce the number of soul shards the warlock receives from maintenance to 5. I figured 5 is a good maximum so their inventory doesn't get clogged with 32 shards. Between "DestroySoulShard" and "CreateSoulShard" actions, they will always have between 1-5 soul shards.
2025-07-12 11:45:33 -07:00
ThePenguinMan96
8ca4ab1344 Hunter Overhaul
Hello everybody,

I saw that the hunter class had one strategy for all 3 specs, and decided to create specialized strategies for each one. Here is a list of the changes:

conf\playerbots.conf.dist: Redid the talent trees and glyphs slightly, for more consistent dps

src\AiFactory.cpp: Changed the default strategies assigned for each spec.

src\strategy\hunter\BeastMasteryHunterStrategy.cpp and src\strategy\hunter\BeastMasteryHunterStrategy.h: Strategy for BM hunters. Includes all of the original logic from DpsHunterStrategy, with the addition of kill command, bestial wrath, and intimidation.

src\strategy\hunter\DpsHunterStrategy.cpp and src\strategy\hunter\DpsHunterStrategy.h: Old Dps strategy used for all 3 specs - removed.

src\strategy\hunter\GenericHunterStrategy.cpp and src\strategy\hunter\GenericHunterStrategy.h: Tidied up code, added Dragonhawk passthrough to hawk, moved rapid fire to inittriggers for generichunterstrategy and increased its priority (it is still a boost trigger, so it will function the same).

src\strategy\hunter\HunterActions.cpp:
Added isuseful check for aspect of the hawk, to ensure it is never cast if the bot knows aspect of the dragonhawk.
Added isuseful check for arcane shot to never use it after explosive shot is learned (so the hunter can use it as it levels survival, but drops it after that). Also added a check for arcane shot, so it won't use it above 435 armor pen rating (steady shot is superior after this arp).
Added isuseful check for immolation trap so it won't use it after explosive shot is learned.

src\strategy\hunter\HunterActions.h:
General cleanup/alignment of actions based on type.
Added TTL checks to all DoTs and debuffs - hunters weren't using hunter's mark, serpent sting, black arrow, explosive shot on enemies that it thought would die too soon.
Black Arrow - added a strategy check here as well so the bot won't use it at all if trap weave is enabled. There was already a check in the trigger, but it was still getting cast, so I added this check.
Explosive Shot Rank 4, 3, 2, 1 actions- Added these so the hunter can downrank explosive shot dynamically based on the level when lock and load procs. So if the hunter is level 70, and a lock and load proc happens, it will fire rank 2 - rank 1 - rank 2, similar to how the level 80 version will fire 4-3-4.

src\strategy\hunter\HunterAiObjectContext.cpp:
Added strategy support for bm, mm, and surv (and their aoes)
Added trigger support for kill command, explosive shot, lock and load, silencing shot (as an spellcasting interrupt), and intimidation
Added action support for the 4 ranks of explosive shot and intimidation.

src\strategy\hunter\HunterTriggers.cpp: Kill command was completely non-functional because there was no buff trigger associated with it. Adding this will correct that, and the hunter will use kill command.

src\strategy\hunter\HunterTriggers.h: Added Kill command, silencing shot, intimidation, lock and load, and explosive shot triggers.

src\strategy\hunter\MarksmanshipHunterStrategy.cpp and src\strategy\hunter\MarksmanshipHunterStrategy.h: Strategy for MM hunters. Includes all of the original logic from DpsHunterStrategy, with the addition of kill command, silencing shot, chimera shot, and aimed shot.

src\strategy\hunter\SurvivalHunterStrategy.cpp and src\strategy\hunter\SurvivalHunterStrategy.h: Strategy for Survival hunters. Includes all of the original logic from DpsHunterStrategy, with the addition of kill command, explosive shot, black arrow, aimed shot, lock and load triggers + downranking explosive shot.
2025-07-11 17:10:03 -07:00
kadeshar
bc5d602326 Merge pull request #1430 from ThePenguinMan96/Warlock-Curse/Spellstone-and-Firestone-Strategies,-Soul-Shard-Replenish
Warlock Curse strategies, Spellstone and Firestone strategies, Soul Shard Replenish
2025-07-08 18:57:50 +02:00
Boxhead78
3611cfbdd3 Minor Battleground tactics improvements (#1431)
* Fix bots ignoring contested Nodes in Arathi Basin

* Use VMAP_INVALID_HEIGHT_VALUE instead of -200000.0f for valid height check
2025-07-08 18:31:08 +02:00
ThePenguinMan96
0400c1c8b3 Modified code to allow the build to complete on mac
Modified code to allow the build to complete on mac
2025-07-07 19:08:01 -07:00
ThePenguinMan96
393a65a15b Warlock Curse/Spellstone and Firestone Strategies, Soul Shard Replenish
This PR aims to achieve 4 main things:

1. Manual Curse Strategies - Each curse has it's own toggleable combat strategy, with curse of agony being the default for affliction and demonology, and curse of the elements being default for destro. The other curses that are available are curse of exhaustion (if specced for it), curse of doom, curse of weakness, and curse of tongues (6 total). You can add these by typing "co +curse of weakness", or similar. Note: Curses are part of the WarlockCurseStrategyFactoryInternal(), so there can only be one active.

2. Firestone/Spellstone Non-Combat Strategies - Players requested to me that they can decide if they want to use a spellstone or firestone for their weapon enchant, so I added them as non-combat strategies. Spellstone is the default for affliction and demonology, firestone is the default for destro. To add these, type "nc +firestone" or similar.

3. Soul Shard Replenishment - I noticed after hours of running a server (15+ hours) that altbots and rndbots would only cast imp and not use their soul shards. This is because they were actually running out of soul shards, without the ability to maintain themselves accordingly. I added a trigger (no soul shard) and action (create soul shard) that triggers if they are out of soul shards, creating only 1 soul shard (to not clog up the inventory). This way, you should never have a warlock using the wrong pet, or failing to cast shadowburn, failing to create soulstone/spellstone/firestone/healthstone, or failing to cast soul shatter.

4. Tidying up the code -

I removed the built-in curse code from the DPS strategies, and migrated it to the manual curse strategies.

I clumped the curse triggers and actions together in the associated files.

I added logic for Curse of Weakness to check for conflicting debuffs.

I moved the summoning strategies and curse strategies to their own strategy factories - WarlockPetStrategyFactoryInternal, and WarlockCurseStrategyFactoryInternal. This way they can only have one curse and one pet strategy active at once. I also renamed the "NonCombatBuffStrategyFactoryInternal" to "WarlockSoulstoneStrategyFactoryInternal", which was more accurate.

I changed a single talent point in the Affliction Warlock PVE spec, taking one from destructive reach and adding it into nightfall for those sweet, sweet free shadowbolts.

I added "ss self" as the default non-combat soulstone strategy, as before, they didn't have one assigned. The player can still of course remove that NC strategy and apply another, such as "ss master", "ss tank", or "ss healer".
2025-07-07 18:51:33 -07:00
kadeshar
8b9bcce3bc Merge pull request #1418 from Boxhead78/bg-tactics-rewrite
Bots Bg Tactics rewrite (WSG, AB, AV, EYE)
2025-07-07 21:45:35 +02:00
Yunfan Li
8ed053ca01 fix warnings (#1428) 2025-07-07 19:43:24 +02:00
Yunfan Li
51ed9c4649 fix trinket (#1429) 2025-07-07 19:41:47 +02:00
kadeshar
86390f90fd - Fixed bug with not respecting InstantFlightPaths config (#1410) 2025-07-06 19:38:15 +08:00
kadeshar
3f39a57fe2 Merge pull request #1426 from NoxMax/delete-all-the-orphans
Fix: DeleteRandomBotAccounts sometimes leaves orphans. Also fix accumulating orphan pet data in DB
2025-07-06 09:51:31 +02:00
kadeshar
102aa24bb8 Merge pull request #1425 from liyunfan1223/rpg_gear_incremental
Added config to make rndbots only get gear from looting/quests
2025-07-06 09:50:48 +02:00
kadeshar
50792e5646 Merge pull request #1422 from ThePenguinMan96/Warlock-Ranged-Designation/DPS-Strat-cleanup
Warlock Ranged Designation/DPS Strategy Cleanup
2025-07-06 09:50:24 +02:00
NoxMax
78f4bd6d29 Ensuring data orphans are deleted 2025-07-05 18:59:30 -06:00
Yunfan Li
b558e86df0 correct typo in conf 2025-07-05 22:03:31 +08:00
Yunfan Li
f0c6aaff0b Random bots gear related enhancements 2025-07-05 20:29:34 +08:00
ThePenguinMan96
1a20d549fe Eureka!
I re-implemented the pet strategies into the "general" strategy area of the WarlockAiObjectContext, and it worked!!! Finally! The issue before was they were under the "Buff" area of the aiobjectcontext, which can only have 1 active at any given time - this is why the soulstone strategy and the pet strategy were cancelling out each other. Now, pets are summoned via a non-combat strategy that is assigned with aifactory by spec!
2025-07-04 22:44:17 -07:00
kadeshar
64df7439d8 Merge pull request #1424 from Raz0r1337/master
Update playerbots.conf.dist
2025-07-04 22:06:13 +02:00
kadeshar
05a3c318d6 Merge pull request #1420 from Wishmaster117/Correction-Code
Revert GetEquipGearScore to Blizzard’s fixed‐slot average item level formula
2025-07-04 22:05:38 +02:00
ThePenguinMan96
1c69490290 Added range checks for running away if too close
Added range checks for running away if too close - The warlocks were happily standing in cleave range and dieing. Now, they will backpedal if too close to an enemy. I also added custom logic to check if in demonology form before backpedaling. I also removed shadow cleave, as the dps increase with negligible (0.1%) and it was causing errors in the actions, resulting in the warlock standing idle in combat.
2025-07-04 11:33:46 -07:00
Alex Dcnh
8fd188ff3b Update PlayerbotAI.cpp 2025-07-04 19:19:29 +02:00
St0ny
7fa6e5833a Update playerbots.conf.dist
Added more GameObject ID's for Bots to ignore
2025-07-04 17:49:05 +02:00
kadeshar
305f769a84 - Added chat command to wipe group (#1408) 2025-07-04 23:14:07 +08:00
kadeshar
c0aa55416b Added configuration on excluded prefixes for trade action (#1401)
* - Added configuration on excluded prefixes for trade action

* - LoadListString usage reference specified

* - Code refactoring
2025-07-04 23:13:19 +08:00
ThePenguinMan96
59af34809c Warlock Ranged Designation/DPS Strategy Cleanup
Hello community,

This PR focuses on 4 things:

Recognizing the Warlock as a "ranged" bot, so they will follow ranged commands and strategies, in GenericWarlockStrategy.h:
uint32 GetType() const override { return CombatStrategy::GetType() | STRATEGY_TYPE_RANGED | STRATEGY_TYPE_DPS; }

Cleanup/deletion of the DpsWarlockStrategy.cpp and .h (no longer used or referenced anywhere)

Fixes soulstone logic so multiple Warlocks don't soulstone the same target, and don't try to soulstone a target that is too far away or out of line of sight (WarlockActions.cpp)

Moved summoning of pets to the main non-combat strategy inittriggers:

// Pet-summoning triggers based on spec
    if (tab == 0)  // Affliction
    {
        triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon felhunter", 29.0f), nullptr)));
    }
    else if (tab == 1)  // Demonology
    {
        triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon felguard", 29.0f), nullptr)));
    }
    else if (tab == 2)  // Destruction
    {
        triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon imp", 29.0f), nullptr)));
    }
2025-07-04 03:32:29 -07:00
Alex Dcnh
326783ed4f PlayerbotAI – Fix GetEquipGearScore to mirror Blizzard’s average-ilvl rules 2025-07-04 10:23:37 +02:00
Alex Dcnh
9503d32d46 Merge branch 'liyunfan1223:master' into Correction-Code 2025-07-03 22:27:54 +02:00
Alex Dcnh
edc9241fa3 Update PlayerbotAI.cpp 2025-07-03 22:21:54 +02:00
kadeshar
40535f6e55 Merge pull request #1419 from Raz0r1337/master
Added GameObject IDs that bots ignore
2025-07-03 19:48:41 +02:00
St0ny
9a980c171e Update playerbots.conf.dist
Added some GameObject IDs to stop Bots from stealing it.
2025-07-03 17:59:33 +02:00
Boxhead78
ce91c335b3 Fix missing BotStrategy enums 2025-07-03 12:30:09 +02:00
Boxhead78
605fa223ce Move GetBotStrategyForTeam from core 2025-07-03 09:39:54 +02:00
Boxhead78
309d177dd8 Battleground Rewrite
- Refactored BattleGroundTactics.cpp
- Bots choose strategies to determine if they are more aggressive or defensive in objectives
- Largely improved bots tactics in WSG, AB, AV and EY
- Improved how bots chase flag carriers
- Fixed some bots stuck in action loops - especially in WSG and AV
- Fixed several other Bugs
2025-07-03 08:25:55 +02:00
kadeshar
36fd5b8f15 Merge pull request #1411 from ThePenguinMan96/PVP-Talents-&-InitGlyph-expansion
PVP Talents and InitGlyph changes, "maintenance" command changes, "talents spec" changes
2025-07-02 21:56:00 +02:00
bash
720c063716 Update PlayerbotMgr.cpp (#1414) 2025-07-02 21:54:52 +02:00
Alex Dcnh
57a2c9a742 Update PlayerbotMgr.cpp (#1412)
Remove double #include "ChannelMngr.h"
2025-07-02 20:27:04 +02:00
ThePenguinMan96
3f7814abb4 PVP Talents and InitGlyph changes
PVP Talents and InitGlyph changes

This PR adds 3 pvp specs for each class, as well as their glyphs. It also adds exceptions to the Initglyph function, based on pvp-based talents for each class.

conf\playerbots.conf.dist - Adds 3 pvp specs/glyphs for each class.

src\factory\PlayerbotFactory.cpp - InitGlyph in its current form is unable to correctly assign glyphs on specindexes (or tab) over 2 without an exception. That is why this exception already exists in the code:

if (bot->getClass() == CLASS_DRUID && tab == DRUID_TAB_FERAL && bot->GetLevel() >= 20 && !bot->HasAura(16931))
        tab = 3;

This checks if the class is a Druid, if the tab is feral, if they are equal to or above level 20, and they don't have the Thick Hide talent. If all of these are true, then it manually sets the tab = 3. I first discovered this when I noticed that my frostfire mage would never be assigned the correct glyphs in the config - aka glyph of frosfire. It is because the frostfire spec is tab = 3, and no such logic exists. When I started adding the additional pvp specs, I noticed that they never would assign the correct glyphs. I had to add an exception to all pvp specs, and have them check for certain pvp related talents to correlate the tab manually. This is because tab is derived from the AiFactory::GetPlayerSpecTab(bot); function. The only possible tab values from this function are 0, 1, and 2.
**TLDR: Added code to support Frostfire Mage, dual-aura Blood DK, and all the PvP specs for correct glyph assignment.**

src\strategy\actions\ChangeTalentsAction.cpp: When you pick a spec with "talents spec" function, such as "talents spec arms pve", it will now correctly assign glyphs without the player having to execute the maintenance command. Setting the InitGlyphs to false removes prior glyphs.

src\strategy\actions\TrainerAction.cpp - Changed factory.InitGlyphs(true); to factory.InitGlyphs(false);. This makes it so all prior glyphs that were assigned are correctly deleted. I first noticed this when switching between specs and using the maintenance command - I had to login to the bot and manually delete the old glyphs, in order for the maintenance command to assign the new, correct glyphs.
2025-07-01 14:35:16 -07:00
brighton-chi
3dd0f11453 Corrections to Vanilla max iLevels (#1409)
* Corrections to Vanilla max iLevels

This reverts commit 681ec5c5b583dc935d60172f1790c6635eaef9ab, reversing
changes made to f4e6b1644ff2c3fa4b5f97a5b4d7cea14e352263.

* Corrections to Vanilla max iLevels
2025-07-01 18:39:12 +02:00
ThePenguinMan96
00cc2468f1 Warlock overhaul (#1397)
This is a complete overhaul of the warlock class, making 3 new strategies (affliction, demonology, and destruction), as well as finishing the warlock tank strategy (shadow ward and searing pain). It also includes a soulstone fix, where the bots can change who they soulstone based on the non-combat strategies you set for them. It also includes a self-resurrect action and trigger that allows the bots to resurrect using a soulstone or reincarnation. Many other skills were added to finish out the warlock skillset.
2025-06-27 20:00:38 +02:00
Jelly
cf8253579e Update Pvp Prohibited Zone Id's (#1390) 2025-06-24 20:00:34 +02:00
Jelly
2000c06167 Fix various hunter bugs (Amended w/ Nullptr check) (#1389)
* Fixes #1093 (Hunter's stuck in Autoshoot and Can't equip gear)

* Fixes #1093 (Hunter's stuck in Autoshoot and Can't equip gear)
2025-06-22 12:55:35 +08:00
Veit F.
453925153f [URGENT Fix] Trinket proc effects are getting cast even if they are not onUse - Leads to big server lags (#1385)
* fix: 🚑 Add spellProcFlag check for flag 2 at UseTrinket Context-Action

Bots will "learn" the trinket proc, so CanCastSpell() will be true e.g. on Item https://www.wowhead.com/wotlk/item=44074/oracle-talisman-of-ablution leading to constant casting of the proc spell onto themselfes https://www.wowhead.com/wotlk/spell=59787/oracle-ablutions. This will lead to multiple hundreds of entries in m_appliedAuras -> Once killing an enemy -> Big diff time spikes. See diagnosis

* perf:  Should futher reduce the problems, hindering trinkets with other proc flags of being used, see https://www.azerothcore.org/wiki/spell_proc_event

I have tested bots with active trinkets and they are still using them, as well as onhit trinkets are still being triggered like they should. Could also fix some other weird behavior.
2025-06-22 00:08:47 +02:00
Jelly
9a10f07c74 Fixes #1093 (Hunter's stuck in Auto shot pose and Can't equip gear) (#1377)
* Fixes #1093 (Hunter's stuck in Autoshoot and Can't equip gear)

* Fixes #1093 (Hunter's stuck in Autoshoot and Can't equip gear)
2025-06-21 16:09:19 +08:00
Noscopezz
7d6f44ab09 ICC Fix/Improve (#1380)
* ICC PP WIP

WIP

* added mutated plague for PP

* BPC added (kinetic and boss targeting need to be done by player)

OT collects dark nucles, bots spread on vortex and other stuff they do ok on their own.
Tested only on 10NM, should work on 25NM

* Tank pos. correction

* BQL, ranged spread, link, flame, bite, tanking tested 10NM

to do (better fire spread, hc tacti, melee spread when in air)

* LDW improved

improved shadow logic, ranged spread for easier shadow handling

* dbs update, fixed teleporting

Bots should only go and teleport to the mage that is actually below zero now

* DBS ranged fix

Ranged should spread more quickly and freak out less

* Festergut && DBS

fixed ranged spread (both)
fixed spore logic (fester)

* Rotface fix

Improved big ooze tanking (static pos, todo kiting)
ooze explosion spread mechanic fix
ooze pool fix
Player needs to mark rotface with skull icon, oterwise bots try to attack oozes

* BQL fixed for 25nm

todo: better melee logic in air phase, better melee flame spread

* VDW, Sister Svalna, Sindy update

Sister Svalna, bots can pickup spears and throw at svalna when she has shield up

VDW added healer strats to use portal and heal boss (atm druids are for raid healing only, so use druide + any other healer, ideally player should be healer)
todo (focus on supressers, add healer rotations, atm they use quickest spell they can)

Sindragosa
Added tank boss manipulation (boss orientation and position)
bots detect (buffet, unchained magic and chilled to the bone and act accordingly)
bots detect frost beacon move to safe spot and los frost bombs around them, while dpsing tombs (todo stop dps if only one tomb is left, if we have frost bombs around, not a big deal atm since in nm they dont one shot)
Last phase bots los behind tomb to loose buffet, tanks swap when they have hi buffet count.
Player should tell bots with skull rti if they should kill tomb or focus boss.
todo (dynamic tomb los in last phase so that healers can see tank but also hide behind tomb to break los from boss)

Removed some debug messages, improved LM spike action (nearest bots also try to help kill it)

Improved Lady Deathwshiper shade action (only targeted bots will run away instead of every bot that is near it)

dbs improved tank switch

I recommend to use 3 healers (just to be safe) and 2 paladin tanks (warr seems to struggle with agro) in 10 man
25 man 6-7 healers (just to be safe) Since most of the bosses are about survival and not dps

* LK Update (doable)

LK added

Improved tank switching for all bosses

Fixed PP gas cloud kiting
Malleable goo todo (dont know how to detect it since its not object or npc) just summon ranged bots to safe position to bypass

BPC  fixed OT sometimes not tanking kele
kinectic bombs todo (for now player should take care of them)

Sindragosa fixed rare case when she is in air phase but tombs went to last phase position

LK
Bots can handle necrotic
Bots can handle adds
Bots should focus valkyre that actually grabbed someone (if unlucky and player just use attack command and summon bots to you if they are far away from you) if they grab bots you can either summon to make them useless or let bots cc them and do it legit way.

Defile should be watched by player and once it was cast just summon bots to you
Vile spirits for some reason go to the ground and get nuked by bots aoe spells so there is not much to be done

**Player needs to be alive the whole LK fight since you will have to watch out for frost spehers (sometimes bots ignore them), summon bots when defile is up and summon ranged bots if they get stuck near shambling or raging spirits since their aoe will wipe you)

all in all LK  is doable both 10 and 25nm, player needs to have knowledge of lk fight and needs to know how to use multibot addon and make macros for eg summoning or commanding groups of bots or individual bots)

Dont forget frost/shadow/nature resist auras in whole ICC since it will help alot

I have done whole icc 10 and 25 with 2 pala tanks, 2/5 heals and rest dps,  if you use +1 or +2 heals it should be easier (since I was testing I did close to 0 dmg in fights same with heals)

* fixed changes made by mistake

fix

* Malleable fix (simple spread mechanic)

Malleable mechanic added (simple spread for now)
Gas cloud fixed (Bots sometimes got stuck between puddle and kite location)

* Defile Update

Bots detect and avoid defile (they struggle to find a way back to the boss around it tho, use summon to help them)

Melee bots should be able to stand behind/to the flank of shambling/spirits

* GS fixed bots not returning to their ship for A and H

Bots will return back to ship after killing mage

* PP gas cloud kiting improved

PP gas cloud kiting improved

* BPC targeting fixed

Bots will mark valid prince with skull RTI now

* BQL added melee spread in air pahse

BQL added melee spread

* VDW healing rotation improved

Healers will now use strong heals and hots

* Fixed Necrotic Plague

Fixed issue with Necrotic where it would get dispelled too soon, or would not get dispelled at all

* LK Update

Refined defile logic
Added 3 points for ranged and melee in winter phase (east, west, south when facing throne)
fixed frost spheres targeting (hunter will focus them)
Atm bots will reset z axis if they fall underground or if they get teleported by lk
Better positioning in 1st phase

* 10HC update until PP

LK defile improved for 10nm (bots sometimes stood 2 close to defile until it grew few times)

Improved rotface for HC

PP remade for 10HC.
Gas cloud is now properly kited
Fixed a rare case of server crash when there were ooze and gas cloud alive at same time.
Bots will move around puddles according to its size now.
Bots that get unboud plague will simply move away from raid and die thus loosing it from raid.
Volatile ooze improved stacking.
Fixed ranged sometimes glitching thru walls when spreading out from other members.

10HC PP is now doable but its hard without summoning (summoning break gascloud and ooze targets so its easier to do). You need to watch boss so you dont phase 2 soon otherwise you will get 2x ooze and cloud which is almoust always a wipe. If abo is not played near perfection bots will struggle with oozes and gas clouds if they are not slowed on time. Always save energy to slow gas cloud since it will wipe the group if it reach its target.
Bots will sometimes stand in puddle, just command them to move and they will figure out what to do.

todo (proper malleable handling)

* Up until Sindy 10HC

BPC added shadow prison handling, bots stop moving if more than 12 stack, tanks more than 18
Improved spreading logic

VDW
fixed issue where bots in portal wold move at half speed compared to real player

* fixed accidental change

* LK 10HC update

Added Shadow trap logic
(if they stand in it, not a big deal since bots wont get yeeted only players will)
When harvest soul, only player will be in another dimension (you must survive)

**Sindy and LK can be done, but I must dissapoint purist, at my skill level I could not achieve to do HC without using summon or other "cheat" bot functions.

Other bosses are all doable now in 10hc

* ICC fixes GS, PP; BQL, SINDY

Minor fixes, bite action improved

* ICC improve Sindy

Bots will now choose non beacon position based on difficulty, 10/25

* ICC fixed missing A/H buff

Fixed missing ICC buff for A and H

Buff will only be present when logged on and in ICC, once any bot or player leave ICC the buff is gone to prevent abuse.

This will make ICC easier now and with recent DPS update and movement improvement bots will now actully do decent dps and even greater healing.

Ally buff
https://www.wowhead.com/wotlk/spell=73828/strength-of-wrynn

Horde buff
https://www.wowhead.com/wotlk/spell=73822/hellscreams-warsong

* revert last change

revert buff

* ICC improve Rotface

Bots will now mark Rotface with skull icon, which will make them focus boss instead of oozes automatically

* ICC Festergut 10 Man fix

There was a rare case in 10 Man when 2 tanks would get spores which made them both stack at melee spot. Now the code will check if any of the spored player is main tank, if it is, it will stay at melee and other spores will go at ranged spot since off tank doesn't really need to stand near main tank all the time.

* ICC BPC major update, fix and improve

Fixed main tank sometimes not tanking both bosses (vala and talda)

Improved marking of current prince

Empowered vortex: bots will now spread out when it is being cast, instead of always spreading(ranged). This will make melee also spread better now since bots will calculate and move to optimal positions.

Added Kinetic bomb handling.
Hunters will take care of bombs, if no hunter is present then any ranged dps will take care of kinetic bombs.

* ICC BQL/VDW major update + minor fixes/improvements

LDW improved spreading for ranged, tanks will move boss in the middle of the room now when shield is depleted which will make bots bug less around pillars

GS Assist tank will not teleport to enemy ship and will keep tanking adds on our ship now

DBS ranged spread improved (they will calcualte spot for each bot and stick with it reducing movment to minimum), fixed bug where in 25 man ranged bots would go behind walls, making them unable to dps boss, improved rune of blood tanking

Festergut ranged spread improved (they will calcualte spot for each bot and stick with it reducing movment to minimum)

BQL Melee spread improved, bite logic improved, added swarming shadows logic (not perfect but at least it wont be all over the room anymore), Tanks will properly handle blood mirror now

VDW boss and raid healers will be automatically assinged depening by number of healers(if more than 3 healers in group, 2 will focus on raid and others will heal boss, otherwise one healer will heal raid). Druid will be assigned to raid healing if no druid present then some other random heal class. Added rotations for druid healers if they end up healing the boss. Raid healers will not use portals anymore. Healers will come to the ground now after using portals (they were stuck in air)

* ICC LK minor update

PP

Removed pre stacking for ranged when volatile ooze spawn (they will kill it much faster now and only stack if someone actually gets targeted by ooze)

LK

Hunters will use trnaq shot to remove enrage from shamblings

Improved valkyr cc

Minimized ping-ponging during winter phase

* ICC minor Sindy improve

Reduced position tolerances and forced movement for beacon and non beacon positions which will make bots move to spot that they actually need to be at instead of randomly running to sindragosa or beaconed players.

* ICC minor update

GS

Bots will mark mage with skull rti

Rotface

Fixed bots glitching thru walls and floors (added check if position is valid before moving)

PP

Bots will mark volatile ooze with skull rti now which will help them focus it and kill asap (usefull for heroic when both volatile ooze and gas cloud are present at the same time)

VDW

Added default group position in the middle if the room so that bots don't spread out too much which will force them to focus supressers more

Fixed Boss healers not keeping themself alive when low on HP

* ICC LK minor update

Commented out z axis bypass since it was fixed with recent core updates.

Bots no longer fall thru buggy platforms so its no longer needed which in return makes valkyrs works as they should.

* ICC LM & LDW Improved

LM

Removed Attack Action since it was buggy and replaced it with RTI. (Improved DPS)

Simplified trigger for spike action, since attack logic is handled by skull RTI now.

Bots (tanks) will mark spikes with skull RTI, which will make bots instantly switch to Spike targets. If no spike is present, the boss will be marked.

Added logic that will make non-tank bots move away from LM to a fixed position behind LM if they happened to be in front of LM (No more insta kills with cleave).
If LM is casting Bone Storm, bots will ignore the above logic and do max dps.

LDW

Removed Attack Action since it was buggy and replaced it with RTI. (Improved DPS)

Bots (tanks) will mark adds with skull RTI, which will make bots instantly switch to adds targets. If no spike is present, the boss will be marked, which will help with keeping allies alive when they get mind controlled by LDW.

Moved 2nd phase position deeper into the room and reduced boss hp trigger to 95%, which will keep tanks where they should be during 1st phase (sometimes the boss dropped below 98% and tanks would ping pong around the room).

Ranged bots will now spread closer to each other now and will ignore spreading if they are 2 far or 2 close to the boss so they can properly reposition.

These changes should also fix recent bugs with bots not casting/standing still on LM and LDW.

* ICC DBS Improve

Replaced attack action with RTI. (Improved DPS)

Ranged bots will move away from blood beast if targeted by it now.

* ICC Sindragosa and LK Improve

Sindragosa

Improved tomb los for frost bombs. Bots will now mark tomb with moon rti to avoid killing it too soon. (If they still attack the tomb, simply reset and after bombs are over, simply attack the tomb to kill or mark with a skull.)
Main Tank will reset mystic buffet stacks now to avoid tank swapping since it's really unreliable which will make sindy less frustrating to kill (other bots will need to do mechanics properly by hiding behind ice tombs).

LK

Necrotic plague action

Bots will now check before moving to the Shambling if they are in front of the Shambling to avoid getting one shoted by shockwave.
Improved multiplier, which will now properly handle dispelling of necrotic plague.

Winter phase action

Bots will properly move behind adds to avoid getting one shoted by shamblings or raging spirits.
If there is a hunter in the grouphunter will focus on spheres; if not, any ranged DPS will focus spheres.

Adds action

Bots will now be in proper positions during the 1st phase, and when the off tank is done collecting shamblings, it will move away from the group.
Bots (non-tanks) will move behind the shambling if they are in front of it to avoid getting one shoted in the 1st phase.
Restored action for checking if bots are at ground level, since they were still sometimes glitching through the floor (valkrys will be able to carry bots and drop them off the edge if not killed in time).
Improved defile calculations for 10 man and 25 man raids since defile was growing differently for 10 and 25, which would make bots in 25 man move very far away from somewhat small defile puddle.
Bots will now properly CC any valkyr that wasn't CC'd, and they won't freak out anymore if multiple valkyrs are present. (They can't tell which valkyr is holding bots/ players, so we should command an attack on valkyrs that are holding someone.)
Bots will now run away from Vile Spirits if they are targeted by them during the last phase.

* ICC 25HC ldw shades fix

Fixed bots could not detect shade if there were multiple present

* ICC trigger optimization

Optimized trigger and action for:

KineticBomb
OozePuddle

* ICC LK Improve

Improved Shadow Trap, Winter phase and 1st phase.

Bots will now have fixed positions for 1st phase if no shadow trap is present.

Tanks will now take care of taunting adds during winter phase. (Assist tank will collect adds and bring them to main tank, main tank will take over the adds so we have minimal chance of adds rotating towards the raid)

Fixed shadow trap detection.

* ToC dungeon update

Added TOC dungeon strategies for 1st encounter.

Other encounters are pretty easy and straightforward so they do not require strategies. They can be all done with follow, reset, summon, attack, skull icon etc...

Bots (A/H) will automatically check if they have lance equiped or in inventory. If they don't have lance they will pick it up from nearest lance stand and equip it.

Bots will mount nearest mount if they have lance equipped.

Once mounted they will keep Defend aura at 3 stacks (top priority).
In fight they will cast charge if more than 5f away, they will cast shield break if target has Defended aura active and they will use Thrust if they cant use anything else.
In short they will melt any target and enemies wont stand a chance keeping their defend aura up.

Once champions are defeated bot will automatically equip best weapon they have in inventory (weapon that was swapped with lance)

* ICC minor update

LM

Bots will now move in smaller increments to get behind boss
They will still move towards fixed position, but as soon they are not in front of boss, they will stop moving and do other actions.

GS

Fixed cannon trigger, now will return true if cannon is 100f from bots.
It used to return true all the time.

* ToC added Eadric strat

bots will look away during radiance cast

* ICC LK minor fix

fixed crash in icclichkingsaddsaction

* ICC GS fix

While encountering edge case when playing horde, the logic was to detect mages, there are usually 2 around, but the longer the fight last the bigger the chance that cannons would kill all the mages thus once we kill the mage that was casting deep freeze bots could not execute action that would return them back to the ship, so the boss had the time to nuke them one by one.
Even tanks have hard time surviving the boss.
I have changed it now (both A/H) so that we keep track of the enemy boss (which is hard if not impossible to kill as main trigger) and we are keeping track of the  mage that is actually casting deep freeze, we mark it with skull, and now as soon the mage is dead, bots will remove the mark so there is 0 confusion about what to do next on their side, and they will come back asap.

* FoS Improved and fixed, ICC LM, LDW and PP updates

FoS

Improved/fixed triggers, Improved/fixed actions.

Bronjahm

Tank will properly kite soul fragment around the room
Replaced attack action with skull rti to focus soul fragments
Non-tank bots will only try to stay within 10f of boss only once we enter last phase, tank will be in the middle of the room if it is not kiting soul fragment

Devourer of Souls

DPS bots will stop dps if boss is casting mirrored soul
Laser can be easily avoided with follow/summon

ICC

LM

Tank will only move to tank position if it actually have the agro, it used to move there without agro and few of the bots would get killed since tank was unable to reach bot

LDW

Fixed edge case where tank would be target of shade and it would be unable to move away it from it due to tank position restrictions, now if Tank is targeted by shade it will be able to move and run away from it (in hc this could cause wipes since explosion is aoe + tons of dmg)

PP

When oozes spawn, tank will move towards green ooze spawn to keep raid near green ooze (faster kill) and far away from gas cloud (better chance to kill if abo didn't manage to slow it)

* PoS Strategies

Ick and Krick

All boss mechanics implemented

Forgemaster Garfrost

Easily doable with follow/stay/summon

Scourgelord Tyrannus

Ranged bots will spread so that they all don't get frozen by dragon

They avoid everything else on their own

* ICC Refactor, All bosses improved NM and HC

Major Update

* Update playerbots.conf.dist

* ICC Fixes/Improvements

LM
Assist tank moving to kill spikes (it should always stack with main tank to soak dmg)

LDW
Improved shade evasion (added los checks before moving to avoid clipping thru walls and floor)
Fixed tanks moving to tank position befor having aggro

Festergut
Fixed ranged position overlaping with each other
Fixed sometimes hunters were placed to close to the boss

PP
Improved gas cloud kiting, bots should not get stuck in corners anymore while moving away
Gas cloud target will use nitro boosts now, so that players dont wipe if abo fails to slow gas cloud
Due to possible oozes duplications in hc caused by bad phasing, bots will kill duplicates to make encounter a bit easier

BQL
Fixed bots failing to link up in 25man

VDW
Group 1 and Group 2 can move freely now around the room, but once group set their marker they will focus left or right side depending on their assigned position
(Positions were to limiting in 25 man and they could not kill adds fast enough)
Fixed marker overlaping
Optimized cloud collecting for portal bots

LK
Fixed in winter phase tank not collecting adds if more than one add is present

* Hotfix

Possible crash fix prevention
Increased kiting radius for assist tank on Rotface

* minor improve

Added nitro boosts on sindy for beaconed player in last phase (they could be all over the place and wipe raid since they could not reach position in time)
2025-06-17 01:18:59 +02:00
NoxMax
bb004825aa AutoScaleActivity refactoring (#1375)
* AutoScaleActivity refactoring

* Abandon AutoScaleActivity 5% stepping
2025-06-12 23:21:35 +02:00
Veit F.
097bd00f38 fix: added missing guildTaskEnabled checks for GuildTaskMgr (#1374)
Even if guildTaskEnabled was set to 0 in the configuration file, a few queries like PLAYERBOTS_SEL_GUILD_TASKS_BY_VALUE, PLAYERBOTS_SEL_GUILD_TASKS_BY_OWNER were executed, which lead to a massive amount of unnecessary database load
2025-06-12 23:18:13 +02:00
Veit F.
290bf50faf fix: added missing checks for PetIsDeadValue (#1376)
* fix:  added missing guildTaskEnabled checks for GuildTaskMgr

Even if guildTaskEnabled was set to 0 in the configuration file, a few queries like PLAYERBOTS_SEL_GUILD_TASKS_BY_VALUE, PLAYERBOTS_SEL_GUILD_TASKS_BY_OWNER were executed, which lead to a massive amount of unnecessary database load

* fix:  added missing checks for PetIsDeadValue

Hunter under level 10, which are not able to summon their pets defined in the database spammed SELECT id FROM character_pet WHERE owner = {}, since the result of the query was true. Bots under level 10 already have pets in their stables. Also the same thing applied to Bots on mounts, because, while mounted, pets get despawned. This should drastically improve server performance.

* fix:  refactored query, pet should only check death state of his current pet

The old query also fetched all pets out of his stable, which is not needed, since we are looking for slot 0 (active pet)

* style: 🎨 and to AND typo

* fix: 🐛 Reverted 5a6182f977

A few hunters with pets in stable, where slot = 100 would not summon their pets
2025-06-12 23:17:43 +02:00
Noscopezz
e739f7820b ICC Refactor, All bosses improved NM/HC (#1370)
* ICC PP WIP

WIP

* added mutated plague for PP

* BPC added (kinetic and boss targeting need to be done by player)

OT collects dark nucles, bots spread on vortex and other stuff they do ok on their own.
Tested only on 10NM, should work on 25NM

* Tank pos. correction

* BQL, ranged spread, link, flame, bite, tanking tested 10NM

to do (better fire spread, hc tacti, melee spread when in air)

* LDW improved

improved shadow logic, ranged spread for easier shadow handling

* dbs update, fixed teleporting

Bots should only go and teleport to the mage that is actually below zero now

* DBS ranged fix

Ranged should spread more quickly and freak out less

* Festergut && DBS

fixed ranged spread (both)
fixed spore logic (fester)

* Rotface fix

Improved big ooze tanking (static pos, todo kiting)
ooze explosion spread mechanic fix
ooze pool fix
Player needs to mark rotface with skull icon, oterwise bots try to attack oozes

* BQL fixed for 25nm

todo: better melee logic in air phase, better melee flame spread

* VDW, Sister Svalna, Sindy update

Sister Svalna, bots can pickup spears and throw at svalna when she has shield up

VDW added healer strats to use portal and heal boss (atm druids are for raid healing only, so use druide + any other healer, ideally player should be healer)
todo (focus on supressers, add healer rotations, atm they use quickest spell they can)

Sindragosa
Added tank boss manipulation (boss orientation and position)
bots detect (buffet, unchained magic and chilled to the bone and act accordingly)
bots detect frost beacon move to safe spot and los frost bombs around them, while dpsing tombs (todo stop dps if only one tomb is left, if we have frost bombs around, not a big deal atm since in nm they dont one shot)
Last phase bots los behind tomb to loose buffet, tanks swap when they have hi buffet count.
Player should tell bots with skull rti if they should kill tomb or focus boss.
todo (dynamic tomb los in last phase so that healers can see tank but also hide behind tomb to break los from boss)

Removed some debug messages, improved LM spike action (nearest bots also try to help kill it)

Improved Lady Deathwshiper shade action (only targeted bots will run away instead of every bot that is near it)

dbs improved tank switch

I recommend to use 3 healers (just to be safe) and 2 paladin tanks (warr seems to struggle with agro) in 10 man
25 man 6-7 healers (just to be safe) Since most of the bosses are about survival and not dps

* LK Update (doable)

LK added

Improved tank switching for all bosses

Fixed PP gas cloud kiting
Malleable goo todo (dont know how to detect it since its not object or npc) just summon ranged bots to safe position to bypass

BPC  fixed OT sometimes not tanking kele
kinectic bombs todo (for now player should take care of them)

Sindragosa fixed rare case when she is in air phase but tombs went to last phase position

LK
Bots can handle necrotic
Bots can handle adds
Bots should focus valkyre that actually grabbed someone (if unlucky and player just use attack command and summon bots to you if they are far away from you) if they grab bots you can either summon to make them useless or let bots cc them and do it legit way.

Defile should be watched by player and once it was cast just summon bots to you
Vile spirits for some reason go to the ground and get nuked by bots aoe spells so there is not much to be done

**Player needs to be alive the whole LK fight since you will have to watch out for frost spehers (sometimes bots ignore them), summon bots when defile is up and summon ranged bots if they get stuck near shambling or raging spirits since their aoe will wipe you)

all in all LK  is doable both 10 and 25nm, player needs to have knowledge of lk fight and needs to know how to use multibot addon and make macros for eg summoning or commanding groups of bots or individual bots)

Dont forget frost/shadow/nature resist auras in whole ICC since it will help alot

I have done whole icc 10 and 25 with 2 pala tanks, 2/5 heals and rest dps,  if you use +1 or +2 heals it should be easier (since I was testing I did close to 0 dmg in fights same with heals)

* fixed changes made by mistake

fix

* Malleable fix (simple spread mechanic)

Malleable mechanic added (simple spread for now)
Gas cloud fixed (Bots sometimes got stuck between puddle and kite location)

* Defile Update

Bots detect and avoid defile (they struggle to find a way back to the boss around it tho, use summon to help them)

Melee bots should be able to stand behind/to the flank of shambling/spirits

* GS fixed bots not returning to their ship for A and H

Bots will return back to ship after killing mage

* PP gas cloud kiting improved

PP gas cloud kiting improved

* BPC targeting fixed

Bots will mark valid prince with skull RTI now

* BQL added melee spread in air pahse

BQL added melee spread

* VDW healing rotation improved

Healers will now use strong heals and hots

* Fixed Necrotic Plague

Fixed issue with Necrotic where it would get dispelled too soon, or would not get dispelled at all

* LK Update

Refined defile logic
Added 3 points for ranged and melee in winter phase (east, west, south when facing throne)
fixed frost spheres targeting (hunter will focus them)
Atm bots will reset z axis if they fall underground or if they get teleported by lk
Better positioning in 1st phase

* 10HC update until PP

LK defile improved for 10nm (bots sometimes stood 2 close to defile until it grew few times)

Improved rotface for HC

PP remade for 10HC.
Gas cloud is now properly kited
Fixed a rare case of server crash when there were ooze and gas cloud alive at same time.
Bots will move around puddles according to its size now.
Bots that get unboud plague will simply move away from raid and die thus loosing it from raid.
Volatile ooze improved stacking.
Fixed ranged sometimes glitching thru walls when spreading out from other members.

10HC PP is now doable but its hard without summoning (summoning break gascloud and ooze targets so its easier to do). You need to watch boss so you dont phase 2 soon otherwise you will get 2x ooze and cloud which is almoust always a wipe. If abo is not played near perfection bots will struggle with oozes and gas clouds if they are not slowed on time. Always save energy to slow gas cloud since it will wipe the group if it reach its target.
Bots will sometimes stand in puddle, just command them to move and they will figure out what to do.

todo (proper malleable handling)

* Up until Sindy 10HC

BPC added shadow prison handling, bots stop moving if more than 12 stack, tanks more than 18
Improved spreading logic

VDW
fixed issue where bots in portal wold move at half speed compared to real player

* fixed accidental change

* LK 10HC update

Added Shadow trap logic
(if they stand in it, not a big deal since bots wont get yeeted only players will)
When harvest soul, only player will be in another dimension (you must survive)

**Sindy and LK can be done, but I must dissapoint purist, at my skill level I could not achieve to do HC without using summon or other "cheat" bot functions.

Other bosses are all doable now in 10hc

* ICC fixes GS, PP; BQL, SINDY

Minor fixes, bite action improved

* ICC improve Sindy

Bots will now choose non beacon position based on difficulty, 10/25

* ICC fixed missing A/H buff

Fixed missing ICC buff for A and H

Buff will only be present when logged on and in ICC, once any bot or player leave ICC the buff is gone to prevent abuse.

This will make ICC easier now and with recent DPS update and movement improvement bots will now actully do decent dps and even greater healing.

Ally buff
https://www.wowhead.com/wotlk/spell=73828/strength-of-wrynn

Horde buff
https://www.wowhead.com/wotlk/spell=73822/hellscreams-warsong

* revert last change

revert buff

* ICC improve Rotface

Bots will now mark Rotface with skull icon, which will make them focus boss instead of oozes automatically

* ICC Festergut 10 Man fix

There was a rare case in 10 Man when 2 tanks would get spores which made them both stack at melee spot. Now the code will check if any of the spored player is main tank, if it is, it will stay at melee and other spores will go at ranged spot since off tank doesn't really need to stand near main tank all the time.

* ICC BPC major update, fix and improve

Fixed main tank sometimes not tanking both bosses (vala and talda)

Improved marking of current prince

Empowered vortex: bots will now spread out when it is being cast, instead of always spreading(ranged). This will make melee also spread better now since bots will calculate and move to optimal positions.

Added Kinetic bomb handling.
Hunters will take care of bombs, if no hunter is present then any ranged dps will take care of kinetic bombs.

* ICC BQL/VDW major update + minor fixes/improvements

LDW improved spreading for ranged, tanks will move boss in the middle of the room now when shield is depleted which will make bots bug less around pillars

GS Assist tank will not teleport to enemy ship and will keep tanking adds on our ship now

DBS ranged spread improved (they will calcualte spot for each bot and stick with it reducing movment to minimum), fixed bug where in 25 man ranged bots would go behind walls, making them unable to dps boss, improved rune of blood tanking

Festergut ranged spread improved (they will calcualte spot for each bot and stick with it reducing movment to minimum)

BQL Melee spread improved, bite logic improved, added swarming shadows logic (not perfect but at least it wont be all over the room anymore), Tanks will properly handle blood mirror now

VDW boss and raid healers will be automatically assinged depening by number of healers(if more than 3 healers in group, 2 will focus on raid and others will heal boss, otherwise one healer will heal raid). Druid will be assigned to raid healing if no druid present then some other random heal class. Added rotations for druid healers if they end up healing the boss. Raid healers will not use portals anymore. Healers will come to the ground now after using portals (they were stuck in air)

* ICC LK minor update

PP

Removed pre stacking for ranged when volatile ooze spawn (they will kill it much faster now and only stack if someone actually gets targeted by ooze)

LK

Hunters will use trnaq shot to remove enrage from shamblings

Improved valkyr cc

Minimized ping-ponging during winter phase

* ICC minor Sindy improve

Reduced position tolerances and forced movement for beacon and non beacon positions which will make bots move to spot that they actually need to be at instead of randomly running to sindragosa or beaconed players.

* ICC minor update

GS

Bots will mark mage with skull rti

Rotface

Fixed bots glitching thru walls and floors (added check if position is valid before moving)

PP

Bots will mark volatile ooze with skull rti now which will help them focus it and kill asap (usefull for heroic when both volatile ooze and gas cloud are present at the same time)

VDW

Added default group position in the middle if the room so that bots don't spread out too much which will force them to focus supressers more

Fixed Boss healers not keeping themself alive when low on HP

* ICC LK minor update

Commented out z axis bypass since it was fixed with recent core updates.

Bots no longer fall thru buggy platforms so its no longer needed which in return makes valkyrs works as they should.

* ICC LM & LDW Improved

LM

Removed Attack Action since it was buggy and replaced it with RTI. (Improved DPS)

Simplified trigger for spike action, since attack logic is handled by skull RTI now.

Bots (tanks) will mark spikes with skull RTI, which will make bots instantly switch to Spike targets. If no spike is present, the boss will be marked.

Added logic that will make non-tank bots move away from LM to a fixed position behind LM if they happened to be in front of LM (No more insta kills with cleave).
If LM is casting Bone Storm, bots will ignore the above logic and do max dps.

LDW

Removed Attack Action since it was buggy and replaced it with RTI. (Improved DPS)

Bots (tanks) will mark adds with skull RTI, which will make bots instantly switch to adds targets. If no spike is present, the boss will be marked, which will help with keeping allies alive when they get mind controlled by LDW.

Moved 2nd phase position deeper into the room and reduced boss hp trigger to 95%, which will keep tanks where they should be during 1st phase (sometimes the boss dropped below 98% and tanks would ping pong around the room).

Ranged bots will now spread closer to each other now and will ignore spreading if they are 2 far or 2 close to the boss so they can properly reposition.

These changes should also fix recent bugs with bots not casting/standing still on LM and LDW.

* ICC DBS Improve

Replaced attack action with RTI. (Improved DPS)

Ranged bots will move away from blood beast if targeted by it now.

* ICC Sindragosa and LK Improve

Sindragosa

Improved tomb los for frost bombs. Bots will now mark tomb with moon rti to avoid killing it too soon. (If they still attack the tomb, simply reset and after bombs are over, simply attack the tomb to kill or mark with a skull.)
Main Tank will reset mystic buffet stacks now to avoid tank swapping since it's really unreliable which will make sindy less frustrating to kill (other bots will need to do mechanics properly by hiding behind ice tombs).

LK

Necrotic plague action

Bots will now check before moving to the Shambling if they are in front of the Shambling to avoid getting one shoted by shockwave.
Improved multiplier, which will now properly handle dispelling of necrotic plague.

Winter phase action

Bots will properly move behind adds to avoid getting one shoted by shamblings or raging spirits.
If there is a hunter in the grouphunter will focus on spheres; if not, any ranged DPS will focus spheres.

Adds action

Bots will now be in proper positions during the 1st phase, and when the off tank is done collecting shamblings, it will move away from the group.
Bots (non-tanks) will move behind the shambling if they are in front of it to avoid getting one shoted in the 1st phase.
Restored action for checking if bots are at ground level, since they were still sometimes glitching through the floor (valkrys will be able to carry bots and drop them off the edge if not killed in time).
Improved defile calculations for 10 man and 25 man raids since defile was growing differently for 10 and 25, which would make bots in 25 man move very far away from somewhat small defile puddle.
Bots will now properly CC any valkyr that wasn't CC'd, and they won't freak out anymore if multiple valkyrs are present. (They can't tell which valkyr is holding bots/ players, so we should command an attack on valkyrs that are holding someone.)
Bots will now run away from Vile Spirits if they are targeted by them during the last phase.

* ICC 25HC ldw shades fix

Fixed bots could not detect shade if there were multiple present

* ICC trigger optimization

Optimized trigger and action for:

KineticBomb
OozePuddle

* ICC LK Improve

Improved Shadow Trap, Winter phase and 1st phase.

Bots will now have fixed positions for 1st phase if no shadow trap is present.

Tanks will now take care of taunting adds during winter phase. (Assist tank will collect adds and bring them to main tank, main tank will take over the adds so we have minimal chance of adds rotating towards the raid)

Fixed shadow trap detection.

* ToC dungeon update

Added TOC dungeon strategies for 1st encounter.

Other encounters are pretty easy and straightforward so they do not require strategies. They can be all done with follow, reset, summon, attack, skull icon etc...

Bots (A/H) will automatically check if they have lance equiped or in inventory. If they don't have lance they will pick it up from nearest lance stand and equip it.

Bots will mount nearest mount if they have lance equipped.

Once mounted they will keep Defend aura at 3 stacks (top priority).
In fight they will cast charge if more than 5f away, they will cast shield break if target has Defended aura active and they will use Thrust if they cant use anything else.
In short they will melt any target and enemies wont stand a chance keeping their defend aura up.

Once champions are defeated bot will automatically equip best weapon they have in inventory (weapon that was swapped with lance)

* ICC minor update

LM

Bots will now move in smaller increments to get behind boss
They will still move towards fixed position, but as soon they are not in front of boss, they will stop moving and do other actions.

GS

Fixed cannon trigger, now will return true if cannon is 100f from bots.
It used to return true all the time.

* ToC added Eadric strat

bots will look away during radiance cast

* ICC LK minor fix

fixed crash in icclichkingsaddsaction

* ICC GS fix

While encountering edge case when playing horde, the logic was to detect mages, there are usually 2 around, but the longer the fight last the bigger the chance that cannons would kill all the mages thus once we kill the mage that was casting deep freeze bots could not execute action that would return them back to the ship, so the boss had the time to nuke them one by one.
Even tanks have hard time surviving the boss.
I have changed it now (both A/H) so that we keep track of the enemy boss (which is hard if not impossible to kill as main trigger) and we are keeping track of the  mage that is actually casting deep freeze, we mark it with skull, and now as soon the mage is dead, bots will remove the mark so there is 0 confusion about what to do next on their side, and they will come back asap.

* FoS Improved and fixed, ICC LM, LDW and PP updates

FoS

Improved/fixed triggers, Improved/fixed actions.

Bronjahm

Tank will properly kite soul fragment around the room
Replaced attack action with skull rti to focus soul fragments
Non-tank bots will only try to stay within 10f of boss only once we enter last phase, tank will be in the middle of the room if it is not kiting soul fragment

Devourer of Souls

DPS bots will stop dps if boss is casting mirrored soul
Laser can be easily avoided with follow/summon

ICC

LM

Tank will only move to tank position if it actually have the agro, it used to move there without agro and few of the bots would get killed since tank was unable to reach bot

LDW

Fixed edge case where tank would be target of shade and it would be unable to move away it from it due to tank position restrictions, now if Tank is targeted by shade it will be able to move and run away from it (in hc this could cause wipes since explosion is aoe + tons of dmg)

PP

When oozes spawn, tank will move towards green ooze spawn to keep raid near green ooze (faster kill) and far away from gas cloud (better chance to kill if abo didn't manage to slow it)

* PoS Strategies

Ick and Krick

All boss mechanics implemented

Forgemaster Garfrost

Easily doable with follow/stay/summon

Scourgelord Tyrannus

Ranged bots will spread so that they all don't get frozen by dragon

They avoid everything else on their own

* ICC Refactor, All bosses improved NM and HC

Major Update

* Update playerbots.conf.dist
2025-06-11 23:27:44 +02:00
bash
6fbaf6510b Revert "Fixed opening trade window while using DBM or Questie addon (#1363)" (#1367)
This reverts commit dfa87faf5e.
2025-06-09 01:53:17 +02:00
kadeshar
499a80db14 - Migrated tips for ilvl gear in config (#1364) 2025-06-08 16:25:03 +02:00
kadeshar
dfa87faf5e Fixed opening trade window while using DBM or Questie addon (#1363)
* - Fixed opening trade window while using DBM or Questie addon

* - Added excluded prefixes for trade actions to config

* -Fixed config description
2025-06-08 16:23:51 +02:00
Jelly
d15ec79252 Addresses #1110 - Add a system to blacklist GameObject GUID's (#1365)
* Addresses #1110

* Addresses #1110
2025-06-08 16:22:06 +02:00
kadeshar
cfc8e85706 - Fixed Thorim strategy positioning in phase 2 (#1362) 2025-06-06 22:00:48 +02:00
kadeshar
fc6309c521 - Added Blessing of Sanctuary to paladin health buff strategy (#1345)
- Removed Blessing of Sanctuary from paladin dps buff strategy
- Added health buff strategy as default for protection paladin
2025-06-06 12:10:45 +02:00
Jelly
9986469042 Addresses #1360 (#1361)
* Addresses #1360

* Additional check, If item exists in bags, don't roll.
2025-06-06 08:55:03 +02:00
kadeshar
db9bcb97ba - Added Mimiron Strategy (#1358)
- Fixed nearest npc value
2025-06-06 08:51:28 +02:00
Brian
3c05e47cb2 CMaNGOS Playerbots "lfg" command implemented (#1291)
* CMaNGOS Playerbots "lfg" command implemented

* Remove logging, fix warning, add suggestion

- Remove LOG_INFO's console clutter, since 'lfg' command is working correctly now.
- Warning C26813 fixed for: placeholders["%role"] = (role == BOT_ROLE_TANK ? "tank" : (role == BOT_ROLE_HEALER ? "healer" : "dps"));
- Added suggestion to let bots do autogear & maintenance, so players can instantly start their dungeon or raid activities without manually having to configure the playerbots gear. It could save a lot of time. This is up to discussion for playerbots maintainers.
2025-06-01 15:31:29 +08:00
Revision
89556dafa1 Fix crash when using random levels with a lowered max player level (#1344) 2025-05-27 18:07:13 +02:00
Yunfan Li
b726f3dfcb Active loot roll (#1338) 2025-05-25 21:24:25 +08:00
NoxMax
45f98e52b4 Update playerbots.conf.dist with DisabledWithoutRealPlayer context (#1340)
* Update playerbots.conf.dist with DisabledWithoutRealPlayer context

* Update playerbots.conf.dist
2025-05-25 11:37:58 +02:00
EricksOliveira
f8660bc939 Do not disband alt bots group when master goes away (#702)
* Do not disband alt bots group when master goes away

* This update makes the original PR functionality optional, which prevents alternative bots (random bots) from automatically leaving the group when the master leaves.

* Fix

---------

Co-authored-by: bash <31279994+hermensbas@users.noreply.github.com>
2025-05-25 00:19:11 +02:00
kadeshar
c24b7a7bb3 - Added disabled without real player feature (#1335) 2025-05-24 20:51:44 +08:00
kadeshar
1195e67c97 - Added casting Blessing of Sanctuary on tank bots (#1329) 2025-05-24 13:32:59 +02:00
Alex Dcnh
faee49beaa Update PlayerbotFactory.cpp (#1324)
To correcte the build warning in VS

Commented:
// bot->GetPetStable()->CurrentPet.value();

Added:
auto petGuid = bot->GetPetStable()->CurrentPet.value();
2025-05-24 17:11:49 +08:00
Brian
d87d5a46c7 Typo fix in AcceptBgInvitationAction (#1332) 2025-05-24 17:11:34 +08:00
kadeshar
c34617e133 - Reverted arena join queue changes (#1333) 2025-05-24 17:11:19 +08:00
kadeshar
a4ff66f12a - [Bugfix] Fixed bug with unequip and item in bags (#1327) 2025-05-22 22:10:53 +02:00
kadeshar
e68c5a76c6 - Added Auriaya fall from floor protection (#1318) 2025-05-20 23:24:45 +08:00
Yunfan Li
5910866362 Calculation of the power of items with random properties (#1312)
* Score calculation of item random property

* Equip auto repair on repop

* Item random property calculation

* Random Property calculation
2025-05-20 23:24:33 +08:00
Yunfan Li
c5b185455c Convert PlayerbotsXPRate to RandomBotXPRate (#1313) 2025-05-18 19:34:17 +02:00
kadeshar
e1a8bd66c5 - Fixed Iron Assembly Rune of Power trigger (#1314) 2025-05-18 19:32:31 +02:00
kadeshar
f7bd9ae5c1 - Added skipping messages from AdvancedWotLKCombatLog addon in trade action (#1310) 2025-05-18 19:31:57 +08:00
NoxMax
b5426152fb Expose zone level brackets to user configuration (#1309)
* Make variable name more intuitive

* Make variable name more intuitive

* Make variable name more intuitive and clarify function

* Update playerbots.conf.dist

* Update PlayerbotAIConfig.cpp

* Update PlayerbotAIConfig.h

* Update RandomPlayerbotMgr.cpp

* Update playerbots.conf.dist

* Update playerbots.conf.dist

* Update playerbots.conf.dist

* Update playerbots.conf.dist

* Update playerbots.conf.dist

* Update playerbots.conf.dist
2025-05-18 19:31:32 +08:00
brighton-chi
f3ee83d471 Additional improvements to readability and accessibility of config (#1301)
* Additional improvements to readability and accessibility of config

Additional improvements to readability and accessibility of config

* Update playerbots.conf.dist

Inconsistency improvements.

* Update playerbots.conf.dist

1 more

* Update playerbots.conf.dist

* Update playerbots.conf.dist

Improved explanation of strategy processing

* Revisions to worldbuff comments

---------

Co-authored-by: SaW <swerkhoven@outlook.com>
2025-05-18 11:48:36 +02:00
Revision
b2534691b8 Fixed a number of issues with texts (#1308) 2025-05-18 11:23:20 +08:00
NotCronky
36adb62f2a druid cat and ret pally offheal strat (#1298)
co +offheal to either cat druid or ret pally with disable dps mode and enable offheal where the bots will now focus damage but heal when necessary.
2025-05-17 22:33:55 +08:00
sd3420625
fd99b373c2 Translated Chinese text documents (#1306) 2025-05-17 22:03:01 +08:00
kadeshar
e2b5ab766d - Added Thorim strategy (#1305) 2025-05-17 21:03:17 +08:00
SaW
9641092078 Revert "fix(#1276) Prevent bots from equipping relics intended for other clas…" (#1303)
This reverts commit f365b79e96.
2025-05-16 13:39:04 +02:00
NoxMax
aaad67f7b9 Clarify EnableNewRpgStrategy relation to RandomBotTeleLowerLevel and RandomBotTeleHigherLevel, and making them more intuitive (#1294)
* Make variable name more intuitive

* Make variable name more intuitive

* Make variable name more intuitive and clarify function

* Update playerbots.conf.dist

* Update playerbots.conf.dist

* Update playerbots.conf.dist

* Update playerbots.conf.dist

* Update playerbots.conf.dist
2025-05-16 01:30:58 +02:00
brighton-chi
7901f5da19 Improve readability and accessibility of config (#1296)
* Improve readability and accessibility of config

* Typo fix
2025-05-15 11:40:09 +02:00
EricksOliveira
4f26a8a09b Fix hunter bot losing dismissed pet during maintenance (#1286)
* Prevents recreating the tamed pet in InitPet() for hunter bots

This update adds a check to prevent the InitPet() method from re-creating a pet for hunter bots that already have a tamed pet (GetPetId() != 0). This prevents pets that have already been trained and have talents set from being lost, especially after login or teleportation. Responsibility for re-summoning an existing pet can be handled separately via LoadPetFromDB.

* Including check at start to avoid creating a new pet if the bot already has one active or saved in the stable (CurrentPet).

Initial check to avoid pet re-creation.

Selection of tamable creatures appropriate to the bot's level.

Creation, configuration and saving of the pet.

Application of visual effects and initialization of talents.

Activation of autocast for non-passive spells.

* Fix

* .
2025-05-14 20:56:50 +08:00
SaW
f17ec36cde Revert "Introduction of the SafeLearn(spellId) lambda that checks if the bot …" (#1289)
This reverts commit 6d24db6999.
2025-05-12 23:46:05 +02:00
Noscopezz
3881940f33 PoS Strategies (#1283)
PoS Strategies
2025-05-11 00:24:09 +02:00
Revision
dc703cc897 Fixed a typo in filename and renamed table for consistency (#1279)
* Fixed a typo in filename and renamed table for consistency

* Added update file to rename the tables

* Drop old tables if they exist and create new ones if they don't exist already
2025-05-10 13:47:22 +02:00
Type1Error
97f582b9b1 Fix bot chest looting exploits in ICC and Halls of Stone (#1280)
- Prevent bots from looting Gunship Armory chest in ICC before the event ends (#1162)
- Fix bots being able to loot Tribunal Cache in Halls of Stone multiple times (#1200)
- Ensure bots do not attempt to loot Deathbringer's Cache in ICC before it is available

Three chests (Gunship Armory, Tribunal Cache, Deathbringer's Cache) were tested and now behave as expected: bots will not approach or attempt to loot chests while they are unlootable, even if players move close to them.

Closes #1162, #1200
2025-05-10 13:45:58 +02:00
Type1Error
95c572bf48 feat(playerbots): staggered taxi take-off for bots (#1281)
* feat(playerbots): staggered taxi take-off for bots

Adds four new configurable settings to playerbots.conf:

- AiPlayerbot.BotTaxiDelayMinMs:   Min random delay before the 1st follower bot clicks the flight-master
- AiPlayerbot.BotTaxiDelayMaxMs:   Upper bound for the overall taxi delay window – larger spreads big raids
- AiPlayerbot.BotTaxiGapMs:        Fixed gap added per group-slot so bots never take off together
- AiPlayerbot.BotTaxiGapJitterMs:  Extra small randomness added to each gap so launches don’t look robotic

These options allow server owners to fine-tune how bots queue up and take off from flight masters, making their behavior appear more natural.

Closes #1017 : Bots use Flight master nearly the same time.

* fixed build errors

Was missing a header and variable declarations.
2025-05-10 13:45:15 +02:00
Not
43b0852438 Update ChatHelper.cpp, Replace sprintf with snprintf (#1278)
Use snprintf instead of sprintf for item color formatting to prevent buffer overflows and to silence warnings in macOS versions.
2025-05-09 22:01:21 +08:00
kadeshar
9ca326c8cb - Added Iron Assembly Rune of Power strategy (#1275) 2025-05-09 12:42:33 +02:00
Type1Error
f365b79e96 fix(#1276) Prevent bots from equipping relics intended for other classes (#1277)
Fixes #1276

This change adds logic to detect if an item is a relic and ensures it can only be equipped by bots of the corresponding class:

- Idols are restricted to druids
- Librams are restricted to paladins
- Sigils are restricted to death knights
- Totems are restricted to shamans

Fixes a bug where bots would attempt to equip relics regardless of class restrictions.

Additionally, this commit resolves two unrelated build warnings found in the same file.
2025-05-08 23:37:33 +02:00
kadeshar
e104c5f8be - Added Kologarn Focused Eyebeam cheat (#1274)
- Added Kologarn Crunch Armor cheat
- Added Kologarn proper targeting and setting rti target
2025-05-07 21:55:56 +02:00
Type1Error
0c6f656236 fix(strategy): Revert InitTriggers to previous implementation (#1272)
Fixes #1169

Reverts TankAssistStrategy and DpsAssistStrategy InitTriggers implementations to their previous versions.
The changes from commit 24efa7e appeared to be optimizations without fixing any documented bugs, but were causing issues.
2025-05-07 21:54:41 +02:00
Noscopezz
4603dbaf35 FoS Improve, ICC LM, LDW, PP improve (#1268)
* ICC PP WIP

WIP

* added mutated plague for PP

* BPC added (kinetic and boss targeting need to be done by player)

OT collects dark nucles, bots spread on vortex and other stuff they do ok on their own.
Tested only on 10NM, should work on 25NM

* Tank pos. correction

* BQL, ranged spread, link, flame, bite, tanking tested 10NM

to do (better fire spread, hc tacti, melee spread when in air)

* LDW improved

improved shadow logic, ranged spread for easier shadow handling

* dbs update, fixed teleporting

Bots should only go and teleport to the mage that is actually below zero now

* DBS ranged fix

Ranged should spread more quickly and freak out less

* Festergut && DBS

fixed ranged spread (both)
fixed spore logic (fester)

* Rotface fix

Improved big ooze tanking (static pos, todo kiting)
ooze explosion spread mechanic fix
ooze pool fix
Player needs to mark rotface with skull icon, oterwise bots try to attack oozes

* BQL fixed for 25nm

todo: better melee logic in air phase, better melee flame spread

* VDW, Sister Svalna, Sindy update

Sister Svalna, bots can pickup spears and throw at svalna when she has shield up

VDW added healer strats to use portal and heal boss (atm druids are for raid healing only, so use druide + any other healer, ideally player should be healer)
todo (focus on supressers, add healer rotations, atm they use quickest spell they can)

Sindragosa
Added tank boss manipulation (boss orientation and position)
bots detect (buffet, unchained magic and chilled to the bone and act accordingly)
bots detect frost beacon move to safe spot and los frost bombs around them, while dpsing tombs (todo stop dps if only one tomb is left, if we have frost bombs around, not a big deal atm since in nm they dont one shot)
Last phase bots los behind tomb to loose buffet, tanks swap when they have hi buffet count.
Player should tell bots with skull rti if they should kill tomb or focus boss.
todo (dynamic tomb los in last phase so that healers can see tank but also hide behind tomb to break los from boss)

Removed some debug messages, improved LM spike action (nearest bots also try to help kill it)

Improved Lady Deathwshiper shade action (only targeted bots will run away instead of every bot that is near it)

dbs improved tank switch

I recommend to use 3 healers (just to be safe) and 2 paladin tanks (warr seems to struggle with agro) in 10 man
25 man 6-7 healers (just to be safe) Since most of the bosses are about survival and not dps

* LK Update (doable)

LK added

Improved tank switching for all bosses

Fixed PP gas cloud kiting
Malleable goo todo (dont know how to detect it since its not object or npc) just summon ranged bots to safe position to bypass

BPC  fixed OT sometimes not tanking kele
kinectic bombs todo (for now player should take care of them)

Sindragosa fixed rare case when she is in air phase but tombs went to last phase position

LK
Bots can handle necrotic
Bots can handle adds
Bots should focus valkyre that actually grabbed someone (if unlucky and player just use attack command and summon bots to you if they are far away from you) if they grab bots you can either summon to make them useless or let bots cc them and do it legit way.

Defile should be watched by player and once it was cast just summon bots to you
Vile spirits for some reason go to the ground and get nuked by bots aoe spells so there is not much to be done

**Player needs to be alive the whole LK fight since you will have to watch out for frost spehers (sometimes bots ignore them), summon bots when defile is up and summon ranged bots if they get stuck near shambling or raging spirits since their aoe will wipe you)

all in all LK  is doable both 10 and 25nm, player needs to have knowledge of lk fight and needs to know how to use multibot addon and make macros for eg summoning or commanding groups of bots or individual bots)

Dont forget frost/shadow/nature resist auras in whole ICC since it will help alot

I have done whole icc 10 and 25 with 2 pala tanks, 2/5 heals and rest dps,  if you use +1 or +2 heals it should be easier (since I was testing I did close to 0 dmg in fights same with heals)

* fixed changes made by mistake

fix

* Malleable fix (simple spread mechanic)

Malleable mechanic added (simple spread for now)
Gas cloud fixed (Bots sometimes got stuck between puddle and kite location)

* Defile Update

Bots detect and avoid defile (they struggle to find a way back to the boss around it tho, use summon to help them)

Melee bots should be able to stand behind/to the flank of shambling/spirits

* GS fixed bots not returning to their ship for A and H

Bots will return back to ship after killing mage

* PP gas cloud kiting improved

PP gas cloud kiting improved

* BPC targeting fixed

Bots will mark valid prince with skull RTI now

* BQL added melee spread in air pahse

BQL added melee spread

* VDW healing rotation improved

Healers will now use strong heals and hots

* Fixed Necrotic Plague

Fixed issue with Necrotic where it would get dispelled too soon, or would not get dispelled at all

* LK Update

Refined defile logic
Added 3 points for ranged and melee in winter phase (east, west, south when facing throne)
fixed frost spheres targeting (hunter will focus them)
Atm bots will reset z axis if they fall underground or if they get teleported by lk
Better positioning in 1st phase

* 10HC update until PP

LK defile improved for 10nm (bots sometimes stood 2 close to defile until it grew few times)

Improved rotface for HC

PP remade for 10HC.
Gas cloud is now properly kited
Fixed a rare case of server crash when there were ooze and gas cloud alive at same time.
Bots will move around puddles according to its size now.
Bots that get unboud plague will simply move away from raid and die thus loosing it from raid.
Volatile ooze improved stacking.
Fixed ranged sometimes glitching thru walls when spreading out from other members.

10HC PP is now doable but its hard without summoning (summoning break gascloud and ooze targets so its easier to do). You need to watch boss so you dont phase 2 soon otherwise you will get 2x ooze and cloud which is almoust always a wipe. If abo is not played near perfection bots will struggle with oozes and gas clouds if they are not slowed on time. Always save energy to slow gas cloud since it will wipe the group if it reach its target.
Bots will sometimes stand in puddle, just command them to move and they will figure out what to do.

todo (proper malleable handling)

* Up until Sindy 10HC

BPC added shadow prison handling, bots stop moving if more than 12 stack, tanks more than 18
Improved spreading logic

VDW
fixed issue where bots in portal wold move at half speed compared to real player

* fixed accidental change

* LK 10HC update

Added Shadow trap logic
(if they stand in it, not a big deal since bots wont get yeeted only players will)
When harvest soul, only player will be in another dimension (you must survive)

**Sindy and LK can be done, but I must dissapoint purist, at my skill level I could not achieve to do HC without using summon or other "cheat" bot functions.

Other bosses are all doable now in 10hc

* ICC fixes GS, PP; BQL, SINDY

Minor fixes, bite action improved

* ICC improve Sindy

Bots will now choose non beacon position based on difficulty, 10/25

* ICC fixed missing A/H buff

Fixed missing ICC buff for A and H

Buff will only be present when logged on and in ICC, once any bot or player leave ICC the buff is gone to prevent abuse.

This will make ICC easier now and with recent DPS update and movement improvement bots will now actully do decent dps and even greater healing.

Ally buff
https://www.wowhead.com/wotlk/spell=73828/strength-of-wrynn

Horde buff
https://www.wowhead.com/wotlk/spell=73822/hellscreams-warsong

* revert last change

revert buff

* ICC improve Rotface

Bots will now mark Rotface with skull icon, which will make them focus boss instead of oozes automatically

* ICC Festergut 10 Man fix

There was a rare case in 10 Man when 2 tanks would get spores which made them both stack at melee spot. Now the code will check if any of the spored player is main tank, if it is, it will stay at melee and other spores will go at ranged spot since off tank doesn't really need to stand near main tank all the time.

* ICC BPC major update, fix and improve

Fixed main tank sometimes not tanking both bosses (vala and talda)

Improved marking of current prince

Empowered vortex: bots will now spread out when it is being cast, instead of always spreading(ranged). This will make melee also spread better now since bots will calculate and move to optimal positions.

Added Kinetic bomb handling.
Hunters will take care of bombs, if no hunter is present then any ranged dps will take care of kinetic bombs.

* ICC BQL/VDW major update + minor fixes/improvements

LDW improved spreading for ranged, tanks will move boss in the middle of the room now when shield is depleted which will make bots bug less around pillars

GS Assist tank will not teleport to enemy ship and will keep tanking adds on our ship now

DBS ranged spread improved (they will calcualte spot for each bot and stick with it reducing movment to minimum), fixed bug where in 25 man ranged bots would go behind walls, making them unable to dps boss, improved rune of blood tanking

Festergut ranged spread improved (they will calcualte spot for each bot and stick with it reducing movment to minimum)

BQL Melee spread improved, bite logic improved, added swarming shadows logic (not perfect but at least it wont be all over the room anymore), Tanks will properly handle blood mirror now

VDW boss and raid healers will be automatically assinged depening by number of healers(if more than 3 healers in group, 2 will focus on raid and others will heal boss, otherwise one healer will heal raid). Druid will be assigned to raid healing if no druid present then some other random heal class. Added rotations for druid healers if they end up healing the boss. Raid healers will not use portals anymore. Healers will come to the ground now after using portals (they were stuck in air)

* ICC LK minor update

PP

Removed pre stacking for ranged when volatile ooze spawn (they will kill it much faster now and only stack if someone actually gets targeted by ooze)

LK

Hunters will use trnaq shot to remove enrage from shamblings

Improved valkyr cc

Minimized ping-ponging during winter phase

* ICC minor Sindy improve

Reduced position tolerances and forced movement for beacon and non beacon positions which will make bots move to spot that they actually need to be at instead of randomly running to sindragosa or beaconed players.

* ICC minor update

GS

Bots will mark mage with skull rti

Rotface

Fixed bots glitching thru walls and floors (added check if position is valid before moving)

PP

Bots will mark volatile ooze with skull rti now which will help them focus it and kill asap (usefull for heroic when both volatile ooze and gas cloud are present at the same time)

VDW

Added default group position in the middle if the room so that bots don't spread out too much which will force them to focus supressers more

Fixed Boss healers not keeping themself alive when low on HP

* ICC LK minor update

Commented out z axis bypass since it was fixed with recent core updates.

Bots no longer fall thru buggy platforms so its no longer needed which in return makes valkyrs works as they should.

* ICC LM & LDW Improved

LM

Removed Attack Action since it was buggy and replaced it with RTI. (Improved DPS)

Simplified trigger for spike action, since attack logic is handled by skull RTI now.

Bots (tanks) will mark spikes with skull RTI, which will make bots instantly switch to Spike targets. If no spike is present, the boss will be marked.

Added logic that will make non-tank bots move away from LM to a fixed position behind LM if they happened to be in front of LM (No more insta kills with cleave).
If LM is casting Bone Storm, bots will ignore the above logic and do max dps.

LDW

Removed Attack Action since it was buggy and replaced it with RTI. (Improved DPS)

Bots (tanks) will mark adds with skull RTI, which will make bots instantly switch to adds targets. If no spike is present, the boss will be marked, which will help with keeping allies alive when they get mind controlled by LDW.

Moved 2nd phase position deeper into the room and reduced boss hp trigger to 95%, which will keep tanks where they should be during 1st phase (sometimes the boss dropped below 98% and tanks would ping pong around the room).

Ranged bots will now spread closer to each other now and will ignore spreading if they are 2 far or 2 close to the boss so they can properly reposition.

These changes should also fix recent bugs with bots not casting/standing still on LM and LDW.

* ICC DBS Improve

Replaced attack action with RTI. (Improved DPS)

Ranged bots will move away from blood beast if targeted by it now.

* ICC Sindragosa and LK Improve

Sindragosa

Improved tomb los for frost bombs. Bots will now mark tomb with moon rti to avoid killing it too soon. (If they still attack the tomb, simply reset and after bombs are over, simply attack the tomb to kill or mark with a skull.)
Main Tank will reset mystic buffet stacks now to avoid tank swapping since it's really unreliable which will make sindy less frustrating to kill (other bots will need to do mechanics properly by hiding behind ice tombs).

LK

Necrotic plague action

Bots will now check before moving to the Shambling if they are in front of the Shambling to avoid getting one shoted by shockwave.
Improved multiplier, which will now properly handle dispelling of necrotic plague.

Winter phase action

Bots will properly move behind adds to avoid getting one shoted by shamblings or raging spirits.
If there is a hunter in the grouphunter will focus on spheres; if not, any ranged DPS will focus spheres.

Adds action

Bots will now be in proper positions during the 1st phase, and when the off tank is done collecting shamblings, it will move away from the group.
Bots (non-tanks) will move behind the shambling if they are in front of it to avoid getting one shoted in the 1st phase.
Restored action for checking if bots are at ground level, since they were still sometimes glitching through the floor (valkrys will be able to carry bots and drop them off the edge if not killed in time).
Improved defile calculations for 10 man and 25 man raids since defile was growing differently for 10 and 25, which would make bots in 25 man move very far away from somewhat small defile puddle.
Bots will now properly CC any valkyr that wasn't CC'd, and they won't freak out anymore if multiple valkyrs are present. (They can't tell which valkyr is holding bots/ players, so we should command an attack on valkyrs that are holding someone.)
Bots will now run away from Vile Spirits if they are targeted by them during the last phase.

* ICC 25HC ldw shades fix

Fixed bots could not detect shade if there were multiple present

* ICC trigger optimization

Optimized trigger and action for:

KineticBomb
OozePuddle

* ICC LK Improve

Improved Shadow Trap, Winter phase and 1st phase.

Bots will now have fixed positions for 1st phase if no shadow trap is present.

Tanks will now take care of taunting adds during winter phase. (Assist tank will collect adds and bring them to main tank, main tank will take over the adds so we have minimal chance of adds rotating towards the raid)

Fixed shadow trap detection.

* ToC dungeon update

Added TOC dungeon strategies for 1st encounter.

Other encounters are pretty easy and straightforward so they do not require strategies. They can be all done with follow, reset, summon, attack, skull icon etc...

Bots (A/H) will automatically check if they have lance equiped or in inventory. If they don't have lance they will pick it up from nearest lance stand and equip it.

Bots will mount nearest mount if they have lance equipped.

Once mounted they will keep Defend aura at 3 stacks (top priority).
In fight they will cast charge if more than 5f away, they will cast shield break if target has Defended aura active and they will use Thrust if they cant use anything else.
In short they will melt any target and enemies wont stand a chance keeping their defend aura up.

Once champions are defeated bot will automatically equip best weapon they have in inventory (weapon that was swapped with lance)

* ICC minor update

LM

Bots will now move in smaller increments to get behind boss
They will still move towards fixed position, but as soon they are not in front of boss, they will stop moving and do other actions.

GS

Fixed cannon trigger, now will return true if cannon is 100f from bots.
It used to return true all the time.

* ToC added Eadric strat

bots will look away during radiance cast

* ICC LK minor fix

fixed crash in icclichkingsaddsaction

* ICC GS fix

While encountering edge case when playing horde, the logic was to detect mages, there are usually 2 around, but the longer the fight last the bigger the chance that cannons would kill all the mages thus once we kill the mage that was casting deep freeze bots could not execute action that would return them back to the ship, so the boss had the time to nuke them one by one.
Even tanks have hard time surviving the boss.
I have changed it now (both A/H) so that we keep track of the enemy boss (which is hard if not impossible to kill as main trigger) and we are keeping track of the  mage that is actually casting deep freeze, we mark it with skull, and now as soon the mage is dead, bots will remove the mark so there is 0 confusion about what to do next on their side, and they will come back asap.

* FoS Improved and fixed, ICC LM, LDW and PP updates

FoS

Improved/fixed triggers, Improved/fixed actions.

Bronjahm

Tank will properly kite soul fragment around the room
Replaced attack action with skull rti to focus soul fragments
Non-tank bots will only try to stay within 10f of boss only once we enter last phase, tank will be in the middle of the room if it is not kiting soul fragment

Devourer of Souls

DPS bots will stop dps if boss is casting mirrored soul
Laser can be easily avoided with follow/summon

ICC

LM

Tank will only move to tank position if it actually have the agro, it used to move there without agro and few of the bots would get killed since tank was unable to reach bot

LDW

Fixed edge case where tank would be target of shade and it would be unable to move away it from it due to tank position restrictions, now if Tank is targeted by shade it will be able to move and run away from it (in hc this could cause wipes since explosion is aoe + tons of dmg)

PP

When oozes spawn, tank will move towards green ooze spawn to keep raid near green ooze (faster kill) and far away from gas cloud (better chance to kill if abo didn't manage to slow it)
2025-05-06 21:10:16 +02:00
Type1Error
e48c3351d3 fix (#1179): resolve bot stuttering in BGs after player death (#1269)
Fixes #1179

This update fixes several issues related to bot behavior in battlegrounds when the player dies and releases spirit:

- Removed conflicting strategies (+stay / +follow): Bots were being told to follow and stay simultaneously each tick, causing oscillating movement ("stuttering"). The +stay strategy is now skipped in BGs to allow bots to continue objectives normally.

- Disabled follow while in BG: When the player died, bots attempted to return to the player repeatedly, abandoning objective-based movement. PlayerbotAI now sets -follow while in BGs to keep bots engaged with the objectives.

- Prevented dead bots from following ghosts: Previously, dead bots would follow ghost players away from the graveyard and potentially miss Rez. Bots now wait at the spirit healer when dead in BGs.

- Removed redundant strategy changes and messages in ReleaseSpiritAction as they no longer apply in battleground contexts.
2025-05-06 21:09:32 +02:00
1060 changed files with 32897 additions and 16946 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
DROP TABLE IF EXISTS `playerbot_account_keys`;
DROP TABLE IF EXISTS `playerbots_account_keys`;
CREATE TABLE `playerbot_account_keys` (
CREATE TABLE `playerbots_account_keys` (
`account_id` INT PRIMARY KEY,
`security_key` VARCHAR(255) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP

View File

@@ -1,6 +1,6 @@
DROP TABLE IF EXISTS `playerbot_account_links`;
DROP TABLE IF EXISTS `playerbots_account_links`;
CREATE TABLE `playerbot_account_links` (
CREATE TABLE `playerbots_account_links` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`account_id` INT NOT NULL,
`linked_account_id` INT NOT NULL,

View File

@@ -0,0 +1,8 @@
DROP TABLE IF EXISTS `playerbots_account_type`;
CREATE TABLE `playerbots_account_type` (
`account_id` int unsigned NOT NULL,
`account_type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0 = unassigned, 1 = RNDbot, 2 = AddClass',
`assignment_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`account_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Playerbot account type assignments';

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
DROP TABLE IF EXISTS `playerbot_account_keys`;
CREATE TABLE IF NOT EXISTS `playerbots_account_keys` (
`account_id` INT PRIMARY KEY,
`security_key` VARCHAR(255) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=INNODB DEFAULT CHARSET=latin1;
DROP TABLE IF EXISTS `playerbot_account_links`;
CREATE TABLE IF NOT EXISTS `playerbots_account_links` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`account_id` INT NOT NULL,
`linked_account_id` INT NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY `account_link` (`account_id`, `linked_account_id`)
) ENGINE=INNODB DEFAULT CHARSET=latin1;

View File

@@ -0,0 +1,9 @@
-- Create playerbots_account_type table for tracking accounts assignments
DROP TABLE IF EXISTS `playerbots_account_type`;
CREATE TABLE `playerbots_account_type` (
`account_id` int unsigned NOT NULL,
`account_type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0 = unassigned, 1 = RNDbot, 2 = AddClass',
`assignment_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`account_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Playerbot account type assignments';

View File

@@ -80,13 +80,16 @@ uint8 AiFactory::GetPlayerSpecTab(Player* bot)
switch (bot->getClass())
{
case CLASS_MAGE:
tab = 1;
tab = MAGE_TAB_FROST;
break;
case CLASS_PALADIN:
tab = 2;
tab = PALADIN_TAB_RETRIBUTION;
break;
case CLASS_PRIEST:
tab = 1;
tab = PRIEST_TAB_HOLY;
break;
case CLASS_WARLOCK:
tab = WARLOCK_TAB_DEMONOLOGY;
break;
}
@@ -302,22 +305,22 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
break;
case CLASS_MAGE:
if (tab == 0)
engine->addStrategiesNoInit("arcane", "arcane aoe", nullptr);
engine->addStrategiesNoInit("arcane", nullptr);
else if (tab == 1)
{
if (player->HasSpell(44614) /*Frostfire Bolt*/ && player->HasAura(15047) /*Ice Shards*/)
{
engine->addStrategiesNoInit("frostfire", "frostfire aoe", nullptr);
engine->addStrategiesNoInit("frostfire", nullptr);
}
else
{
engine->addStrategiesNoInit("fire", "fire aoe", nullptr);
engine->addStrategiesNoInit("fire", nullptr);
}
}
else
engine->addStrategiesNoInit("frost", "frost aoe", nullptr);
engine->addStrategiesNoInit("frost", nullptr);
engine->addStrategiesNoInit("dps", "dps assist", "cure", nullptr);
engine->addStrategiesNoInit("dps", "dps assist", "cure", "aoe", nullptr);
break;
case CLASS_WARRIOR:
if (tab == 2)
@@ -367,12 +370,14 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
}
break;
case CLASS_HUNTER:
engine->addStrategiesNoInit("dps", "aoe", "bdps", "dps assist", nullptr);
engine->addStrategy("dps debuff", false);
// if (tab == HUNTER_TAB_SURVIVAL)
// {
// engine->addStrategy("trap weave", false);
// }
if (tab == 0) // Beast Mastery
engine->addStrategiesNoInit("bm", nullptr);
else if (tab == 1) // Marksmanship
engine->addStrategiesNoInit("mm", nullptr);
else if (tab == 2) // Survival
engine->addStrategiesNoInit("surv", nullptr);
engine->addStrategiesNoInit("cc", "dps assist", "aoe", nullptr);
break;
case CLASS_ROGUE:
if (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY)
@@ -385,8 +390,16 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
}
break;
case CLASS_WARLOCK:
engine->addStrategiesNoInit("dps assist", "dps", "dps debuff", "aoe", nullptr);
if (tab == 0) // Affliction
engine->addStrategiesNoInit("affli", "curse of agony", nullptr);
else if (tab == 1) // Demonology
engine->addStrategiesNoInit("demo", "curse of agony", "meta melee", nullptr);
else if (tab == 2) // Destruction
engine->addStrategiesNoInit("destro", "curse of elements", nullptr);
engine->addStrategiesNoInit("cc", "dps assist", "aoe", nullptr);
break;
case CLASS_DEATH_KNIGHT:
if (tab == 0)
engine->addStrategiesNoInit("blood", "tank assist", nullptr);
@@ -407,7 +420,8 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
{
if (sPlayerbotAIConfig->autoSaveMana)
engine->addStrategy("save mana", false);
engine->addStrategy("healer dps", false);
if (!sPlayerbotAIConfig->IsRestrictedHealerDPSMap(player->GetMapId()))
engine->addStrategy("healer dps", false);
}
if (facade->IsRealPlayer() || sRandomPlayerbotMgr->IsRandomBot(player))
{
@@ -531,7 +545,7 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
nonCombatEngine->addStrategiesNoInit("bthreat", "tank assist", "barmor", nullptr);
if (player->GetLevel() >= 20)
{
nonCombatEngine->addStrategy("bstats", false);
nonCombatEngine->addStrategy("bhealth", false);
}
else
{
@@ -588,17 +602,17 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
case CLASS_WARLOCK:
if (tab == WARLOCK_TAB_AFFLICATION)
{
nonCombatEngine->addStrategiesNoInit("bmana", nullptr);
nonCombatEngine->addStrategiesNoInit("felhunter", "spellstone", nullptr);
}
else if (tab == WARLOCK_TAB_DEMONOLOGY)
{
nonCombatEngine->addStrategiesNoInit("bdps", nullptr);
nonCombatEngine->addStrategiesNoInit("felguard", "spellstone", nullptr);
}
else if (tab == WARLOCK_TAB_DESTRUCTION)
{
nonCombatEngine->addStrategiesNoInit("bhealth", nullptr);
nonCombatEngine->addStrategiesNoInit("imp", "firestone", nullptr);
}
nonCombatEngine->addStrategiesNoInit("dps assist", nullptr);
nonCombatEngine->addStrategiesNoInit("dps assist", "ss self", nullptr);
break;
case CLASS_DEATH_KNIGHT:
if (tab == 0)

View File

@@ -305,6 +305,49 @@ ItemIds ChatHelper::parseItems(std::string const text)
return itemIds;
}
ItemWithRandomProperty ChatHelper::parseItemWithRandomProperty(std::string const text)
{
ItemWithRandomProperty res;
size_t itemStart = text.find("Hitem:");
if (itemStart == std::string::npos)
return res;
itemStart += 6;
if (itemStart >= text.length())
return res;
size_t colonPos = text.find(':', itemStart);
if (colonPos == std::string::npos)
return res;
std::string itemIdStr = text.substr(itemStart, colonPos - itemStart);
res.itemId = atoi(itemIdStr.c_str());
std::vector<std::string> params;
size_t currentPos = colonPos + 1;
while (currentPos < text.length()) {
size_t nextColon = text.find(':', currentPos);
if (nextColon == std::string::npos) {
size_t hTag = text.find("|h", currentPos);
if (hTag != std::string::npos) {
params.push_back(text.substr(currentPos, hTag - currentPos));
}
break;
}
params.push_back(text.substr(currentPos, nextColon - currentPos));
currentPos = nextColon + 1;
}
if (params.size() >= 6) {
res.randomPropertyId = atoi(params[5].c_str());
}
return res;
}
std::string const ChatHelper::FormatQuest(Quest const* quest)
{
if (!quest)
@@ -382,7 +425,7 @@ std::string const ChatHelper::FormatSpell(SpellInfo const* spellInfo)
std::string const ChatHelper::FormatItem(ItemTemplate const* proto, uint32 count, uint32 total)
{
char color[32];
sprintf(color, "%x", ItemQualityColors[proto->Quality]);
snprintf(color, sizeof(color), "%x", ItemQualityColors[proto->Quality]);
std::string itemName;
const ItemLocale* locale = sObjectMgr->GetItemLocale(proto->ItemId);
@@ -409,7 +452,7 @@ std::string const ChatHelper::FormatItem(ItemTemplate const* proto, uint32 count
std::string const ChatHelper::FormatQItem(uint32 itemId)
{
char color[32];
sprintf(color, "%x", ItemQualityColors[0]);
snprintf(color, sizeof(color), "%x", ItemQualityColors[0]);
std::ostringstream out;
out << "|c" << color << "|Hitem:" << itemId << ":0:0:0:0:0:0:0"

View File

@@ -25,6 +25,11 @@ struct ItemTemplate;
typedef std::set<uint32> ItemIds;
typedef std::set<uint32> SpellIds;
struct ItemWithRandomProperty {
uint32 itemId{0};
int32 randomPropertyId{0};
};
class ChatHelper : public PlayerbotAIAware
{
public:
@@ -33,6 +38,7 @@ public:
static std::string const formatMoney(uint32 copper);
static uint32 parseMoney(std::string const text);
static ItemIds parseItems(std::string const text);
static ItemWithRandomProperty parseItemWithRandomProperty(std::string const text);
uint32 parseSpell(std::string const text);
static std::string parseValue(const std::string& type, const std::string& text);

View File

@@ -525,6 +525,11 @@ uint32 GuildTaskMgr::GetMaxItemTaskCount(uint32 itemId)
bool GuildTaskMgr::IsGuildTaskItem(uint32 itemId, uint32 guildId)
{
if (!sPlayerbotAIConfig->guildTaskEnabled)
{
return 0;
}
uint32 value = 0;
PlayerbotsDatabasePreparedStatement* stmt =
@@ -548,6 +553,11 @@ bool GuildTaskMgr::IsGuildTaskItem(uint32 itemId, uint32 guildId)
std::map<uint32, uint32> GuildTaskMgr::GetTaskValues(uint32 owner, std::string const type,
[[maybe_unused]] uint32* validIn /* = nullptr */)
{
if (!sPlayerbotAIConfig->guildTaskEnabled)
{
return std::map<uint32, uint32>();
}
std::map<uint32, uint32> results;
PlayerbotsDatabasePreparedStatement* stmt =
@@ -576,6 +586,11 @@ std::map<uint32, uint32> GuildTaskMgr::GetTaskValues(uint32 owner, std::string c
uint32 GuildTaskMgr::GetTaskValue(uint32 owner, uint32 guildId, std::string const type, [[maybe_unused]] uint32* validIn /* = nullptr */)
{
if (!sPlayerbotAIConfig->guildTaskEnabled)
{
return 0;
}
uint32 value = 0;
PlayerbotsDatabasePreparedStatement* stmt =

View File

@@ -6,6 +6,8 @@
#include "LootObjectStack.h"
#include "LootMgr.h"
#include "Object.h"
#include "ObjectAccessor.h"
#include "Playerbots.h"
#include "Unit.h"
@@ -85,7 +87,7 @@ void LootObject::Refresh(Player* bot, ObjectGuid lootGUID)
bool hasAnyQuestItems = false;
GameObjectQuestItemList const* items = sObjectMgr->GetGameObjectQuestItemList(go->GetEntry());
for (int i = 0; i < MAX_GAMEOBJECT_QUEST_ITEMS; i++)
for (size_t i = 0; i < MAX_GAMEOBJECT_QUEST_ITEMS; i++)
{
if (!items || i >= items->size())
break;
@@ -287,7 +289,7 @@ bool LootObject::IsLootPossible(Player* bot)
if (reqItem && !bot->HasItemCount(reqItem, 1))
return false;
if (abs(worldObj->GetPositionZ() - bot->GetPositionZ()) > INTERACTION_DISTANCE -2.0f)
if (abs(worldObj->GetPositionZ() - bot->GetPositionZ()) > INTERACTION_DISTANCE - 2.0f)
return false;
Creature* creature = botAI->GetCreature(guid);
@@ -297,6 +299,11 @@ bool LootObject::IsLootPossible(Player* bot)
return false;
}
// Prevent bot from running to chests that are unlootable (e.g. Gunship Armory before completing the event)
GameObject* go = botAI->GetGameObject(guid);
if (go && go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_INTERACT_COND | GO_FLAG_NOT_SELECTABLE))
return false;
if (skillId == SKILL_NONE)
return true;
@@ -312,29 +319,17 @@ bool LootObject::IsLootPossible(Player* bot)
uint32 skillValue = uint32(bot->GetSkillValue(skillId));
if (reqSkillValue > skillValue)
return false;
if (skillId == SKILL_MINING &&
!bot->HasItemCount(756, 1) &&
!bot->HasItemCount(778, 1) &&
!bot->HasItemCount(1819, 1) &&
!bot->HasItemCount(1893, 1) &&
!bot->HasItemCount(1959, 1) &&
!bot->HasItemCount(2901, 1) &&
!bot->HasItemCount(9465, 1) &&
!bot->HasItemCount(20723, 1) &&
!bot->HasItemCount(40772, 1) &&
!bot->HasItemCount(40892, 1) &&
!bot->HasItemCount(40893, 1))
if (skillId == SKILL_MINING && !bot->HasItemCount(756, 1) && !bot->HasItemCount(778, 1) &&
!bot->HasItemCount(1819, 1) && !bot->HasItemCount(1893, 1) && !bot->HasItemCount(1959, 1) &&
!bot->HasItemCount(2901, 1) && !bot->HasItemCount(9465, 1) && !bot->HasItemCount(20723, 1) &&
!bot->HasItemCount(40772, 1) && !bot->HasItemCount(40892, 1) && !bot->HasItemCount(40893, 1))
{
return false; // Bot is missing a mining pick
}
if (skillId == SKILL_SKINNING &&
!bot->HasItemCount(7005, 1) &&
!bot->HasItemCount(40772, 1) &&
!bot->HasItemCount(40893, 1) &&
!bot->HasItemCount(12709, 1) &&
!bot->HasItemCount(19901, 1))
if (skillId == SKILL_SKINNING && !bot->HasItemCount(7005, 1) && !bot->HasItemCount(40772, 1) &&
!bot->HasItemCount(40893, 1) && !bot->HasItemCount(12709, 1) && !bot->HasItemCount(19901, 1))
{
return false; // Bot is missing a skinning knife
}
@@ -371,42 +366,45 @@ void LootObjectStack::Clear() { availableLoot.clear(); }
bool LootObjectStack::CanLoot(float maxDistance)
{
std::vector<LootObject> ordered = OrderByDistance(maxDistance);
return !ordered.empty();
LootObject nearest = GetNearest(maxDistance);
return !nearest.IsEmpty();
}
LootObject LootObjectStack::GetLoot(float maxDistance)
{
std::vector<LootObject> ordered = OrderByDistance(maxDistance);
return ordered.empty() ? LootObject() : *ordered.begin();
LootObject nearest = GetNearest(maxDistance);
return nearest.IsEmpty() ? LootObject() : nearest;
}
std::vector<LootObject> LootObjectStack::OrderByDistance(float maxDistance)
LootObject LootObjectStack::GetNearest(float maxDistance)
{
availableLoot.shrink(time(nullptr) - 30);
std::map<float, LootObject> sortedMap;
LootObject nearest;
float nearestDistance = std::numeric_limits<float>::max();
LootTargetList safeCopy(availableLoot);
for (LootTargetList::iterator i = safeCopy.begin(); i != safeCopy.end(); i++)
{
ObjectGuid guid = i->guid;
LootObject lootObject(bot, guid);
if (!lootObject.IsLootPossible(bot)) // Ensure loot object is valid
continue;
WorldObject* worldObj = lootObject.GetWorldObject(bot);
if (!worldObj) // Prevent null pointer dereference
{
WorldObject* worldObj = ObjectAccessor::GetWorldObject(*bot, guid);
if (!worldObj)
continue;
}
float distance = bot->GetDistance(worldObj);
if (!maxDistance || distance <= maxDistance)
sortedMap[distance] = lootObject;
if (distance >= nearestDistance || (maxDistance && distance > maxDistance))
continue;
LootObject lootObject(bot, guid);
if (!lootObject.IsLootPossible(bot))
continue;
nearestDistance = distance;
nearest = lootObject;
}
std::vector<LootObject> result;
for (auto& [_, lootObject] : sortedMap)
result.push_back(lootObject);
return result;
}
return nearest;
}

View File

@@ -29,6 +29,7 @@ public:
LootObject() : skillId(0), reqSkillValue(0), reqItem(0) {}
LootObject(Player* bot, ObjectGuid guid);
LootObject(LootObject const& other);
LootObject& operator=(LootObject const& other) = default;
bool IsEmpty() { return !guid; }
bool IsLootPossible(Player* bot);
@@ -77,7 +78,7 @@ public:
LootObject GetLoot(float maxDistance = 0);
private:
std::vector<LootObject> OrderByDistance(float maxDistance = 0);
LootObject GetNearest(float maxDistance = 0);
Player* bot;
LootTargetList availableLoot;

View File

@@ -19,6 +19,7 @@
#include "CreatureData.h"
#include "EmoteAction.h"
#include "Engine.h"
#include "EventProcessor.h"
#include "ExternalEventHelper.h"
#include "GameObjectData.h"
#include "GameTime.h"
@@ -156,7 +157,7 @@ PlayerbotAI::PlayerbotAI(Player* bot)
masterIncomingPacketHandlers.AddHandler(CMSG_GAMEOBJ_USE, "use game object");
masterIncomingPacketHandlers.AddHandler(CMSG_AREATRIGGER, "area trigger");
// masterIncomingPacketHandlers.AddHandler(CMSG_GAMEOBJ_USE, "use game object");
masterIncomingPacketHandlers.AddHandler(CMSG_LOOT_ROLL, "loot roll");
// masterIncomingPacketHandlers.AddHandler(CMSG_LOOT_ROLL, "loot roll");
masterIncomingPacketHandlers.AddHandler(CMSG_GOSSIP_HELLO, "gossip hello");
masterIncomingPacketHandlers.AddHandler(CMSG_QUESTGIVER_HELLO, "gossip hello");
masterIncomingPacketHandlers.AddHandler(CMSG_ACTIVATETAXI, "activate taxi");
@@ -211,7 +212,8 @@ PlayerbotAI::PlayerbotAI(Player* bot)
masterIncomingPacketHandlers.AddHandler(CMSG_PUSHQUESTTOPARTY, "quest share");
botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_COMPLETE, "quest update complete");
botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_ADD_KILL, "quest update add kill");
// botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_ADD_ITEM, "quest update add item"); // SMSG_QUESTUPDATE_ADD_ITEM no longer used
// SMSG_QUESTUPDATE_ADD_ITEM no longer used
// botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_ADD_ITEM, "quest update add item");
botOutgoingPacketHandlers.AddHandler(SMSG_QUEST_CONFIRM_ACCEPT, "confirm quest");
}
@@ -720,6 +722,7 @@ void PlayerbotAI::HandleTeleportAck()
// SetNextCheckDelay(urand(2000, 5000));
if (sPlayerbotAIConfig->applyInstanceStrategies)
ApplyInstanceStrategies(bot->GetMapId(), true);
EvaluateHealerDpsStrategy();
Reset(true);
}
@@ -796,6 +799,7 @@ bool PlayerbotAI::IsAllowedCommand(std::string const text)
unsecuredCommands.insert("sendmail");
unsecuredCommands.insert("invite");
unsecuredCommands.insert("leave");
unsecuredCommands.insert("lfg");
unsecuredCommands.insert("rpg status");
}
@@ -1046,6 +1050,9 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
default:
return;
}
if (chanName == "World")
return;
// do not reply to self but always try to reply to real player
if (guid1 != bot->GetGUID())
@@ -1289,11 +1296,6 @@ void PlayerbotAI::DoNextAction(bool min)
return;
}
if (bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
{
SetNextCheckDelay(sPlayerbotAIConfig->passiveDelay);
return;
}
// Change engine if just died
bool isBotAlive = bot->IsAlive();
@@ -1423,12 +1425,21 @@ void PlayerbotAI::DoNextAction(bool min)
master = newMaster;
botAI->SetMaster(newMaster);
botAI->ResetStrategies();
botAI->ChangeStrategy("+follow", BOT_STATE_NON_COMBAT);
if (botAI->GetMaster() == botAI->GetGroupMaster())
botAI->TellMaster("Hello, I follow you!");
if (!bot->InBattleground())
{
botAI->ChangeStrategy("+follow", BOT_STATE_NON_COMBAT);
if (botAI->GetMaster() == botAI->GetGroupMaster())
botAI->TellMaster("Hello, I follow you!");
else
botAI->TellMaster(!urand(0, 2) ? "Hello!" : "Hi!");
}
else
botAI->TellMaster(!urand(0, 2) ? "Hello!" : "Hi!");
{
// we're in a battleground, stay with the pack and focus on objective
botAI->ChangeStrategy("-follow", BOT_STATE_NON_COMBAT);
}
}
}
@@ -2347,7 +2358,6 @@ std::string PlayerbotAI::GetLocalizedCreatureName(uint32 entry)
return name;
}
std::string PlayerbotAI::GetLocalizedGameObjectName(uint32 entry)
{
std::string name;
@@ -2926,6 +2936,18 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell,
return false;
}
if ((bot->GetShapeshiftForm() == FORM_FLIGHT || bot->GetShapeshiftForm() == FORM_FLIGHT_EPIC) && !bot->IsInCombat())
{
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster()))
{
LOG_DEBUG(
"playerbots",
"Can cast spell failed. In flight form (not in combat). - target name: {}, spellid: {}, bot name: {}",
target->GetName(), spellid, bot->GetName());
}
return false;
}
uint32 CastingTime = !spellInfo->IsChanneled() ? spellInfo->CalcCastTime(bot) : spellInfo->GetDuration();
// bool interruptOnMove = spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT;
if ((CastingTime || spellInfo->IsAutoRepeatRangedSpell()) && bot->isMoving())
@@ -2940,14 +2962,19 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell,
if (!itemTarget)
{
// Exception for Deep Freeze (44572) - allow cast for damage on immune targets (e.g., bosses)
if (target->IsImmunedToSpell(spellInfo))
{
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster()))
if (spellid != 44572) // Deep Freeze
{
LOG_DEBUG("playerbots", "target is immuned to spell - target name: {}, spellid: {}, bot name: {}",
target->GetName(), spellid, bot->GetName());
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster()))
{
LOG_DEBUG("playerbots", "target is immuned to spell - target name: {}, spellid: {}, bot name: {}",
target->GetName(), spellid, bot->GetName());
}
return false;
}
return false;
// Otherwise, allow Deep Freeze even if immune
}
if (bot != target && sServerFacade->GetDistance2d(bot, target) > sPlayerbotAIConfig->sightDistance)
@@ -3143,22 +3170,41 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (pet && pet->HasSpell(spellId))
{
bool autocast = false;
for (unsigned int& m_autospell : pet->m_autospells)
// List of spell IDs for which we do NOT want to toggle auto-cast or send message
// We are excluding Spell Lock and Devour Magic because they are casted in the GenericWarlockStrategy
// Without this exclusion, the skill would be togged for auto-cast and the player would
// be spammed with messages about enabling/disabling auto-cast
switch (spellId)
{
if (m_autospell == spellId)
{
autocast = true;
case 19244: // Spell Lock rank 1
case 19647: // Spell Lock rank 2
case 19505: // Devour Magic rank 1
case 19731: // Devour Magic rank 2
case 19734: // Devour Magic rank 3
case 19736: // Devour Magic rank 4
case 27276: // Devour Magic rank 5
case 27277: // Devour Magic rank 6
case 48011: // Devour Magic rank 7
// No message - just break out of the switch and let normal cast logic continue
break;
}
}
default:
bool autocast = false;
for (unsigned int& m_autospell : pet->m_autospells)
{
if (m_autospell == spellId)
{
autocast = true;
break;
}
}
pet->ToggleAutocast(spellInfo, !autocast);
std::ostringstream out;
out << (autocast ? "|cffff0000|Disabling" : "|cFF00ff00|Enabling") << " pet auto-cast for ";
out << chatHelper.FormatSpell(spellInfo);
TellMaster(out);
return true;
pet->ToggleAutocast(spellInfo, !autocast);
std::ostringstream out;
out << (autocast ? "|cffff0000|Disabling" : "|cFF00ff00|Enabling") << " pet auto-cast for ";
out << chatHelper.FormatSpell(spellInfo);
TellMaster(out);
return true;
}
}
// aiObjectContext->GetValue<LastMovement&>("last movement")->Get().Set(nullptr);
@@ -3300,13 +3346,14 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
std::ostringstream out;
out << "Spell cast failed - ";
out << "Spell ID: " << spellId << " (" << ChatHelper::FormatSpell(spellInfo) << "), ";
out << "Error Code: " << static_cast<int>(result) << " (0x" << std::hex << static_cast<int>(result) << std::dec << "), ";
out << "Error Code: " << static_cast<int>(result) << " (0x" << std::hex << static_cast<int>(result)
<< std::dec << "), ";
out << "Bot: " << bot->GetName() << ", ";
// Check spell target type
if (targets.GetUnitTarget())
{
out << "Target: Unit (" << targets.GetUnitTarget()->GetName()
out << "Target: Unit (" << targets.GetUnitTarget()->GetName()
<< ", Low GUID: " << targets.GetUnitTarget()->GetGUID().GetCounter()
<< ", High GUID: " << static_cast<uint32>(targets.GetUnitTarget()->GetGUID().GetHigh()) << "), ";
}
@@ -3322,7 +3369,7 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
out << "Target: Item (Low GUID: " << targets.GetItemTarget()->GetGUID().GetCounter()
<< ", High GUID: " << static_cast<uint32>(targets.GetItemTarget()->GetGUID().GetHigh()) << "), ";
}
// Check if bot is in trade mode
if (bot->GetTradeData())
{
@@ -3330,7 +3377,7 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
Item* tradeItem = bot->GetTradeData()->GetTraderData()->GetItem(TRADE_SLOT_NONTRADED);
if (tradeItem)
{
out << "Trade Item: " << tradeItem->GetEntry()
out << "Trade Item: " << tradeItem->GetEntry()
<< " (Low GUID: " << tradeItem->GetGUID().GetCounter()
<< ", High GUID: " << static_cast<uint32>(tradeItem->GetGUID().GetHigh()) << "), ";
}
@@ -3343,7 +3390,7 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
{
out << "Trade Mode: Inactive, ";
}
TellMasterNoFacing(out);
}
@@ -3923,15 +3970,37 @@ bool IsAlliance(uint8 race)
bool PlayerbotAI::HasRealPlayerMaster()
{
if (master)
// if (master)
// {
// PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(master);
// return !masterBotAI || masterBotAI->IsRealPlayer();
// }
//
// return false;
// Removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
/* 1) The "master" pointer can be null if the bot was created
without a master player or if the master was just removed. */
if (!master)
return false;
/* 2) Is the master player still present in the world?
If FindPlayer fails, we invalidate "master" and stop here. */
if (!ObjectAccessor::FindPlayer(master->GetGUID()))
{
PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(master);
return !masterBotAI || masterBotAI->IsRealPlayer();
master = nullptr; // avoids repeating the check on the next tick
return false;
}
return false;
/* 3) If the master is a bot, we check that it is itself controlled
by a real player. Otherwise, it's already a real player → true. */
if (PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(master))
return masterBotAI->IsRealPlayer(); // bot controlled by a player?
return true; // master = real player
}
bool PlayerbotAI::HasActivePlayerMaster() { return master && !GET_PLAYERBOT_AI(master); }
bool PlayerbotAI::IsAlt() { return HasRealPlayerMaster() && !sRandomPlayerbotMgr->IsRandomBot(bot); }
@@ -4282,7 +4351,7 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
if (!player || !player->IsInWorld())
continue;
Player* connectedPlayer = ObjectAccessor::FindPlayer(player->GetGUID());
Player* connectedPlayer = ObjectAccessor::FindPlayer(player->GetGUID());
if (!connectedPlayer)
continue;
@@ -4354,26 +4423,28 @@ bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow)
uint32 PlayerbotAI::AutoScaleActivity(uint32 mod)
{
// Current max server update time (ms), and the configured floor/ceiling values for bot scaling
uint32 maxDiff = sWorldUpdateTime.GetMaxUpdateTimeOfCurrentTable();
uint32 diffLimitFloor = sPlayerbotAIConfig->botActiveAloneSmartScaleDiffLimitfloor;
uint32 diffLimitCeiling = sPlayerbotAIConfig->botActiveAloneSmartScaleDiffLimitCeiling;
double spreadSize = (double)(diffLimitCeiling - diffLimitFloor) / 6;
// apply scaling
if (diffLimitCeiling <= diffLimitFloor)
{
// Perfrom binary decision if ceiling <= floor: Either all bots are active or none are
return (maxDiff > diffLimitCeiling) ? 0 : mod;
}
if (maxDiff > diffLimitCeiling)
return 0;
if (maxDiff > diffLimitFloor + (4 * spreadSize))
return (mod * 1) / 10;
if (maxDiff > diffLimitFloor + (3 * spreadSize))
return (mod * 3) / 10;
if (maxDiff > diffLimitFloor + (2 * spreadSize))
return (mod * 5) / 10;
if (maxDiff > diffLimitFloor + (1 * spreadSize))
return (mod * 7) / 10;
if (maxDiff > diffLimitFloor)
return (mod * 9) / 10;
return mod;
if (maxDiff <= diffLimitFloor)
return mod;
// Calculate lag progress from floor to ceiling (0 to 1)
double lagProgress = (maxDiff - diffLimitFloor) / (double)(diffLimitCeiling - diffLimitFloor);
// Apply the percentage of active bots (the complement of lag progress) to the mod value
return static_cast<uint32>(mod * (1 - lagProgress));
}
bool PlayerbotAI::IsOpposing(Player* player) { return IsOpposing(player->getRace(), bot->getRace()); }
@@ -4398,12 +4469,51 @@ void PlayerbotAI::RemoveShapeshift()
// RemoveAura("tree of life");
}
// NOTE : function rewritten as flags "withBags" and "withBank" not used, and _fillGearScoreData sometimes attribute
// one-hand/2H Weapon in wrong slots
// Mirrors Blizzards GetAverageItemLevel rules :
// https://wowpedia.fandom.com/wiki/API_GetAverageItemLevel
uint32 PlayerbotAI::GetEquipGearScore(Player* player)
{
constexpr uint8 TOTAL_SLOTS = 17; // every slot except Body & Tabard
uint32 sumLevel = 0;
/* ---------- 0. Detect “ignore off-hand” situations --------- */
Item* main = player->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
Item* off = player->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
bool ignoreOffhand = false; // true → divisor = 16
if (main)
{
bool twoHand = (main->GetTemplate()->InventoryType == INVTYPE_2HWEAPON);
if (twoHand && !player->HasAura(SPELL_TITAN_GRIP))
ignoreOffhand = true; // classic 2-hander
}
else if (!off) // both hands empty
ignoreOffhand = true;
/* ---------- 1. Sum up item-levels -------------------------- */
for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot)
{
if (slot == EQUIPMENT_SLOT_BODY || slot == EQUIPMENT_SLOT_TABARD)
continue; // Blizzard never counts these
if (ignoreOffhand && slot == EQUIPMENT_SLOT_OFFHAND)
continue; // skip off-hand in 2-H case
if (Item* it = player->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
sumLevel += it->GetTemplate()->ItemLevel; // missing items add 0
}
/* ---------- 2. Divide by 17 or 16 -------------------------- */
const uint8 divisor = ignoreOffhand ? TOTAL_SLOTS - 1 : TOTAL_SLOTS; // 16 or 17
return sumLevel / divisor;
}
// NOTE : function rewritten as flags "withBags" and "withBank" not used, and _fillGearScoreData sometimes attribute
// one-hand/2H Weapon in wrong slots
/*uint32 PlayerbotAI::GetEquipGearScore(Player* player)
{
// This function aims to calculate the equipped gear score
uint32 sum = 0;
uint8 count = EQUIPMENT_SLOT_END - 2; // ignore body and tabard slots
uint8 mh_type = 0;
@@ -4412,21 +4522,21 @@ uint32 PlayerbotAI::GetEquipGearScore(Player* player)
{
Item* item =player->GetItemByPos(INVENTORY_SLOT_BAG_0, i);
if (item && i != EQUIPMENT_SLOT_BODY && i != EQUIPMENT_SLOT_TABARD)
{
{
ItemTemplate const* proto = item->GetTemplate();
sum += proto->ItemLevel;
// If character is not warfury and have 2 hand weapon equipped, main hand will be counted twice
if (i == SLOT_MAIN_HAND)
mh_type = item->GetTemplate()->InventoryType;
if (!player->HasAura(SPELL_TITAN_GRIP) && mh_type == INVTYPE_2HWEAPON && i == SLOT_MAIN_HAND)
sum += item->GetTemplate()->ItemLevel;
}
}
}
uint32 gs = uint32(sum / count);
return gs;
}
}*/
/*uint32 PlayerbotAI::GetEquipGearScore(Player* player, bool withBags, bool withBank)
{
@@ -4659,7 +4769,7 @@ void PlayerbotAI::_fillGearScoreData(Player* player, Item* item, std::vector<uin
case INVTYPE_SHOULDERS:
(*gearScore)[EQUIPMENT_SLOT_SHOULDERS] = std::max((*gearScore)[EQUIPMENT_SLOT_SHOULDERS], level);
break;
case INVTYPE_BODY: //Shouldn't be considered when calculating average ilevel
case INVTYPE_BODY: // Shouldn't be considered when calculating average ilevel
(*gearScore)[EQUIPMENT_SLOT_BODY] = std::max((*gearScore)[EQUIPMENT_SLOT_BODY], level);
break;
case INVTYPE_CHEST:
@@ -5017,13 +5127,13 @@ Item* PlayerbotAI::FindAmmo() const
}
// Find Consumable
Item* PlayerbotAI::FindConsumable(uint32 displayId) const
Item* PlayerbotAI::FindConsumable(uint32 itemId) const
{
return FindItemInInventory(
[displayId](ItemTemplate const* pItemProto) -> bool
[itemId](ItemTemplate const* pItemProto) -> bool
{
return (pItemProto->Class == ITEM_CLASS_CONSUMABLE || pItemProto->Class == ITEM_CLASS_TRADE_GOODS) &&
pItemProto->DisplayInfoID == displayId;
pItemProto->ItemId == itemId;
});
}
@@ -5037,80 +5147,80 @@ Item* PlayerbotAI::FindBandage() const
Item* PlayerbotAI::FindOpenableItem() const
{
return FindItemInInventory([this](ItemTemplate const* itemTemplate) -> bool
{
return (itemTemplate->Flags & ITEM_FLAG_HAS_LOOT) &&
(itemTemplate->LockID == 0 || !this->bot->GetItemByEntry(itemTemplate->ItemId)->IsLocked());
});
return FindItemInInventory(
[this](ItemTemplate const* itemTemplate) -> bool
{
return (itemTemplate->Flags & ITEM_FLAG_HAS_LOOT) &&
(itemTemplate->LockID == 0 || !this->bot->GetItemByEntry(itemTemplate->ItemId)->IsLocked());
});
}
Item* PlayerbotAI::FindLockedItem() const
{
return FindItemInInventory([this](ItemTemplate const* itemTemplate) -> bool
{
if (!this->bot->HasSkill(SKILL_LOCKPICKING)) // Ensure bot has Lockpicking skill
return false;
if (itemTemplate->LockID == 0) // Ensure the item is actually locked
return false;
Item* item = this->bot->GetItemByEntry(itemTemplate->ItemId);
if (!item || !item->IsLocked()) // Ensure item instance is locked
return false;
// Check if bot has enough Lockpicking skill
LockEntry const* lockInfo = sLockStore.LookupEntry(itemTemplate->LockID);
if (!lockInfo)
return false;
for (uint8 j = 0; j < 8; ++j)
return FindItemInInventory(
[this](ItemTemplate const* itemTemplate) -> bool
{
if (lockInfo->Type[j] == LOCK_KEY_SKILL)
if (!this->bot->HasSkill(SKILL_LOCKPICKING)) // Ensure bot has Lockpicking skill
return false;
if (itemTemplate->LockID == 0) // Ensure the item is actually locked
return false;
Item* item = this->bot->GetItemByEntry(itemTemplate->ItemId);
if (!item || !item->IsLocked()) // Ensure item instance is locked
return false;
// Check if bot has enough Lockpicking skill
LockEntry const* lockInfo = sLockStore.LookupEntry(itemTemplate->LockID);
if (!lockInfo)
return false;
for (uint8 j = 0; j < 8; ++j)
{
uint32 skillId = SkillByLockType(LockType(lockInfo->Index[j]));
if (skillId == SKILL_LOCKPICKING)
if (lockInfo->Type[j] == LOCK_KEY_SKILL)
{
uint32 requiredSkill = lockInfo->Skill[j];
uint32 botSkill = this->bot->GetSkillValue(SKILL_LOCKPICKING);
return botSkill >= requiredSkill;
uint32 skillId = SkillByLockType(LockType(lockInfo->Index[j]));
if (skillId == SKILL_LOCKPICKING)
{
uint32 requiredSkill = lockInfo->Skill[j];
uint32 botSkill = this->bot->GetSkillValue(SKILL_LOCKPICKING);
return botSkill >= requiredSkill;
}
}
}
}
return false;
});
return false;
});
}
static const uint32 uPriorizedSharpStoneIds[8] = {ADAMANTITE_SHARPENING_DISPLAYID, FEL_SHARPENING_DISPLAYID,
ELEMENTAL_SHARPENING_DISPLAYID, DENSE_SHARPENING_DISPLAYID,
SOLID_SHARPENING_DISPLAYID, HEAVY_SHARPENING_DISPLAYID,
COARSE_SHARPENING_DISPLAYID, ROUGH_SHARPENING_DISPLAYID};
static const uint32 uPriorizedWeightStoneIds[7] = {ADAMANTITE_WEIGHTSTONE_DISPLAYID, FEL_WEIGHTSTONE_DISPLAYID,
DENSE_WEIGHTSTONE_DISPLAYID, SOLID_WEIGHTSTONE_DISPLAYID,
HEAVY_WEIGHTSTONE_DISPLAYID, COARSE_WEIGHTSTONE_DISPLAYID,
ROUGH_WEIGHTSTONE_DISPLAYID};
/**
* FindStoneFor()
* return Item* Returns sharpening/weight stone item eligible to enchant a bot weapon
*
* params:weapon Item* the weap<61>n the function should search and return a enchanting item for
* return nullptr if no relevant item is found in bot inventory, else return a sharpening or weight
* stone based on the weapon subclass
*
*/
Item* PlayerbotAI::FindStoneFor(Item* weapon) const
{
if (!weapon)
return nullptr;
const ItemTemplate* item_template = weapon->GetTemplate();
if (!item_template)
return nullptr;
static const std::vector<uint32_t> uPrioritizedSharpStoneIds = {
ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, ELEMENTAL_SHARPENING_STONE, DENSE_SHARPENING_STONE,
SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE
};
static const std::vector<uint32_t> uPrioritizedWeightStoneIds = {
ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE,
HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE
};
Item* stone = nullptr;
ItemTemplate const* pProto = weapon->GetTemplate();
if (pProto && (pProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD || pProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2 ||
pProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE || pProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 ||
pProto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER))
pProto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER || pProto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM))
{
for (uint8 i = 0; i < std::size(uPriorizedSharpStoneIds); ++i)
for (uint8 i = 0; i < std::size(uPrioritizedSharpStoneIds); ++i)
{
stone = FindConsumable(uPriorizedSharpStoneIds[i]);
stone = FindConsumable(uPrioritizedSharpStoneIds[i]);
if (stone)
{
return stone;
@@ -5118,11 +5228,12 @@ Item* PlayerbotAI::FindStoneFor(Item* weapon) const
}
}
else if (pProto &&
(pProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE || pProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2))
(pProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE || pProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2 ||
pProto->SubClass == ITEM_SUBCLASS_WEAPON_STAFF || pProto->SubClass == ITEM_SUBCLASS_WEAPON_FIST))
{
for (uint8 i = 0; i < std::size(uPriorizedWeightStoneIds); ++i)
for (uint8 i = 0; i < std::size(uPrioritizedWeightStoneIds); ++i)
{
stone = FindConsumable(uPriorizedWeightStoneIds[i]);
stone = FindConsumable(uPrioritizedWeightStoneIds[i]);
if (stone)
{
return stone;
@@ -5135,6 +5246,7 @@ Item* PlayerbotAI::FindStoneFor(Item* weapon) const
Item* PlayerbotAI::FindOilFor(Item* weapon) const
{
if (!weapon)
return nullptr;
@@ -5142,35 +5254,60 @@ Item* PlayerbotAI::FindOilFor(Item* weapon) const
if (!item_template)
return nullptr;
// static const will only get created once whatever the call amout
static const std::vector<uint32_t> uPriorizedWizardOilIds = {
MINOR_WIZARD_OIL, MINOR_MANA_OIL, LESSER_WIZARD_OIL, LESSER_MANA_OIL, BRILLIANT_WIZARD_OIL,
BRILLIANT_MANA_OIL, WIZARD_OIL, SUPERIOR_MANA_OIL, SUPERIOR_WIZARD_OIL};
static const std::vector<uint32_t> uPrioritizedWizardOilIds = {
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL,
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
// static const will only get created once whatever the call amout
static const std::vector<uint32_t> uPriorizedManaOilIds = {
MINOR_MANA_OIL, MINOR_WIZARD_OIL, LESSER_MANA_OIL, LESSER_WIZARD_OIL, BRILLIANT_MANA_OIL,
BRILLIANT_WIZARD_OIL, SUPERIOR_MANA_OIL, WIZARD_OIL, SUPERIOR_WIZARD_OIL};
static const std::vector<uint32_t> uPrioritizedManaOilIds = {
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL,
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
Item* oil = nullptr;
if (item_template->SubClass == ITEM_SUBCLASS_WEAPON_SWORD ||
item_template->SubClass == ITEM_SUBCLASS_WEAPON_STAFF || item_template->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER)
int botClass = bot->getClass();
int specTab = AiFactory::GetPlayerSpecTab(bot);
const std::vector<uint32_t>* prioritizedOils = nullptr;
switch (botClass)
{
for (const auto& id : uPriorizedWizardOilIds)
{
oil = FindConsumable(id);
if (oil)
return oil;
}
case CLASS_PRIEST:
prioritizedOils = (specTab == 2) ? &uPrioritizedWizardOilIds : &uPrioritizedManaOilIds;
break;
case CLASS_MAGE:
prioritizedOils = &uPrioritizedWizardOilIds;
break;
case CLASS_DRUID:
if (specTab == 0) // Balance
prioritizedOils = &uPrioritizedWizardOilIds;
else if (specTab == 1) // Feral
prioritizedOils = nullptr;
else // Restoration (specTab == 2) or any other/unspecified spec
prioritizedOils = &uPrioritizedManaOilIds;
break;
case CLASS_HUNTER:
prioritizedOils = &uPrioritizedManaOilIds;
break;
case CLASS_PALADIN:
if (specTab == 1) // Protection
prioritizedOils = &uPrioritizedWizardOilIds;
else if (specTab == 2) // Retribution
prioritizedOils = nullptr;
else // Holy (specTab == 0) or any other/unspecified spec
prioritizedOils = &uPrioritizedManaOilIds;
break;
default:
prioritizedOils = &uPrioritizedManaOilIds;
break;
}
else if (item_template->SubClass == ITEM_SUBCLASS_WEAPON_MACE ||
item_template->SubClass == ITEM_SUBCLASS_WEAPON_MACE2)
if (prioritizedOils)
{
for (const auto& id : uPriorizedManaOilIds)
for (const auto& id : *prioritizedOils)
{
oil = FindConsumable(id);
if (oil)
{
return oil;
}
}
}
@@ -6001,6 +6138,35 @@ ChatChannelSource PlayerbotAI::GetChatChannelSource(Player* bot, uint32 type, st
return ChatChannelSource::SRC_UNDEFINED;
}
bool PlayerbotAI::CheckLocationDistanceByLevel(Player* player, const WorldLocation& loc, bool fromStartUp)
{
if (player->GetLevel() > 16)
return true;
float dis = 0.0f;
if (fromStartUp)
{
PlayerInfo const* pInfo = sObjectMgr->GetPlayerInfo(player->getRace(true), player->getClass());
if (loc.GetMapId() != pInfo->mapId)
return false;
dis = loc.GetExactDist(pInfo->positionX, pInfo->positionY, pInfo->positionZ);
}
else
{
if (loc.GetMapId() != player->GetMapId())
return false;
dis = loc.GetExactDist(player);
}
float bound = 10000.0f;
if (player->GetLevel() <= 4)
bound = 500.0f;
else if (player->GetLevel() <= 10)
bound = 2500.0f;
return dis <= bound;
}
std::vector<const Quest*> PlayerbotAI::GetAllCurrentQuests()
{
std::vector<const Quest*> result;
@@ -6270,3 +6436,33 @@ SpellFamilyNames PlayerbotAI::Class2SpellFamilyName(uint8 cls)
}
return SPELLFAMILY_GENERIC;
}
void PlayerbotAI::AddTimedEvent(std::function<void()> callback, uint32 delayMs)
{
class LambdaEvent final : public BasicEvent
{
std::function<void()> _cb;
public:
explicit LambdaEvent(std::function<void()> cb) : _cb(std::move(cb)) {}
bool Execute(uint64 /*execTime*/, uint32 /*diff*/) override
{
_cb();
return true; // remove after execution
}
};
// Every Player already owns an EventMap called m_Events
bot->m_Events.AddEvent(new LambdaEvent(std::move(callback)), bot->m_Events.CalculateTime(delayMs));
}
void PlayerbotAI::EvaluateHealerDpsStrategy()
{
if (!IsHeal(bot, true))
return;
if (sPlayerbotAIConfig->IsRestrictedHealerDPSMap(bot->GetMapId()))
ChangeStrategy("-healer dps", BOT_STATE_COMBAT);
else
ChangeStrategy("+healer dps", BOT_STATE_COMBAT);
}

View File

@@ -46,27 +46,27 @@ struct GameObjectData;
enum StrategyType : uint32;
enum HealingItemDisplayId
enum HealingItemId
{
HEALTHSTONE_DISPLAYID = 8026,
MAJOR_HEALING_POTION = 24152,
WHIPPER_ROOT_TUBER = 21974,
NIGHT_DRAGON_BREATH = 21975,
LIMITED_INVULNERABILITY_POTION = 24213,
GREATER_DREAMLESS_SLEEP_POTION = 17403,
SUPERIOR_HEALING_POTION = 15714,
CRYSTAL_RESTORE = 2516,
DREAMLESS_SLEEP_POTION = 17403,
GREATER_HEALING_POTION = 15713,
HEALING_POTION = 15712,
LESSER_HEALING_POTION = 15711,
DISCOLORED_HEALING_POTION = 15736,
MINOR_HEALING_POTION = 15710,
VOLATILE_HEALING_POTION = 24212,
SUPER_HEALING_POTION = 37807,
CRYSTAL_HEALING_POTION = 47132,
FEL_REGENERATION_POTION = 37864,
MAJOR_DREAMLESS_SLEEP_POTION = 37845
HEALTHSTONE = 5512,
MAJOR_HEALING_POTION = 13446,
WHIPPER_ROOT_TUBER = 11951,
NIGHT_DRAGON_BREATH = 11952,
LIMITED_INVULNERABILITY_POTION = 3387,
GREATER_DREAMLESS_SLEEP_POTION = 22886,
SUPERIOR_HEALING_POTION = 3928,
CRYSTAL_RESTORE = 11564,
DREAMLESS_SLEEP_POTION = 12190,
GREATER_HEALING_POTION = 1710,
HEALING_POTION = 929,
LESSER_HEALING_POTION = 858,
DISCOLORED_HEALING_POTION = 3391,
MINOR_HEALING_POTION = 118,
VOLATILE_HEALING_POTION = 28100,
SUPER_HEALING_POTION = 22829,
CRYSTAL_HEALING_POTION = 13462,
FEL_REGENERATION_POTION = 28101,
MAJOR_DREAMLESS_SLEEP_POTION = 20002
};
enum BotState
@@ -152,6 +152,7 @@ static std::map<ChatChannelSource, std::string> ChatChannelSourceStr = {
{SRC_RAID, "SRC_RAID"},
{SRC_UNDEFINED, "SRC_UNDEFINED"}};
enum ChatChannelId
{
GENERAL = 1,
@@ -162,60 +163,66 @@ enum ChatChannelId
GUILD_RECRUITMENT = 25,
};
enum RoguePoisonDisplayId
enum RoguePoisonId
{
DEADLY_POISON_DISPLAYID = 13707,
INSTANT_POISON_DISPLAYID = 13710,
WOUND_POISON_DISPLAYID = 37278
INSTANT_POISON = 6947,
INSTANT_POISON_II = 6949,
INSTANT_POISON_III = 6950,
INSTANT_POISON_IV = 8926,
INSTANT_POISON_V = 8927,
INSTANT_POISON_VI = 8928,
INSTANT_POISON_VII = 21927,
INSTANT_POISON_VIII = 43230,
INSTANT_POISON_IX = 43231,
DEADLY_POISON = 2892,
DEADLY_POISON_II = 2893,
DEADLY_POISON_III = 8984,
DEADLY_POISON_IV = 8985,
DEADLY_POISON_V = 20844,
DEADLY_POISON_VI = 22053,
DEADLY_POISON_VII = 22054,
DEADLY_POISON_VIII = 43232,
DEADLY_POISON_IX = 43233
};
enum SharpeningStoneDisplayId
enum SharpeningStoneId
{
ROUGH_SHARPENING_DISPLAYID = 24673,
COARSE_SHARPENING_DISPLAYID = 24674,
HEAVY_SHARPENING_DISPLAYID = 24675,
SOLID_SHARPENING_DISPLAYID = 24676,
DENSE_SHARPENING_DISPLAYID = 24677,
CONSECRATED_SHARPENING_DISPLAYID =
24674, // will not be used because bot can not know if it will face undead targets
ELEMENTAL_SHARPENING_DISPLAYID = 21072,
FEL_SHARPENING_DISPLAYID = 39192,
ADAMANTITE_SHARPENING_DISPLAYID = 39193
ROUGH_SHARPENING_STONE = 2862,
COARSE_SHARPENING_STONE = 2863,
HEAVY_SHARPENING_STONE = 2871,
SOLID_SHARPENING_STONE = 7964,
DENSE_SHARPENING_STONE = 12404,
ELEMENTAL_SHARPENING_STONE = 18262,
FEL_SHARPENING_STONE = 23528,
ADAMANTITE_SHARPENING_STONE = 23529
};
enum WeightStoneDisplayId
enum WeightstoneId
{
ROUGH_WEIGHTSTONE_DISPLAYID = 24683,
COARSE_WEIGHTSTONE_DISPLAYID = 24684,
HEAVY_WEIGHTSTONE_DISPLAYID = 24685,
SOLID_WEIGHTSTONE_DISPLAYID = 24686,
DENSE_WEIGHTSTONE_DISPLAYID = 24687,
FEL_WEIGHTSTONE_DISPLAYID = 39548,
ADAMANTITE_WEIGHTSTONE_DISPLAYID = 39549
ROUGH_WEIGHTSTONE = 3239,
COARSE_WEIGHTSTONE = 3240,
HEAVY_WEIGHTSTONE = 3241,
SOLID_WEIGHTSTONE = 7965,
DENSE_WEIGHTSTONE = 12643,
FEL_WEIGHTSTONE = 28420,
ADAMANTITE_WEIGHTSTONE = 28421
};
enum WizardOilDisplayId
enum WizardOilId
{
MINOR_WIZARD_OIL = 9731,
LESSER_WIZARD_OIL = 47903,
BRILLIANT_WIZARD_OIL = 47901,
WIZARD_OIL = 47905,
SUPERIOR_WIZARD_OIL = 47904,
/// Blessed Wizard Oil = 26865 //scourge inv
MINOR_WIZARD_OIL = 20744,
LESSER_WIZARD_OIL = 20746,
WIZARD_OIL = 20750,
BRILLIANT_WIZARD_OIL = 20749,
SUPERIOR_WIZARD_OIL = 22522
};
enum ManaOilDisplayId
enum ManaOilId
{
MINOR_MANA_OIL = 34492,
LESSER_MANA_OIL = 47902,
BRILLIANT_MANA_OIL = 41488,
SUPERIOR_MANA_OIL = 36862
};
enum ShieldWardDisplayId
{
LESSER_WARD_OFSHIELDING = 38759,
GREATER_WARD_OFSHIELDING = 38760
MINOR_MANA_OIL = 20745,
LESSER_MANA_OIL = 20747,
BRILLIANT_MANA_OIL = 20748,
SUPERIOR_MANA_OIL = 22521
};
enum class BotTypeNumber : uint8
@@ -401,6 +408,7 @@ public:
void ClearStrategies(BotState type);
std::vector<std::string> GetStrategies(BotState type);
void ApplyInstanceStrategies(uint32 mapId, bool tellMaster = false);
void EvaluateHealerDpsStrategy();
bool ContainsStrategy(StrategyType type);
bool HasStrategy(std::string const name, BotState type);
BotState GetState() { return currentState; };
@@ -471,7 +479,7 @@ public:
Item* FindBandage() const;
Item* FindOpenableItem() const;
Item* FindLockedItem() const;
Item* FindConsumable(uint32 displayId) const;
Item* FindConsumable(uint32 itemId) const;
Item* FindStoneFor(Item* weapon) const;
Item* FindOilFor(Item* weapon) const;
void ImbueItem(Item* item, uint32 targetFlag, ObjectGuid targetGUID);
@@ -544,6 +552,8 @@ public:
bool IsSafe(Player* player);
bool IsSafe(WorldObject* obj);
ChatChannelSource GetChatChannelSource(Player* bot, uint32 type, std::string channelName);
bool CheckLocationDistanceByLevel(Player* player, const WorldLocation &loc, bool fromStartUp = false);
bool HasCheat(BotCheatMask mask)
{
@@ -590,6 +600,9 @@ public:
NewRpgStatistic rpgStatistic;
std::unordered_set<uint32> lowPriorityQuest;
// Schedules a callback to run once after <delayMs> milliseconds.
void AddTimedEvent(std::function<void()> callback, uint32 delayMs);
private:
static void _fillGearScoreData(Player* player, Item* item, std::vector<uint32>* gearScore, uint32& twoHandScore,
bool mixed = false);

View File

@@ -4,10 +4,9 @@
*/
#include "PlayerbotAIConfig.h"
#include <iostream>
#include "Config.h"
#include "NewRpgInfo.h"
#include "PlayerbotDungeonSuggestionMgr.h"
#include "PlayerbotFactory.h"
#include "Playerbots.h"
@@ -82,6 +81,8 @@ bool PlayerbotAIConfig::Initialize()
sitDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.SitDelay", 30000);
returnDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.ReturnDelay", 7000);
lootDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.LootDelay", 1000);
disabledWithoutRealPlayerLoginDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.DisabledWithoutRealPlayerLoginDelay", 30);
disabledWithoutRealPlayerLogoutDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.DisabledWithoutRealPlayerLogoutDelay", 300);
farDistance = sConfigMgr->GetOption<float>("AiPlayerbot.FarDistance", 20.0f);
sightDistance = sConfigMgr->GetOption<float>("AiPlayerbot.SightDistance", 75.0f);
@@ -117,6 +118,7 @@ bool PlayerbotAIConfig::Initialize()
tellWhenAvoidAoe = sConfigMgr->GetOption<bool>("AiPlayerbot.TellWhenAvoidAoe", false);
randomGearLoweringChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomGearLoweringChance", 0.0f);
incrementalGearInit = sConfigMgr->GetOption<bool>("AiPlayerbot.IncrementalGearInit", true);
randomGearQualityLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearQualityLimit", 3);
randomGearScoreLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearScoreLimit", 0);
@@ -129,6 +131,7 @@ bool PlayerbotAIConfig::Initialize()
allowAccountBots = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowAccountBots", true);
allowGuildBots = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowGuildBots", true);
allowTrustedAccountBots = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowTrustedAccountBots", true);
disabledWithoutRealPlayer = sConfigMgr->GetOption<bool>("AiPlayerbot.DisabledWithoutRealPlayer", false);
randomBotGuildNearby = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotGuildNearby", false);
randomBotInvitePlayer = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotInvitePlayer", false);
inviteChat = sConfigMgr->GetOption<bool>("AiPlayerbot.InviteChat", false);
@@ -145,7 +148,7 @@ bool PlayerbotAIConfig::Initialize()
LoadList<std::vector<uint32>>(
sConfigMgr->GetOption<std::string>("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"),
"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);
@@ -154,6 +157,8 @@ bool PlayerbotAIConfig::Initialize()
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);
botAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.BotAutologin", false);
randomBotAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotAutologin", true);
minRandomBots = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBots", 50);
@@ -185,6 +190,19 @@ bool PlayerbotAIConfig::Initialize()
sConfigMgr->GetOption<int32>("AiPlayerbot.MaxRandomBotsPriceChangeInterval", 48 * HOUR);
randomBotJoinLfg = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotJoinLfg", true);
restrictHealerDPS = sConfigMgr->GetOption<bool>("AiPlayerbot.HealerDPSMapRestriction", false);
LoadList<std::vector<uint32>>(
sConfigMgr->GetOption<std::string>("AiPlayerbot.RestrictedHealerDPSMaps",
"33,34,36,43,47,48,70,90,109,129,209,229,230,329,349,389,429,1001,1004,"
"1007,269,540,542,543,545,546,547,552,553,554,555,556,557,558,560,585,574,"
"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);
//////////////////////////// CHAT
enableBroadcasts = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableBroadcasts", true);
randomBotTalk = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotTalk", false);
@@ -307,11 +325,48 @@ bool PlayerbotAIConfig::Initialize()
summonAtInnkeepersEnabled = sConfigMgr->GetOption<bool>("AiPlayerbot.SummonAtInnkeepersEnabled", true);
randomBotMinLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotMinLevel", 1);
randomBotMaxLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotMaxLevel", 80);
if (randomBotMaxLevel > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
randomBotMaxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
randomBotLoginAtStartup = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotLoginAtStartup", true);
randomBotTeleLowerLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotTeleLowerLevel", 3);
randomBotTeleHigherLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotTeleHigherLevel", 1);
randomBotTeleLowerLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotTeleLowerLevel", 1);
randomBotTeleHigherLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotTeleHigherLevel", 3);
openGoSpell = sConfigMgr->GetOption<int32>("AiPlayerbot.OpenGoSpell", 6477);
// Zones for NewRpgStrategy teleportation brackets
std::vector<uint32> zoneIds = {
// Classic WoW - Low-level zones
1, 12, 14, 85, 141, 215, 3430, 3524,
// Classic WoW - Mid-level zones
17, 38, 40, 130, 148, 3433, 3525,
// Classic WoW - High-level zones
10, 11, 44, 267, 331, 400, 406,
// Classic WoW - Higher-level zones
3, 8, 15, 16, 33, 45, 47, 51, 357, 405, 440,
// Classic WoW - Top-level zones
4, 28, 46, 139, 361, 490, 618, 1377,
// The Burning Crusade - Zones
3483, 3518, 3519, 3520, 3521, 3522, 3523, 4080,
// Wrath of the Lich King - Zones
65, 66, 67, 210, 394, 495, 2817, 3537, 3711, 4197
};
for (uint32 zoneId : zoneIds)
{
std::string setting = "AiPlayerbot.ZoneBracket." + std::to_string(zoneId);
std::string value = sConfigMgr->GetOption<std::string>(setting, "");
if (!value.empty())
{
size_t commaPos = value.find(',');
if (commaPos != std::string::npos)
{
uint32 minLevel = atoi(value.substr(0, commaPos).c_str());
uint32 maxLevel = atoi(value.substr(commaPos + 1).c_str());
zoneBrackets[zoneId] = std::make_pair(minLevel, maxLevel);
}
}
}
randomChangeMultiplier = sConfigMgr->GetOption<float>("AiPlayerbot.RandomChangeMultiplier", 1.0);
randomBotCombatStrategies = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotCombatStrategies", "-threat");
@@ -331,6 +386,12 @@ bool PlayerbotAIConfig::Initialize()
useFlyMountAtMinLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.UseFlyMountAtMinLevel", 60);
useFastFlyMountAtMinLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.UseFastFlyMountAtMinLevel", 70);
// stagger bot flightpath takeoff
delayMin = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiDelayMinMs", 350u);
delayMax = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiDelayMaxMs", 5000u);
gapMs = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiGapMs", 200u);
gapJitterMs = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiGapJitterMs", 100u);
LOG_INFO("server.loading", "Loading TalentSpecs...");
for (uint32 cls = 1; cls < MAX_CLASSES; ++cls)
@@ -395,7 +456,7 @@ bool PlayerbotAIConfig::Initialize()
}
botCheats.clear();
LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.BotCheats", "taxi"),
LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.BotCheats", "taxi,raid"),
botCheats);
botCheatMask = 0;
@@ -410,30 +471,17 @@ bool PlayerbotAIConfig::Initialize()
botCheatMask |= (uint32)BotCheatMask::mana;
if (std::find(botCheats.begin(), botCheats.end(), "power") != botCheats.end())
botCheatMask |= (uint32)BotCheatMask::power;
if (std::find(botCheats.begin(), botCheats.end(), "raid") != botCheats.end())
botCheatMask |= (uint32)BotCheatMask::raid;
LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.AllowedLogFiles", ""),
allowedLogFiles);
LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.TradeActionExcludedPrefixes", ""),
tradeActionExcludedPrefixes);
worldBuffs.clear();
LOG_INFO("playerbots", "Loading Worldbuff...");
for (uint32 factionId = 0; factionId < 3; factionId++)
{
for (uint32 classId = 0; classId < MAX_CLASSES; classId++)
{
for (uint32 specId = 0; specId <= MAX_WORLDBUFF_SPECNO; specId++)
{
for (uint32 minLevel = 0; minLevel <= randomBotMaxLevel; minLevel++)
{
for (uint32 maxLevel = minLevel; maxLevel <= randomBotMaxLevel; maxLevel++)
{
loadWorldBuff(factionId, classId, specId, minLevel, maxLevel);
}
loadWorldBuff(factionId, classId, specId, minLevel, 0);
}
}
}
}
loadWorldBuff();
LOG_INFO("playerbots", "Loading World Buff Feature...");
randomBotAccountPrefix = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotAccountPrefix", "rndbot");
randomBotAccountCount = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAccountCount", 0);
@@ -466,6 +514,7 @@ bool PlayerbotAIConfig::Initialize()
equipmentPersistence = sConfigMgr->GetOption<bool>("AiPlayerbot.EquipmentPersistence", false);
equipmentPersistenceLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.EquipmentPersistenceLevel", 80);
groupInvitationPermission = sConfigMgr->GetOption<int32>("AiPlayerbot.GroupInvitationPermission", 1);
keepAltsInGroup = sConfigMgr->GetOption<bool>("AiPlayerbot.KeepAltsInGroup", false);
allowSummonInCombat = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowSummonInCombat", true);
allowSummonWhenMasterIsDead = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowSummonWhenMasterIsDead", true);
allowSummonWhenBotIsDead = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowSummonWhenBotIsDead", true);
@@ -483,7 +532,7 @@ bool PlayerbotAIConfig::Initialize()
autoGearQualityLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearQualityLimit", 3);
autoGearScoreLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearScoreLimit", 0);
playerbotsXPrate = sConfigMgr->GetOption<float>("AiPlayerbot.PlayerbotsXPRate", 1.0);
randomBotXPRate = sConfigMgr->GetOption<float>("AiPlayerbot.RandomBotXPRate", 1.0);
randomBotAllianceRatio = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAllianceRatio", 50);
randomBotHordeRatio = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotHordeRatio", 50);
disableDeathKnightLogin = sConfigMgr->GetOption<bool>("AiPlayerbot.DisableDeathKnightLogin", 0);
@@ -530,7 +579,16 @@ bool PlayerbotAIConfig::Initialize()
autoLearnQuestSpells = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoLearnQuestSpells", false);
autoTeleportForLevel = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoTeleportForLevel", false);
autoDoQuests = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoDoQuests", true);
enableNewRpgStrategy = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableNewRpgStrategy", false);
enableNewRpgStrategy = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableNewRpgStrategy", true);
RpgStatusProbWeight[RPG_WANDER_RANDOM] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.WanderRandom", 15);
RpgStatusProbWeight[RPG_WANDER_NPC] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.WanderNpc", 20);
RpgStatusProbWeight[RPG_GO_GRIND] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.GoGrind", 15);
RpgStatusProbWeight[RPG_GO_CAMP] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.GoCamp", 10);
RpgStatusProbWeight[RPG_DO_QUEST] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.DoQuest", 60);
RpgStatusProbWeight[RPG_TRAVEL_FLIGHT] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.TravelFlight", 15);
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);
@@ -551,6 +609,9 @@ bool PlayerbotAIConfig::Initialize()
return true;
}
// Assign account types after accounts are created
sRandomPlayerbotMgr->AssignAccountTypes();
if (sPlayerbotAIConfig->enabled)
{
sRandomPlayerbotMgr->Init();
@@ -562,18 +623,16 @@ bool PlayerbotAIConfig::Initialize()
sPlayerbotTextMgr->LoadBotTextChance();
PlayerbotFactory::Init();
if (!sPlayerbotAIConfig->autoDoQuests)
{
LOG_INFO("server.loading", "Loading Quest Detail Data...");
sTravelMgr->LoadQuestTravelTable();
}
AiObjectContext::BuildAllSharedContexts();
if (sPlayerbotAIConfig->randomBotSuggestDungeons)
{
sPlayerbotDungeonSuggestionMgr->LoadDungeonSuggestions();
}
excludedHunterPetFamilies.clear();
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.ExcludedHunterPetFamilies", ""), excludedHunterPetFamilies);
LOG_INFO("server.loading", "---------------------------------------");
LOG_INFO("server.loading", " AI Playerbots initialized ");
LOG_INFO("server.loading", "---------------------------------------");
@@ -606,6 +665,12 @@ bool PlayerbotAIConfig::IsInPvpProhibitedArea(uint32 id)
return find(pvpProhibitedAreaIds.begin(), pvpProhibitedAreaIds.end(), id) != pvpProhibitedAreaIds.end();
}
bool PlayerbotAIConfig::IsRestrictedHealerDPSMap(uint32 mapId) const
{
return restrictHealerDPS &&
std::find(restrictedHealerDPSMaps.begin(), restrictedHealerDPSMaps.end(), mapId) != restrictedHealerDPSMaps.end();
}
std::string const PlayerbotAIConfig::GetTimestampStr()
{
time_t t = time(nullptr);
@@ -678,88 +743,62 @@ void PlayerbotAIConfig::log(std::string const fileName, char const* str, ...)
fflush(stdout);
}
void PlayerbotAIConfig::loadWorldBuff(uint32 factionId1, uint32 classId1, uint32 specId1, uint32 minLevel1, uint32 maxLevel1)
void PlayerbotAIConfig::loadWorldBuff()
{
std::vector<uint32> buffs;
std::string matrix = sConfigMgr->GetOption<std::string>("AiPlayerbot.WorldBuffMatrix", "", true);
if (matrix.empty())
return;
std::ostringstream os;
os << "AiPlayerbot.WorldBuff." << factionId1 << "." << classId1 << "." << specId1 << "." << minLevel1 << "." << maxLevel1;
std::istringstream entryStream(matrix);
std::string entry;
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
for (auto buff : buffs)
while (std::getline(entryStream, entry, ';'))
{
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
worldBuffs.push_back(wb);
}
if (maxLevel1 == 0)
{
std::ostringstream os;
os << "AiPlayerbot.WorldBuff." << factionId1 << "." << classId1 << "." << specId1 << "." << minLevel1;
entry.erase(0, entry.find_first_not_of(" \t\r\n"));
entry.erase(entry.find_last_not_of(" \t\r\n") + 1);
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
size_t firstColon = entry.find(':');
size_t secondColon = entry.find(':', firstColon + 1);
for (auto buff : buffs)
if (firstColon == std::string::npos || secondColon == std::string::npos)
{
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
worldBuffs.push_back(wb);
LOG_ERROR("playerbots", "Malformed entry: [{}]", entry);
continue;
}
}
if (maxLevel1 == 0 && minLevel1 == 0)
{
std::ostringstream os;
os << "AiPlayerbot.WorldBuff." << factionId1 << "." << factionId1 << "." << classId1 << "." << specId1;
std::string metaPart = entry.substr(firstColon + 1, secondColon - firstColon - 1);
std::string spellPart = entry.substr(secondColon + 1);
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
for (auto buff : buffs)
std::vector<uint32> ids;
std::istringstream metaStream(metaPart);
std::string token;
while (std::getline(metaStream, token, ','))
{
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
worldBuffs.push_back(wb);
try {
ids.push_back(static_cast<uint32>(std::stoi(token)));
} catch (...) {
LOG_ERROR("playerbots", "Invalid meta token in [{}]", entry);
break;
}
}
}
if (maxLevel1 == 0 && minLevel1 == 0 && specId1 == 0)
{
std::ostringstream os;
os << "AiPlayerbot.WorldBuff." << factionId1 << "." << factionId1 << "." << classId1;
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
for (auto buff : buffs)
if (ids.size() != 5)
{
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
worldBuffs.push_back(wb);
LOG_ERROR("playerbots", "Entry [{}] has incomplete meta block", entry);
continue;
}
}
if (classId1 == 0 && maxLevel1 == 0 && minLevel1 == 0 && specId1 == 0)
{
std::ostringstream os;
os << "AiPlayerbot.WorldBuff." << factionId1;
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
for (auto buff : buffs)
std::istringstream spellStream(spellPart);
while (std::getline(spellStream, token, ','))
{
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
worldBuffs.push_back(wb);
}
}
if (factionId1 == 0 && classId1 == 0 && maxLevel1 == 0 && minLevel1 == 0 && specId1 == 0)
{
std::ostringstream os;
os << "AiPlayerbot.WorldBuff";
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
for (auto buff : buffs)
{
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
worldBuffs.push_back(wb);
try {
uint32 spellId = static_cast<uint32>(std::stoi(token));
worldBuff wb = { spellId, ids[0], ids[1], ids[2], ids[3], ids[4] };
worldBuffs.push_back(wb);
} catch (...) {
LOG_ERROR("playerbots", "Invalid spell ID in [{}]", entry);
}
}
}
}

View File

@@ -7,6 +7,7 @@
#define _PLAYERBOT_PLAYERbotAICONFIG_H
#include <mutex>
#include <unordered_map>
#include "Common.h"
#include "DBCEnums.h"
@@ -21,7 +22,8 @@ enum class BotCheatMask : uint32
health = 4,
mana = 8,
power = 16,
maxMask = 32
raid = 32,
maxMask = 64
};
enum class HealingManaEfficiency : uint8
@@ -34,8 +36,27 @@ enum class HealingManaEfficiency : uint8
SUPERIOR = 32
};
enum NewRpgStatus : int
{
RPG_STATUS_START = 0,
// Going to far away place
RPG_GO_GRIND = 0,
RPG_GO_CAMP = 1,
// Exploring nearby
RPG_WANDER_RANDOM = 2,
RPG_WANDER_NPC = 3,
// Do Quest (based on quest status)
RPG_DO_QUEST = 4,
// Travel
RPG_TRAVEL_FLIGHT = 5,
// Taking a break
RPG_REST = 6,
// Initial status
RPG_IDLE = 7,
RPG_STATUS_END = 8
};
#define MAX_SPECNO 20
#define MAX_WORLDBUFF_SPECNO 3
class PlayerbotAIConfig
{
@@ -55,6 +76,8 @@ public:
bool IsInPvpProhibitedArea(uint32 id);
bool enabled;
bool disabledWithoutRealPlayer;
bool EnableICCBuffs;
bool allowAccountBots, allowGuildBots, allowTrustedAccountBots;
bool randomBotGuildNearby, randomBotInvitePlayer, inviteChat;
uint32 globalCoolDown, reactDelay, maxWaitForMove, disableMoveSplinePath, maxMovementSearchTime, expireActionTime,
@@ -71,6 +94,7 @@ public:
float maxAoeAvoidRadius;
std::set<uint32> aoeAvoidSpellWhitelist;
bool tellWhenAvoidAoe;
std::set<uint32> disallowedGameObjects;
uint32 openGoSpell;
bool randomBotAutologin;
@@ -84,6 +108,7 @@ public:
std::vector<uint32> randomBotQuestIds;
uint32 randomBotTeleportDistance;
float randomGearLoweringChance;
bool incrementalGearInit;
int32 randomGearQualityLimit;
int32 randomGearScoreLimit;
float randomBotMinLevelChance, randomBotMaxLevelChance;
@@ -99,6 +124,7 @@ public:
uint32 minRandomBotPvpTime, maxRandomBotPvpTime;
uint32 randomBotsPerInterval;
uint32 minRandomBotsPriceChangeInterval, maxRandomBotsPriceChangeInterval;
uint32 disabledWithoutRealPlayerLoginDelay, disabledWithoutRealPlayerLogoutDelay;
bool randomBotJoinLfg;
// chat
@@ -195,6 +221,7 @@ public:
bool randomBotLoginAtStartup;
uint32 randomBotTeleLowerLevel, randomBotTeleHigherLevel;
std::map<uint32, std::pair<uint32, uint32>> zoneBrackets;
bool logInGroupOnly, logValuesPerTick;
bool fleeingEnabled;
bool summonAtInnkeepersEnabled;
@@ -250,6 +277,7 @@ public:
uint32 iterationsPerTick;
std::mutex m_logMtx;
std::vector<std::string> tradeActionExcludedPrefixes;
std::vector<std::string> allowedLogFiles;
std::unordered_map<std::string, std::pair<FILE*, bool>> logFiles;
@@ -259,11 +287,11 @@ public:
struct worldBuff
{
uint32 spellId;
uint32 factionId = 0;
uint32 classId = 0;
uint32 specId = 0;
uint32 minLevel = 0;
uint32 maxLevel = 0;
uint32 factionId;
uint32 classId;
uint32 specId;
uint32 minLevel;
uint32 maxLevel;
};
std::vector<worldBuff> worldBuffs;
@@ -275,7 +303,7 @@ public:
bool randomBotShowCloak;
bool randomBotFixedLevel;
bool disableRandomLevels;
float playerbotsXPrate;
float randomBotXPRate;
uint32 randomBotAllianceRatio;
uint32 randomBotHordeRatio;
bool disableDeathKnightLogin;
@@ -308,6 +336,7 @@ public:
bool autoLearnTrainerSpells;
bool autoDoQuests;
bool enableNewRpgStrategy;
std::unordered_map<NewRpgStatus, uint32> RpgStatusProbWeight;
bool syncLevelWithPlayers;
bool freeFood;
bool autoLearnQuestSpells;
@@ -330,6 +359,8 @@ public:
bool equipmentPersistence;
int32 equipmentPersistenceLevel;
int32 groupInvitationPermission;
bool keepAltsInGroup = false;
bool KeepAltsInGroup() const { return keepAltsInGroup; }
bool allowSummonInCombat;
bool allowSummonWhenMasterIsDead;
bool allowSummonWhenBotIsDead;
@@ -348,6 +379,12 @@ public:
uint32 useFlyMountAtMinLevel;
uint32 useFastFlyMountAtMinLevel;
// stagger flightpath takeoff
uint32 delayMin;
uint32 delayMax;
uint32 gapMs;
uint32 gapJitterMs;
std::string const GetTimestampStr();
bool hasLog(std::string const fileName)
{
@@ -361,9 +398,16 @@ public:
}
void log(std::string const fileName, const char* str, ...);
void loadWorldBuff(uint32 factionId, uint32 classId, uint32 specId, uint32 minLevel, uint32 maxLevel);
void loadWorldBuff();
static std::vector<std::vector<uint32>> ParseTempTalentsOrder(uint32 cls, std::string temp_talents_order);
static std::vector<std::vector<uint32>> ParseTempPetTalentsOrder(uint32 spec, std::string temp_talents_order);
bool restrictHealerDPS = false;
std::vector<uint32> restrictedHealerDPSMaps;
bool IsRestrictedHealerDPSMap(uint32 mapId) const;
std::vector<uint32> excludedHunterPetFamilies;
};
#define sPlayerbotAIConfig PlayerbotAIConfig::instance()

View File

@@ -36,6 +36,10 @@
#include "BroadcastHelper.h"
#include "PlayerbotDbStore.h"
#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
{
@@ -157,7 +161,7 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
bool PlayerbotHolder::IsAccountLinked(uint32 accountId, uint32 linkedAccountId)
{
QueryResult result = PlayerbotsDatabase.Query(
"SELECT 1 FROM playerbot_account_links WHERE account_id = {} AND linked_account_id = {}", accountId, linkedAccountId);
"SELECT 1 FROM playerbots_account_links WHERE account_id = {} AND linked_account_id = {}", accountId, linkedAccountId);
return result != nullptr;
}
@@ -166,7 +170,7 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
uint32 botAccountId = holder.GetAccountId();
// At login DBC locale should be what the server is set to use by default (as spells etc are hardcoded to ENUS this
// allows channels to work as intended)
WorldSession* botSession = new WorldSession(botAccountId, "", nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING,
WorldSession* botSession = new WorldSession(botAccountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING,
time_t(0), sWorld->GetDefaultDbcLocale(), 0, false, false, 0, true);
botSession->HandlePlayerLoginFromDB(holder); // will delete lqh
@@ -473,11 +477,11 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
}
Player* master = botAI->GetMaster();
if (!master)
if (master)
{
// Log a warning to indicate that the master is null
LOG_DEBUG("mod-playerbots", "Master is null for bot with GUID: {}", bot->GetGUID().GetRawValue());
return;
ObjectGuid masterGuid = master->GetGUID();
if (master->GetGroup() && !master->GetGroup()->IsLeader(masterGuid))
master->GetGroup()->ChangeLeader(masterGuid);
}
Group* group = bot->GetGroup();
@@ -496,7 +500,10 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
break;
}
}
else
// Don't disband alt groups when master goes away
// Controlled by config
if (sPlayerbotAIConfig->KeepAltsInGroup())
{
uint32 account = sCharacterCache->GetCharacterAccountIdByGuid(member);
if (!sPlayerbotAIConfig->IsInRandomAccountList(account))
@@ -581,8 +588,8 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
}
bot->SaveToDB(false, false);
bool addClassBot = sRandomPlayerbotMgr->IsAddclassBot(bot->GetGUID().GetCounter());
if (addClassBot && master && isRandomAccount && abs((int)master->GetLevel() - (int)bot->GetLevel()) > 3)
bool addClassBot = sRandomPlayerbotMgr->IsAccountType(accountId, 2);
if (addClassBot && master && abs((int)master->GetLevel() - (int)bot->GetLevel()) > 3)
{
// PlayerbotFactory factory(bot, master->GetLevel());
// factory.Randomize(false);
@@ -834,6 +841,18 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje
return "unknown command";
}
// Added for gender choice : Returns the gender of an offline character: 0 = male, 1 = female.
static uint8 GetOfflinePlayerGender(ObjectGuid guid)
{
QueryResult result = CharacterDatabase.Query(
"SELECT gender FROM characters WHERE guid = {}", guid.GetCounter());
if (result)
return (*result)[0].Get<uint8>(); // 0 = male, 1 = female
return GENDER_MALE; // fallback value
}
bool PlayerbotMgr::HandlePlayerbotMgrCommand(ChatHandler* handler, char const* args)
{
if (!sPlayerbotAIConfig->enabled)
@@ -876,15 +895,17 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
if (!*args)
{
messages.push_back("usage: list/reload/tweak/self or add/addaccount/init/remove PLAYERNAME\n");
messages.push_back("usage: addclass CLASSNAME");
messages.push_back("usage: addclass CLASSNAME [male|female|0|1]");
return messages;
}
char* cmd = strtok((char*)args, " ");
char* charname = strtok(nullptr, " ");
char* genderArg = strtok(nullptr, " "); // Added for gender choice [male|female|0|1] optionnel
if (!cmd)
{
messages.push_back("usage: list/reload/tweak/self or add/init/remove PLAYERNAME or addclass CLASSNAME");
messages.push_back("usage: list/reload/tweak/self or add/init/remove PLAYERNAME or addclass CLASSNAME [male|female]");
return messages;
}
@@ -1107,6 +1128,24 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
messages.push_back("Error: Invalid Class. Try again.");
return messages;
}
// Added for gender choice : Parsing gender
int8 gender = -1; // -1 = gender will be random
if (genderArg)
{
std::string g = genderArg;
std::transform(g.begin(), g.end(), g.begin(), ::tolower);
if (g == "male" || g == "0")
gender = GENDER_MALE; // 0
else if (g == "female" || g == "1")
gender = GENDER_FEMALE; // 1
else
{
messages.push_back("Unknown gender : " + g + " (male/female/0/1)");
return messages;
}
} //end
if (claz == 6 && master->GetLevel() < sWorld->getIntConfig(CONFIG_START_HEROIC_PLAYER_LEVEL))
{
messages.push_back("Your level is too low to summon Deathknight");
@@ -1116,6 +1155,9 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
const std::unordered_set<ObjectGuid> &guidCache = sRandomPlayerbotMgr->addclassCache[RandomPlayerbotMgr::GetTeamClassIdx(teamId == TEAM_ALLIANCE, claz)];
for (const ObjectGuid &guid: guidCache)
{
// If the user requested a specific gender, skip any character that doesn't match.
if (gender != -1 && GetOfflinePlayerGender(guid) != gender)
continue;
if (botLoading.find(guid) != botLoading.end())
continue;
if (ObjectAccessor::FindConnectedPlayer(guid))
@@ -1686,23 +1728,72 @@ void PlayerbotsMgr::RemovePlayerBotData(ObjectGuid const& guid, bool is_AI)
PlayerbotAI* PlayerbotsMgr::GetPlayerbotAI(Player* player)
{
if (!(sPlayerbotAIConfig->enabled) || !player)
{
return nullptr;
}
// if (player->GetSession()->isLogingOut() || player->IsDuringRemoveFromWorld()) {
// if (!(sPlayerbotAIConfig->enabled) || !player)
// {
// return nullptr;
// }
auto itr = _playerbotsAIMap.find(player->GetGUID());
if (itr != _playerbotsAIMap.end())
{
if (itr->second->IsBotAI())
return reinterpret_cast<PlayerbotAI*>(itr->second);
// // 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))
{
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 (removeMgrEntry)
_playerbotsMgrMap.erase(guid); // we NO longer touch the relation in a "soft" purge
}
PlayerbotMgr* PlayerbotsMgr::GetPlayerbotMgr(Player* player)
{
if (!(sPlayerbotAIConfig->enabled) || !player)
@@ -1734,7 +1825,7 @@ void PlayerbotMgr::HandleSetSecurityKeyCommand(Player* player, const std::string
// Store the hashed key in the database
PlayerbotsDatabase.Execute(
"REPLACE INTO playerbot_account_keys (account_id, security_key) VALUES ({}, '{}')",
"REPLACE INTO playerbots_account_keys (account_id, security_key) VALUES ({}, '{}')",
accountId, hashedKey.str());
ChatHandler(player->GetSession()).PSendSysMessage("Security key set successfully.");
@@ -1752,7 +1843,7 @@ void PlayerbotMgr::HandleLinkAccountCommand(Player* player, const std::string& a
Field* fields = result->Fetch();
uint32 linkedAccountId = fields[0].Get<uint32>();
result = PlayerbotsDatabase.Query("SELECT security_key FROM playerbot_account_keys WHERE account_id = {}", linkedAccountId);
result = PlayerbotsDatabase.Query("SELECT security_key FROM playerbots_account_keys WHERE account_id = {}", linkedAccountId);
if (!result)
{
ChatHandler(player->GetSession()).PSendSysMessage("Invalid security key.");
@@ -1778,10 +1869,10 @@ void PlayerbotMgr::HandleLinkAccountCommand(Player* player, const std::string& a
uint32 accountId = player->GetSession()->GetAccountId();
PlayerbotsDatabase.Execute(
"INSERT IGNORE INTO playerbot_account_links (account_id, linked_account_id) VALUES ({}, {})",
"INSERT IGNORE INTO playerbots_account_links (account_id, linked_account_id) VALUES ({}, {})",
accountId, linkedAccountId);
PlayerbotsDatabase.Execute(
"INSERT IGNORE INTO playerbot_account_links (account_id, linked_account_id) VALUES ({}, {})",
"INSERT IGNORE INTO playerbots_account_links (account_id, linked_account_id) VALUES ({}, {})",
linkedAccountId, accountId);
ChatHandler(player->GetSession()).PSendSysMessage("Account linked successfully.");
@@ -1790,7 +1881,7 @@ void PlayerbotMgr::HandleLinkAccountCommand(Player* player, const std::string& a
void PlayerbotMgr::HandleViewLinkedAccountsCommand(Player* player)
{
uint32 accountId = player->GetSession()->GetAccountId();
QueryResult result = PlayerbotsDatabase.Query("SELECT linked_account_id FROM playerbot_account_links WHERE account_id = {}", accountId);
QueryResult result = PlayerbotsDatabase.Query("SELECT linked_account_id FROM playerbots_account_links WHERE account_id = {}", accountId);
if (!result)
{
@@ -1831,7 +1922,7 @@ void PlayerbotMgr::HandleUnlinkAccountCommand(Player* player, const std::string&
uint32 linkedAccountId = fields[0].Get<uint32>();
uint32 accountId = player->GetSession()->GetAccountId();
PlayerbotsDatabase.Execute("DELETE FROM playerbot_account_links WHERE (account_id = {} AND linked_account_id = {}) OR (account_id = {} AND linked_account_id = {})",
PlayerbotsDatabase.Execute("DELETE FROM playerbots_account_links WHERE (account_id = {} AND linked_account_id = {}) OR (account_id = {} AND linked_account_id = {})",
accountId, linkedAccountId, linkedAccountId, accountId);
ChatHandler(player->GetSession()).PSendSysMessage("Account unlinked successfully.");

View File

@@ -12,6 +12,7 @@
#include "PlayerbotAIBase.h"
#include "QueryHolder.h"
#include "QueryResult.h"
#include <shared_mutex> // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
class ChatHandler;
class PlayerbotAI;
@@ -114,13 +115,38 @@ 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

@@ -29,6 +29,7 @@
#include "ScriptMgr.h"
#include "cs_playerbots.h"
#include "cmath"
#include "BattleGroundTactics.h"
class PlayerbotsDatabaseScript : public DatabaseScript
{
@@ -196,33 +197,42 @@ public:
sRandomPlayerbotMgr->HandleCommand(type, msg, player);
}
bool OnPlayerBeforeCriteriaProgress(Player* player, AchievementCriteriaEntry const* /*criteria*/) override
bool OnPlayerBeforeAchievementComplete(Player* player, AchievementEntry const* achievement) override
{
if (sRandomPlayerbotMgr->IsRandomBot(player))
if (sRandomPlayerbotMgr->IsRandomBot(player) && (achievement->flags == 256 || achievement->flags == 768))
{
return false;
}
return true;
}
bool OnPlayerBeforeAchievementComplete(Player* player, AchievementEntry const* /*achievement*/) override
{
if (sRandomPlayerbotMgr->IsRandomBot(player))
{
return false;
}
return true;
}
void OnPlayerGiveXP(Player* player, uint32& amount, Unit* /*victim*/, uint8 /*xpSource*/) override
{
if (!player->GetSession()->IsBot())
// early return
if (sPlayerbotAIConfig->randomBotXPRate == 1.0 || !player)
return;
if (sPlayerbotAIConfig->playerbotsXPrate != 1.0)
// no XP multiplier, when player is no bot.
if (!player->GetSession()->IsBot() || !sRandomPlayerbotMgr->IsRandomBot(player))
return;
// no XP multiplier, when bot has group where leader is a real player.
if (Group* group = player->GetGroup())
{
amount = static_cast<uint32>(std::round(static_cast<float>(amount) * sPlayerbotAIConfig->playerbotsXPrate));
Player* leader = group->GetLeader();
if (leader && leader != player)
{
if (PlayerbotAI* leaderBotAI = GET_PLAYERBOT_AI(leader))
{
if (leaderBotAI->HasRealPlayerMaster())
return;
}
}
}
// otherwise apply bot XP multiplier.
amount = static_cast<uint32>(std::round(static_cast<float>(amount) * sPlayerbotAIConfig->randomBotXPRate));
}
};
@@ -367,6 +377,10 @@ 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);
@@ -386,6 +400,43 @@ public:
}
};
class PlayerBotsBGScript : public BGScript
{
public:
PlayerBotsBGScript() : BGScript("PlayerBotsBGScript") {}
void OnBattlegroundStart(Battleground* bg) override
{
BGStrategyData data;
switch (bg->GetBgTypeID())
{
case BATTLEGROUND_WS:
data.allianceStrategy = urand(0, WS_STRATEGY_MAX - 1);
data.hordeStrategy = urand(0, WS_STRATEGY_MAX - 1);
break;
case BATTLEGROUND_AB:
data.allianceStrategy = urand(0, AB_STRATEGY_MAX - 1);
data.hordeStrategy = urand(0, AB_STRATEGY_MAX - 1);
break;
case BATTLEGROUND_AV:
data.allianceStrategy = urand(0, AV_STRATEGY_MAX - 1);
data.hordeStrategy = urand(0, AV_STRATEGY_MAX - 1);
break;
case BATTLEGROUND_EY:
data.allianceStrategy = urand(0, EY_STRATEGY_MAX - 1);
data.hordeStrategy = urand(0, EY_STRATEGY_MAX - 1);
break;
default:
break;
}
bgStrategies[bg->GetInstanceID()] = data;
}
void OnBattlegroundEnd(Battleground* bg, TeamId /*winnerTeam*/) override { bgStrategies.erase(bg->GetInstanceID()); }
};
void AddPlayerbotsScripts()
{
new PlayerbotsDatabaseScript();
@@ -394,6 +445,7 @@ void AddPlayerbotsScripts()
new PlayerbotsServerScript();
new PlayerbotsWorldScript();
new PlayerbotsScript();
new PlayerBotsBGScript();
AddSC_playerbots_commandscript();
}

View File

@@ -393,37 +393,118 @@ std::string const RandomPlayerbotFactory::CreateRandomBotName(NameRaceAndGender
return std::move(botName);
}
// Calculates the total number of required accounts, either using the specified randomBotAccountCount
// or determining it dynamically based on MaxRandomBots, EnablePeriodicOnlineOffline and its ratio,
// and AddClassAccountPoolSize. The system also factors in the types of existing account, as assigned by
// AssignAccountTypes()
uint32 RandomPlayerbotFactory::CalculateTotalAccountCount()
{
// Calculates the total number of required accounts, either using the specified randomBotAccountCount
// or determining it dynamically based on the WOTLK condition, max random bots, rotation pool size,
// and additional class account pool size.
// Reset account types if features are disabled
// Reset is done here to precede needed accounts calculations
if (sPlayerbotAIConfig->maxRandomBots == 0 || sPlayerbotAIConfig->addClassAccountPoolSize == 0)
{
if (sPlayerbotAIConfig->maxRandomBots == 0)
{
PlayerbotsDatabase.Execute("UPDATE playerbots_account_type SET account_type = 0 WHERE account_type = 1");
LOG_INFO("playerbots", "MaxRandomBots set to 0, any RNDbot accounts (type 1) will be unassigned (type 0)");
}
if (sPlayerbotAIConfig->addClassAccountPoolSize == 0)
{
PlayerbotsDatabase.Execute("UPDATE playerbots_account_type SET account_type = 0 WHERE account_type = 2");
LOG_INFO("playerbots", "AddClassAccountPoolSize set to 0, any AddClass accounts (type 2) will be unassigned (type 0)");
}
// Wait for DB to reflect the change, up to 1 second max. This is needed to make sure other logs don't show wrong info
for (int waited = 0; waited < 1000; waited += 50)
{
QueryResult res = PlayerbotsDatabase.Query("SELECT COUNT(*) FROM playerbots_account_type WHERE account_type IN ({}, {})",
sPlayerbotAIConfig->maxRandomBots == 0 ? 1 : -1,
sPlayerbotAIConfig->addClassAccountPoolSize == 0 ? 2 : -1);
if (!res || res->Fetch()[0].Get<uint64>() == 0)
{
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Extra 50ms fixed delay for safety.
}
}
// Checks if randomBotAccountCount is set, otherwise calculate it dynamically.
if (sPlayerbotAIConfig->randomBotAccountCount > 0)
return sPlayerbotAIConfig->randomBotAccountCount;
// Avoid creating accounts if both maxRandom & ClassBots are set to zero.
if (sPlayerbotAIConfig->maxRandomBots == 0 &&
sPlayerbotAIConfig->addClassAccountPoolSize == 0)
return 0;
// Check existing account types
uint32 existingRndBotAccounts = 0;
uint32 existingAddClassAccounts = 0;
uint32 existingUnassignedAccounts = 0;
//bool isWOTLK = sWorld->getIntConfig(CONFIG_EXPANSION) == EXPANSION_WRATH_OF_THE_LICH_KING; //not used, line marked for removal.
QueryResult typeCheck = PlayerbotsDatabase.Query("SELECT account_type, COUNT(*) FROM playerbots_account_type GROUP BY account_type");
if (typeCheck)
{
do
{
Field* fields = typeCheck->Fetch();
uint8 accountType = fields[0].Get<uint8>();
uint32 count = fields[1].Get<uint32>();
// Determine divisor based on WOTLK condition
if (accountType == 0) existingUnassignedAccounts = count;
else if (accountType == 1) existingRndBotAccounts = count;
else if (accountType == 2) existingAddClassAccounts = count;
} while (typeCheck->NextRow());
}
// Determine divisor based on Death Knight login eligibility and requested A&H faction ratio
int divisor = CalculateAvailableCharsPerAccount();
// Calculate max bots
int maxBots = sPlayerbotAIConfig->maxRandomBots;
// Take perodic online - offline into account
// Take periodic online - offline into account
if (sPlayerbotAIConfig->enablePeriodicOnlineOffline)
{
maxBots *= sPlayerbotAIConfig->periodicOnlineOfflineRatio;
}
// Calculate base accounts, add class account pool size, and add 1 as a fixed offset
uint32 baseAccounts = maxBots / divisor;
return baseAccounts + sPlayerbotAIConfig->addClassAccountPoolSize + 1;
// Calculate number of accounts needed for RNDbots
// Result is rounded up for maxBots not cleanly divisible by the divisor
uint32 neededRndBotAccounts = (maxBots + divisor - 1) / divisor;
uint32 neededAddClassAccounts = sPlayerbotAIConfig->addClassAccountPoolSize;
// Start with existing total
uint32 existingTotal = existingRndBotAccounts + existingAddClassAccounts + existingUnassignedAccounts;
// Calculate shortfalls after using unassigned accounts
uint32 availableUnassigned = existingUnassignedAccounts;
uint32 additionalAccountsNeeded = 0;
// Check RNDbot needs
if (neededRndBotAccounts > existingRndBotAccounts)
{
uint32 rndBotShortfall = neededRndBotAccounts - existingRndBotAccounts;
if (rndBotShortfall <= availableUnassigned)
availableUnassigned -= rndBotShortfall;
else
{
additionalAccountsNeeded += (rndBotShortfall - availableUnassigned);
availableUnassigned = 0;
}
}
// Check AddClass needs
if (neededAddClassAccounts > existingAddClassAccounts)
{
uint32 addClassShortfall = neededAddClassAccounts - existingAddClassAccounts;
if (addClassShortfall <= availableUnassigned)
availableUnassigned -= addClassShortfall;
else
{
additionalAccountsNeeded += (addClassShortfall - availableUnassigned);
availableUnassigned = 0;
}
}
// Return existing total plus any additional accounts needed
return existingTotal + additionalAccountsNeeded;
}
uint32 RandomPlayerbotFactory::CalculateAvailableCharsPerAccount()
@@ -475,8 +556,9 @@ void RandomPlayerbotFactory::CreateRandomBots()
LOG_INFO("playerbots", "Deleting all random bot characters and accounts...");
// First execute all the cleanup SQL commands
// Clear playerbots_random_bots table
// Clear playerbots_random_bots and playerbots_account_type
PlayerbotsDatabase.Execute("DELETE FROM playerbots_random_bots");
PlayerbotsDatabase.Execute("DELETE FROM playerbots_account_type");
// Get the database names dynamically
std::string loginDBName = LoginDatabase.GetConnectionInfo()->database;
@@ -486,6 +568,13 @@ void RandomPlayerbotFactory::CreateRandomBots()
CharacterDatabase.Execute("DELETE FROM characters WHERE account IN (SELECT id FROM " + loginDBName + ".account WHERE username LIKE '{}%%')",
sPlayerbotAIConfig->randomBotAccountPrefix.c_str());
// Wait for the characters to be deleted before proceeding to dependent deletes
while (CharacterDatabase.QueueSize())
{
std::this_thread::sleep_for(1s);
}
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Extra 100ms fixed delay for safety.
// Clean up orphaned entries in playerbots_guild_tasks
PlayerbotsDatabase.Execute("DELETE FROM playerbots_guild_tasks WHERE owner NOT IN (SELECT guid FROM " + characterDBName + ".characters)");
@@ -500,11 +589,13 @@ void RandomPlayerbotFactory::CreateRandomBots()
CharacterDatabase.Execute("DELETE FROM character_achievement WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_achievement_progress WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_action WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_arena_stats WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_aura WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_entry_point WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_glyphs WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_homebind WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM item_instance WHERE owner_guid NOT IN (SELECT guid FROM characters) AND owner_guid > 0");
CharacterDatabase.Execute("DELETE FROM character_inventory WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM item_instance WHERE owner_guid NOT IN (SELECT guid FROM characters) AND owner_guid > 0");
// Clean up pet data
CharacterDatabase.Execute("DELETE FROM character_pet WHERE owner NOT IN (SELECT guid FROM characters)");
@@ -559,11 +650,23 @@ void RandomPlayerbotFactory::CreateRandomBots()
uint32 timer = getMSTime();
// After ALL deletions, make sure data is commited to DB
LoginDatabase.Execute("COMMIT");
CharacterDatabase.Execute("COMMIT");
PlayerbotsDatabase.Execute("COMMIT");
// Wait for all pending database operations to complete
while (LoginDatabase.QueueSize() || CharacterDatabase.QueueSize() || PlayerbotsDatabase.QueueSize())
{
std::this_thread::sleep_for(1s);
}
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Extra 100ms fixed delay for safety.
// Flush tables to ensure all data in memory are written to disk
LoginDatabase.Execute("FLUSH TABLES");
CharacterDatabase.Execute("FLUSH TABLES");
PlayerbotsDatabase.Execute("FLUSH TABLES");
LOG_INFO("playerbots", ">> Random bot accounts and data deleted in {} ms", GetMSTimeDiffToNow(timer));
LOG_INFO("playerbots", "Please reset the AiPlayerbot.DeleteRandomBotAccounts to 0 and restart the server...");
World::StopNow(SHUTDOWN_EXIT_CODE);
@@ -682,7 +785,7 @@ void RandomPlayerbotFactory::CreateRandomBots()
LOG_DEBUG("playerbots", "Creating random bot characters for account: [{}/{}]", accountNumber + 1, totalAccountCount);
RandomPlayerbotFactory factory(accountId);
WorldSession* session = new WorldSession(accountId, "", nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING,
WorldSession* session = new WorldSession(accountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING,
time_t(0), LOCALE_enUS, 0, false, false, 0, true);
sessionBots.push_back(session);

File diff suppressed because it is too large Load Diff

View File

@@ -60,7 +60,6 @@ public:
bool IsEmpty() { return !lastChangeTime; }
public:
uint32 value;
uint32 lastChangeTime;
uint32 validIn;
@@ -104,10 +103,6 @@ public:
void LogPlayerLocation();
void UpdateAIInternal(uint32 elapsed, bool minimal = false) override;
private:
//void ScaleBotActivity();
public:
uint32 activeBots = 0;
static bool HandlePlayerbotConsoleCommand(ChatHandler* handler, char const* args);
bool IsRandomBot(Player* bot);
@@ -180,6 +175,8 @@ public:
std::map<uint8, std::vector<WorldLocation>> locsPerLevelCache;
std::map<uint8, std::vector<WorldLocation>> allianceStarterPerLevelCache;
std::map<uint8, std::vector<WorldLocation>> hordeStarterPerLevelCache;
std::vector<uint32> allianceFlightMasterCache;
std::vector<uint32> hordeFlightMasterCache;
struct LevelBracket {
uint32 low;
uint32 high;
@@ -187,6 +184,11 @@ public:
};
std::map<uint32, LevelBracket> zone2LevelBracket;
std::map<uint8, std::vector<WorldLocation>> bankerLocsPerLevelCache;
// Account type management
void AssignAccountTypes();
bool IsAccountType(uint32 accountId, uint8 accountType);
protected:
void OnBotLoginInternal(Player* const bot) override;
@@ -206,6 +208,8 @@ private:
time_t BgCheckTimer;
time_t LfgCheckTimer;
time_t PlayersCheckTimer;
time_t RealPlayerLastTimeSeen = 0;
time_t DelayLoginBotsTimer;
time_t printStatsTimer;
uint32 AddRandomBots();
bool ProcessBot(uint32 bot);
@@ -214,10 +218,8 @@ private:
void RandomTeleport(Player* bot, std::vector<WorldLocation>& locs, bool hearth = false);
uint32 GetZoneLevel(uint16 mapId, float teleX, float teleY, float teleZ);
typedef void (RandomPlayerbotMgr::*ConsoleCommandHandler)(Player*);
std::vector<Player*> players;
uint32 processTicks;
// std::map<uint32, std::vector<WorldLocation>> rpgLocsCache;
std::map<uint32, std::map<uint32, std::vector<WorldLocation>>> rpgLocsCacheLevel;
@@ -226,6 +228,12 @@ private:
std::list<uint32> currentBots;
uint32 bgBotsCount;
uint32 playersLevel;
// Account lists
std::vector<uint32> rndBotTypeAccounts; // Accounts marked as RNDbot (type 1)
std::vector<uint32> addClassTypeAccounts; // Accounts marked as AddClass (type 2)
//void ScaleBotActivity(); // Deprecated function
};
#define sRandomPlayerbotMgr RandomPlayerbotMgr::instance()

View File

@@ -3336,7 +3336,7 @@ void TravelMgr::LoadQuestTravelTable()
uint32 accountId = fields[0].Get<uint32>();
WorldSession* session =
new WorldSession(accountId, "", nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING, time_t(0),
new WorldSession(accountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING, time_t(0),
LOCALE_enUS, 0, false, false, 0, true);
std::vector<std::pair<std::pair<uint32, uint32>, uint32>> classSpecLevel;

View File

@@ -39,6 +39,7 @@
#include "SpellAuraDefines.h"
#include "StatsWeightCalculator.h"
#include "World.h"
#include "AiObjectContext.h"
const uint64 diveMask = (1LL << 7) | (1LL << 44) | (1LL << 37) | (1LL << 38) | (1LL << 26) | (1LL << 30) | (1LL << 27) |
(1LL << 33) | (1LL << 24) | (1LL << 34);
@@ -224,24 +225,22 @@ void PlayerbotFactory::Randomize(bool incremental)
{
bot->resetTalents(true);
}
// bot->SaveToDB(false, false);
ClearSkills();
// bot->SaveToDB(false, false);
ClearSpells();
// bot->SaveToDB(false, false);
if (!incremental)
{
ClearSkills();
ClearSpells();
ResetQuests();
if (!sPlayerbotAIConfig->equipmentPersistence || level < sPlayerbotAIConfig->equipmentPersistenceLevel)
{
ClearAllItems();
}
}
if (!sPlayerbotAIConfig->equipmentPersistence || level < sPlayerbotAIConfig->equipmentPersistenceLevel)
{
ClearAllItems();
}
ClearInventory();
bot->RemoveAllSpellCooldown();
UnbindInstance();
bot->GiveLevel(level);
bot->InitStatsForLevel();
bot->InitStatsForLevel(true);
CancelAuras();
// bot->SaveToDB(false, false);
if (pmo)
@@ -280,7 +279,6 @@ void PlayerbotFactory::Randomize(bool incremental)
LOG_DEBUG("playerbots", "Initializing skills (step 1)...");
pmo = sPerformanceMonitor->start(PERF_MON_RNDBOT, "PlayerbotFactory_Skills1");
InitSkills();
// InitTradeSkills();
if (pmo)
pmo->finish();
@@ -337,7 +335,8 @@ void PlayerbotFactory::Randomize(bool incremental)
if (!incremental || !sPlayerbotAIConfig->equipmentPersistence ||
bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel)
{
InitEquipment(incremental, incremental ? false : sPlayerbotAIConfig->twoRoundsGearInit);
if (sPlayerbotAIConfig->incrementalGearInit || !incremental)
InitEquipment(incremental, incremental ? false : sPlayerbotAIConfig->twoRoundsGearInit);
}
// bot->SaveToDB(false, false);
if (pmo)
@@ -415,7 +414,7 @@ void PlayerbotFactory::Randomize(bool incremental)
pmo = sPerformanceMonitor->start(PERF_MON_RNDBOT, "PlayerbotFactory_Consumable");
LOG_DEBUG("playerbots", "Initializing consumables...");
AddConsumables();
InitConsumables();
if (pmo)
pmo->finish();
@@ -483,11 +482,8 @@ void PlayerbotFactory::Refresh()
InitAmmo();
InitFood();
InitReagents();
// InitPotions();
if (!sPlayerbotAIConfig->equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel)
{
InitTalentsTree(true, true, true);
}
InitConsumables();
InitPotions();
InitPet();
InitPetTalents();
InitClassSpells();
@@ -497,7 +493,10 @@ void PlayerbotFactory::Refresh()
InitSpecialSpells();
InitMounts();
InitKeyring();
InitPotions();
if (!sPlayerbotAIConfig->equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel)
{
InitTalentsTree(true, true, true);
}
if (bot->GetLevel() >= sPlayerbotAIConfig->minEnchantingBotLevel)
{
ApplyEnchantAndGemsNew();
@@ -511,151 +510,218 @@ void PlayerbotFactory::Refresh()
// bot->SaveToDB(false, false);
}
void PlayerbotFactory::AddConsumables()
void PlayerbotFactory::InitConsumables()
{
int specTab = AiFactory::GetPlayerSpecTab(bot);
std::vector<std::pair<uint32, uint32>> items;
switch (bot->getClass())
{
case CLASS_PRIEST:
case CLASS_MAGE:
case CLASS_WARLOCK:
{
if (level >= 5 && level < 20)
// Discipline or Holy: Mana Oil
if (specTab == 0 || specTab == 1)
{
StoreItem(CONSUM_ID_MINOR_WIZARD_OIL, 5);
std::vector<uint32> mana_oils = {BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
for (uint32 itemId : mana_oils)
{
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (proto->RequiredLevel > level || level > 75)
continue;
items.push_back({itemId, 4});
break;
}
}
if (level >= 20 && level < 40)
// Shadow: Wizard Oil
if (specTab == 2)
{
StoreItem(CONSUM_ID_MINOR_MANA_OIL, 5);
StoreItem(CONSUM_ID_MINOR_WIZARD_OIL, 5);
std::vector<uint32> wizard_oils = {BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
for (uint32 itemId : wizard_oils)
{
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (proto->RequiredLevel > level || level > 75)
continue;
items.push_back({itemId, 4});
break;
}
}
if (level >= 40 && level < 45)
break;
}
case CLASS_MAGE:
{
// Always Wizard Oil
std::vector<uint32> wizard_oils = {BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
for (uint32 itemId : wizard_oils)
{
StoreItem(CONSUM_ID_MINOR_MANA_OIL, 5);
StoreItem(CONSUM_ID_WIZARD_OIL, 5);
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (proto->RequiredLevel > level || level > 75)
continue;
items.push_back({itemId, 4});
break;
}
if (level >= 45 && level < 52)
break;
}
case CLASS_DRUID:
{
// Balance: Wizard Oil
if (specTab == 0)
{
StoreItem(CONSUM_ID_BRILLIANT_MANA_OIL, 5);
StoreItem(CONSUM_ID_BRILLIANT_WIZARD_OIL, 5);
std::vector<uint32> wizard_oils = {BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
for (uint32 itemId : wizard_oils)
{
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (proto->RequiredLevel > level || level > 75)
continue;
items.push_back({itemId, 4});
break;
}
}
if (level >= 52 && level < 58)
// Feral: Sharpening Stones & Weightstones
else if (specTab == 1)
{
StoreItem(CONSUM_ID_SUPERIOR_MANA_OIL, 5);
StoreItem(CONSUM_ID_BRILLIANT_WIZARD_OIL, 5);
std::vector<uint32> sharpening_stones = {ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, DENSE_SHARPENING_STONE, SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE};
std::vector<uint32> weightstones = {ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE, HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE};
for (uint32 itemId : sharpening_stones)
{
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (proto->RequiredLevel > level || level > 75)
continue;
items.push_back({itemId, 20});
break;
}
for (uint32 itemId : weightstones)
{
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (proto->RequiredLevel > level || level > 75)
continue;
items.push_back({itemId, 20});
break;
}
}
if (level >= 58)
// Restoration: Mana Oil
else if (specTab == 2)
{
StoreItem(CONSUM_ID_SUPERIOR_MANA_OIL, 5);
StoreItem(CONSUM_ID_SUPERIOR_WIZARD_OIL, 5);
std::vector<uint32> mana_oils = {BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
for (uint32 itemId : mana_oils)
{
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (proto->RequiredLevel > level || level > 75)
continue;
items.push_back({itemId, 4});
break;
}
}
break;
}
case CLASS_PALADIN:
{
// Holy: Mana Oil
if (specTab == 0)
{
std::vector<uint32> mana_oils = {BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
for (uint32 itemId : mana_oils)
{
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (proto->RequiredLevel > level || level > 75)
continue;
items.push_back({itemId, 4});
break;
}
}
// Protection: Wizard Oil (Protection prioritizes Superior over Brilliant)
else if (specTab == 1)
{
std::vector<uint32> wizard_oils = {BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
for (uint32 itemId : wizard_oils)
{
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (proto->RequiredLevel > level || level > 75)
continue;
items.push_back({itemId, 4});
break;
}
}
// Retribution: Sharpening Stones & Weightstones
else if (specTab == 2)
{
std::vector<uint32> sharpening_stones = {ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, DENSE_SHARPENING_STONE, SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE};
std::vector<uint32> weightstones = {ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE, HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE};
for (uint32 itemId : sharpening_stones)
{
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (proto->RequiredLevel > level || level > 75)
continue;
items.push_back({itemId, 20});
break;
}
for (uint32 itemId : weightstones)
{
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (proto->RequiredLevel > level || level > 75)
continue;
items.push_back({itemId, 20});
break;
}
}
break;
}
case CLASS_WARRIOR:
case CLASS_HUNTER:
{
if (level >= 1 && level < 5)
// Sharpening Stones & Weightstones
std::vector<uint32> sharpening_stones = {ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, DENSE_SHARPENING_STONE, SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE};
std::vector<uint32> weightstones = {ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE, HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE};
for (uint32 itemId : sharpening_stones)
{
StoreItem(CONSUM_ID_ROUGH_SHARPENING_STONE, 5);
StoreItem(CONSUM_ID_ROUGH_WEIGHTSTONE, 5);
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (proto->RequiredLevel > level || level > 75)
continue;
items.push_back({itemId, 20});
break;
}
if (level >= 5 && level < 15)
for (uint32 itemId : weightstones)
{
StoreItem(CONSUM_ID_COARSE_WEIGHTSTONE, 5);
StoreItem(CONSUM_ID_COARSE_SHARPENING_STONE, 5);
}
if (level >= 15 && level < 25)
{
StoreItem(CONSUM_ID_HEAVY_WEIGHTSTONE, 5);
StoreItem(CONSUM_ID_HEAVY_SHARPENING_STONE, 5);
}
if (level >= 25 && level < 35)
{
StoreItem(CONSUM_ID_SOL_SHARPENING_STONE, 5);
StoreItem(CONSUM_ID_SOLID_WEIGHTSTONE, 5);
}
if (level >= 35 && level < 50)
{
StoreItem(CONSUM_ID_DENSE_WEIGHTSTONE, 5);
StoreItem(CONSUM_ID_DENSE_SHARPENING_STONE, 5);
}
if (level >= 50 && level < 60)
{
StoreItem(CONSUM_ID_FEL_SHARPENING_STONE, 5);
StoreItem(CONSUM_ID_FEL_WEIGHTSTONE, 5);
}
if (level >= 60)
{
StoreItem(CONSUM_ID_ADAMANTITE_WEIGHTSTONE, 5);
StoreItem(CONSUM_ID_ADAMANTITE_SHARPENING_STONE, 5);
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (proto->RequiredLevel > level || level > 75)
continue;
items.push_back({itemId, 20});
break;
}
break;
}
case CLASS_ROGUE:
{
if (level >= 20 && level < 28)
// Poisons
std::vector<uint32> instant_poisons = {INSTANT_POISON_IX, INSTANT_POISON_VIII, INSTANT_POISON_VII, INSTANT_POISON_VI, INSTANT_POISON_V, INSTANT_POISON_IV, INSTANT_POISON_III, INSTANT_POISON_II, INSTANT_POISON};
std::vector<uint32> deadly_poisons = {DEADLY_POISON_IX, DEADLY_POISON_VIII, DEADLY_POISON_VII, DEADLY_POISON_VI, DEADLY_POISON_V, DEADLY_POISON_IV, DEADLY_POISON_III, DEADLY_POISON_II, DEADLY_POISON};
for (uint32 itemId : deadly_poisons)
{
StoreItem(CONSUM_ID_INSTANT_POISON, 5);
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (proto->RequiredLevel > level)
continue;
items.push_back({itemId, 20});
break;
}
if (level >= 28 && level < 30)
for (uint32 itemId : instant_poisons)
{
StoreItem(CONSUM_ID_INSTANT_POISON_II, 5);
}
if (level >= 30 && level < 36)
{
StoreItem(CONSUM_ID_DEADLY_POISON, 5);
StoreItem(CONSUM_ID_INSTANT_POISON_II, 5);
}
if (level >= 36 && level < 44)
{
StoreItem(CONSUM_ID_DEADLY_POISON_II, 5);
StoreItem(CONSUM_ID_INSTANT_POISON_III, 5);
}
if (level >= 44 && level < 52)
{
StoreItem(CONSUM_ID_DEADLY_POISON_III, 5);
StoreItem(CONSUM_ID_INSTANT_POISON_IV, 5);
}
if (level >= 52 && level < 60)
{
StoreItem(CONSUM_ID_DEADLY_POISON_IV, 5);
StoreItem(CONSUM_ID_INSTANT_POISON_V, 5);
}
if (level >= 60 && level < 62)
{
StoreItem(CONSUM_ID_DEADLY_POISON_V, 5);
StoreItem(CONSUM_ID_INSTANT_POISON_VI, 5);
}
if (level >= 62 && level < 68)
{
StoreItem(CONSUM_ID_DEADLY_POISON_VI, 5);
StoreItem(CONSUM_ID_INSTANT_POISON_VI, 5);
}
if (level >= 68)
{
StoreItem(CONSUM_ID_DEADLY_POISON_VII, 5);
StoreItem(CONSUM_ID_INSTANT_POISON_VII, 5);
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (proto->RequiredLevel > level)
continue;
items.push_back({itemId, 20});
break;
}
break;
}
default:
break;
}
for (std::pair<uint32, uint32> item : items)
{
int count = (int)item.second - (int)bot->GetItemCount(item.first);
if (count > 0)
StoreItem(item.first, count);
}
}
void PlayerbotFactory::InitPetTalents()
@@ -813,6 +879,10 @@ void PlayerbotFactory::InitPetTalents()
void PlayerbotFactory::InitPet()
{
Pet* pet = bot->GetPet();
if (!pet && bot->GetPetStable() && bot->GetPetStable()->CurrentPet)
return;
if (!pet)
{
if (bot->getClass() != CLASS_HUNTER || bot->GetLevel() < 10)
@@ -825,6 +895,7 @@ void PlayerbotFactory::InitPet()
std::vector<uint32> ids;
CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates();
for (CreatureTemplateContainer::const_iterator itr = creatures->begin(); itr != creatures->end(); ++itr)
{
if (!itr->second.IsTameable(bot->CanTameExoticPets()))
@@ -840,6 +911,12 @@ void PlayerbotFactory::InitPet()
if (onlyWolf && itr->second.family != CREATURE_FAMILY_WOLF)
continue;
// Exclude configured pet families
if (std::find(sPlayerbotAIConfig->excludedHunterPetFamilies.begin(),
sPlayerbotAIConfig->excludedHunterPetFamilies.end(),
itr->second.family) != sPlayerbotAIConfig->excludedHunterPetFamilies.end())
continue;
ids.push_back(itr->first);
}
@@ -861,7 +938,8 @@ void PlayerbotFactory::InitPet()
uint32 pet_number = sObjectMgr->GeneratePetNumber();
if (bot->GetPetStable() && bot->GetPetStable()->CurrentPet)
{
bot->GetPetStable()->CurrentPet.value();
auto petGuid = bot->GetPetStable()->CurrentPet.value(); // To correct the build warnin in VS
// bot->GetPetStable()->CurrentPet.value();
// bot->GetPetStable()->CurrentPet.reset();
bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT);
bot->RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT);
@@ -1019,9 +1097,21 @@ void PlayerbotFactory::InitTalentsTree(bool increment /*false*/, bool use_templa
/// @todo: match current talent with template
specTab = AiFactory::GetPlayerSpecTab(bot);
/// @todo: fix cat druid hardcode
if (bot->getClass() == CLASS_DRUID && specTab == DRUID_TAB_FERAL && bot->GetLevel() >= 20 &&
!bot->HasAura(16931))
specTab = 3;
if (bot->getClass() == CLASS_DRUID && specTab == DRUID_TAB_FERAL && bot->GetLevel() >= 20)
{
bool isCat = !bot->HasAura(16931);
if (!isCat && bot->GetLevel() == 20)
{
uint32 bearP = sPlayerbotAIConfig->randomClassSpecProb[cls][1];
uint32 catP = sPlayerbotAIConfig->randomClassSpecProb[cls][3];
if (urand(1, bearP + catP) <= catP)
isCat = true;
}
if (isCat)
{
specTab = 3;
}
}
}
else
{
@@ -1594,9 +1684,51 @@ void Shuffle(std::vector<uint32>& items)
void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
{
if (incremental && !sPlayerbotAIConfig->incrementalGearInit)
return;
if (level < 5) {
// original items
if (CharStartOutfitEntry const* oEntry = GetCharStartOutfitEntry(bot->getRace(), bot->getClass(), bot->getGender()))
{
for (int j = 0; j < MAX_OUTFIT_ITEMS; ++j)
{
if (oEntry->ItemId[j] <= 0)
continue;
uint32 itemId = oEntry->ItemId[j];
// skip hearthstone
if (itemId == 6948)
continue;
// just skip, reported in ObjectMgr::LoadItemTemplates
ItemTemplate const* iProto = sObjectMgr->GetItemTemplate(itemId);
if (!iProto)
continue;
// BuyCount by default
uint32 count = iProto->BuyCount;
// special amount for food/drink
if (iProto->Class == ITEM_CLASS_CONSUMABLE && iProto->SubClass == ITEM_SUBCLASS_FOOD)
{
continue;
}
if (bot->HasItemCount(itemId, count)) {
continue;
}
bot->StoreNewItemInBestSlots(itemId, count);
}
}
return;
}
std::unordered_map<uint8, std::vector<uint32>> items;
// int tab = AiFactory::GetPlayerSpecTab(bot);
uint32 blevel = bot->GetLevel();
int32 delta = std::min(blevel, 10u);
@@ -1738,7 +1870,7 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
if (incremental && oldItem)
{
float old_score = calculator.CalculateItem(oldItem->GetEntry());
float old_score = calculator.CalculateItem(oldItem->GetEntry(), oldItem->GetItemRandomPropertyId());
if (bestScoreForSlot < 1.2f * old_score)
continue;
}
@@ -2200,33 +2332,15 @@ void PlayerbotFactory::InitSkills()
//uint32 maxValue = level * 5; //not used, line marked for removal.
bot->UpdateSkillsForLevel();
auto SafeLearn = [this](uint32 spellId)
{
if (!bot->HasSpell(spellId))
bot->learnSpell(spellId, false, true); // Avoid duplicate attempts in DB
};
// Define Riding skill according to level
if (bot->GetLevel() >= 70)
bot->SetSkill(SKILL_RIDING, 300, 300, 300);
else if (bot->GetLevel() >= 60)
bot->SetSkill(SKILL_RIDING, 225, 225, 225);
else if (bot->GetLevel() >= 40)
bot->SetSkill(SKILL_RIDING, 150, 150, 150);
else if (bot->GetLevel() >= 20)
bot->SetSkill(SKILL_RIDING, 75, 75, 75);
else
bot->SetSkill(SKILL_RIDING, 0, 0, 0);
// Safe learning of mount spells
bot->SetSkill(SKILL_RIDING, 0, 0, 0);
if (bot->GetLevel() >= sPlayerbotAIConfig->useGroundMountAtMinLevel)
SafeLearn(33388); // Apprentice
bot->learnSpell(33388);
if (bot->GetLevel() >= sPlayerbotAIConfig->useFastGroundMountAtMinLevel)
SafeLearn(33391); // Journeyman
bot->learnSpell(33391);
if (bot->GetLevel() >= sPlayerbotAIConfig->useFlyMountAtMinLevel)
SafeLearn(34090); // Expert
bot->learnSpell(34090);
if (bot->GetLevel() >= sPlayerbotAIConfig->useFastFlyMountAtMinLevel)
SafeLearn(34091); // Artisan
bot->learnSpell(34091);
uint32 skillLevel = bot->GetLevel() < 40 ? 0 : 1;
uint32 dualWieldLevel = bot->GetLevel() < 20 ? 0 : 1;
@@ -3180,102 +3294,97 @@ void PlayerbotFactory::InitFood()
void PlayerbotFactory::InitReagents()
{
int level = bot->GetLevel();
int specTab = AiFactory::GetPlayerSpecTab(bot);
std::vector<std::pair<uint32, uint32>> items;
switch (bot->getClass())
{
case CLASS_ROGUE:
{
std::vector<int> instant_poison_ids = {43231, 43230, 21927, 8928, 8927, 8926, 6950, 6949, 6947};
std::vector<int> deadly_poison_ids = {43233, 43232, 22054, 22053, 20844, 8985, 8984, 2893, 2892};
for (int& itemId : deadly_poison_ids)
{
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (proto->RequiredLevel > bot->GetLevel())
continue;
items.push_back({itemId, 20}); // deadly poison
break;
}
for (int& itemId : instant_poison_ids)
{
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (proto->RequiredLevel > bot->GetLevel())
continue;
items.push_back({itemId, 20}); // instant poison
break;
}
}
break;
case CLASS_SHAMAN:
// items.push_back({46978, 1}); // Totem
items.push_back({5175, 1}); // Earth Totem
items.push_back({5176, 1}); // Flame Totem
items.push_back({5177, 1}); // Water Totem
items.push_back({5178, 1}); // Air Totem
if (bot->GetLevel() >= 30)
items.push_back({17030, 40}); // Ankh
break;
case CLASS_WARLOCK:
items.push_back({6265, 20}); // shard
break;
case CLASS_PRIEST:
if (level >= 48 && level < 60)
{
items.push_back({17028, 40}); // Wild Berries
}
else if (level >= 60 && level < 80)
{
items.push_back({17029, 40}); // Wild Berries
}
else if (level >= 80)
{
items.push_back({44615, 40}); // Wild Berries
}
break;
case CLASS_MAGE:
items.push_back({17020, 40}); // Arcane Powder
items.push_back({17031, 40}); // portal
items.push_back({17032, 40}); // portal
case CLASS_DEATH_KNIGHT:
if (level >= 56)
items.push_back({37201, 40}); // Corpse Dust
break;
case CLASS_DRUID:
if (level >= 20 && level < 30)
items.push_back({17034, 20}); // Maple Seed
else if (level >= 30 && level < 40)
items.push_back({17035, 20}); // Stranglethorn Seed
else if (level >= 40 && level < 50)
items.push_back({17036, 20}); // Ashwood Seed
else if (level >= 50 && level < 60)
{
items.push_back({17034, 40});
items.push_back({17037, 20}); // Hornbeam Seed
items.push_back({17021, 20}); // Wild Berries
}
if (level >= 30 && level < 40)
else if (level >= 60 && level < 69)
{
items.push_back({17035, 40});
items.push_back({17038, 20}); // Ironwood Seed
items.push_back({17026, 20}); // Wild Thornroot
}
if (level >= 40 && level < 50)
else if (level == 69)
{
items.push_back({17036, 40});
items.push_back({22147, 20}); // Flintweed Seed
items.push_back({17026, 20}); // Wild Thornroot
}
if (level >= 50 && level < 60)
else if (level >= 70 && level < 79)
{
items.push_back({17037, 40});
items.push_back({17021, 40});
items.push_back({22147, 20}); // Flintweed Seed
items.push_back({22148, 20}); // Wild Quillvine
}
if (level >= 60 && level < 70)
else if (level == 79)
{
items.push_back({17038, 40});
items.push_back({17026, 40});
items.push_back({44614, 20}); // Starleaf Seed
items.push_back({22148, 20}); // Wild Quillvine
}
if (level >= 70 && level < 80)
else if (level >= 80)
{
items.push_back({22147, 40});
items.push_back({22148, 40});
}
if (level >= 80)
{
items.push_back({44614, 40});
items.push_back({44605, 40});
items.push_back({44614, 20}); // Starleaf Seed
items.push_back({44605, 20}); // Wild Spineleaf
}
break;
case CLASS_MAGE:
if (level >= 20)
items.push_back({17031, 20}); // Rune of Teleportation
if (level >= 40)
items.push_back({17032, 20}); // Rune of Portals
if (level >= 56)
items.push_back({17020, 20}); // Arcane Powder
break;
case CLASS_PALADIN:
items.push_back({21177, 100});
if (level >= 52)
items.push_back({21177, 80}); // Symbol of Kings
break;
case CLASS_DEATH_KNIGHT:
items.push_back({37201, 40});
case CLASS_PRIEST:
if (level >= 48 && level < 56)
items.push_back({17028, 40}); // Holy Candle
else if (level >= 56 && level < 60)
{
items.push_back({17028, 20}); // Holy Candle
items.push_back({17029, 20}); // Sacred Candle
}
else if (level >= 60 && level < 77)
items.push_back({17029, 40}); // Sacred Candle
else if (level >= 77 && level < 80)
{
items.push_back({17029, 20}); // Sacred Candle
items.push_back({44615, 20}); // Devout Candle
}
else if (level >= 80)
items.push_back({44615, 40}); // Devout Candle
break;
case CLASS_SHAMAN:
if (level >= 4)
items.push_back({5175, 1}); // Earth Totem
if (level >= 10)
items.push_back({5176, 1}); // Flame Totem
if (level >= 20)
items.push_back({5177, 1}); // Water Totem
if (level >= 30)
{
items.push_back({5178, 1}); // Air Totem
items.push_back({17030, 20}); // Ankh
}
break;
case CLASS_WARLOCK:
items.push_back({6265, 5}); // Soul Shard
break;
default:
break;
@@ -3288,9 +3397,75 @@ void PlayerbotFactory::InitReagents()
}
}
void PlayerbotFactory::CleanupConsumables() // remove old consumables as part of randombot level-up maintenance
{
std::vector<Item*> itemsToDelete;
std::vector<Item*> items;
for (uint32 i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i)
if (Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
items.push_back(item);
for (uint32 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
if (Bag* bag = (Bag*)bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
for (uint32 j = 0; j < bag->GetBagSize(); ++j)
if (Item* item = bag->GetItemByPos(j))
items.push_back(item);
for (Item* item : items)
{
ItemTemplate const* proto = item->GetTemplate();
if (!proto) continue;
// Remove ammo
if (proto->Class == ITEM_CLASS_PROJECTILE)
itemsToDelete.push_back(item);
// Remove food/drink
if (proto->Class == ITEM_CLASS_CONSUMABLE && proto->SubClass == ITEM_SUBCLASS_FOOD)
itemsToDelete.push_back(item);
// Remove potions
if (proto->Class == ITEM_CLASS_CONSUMABLE && proto->SubClass == ITEM_SUBCLASS_POTION)
itemsToDelete.push_back(item);
// Remove reagents
if (proto->Class == ITEM_CLASS_REAGENT || (proto->Class == ITEM_CLASS_MISC && proto->SubClass == ITEM_SUBCLASS_REAGENT))
itemsToDelete.push_back(item);
}
std::set<uint32> idsToDelete = {
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL,
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL,
ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, DENSE_SHARPENING_STONE, SOLID_SHARPENING_STONE,
HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE,
ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE,
HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE,
INSTANT_POISON_IX, INSTANT_POISON_VIII, INSTANT_POISON_VII, INSTANT_POISON_VI, INSTANT_POISON_V,
INSTANT_POISON_IV, INSTANT_POISON_III, INSTANT_POISON_II, INSTANT_POISON,
DEADLY_POISON_IX, DEADLY_POISON_VIII, DEADLY_POISON_VII, DEADLY_POISON_VI, DEADLY_POISON_V,
DEADLY_POISON_IV, DEADLY_POISON_III, DEADLY_POISON_II, DEADLY_POISON
};
for (Item* item : items)
{
ItemTemplate const* proto = item->GetTemplate();
if (!proto) continue;
if (idsToDelete.find(proto->ItemId) != idsToDelete.end())
itemsToDelete.push_back(item);
}
for (Item* item : itemsToDelete)
bot->DestroyItem(item->GetBagSlot(), item->GetSlot(), true);
}
void PlayerbotFactory::InitGlyphs(bool increment)
{
bot->InitGlyphsForLevel();
if (!increment && botAI &&
botAI->GetAiObjectContext()->GetValue<bool>("custom_glyphs")->Get())
return; // // Added for custom Glyphs - custom glyphs flag test
if (!increment)
{
@@ -3349,8 +3524,157 @@ void PlayerbotFactory::InitGlyphs(bool increment)
uint8 cls = bot->getClass();
uint8 tab = AiFactory::GetPlayerSpecTab(bot);
/// @todo: fix cat druid hardcode
if (bot->getClass() == CLASS_DRUID && tab == DRUID_TAB_FERAL && bot->GetLevel() >= 20 && !bot->HasAura(16931))
tab = 3;
// Warrior PVP exceptions
if (bot->getClass() == CLASS_WARRIOR)
{
// Arms PvP (spec index 3): If the bot has the Second Wind talent
if (bot->HasAura(29838))
tab = 3;
// Fury PvP (spec index 4): If the bot has the Blood Craze talent
else if (bot->HasAura(16492))
tab = 4;
// Protection PvP (spec index 5): If the bot has the Gag Order talent
else if (bot->HasAura(12958))
tab = 5;
}
// Paladin PvP exceptions
if (bot->getClass() == CLASS_PALADIN)
{
// Holy PvP (spec index 3): If the bot has the Sacred Cleansing talent
if (bot->HasAura(53553))
tab = 3;
// Protection PvP (spec index 4): If the bot has the Reckoning talent
else if (bot->HasAura(20179))
tab = 4;
// Retribution PvP (spec index 5): If the bot has the Divine Purpose talent
else if (bot->HasAura(31872))
tab = 5;
}
// Hunter PvP exceptions
if (bot->getClass() == CLASS_HUNTER)
{
// Beast Mastery PvP (spec index 3): If the bot has the Thick Hide talent
if (bot->HasAura(19612))
tab = 3;
// Marksmanship PvP (spec index 4): If the bot has the Concussive Barrage talent
else if (bot->HasAura(35102))
tab = 4;
// Survival PvP (spec index 5): If the bot has the Entrapment talent and does NOT have the Concussive Barrage talent
else if (bot->HasAura(19388) && !bot->HasAura(35102))
tab = 5;
}
// Rogue PvP exceptions
if (bot->getClass() == CLASS_ROGUE)
{
// Assassination PvP (spec index 3): If the bot has the Deadly Brew talent
if (bot->HasAura(51626))
tab = 3;
// Combat PvP (spec index 4): If the bot has the Throwing Specialization talent
else if (bot->HasAura(51679))
tab = 4;
// Subtlety PvP (spec index 5): If the bot has the Waylay talent
else if (bot->HasAura(51696))
tab = 5;
}
// Priest PvP exceptions
if (bot->getClass() == CLASS_PRIEST)
{
// Discipline PvP (spec index 3): If the bot has the Improved Mana Burn talent
if (bot->HasAura(14772))
tab = 3;
// Holy PvP (spec index 4): If the bot has the Body and Soul talent
else if (bot->HasAura(64129))
tab = 4;
// Shadow PvP (spec index 5): If the bot has the Improved Vampiric Embrace talent
else if (bot->HasAura(27840))
tab = 5;
}
// Death Knight PvE/PvP exceptions
if (bot->getClass() == CLASS_DEATH_KNIGHT)
{
// Double Aura Blood PvE (spec index 3): If the bot has both the Abomination's Might and Improved Icy Talons
// talents
if (bot->HasAura(53138) && bot->HasAura(55610))
tab = 3;
// Blood PvP (spec index 4): If the bot has the Sudden Doom talent
else if (bot->HasAura(49529))
tab = 4;
// Frost PvP (spec index 5): If the bot has the Acclimation talent
else if (bot->HasAura(50152))
tab = 5;
// Unholy PvP (spec index 6): If the bot has the Magic Suppression talent
else if (bot->HasAura(49611))
tab = 6;
}
// Shaman PvP exceptions
if (bot->getClass() == CLASS_SHAMAN)
{
// Elemental PvP (spec index 3): If the bot has the Astral Shift talent
if (bot->HasAura(51479))
tab = 3;
// Enhancement PvP (spec index 4): If the bot has the Earthen Power talent
else if (bot->HasAura(51524))
tab = 4;
// Restoration PvP (spec index 5): If the bot has the Focused Mind talent
else if (bot->HasAura(30866))
tab = 5;
}
// Mage PvE/PvP exceptions
if (bot->getClass() == CLASS_MAGE)
{
// Frostfire PvE (spec index 3): If the bot has both the Burnout talent and the Ice Shards talent
if (bot->HasAura(44472) && bot->HasAura(15047))
tab = 3;
// Arcane PvP (spec index 4): If the bot has the Improved Blink talent
else if (bot->HasAura(31570))
tab = 4;
// Fire PvP (spec index 5): If the bot has the Fiery Payback talent
else if (bot->HasAura(64357))
tab = 5;
// Frost PvP (spec index 6): If the bot has the Shattered Barrier talent
else if (bot->HasAura(54787))
tab = 6;
}
// Warlock PvP exceptions
if (bot->getClass() == CLASS_WARLOCK)
{
// Affliction PvP (spec index 3): If the bot has the Improved Howl of Terror talent
if (bot->HasAura(30057))
tab = 3;
// Demonology PvP (spec index 4): If the bot has both the Nemesis talent and the Intensity talent
else if (bot->HasAura(63123) && bot->HasAura(18136))
tab = 4;
// Destruction PvP (spec index 5): If the bot has the Nether Protection talent
else if (bot->HasAura(30302))
tab = 5;
}
// Druid PvE/PvP exceptions
if (bot->getClass() == CLASS_DRUID)
{
// Cat PvE (spec index 3): If the bot is Feral spec, level 20 or higher, and does NOT have the Thick Hide talent
if (tab == DRUID_TAB_FERAL && bot->GetLevel() >= 20 && !bot->HasAura(16931))
tab = 3;
// Balance PvP (spec index 4): If the bot has the Owlkin Frenzy talent
else if (bot->HasAura(48393))
tab = 4;
// Feral PvP (spec index 5): If the bot has the Primal Tenacity talent
else if (bot->HasAura(33957))
tab = 5;
// Resto PvP (spec index 6): If the bot has the Improved Barkskin talent
else if (bot->HasAura(63411))
tab = 6;
}
std::list<uint32> glyphs;
ItemTemplateContainer const* itemTemplates = sObjectMgr->GetItemTemplateStore();
for (ItemTemplateContainer::const_iterator i = itemTemplates->begin(); i != itemTemplates->end(); ++i)

View File

@@ -45,63 +45,6 @@ enum spec : uint8
ROLE_CDPS = 3
};*/
enum PriorizedConsumables
{
CONSUM_ID_ROUGH_WEIGHTSTONE = 3239,
CONSUM_ID_COARSE_WEIGHTSTONE = 3239,
CONSUM_ID_HEAVY_WEIGHTSTONE = 3241,
CONSUM_ID_SOLID_WEIGHTSTONE = 7965,
CONSUM_ID_DENSE_WEIGHTSTONE = 12643,
CONSUM_ID_FEL_WEIGHTSTONE = 28420,
CONSUM_ID_ADAMANTITE_WEIGHTSTONE = 28421,
CONSUM_ID_ROUGH_SHARPENING_STONE = 2862,
CONSUM_ID_COARSE_SHARPENING_STONE = 2863,
CONSUM_ID_HEAVY_SHARPENING_STONE = 2871,
CONSUM_ID_SOL_SHARPENING_STONE = 7964,
CONSUM_ID_DENSE_SHARPENING_STONE = 12404,
CONSUM_ID_ELEMENTAL_SHARPENING_STONE = 18262,
CONSUM_ID_CONSECRATED_SHARPENING_STONE = 23122,
CONSUM_ID_FEL_SHARPENING_STONE = 23528,
CONSUM_ID_ADAMANTITE_SHARPENING_STONE = 23529,
CONSUM_ID_LINEN_BANDAGE = 1251,
CONSUM_ID_HEAVY_LINEN_BANDAGE = 2581,
CONSUM_ID_WOOL_BANDAGE = 3530,
CONSUM_ID_HEAVY_WOOL_BANDAGE = 3531,
CONSUM_ID_SILK_BANDAGE = 6450,
CONSUM_ID_HEAVY_SILK_BANDAGE = 6451,
CONSUM_ID_MAGEWEAVE_BANDAGE = 8544,
CONSUM_ID_HEAVY_MAGEWEAVE_BANDAGE = 8545,
CONSUM_ID_RUNECLOTH_BANDAGE = 14529,
CONSUM_ID_HEAVY_RUNECLOTH_BANDAGE = 14530,
CONSUM_ID_NETHERWEAVE_BANDAGE = 21990,
CONSUM_ID_HEAVY_NETHERWEAVE_BANDAGE = 21991,
CONSUM_ID_BRILLIANT_MANA_OIL = 20748,
CONSUM_ID_MINOR_MANA_OIL = 20745,
CONSUM_ID_SUPERIOR_MANA_OIL = 22521,
CONSUM_ID_LESSER_MANA_OIL = 20747,
CONSUM_ID_BRILLIANT_WIZARD_OIL = 20749,
CONSUM_ID_MINOR_WIZARD_OIL = 20744,
CONSUM_ID_SUPERIOR_WIZARD_OIL = 22522,
CONSUM_ID_WIZARD_OIL = 20750,
CONSUM_ID_LESSER_WIZARD_OIL = 20746,
CONSUM_ID_INSTANT_POISON = 6947,
CONSUM_ID_INSTANT_POISON_II = 6949,
CONSUM_ID_INSTANT_POISON_III = 6950,
CONSUM_ID_INSTANT_POISON_IV = 8926,
CONSUM_ID_INSTANT_POISON_V = 8927,
CONSUM_ID_INSTANT_POISON_VI = 8928,
CONSUM_ID_INSTANT_POISON_VII = 21927,
CONSUM_ID_DEADLY_POISON = 2892,
CONSUM_ID_DEADLY_POISON_II = 2893,
CONSUM_ID_DEADLY_POISON_III = 8984,
CONSUM_ID_DEADLY_POISON_IV = 8985,
CONSUM_ID_DEADLY_POISON_V = 20844,
CONSUM_ID_DEADLY_POISON_VI = 22053,
CONSUM_ID_DEADLY_POISON_VII = 22054
};
#define MAX_CONSUM_ID 28
class PlayerbotFactory
{
public:
@@ -128,8 +71,10 @@ public:
void InitAmmo();
static uint32 CalcMixedGearScore(uint32 gs, uint32 quality);
void InitPetTalents();
void CleanupConsumables();
void InitReagents();
void InitConsumables();
void InitPotions();
void InitGlyphs(bool increment = false);
void InitFood();
void InitMounts();
@@ -140,7 +85,6 @@ public:
void InitKeyring();
void InitReputation();
void InitAttunementQuests();
void InitPotions();
private:
void Prepare();
@@ -177,7 +121,6 @@ private:
void InitGuild();
void InitArenaTeam();
void InitImmersive();
void AddConsumables();
static void AddPrevQuests(uint32 questId, std::list<uint32>& questIds);
void LoadEnchantContainer();
void ApplyEnchantTemplate();

View File

@@ -3,6 +3,7 @@
#include <cstdint>
#include "DBCStores.h"
#include "ItemEnchantmentMgr.h"
#include "ItemTemplate.h"
#include "ObjectMgr.h"
#include "PlayerbotAI.h"
@@ -28,12 +29,12 @@ void StatsCollector::CollectItemStats(ItemTemplate const* proto)
{
if (proto->IsRangedWeapon())
{
uint32 val = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2 / proto->Delay;
float val = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2 / proto->Delay;
stats[STATS_TYPE_RANGED_DPS] += val;
}
else if (proto->IsWeapon())
{
uint32 val = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2 / proto->Delay;
float val = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2 / proto->Delay;
stats[STATS_TYPE_MELEE_DPS] += val;
}
stats[STATS_TYPE_ARMOR] += proto->Armor;
@@ -205,7 +206,7 @@ void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 s
}
}
void StatsCollector::CollectEnchantStats(SpellItemEnchantmentEntry const* enchant)
void StatsCollector::CollectEnchantStats(SpellItemEnchantmentEntry const* enchant, uint32 default_enchant_amount)
{
for (int s = 0; s < MAX_SPELL_ITEM_ENCHANTMENT_EFFECTS; ++s)
{
@@ -231,6 +232,10 @@ void StatsCollector::CollectEnchantStats(SpellItemEnchantmentEntry const* enchan
}
case ITEM_ENCHANTMENT_TYPE_STAT:
{
// for item random suffix
if (!enchant_amount)
enchant_amount = default_enchant_amount;
if (!enchant_amount)
{
break;
@@ -250,7 +255,7 @@ bool StatsCollector::SpecialSpellFilter(uint32 spellId)
// trinket
switch (spellId)
{
case 60764: // Totem of Splintering
case 60764: // Totem of Splintering
if (type_ & (CollectorType::SPELL))
return true;
break;
@@ -431,10 +436,10 @@ void StatsCollector::CollectByItemStatType(uint32 itemStatType, int32 val)
switch (itemStatType)
{
case ITEM_MOD_MANA:
stats[STATS_TYPE_MANA_REGENERATION] += val / 10;
stats[STATS_TYPE_MANA_REGENERATION] += (float)val / 10;
break;
case ITEM_MOD_HEALTH:
stats[STATS_TYPE_STAMINA] += val / 15;
stats[STATS_TYPE_STAMINA] += (float)val / 15;
break;
case ITEM_MOD_AGILITY:
stats[STATS_TYPE_AGILITY] += val;
@@ -742,11 +747,11 @@ void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float mu
}
}
int32 StatsCollector::AverageValue(const SpellEffectInfo& effectInfo)
float StatsCollector::AverageValue(const SpellEffectInfo& effectInfo)
{
//float basePointsPerLevel = effectInfo.RealPointsPerLevel; //not used, line marked for removal.
int32 basePoints = effectInfo.BasePoints;
int32 randomPoints = int32(effectInfo.DieSides);
// float basePointsPerLevel = effectInfo.RealPointsPerLevel; //not used, line marked for removal.
float basePoints = effectInfo.BasePoints;
int32 randomPoints = effectInfo.DieSides;
switch (randomPoints)
{
@@ -756,7 +761,7 @@ int32 StatsCollector::AverageValue(const SpellEffectInfo& effectInfo)
basePoints += 1;
break;
default:
int32 randvalue = (1 + randomPoints) / 2;
float randvalue = (1 + randomPoints) / 2.0f;
basePoints += randvalue;
break;
}
@@ -767,7 +772,7 @@ bool StatsCollector::CheckSpellValidation(uint32 spellFamilyName, flag96 spelFal
{
if (PlayerbotAI::Class2SpellFamilyName(cls_) != spellFamilyName)
return false;
bool isHealingSpell = PlayerbotAI::IsHealingSpell(spellFamilyName, spelFalimyFlags);
// strict to healer
if (strict && (type_ & CollectorType::SPELL_HEAL))

View File

@@ -66,12 +66,12 @@ public:
void Reset();
void CollectItemStats(ItemTemplate const* proto);
void CollectSpellStats(uint32 spellId, float multiplier = 1.0f, int32 spellCooldown = -1);
void CollectEnchantStats(SpellItemEnchantmentEntry const* enchant);
void CollectEnchantStats(SpellItemEnchantmentEntry const* enchant, uint32 default_enchant_amount = 0);
bool CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 procFlags, bool strict = true);
bool CheckSpellValidation(uint32 spellFamilyName, flag96 spelFalimyFlags, bool strict = true);
public:
int32 stats[STATS_TYPE_MAX];
float stats[STATS_TYPE_MAX];
private:
void CollectByItemStatType(uint32 itemStatType, int32 val);
@@ -80,7 +80,7 @@ private:
void HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger,
uint32 triggerCooldown);
int32 AverageValue(const SpellEffectInfo& effectInfo);
float AverageValue(const SpellEffectInfo& effectInfo);
private:
CollectorType type_;

View File

@@ -9,6 +9,7 @@
#include "AiFactory.h"
#include "DBCStores.h"
#include "ItemEnchantmentMgr.h"
#include "ItemTemplate.h"
#include "ObjectMgr.h"
#include "PlayerbotAI.h"
@@ -32,6 +33,7 @@ StatsWeightCalculator::StatsWeightCalculator(Player* player) : player_(player)
else
type_ = CollectorType::RANGED;
cls = player->getClass();
lvl = player->GetLevel();
tab = AiFactory::GetPlayerSpecTab(player);
collector_ = std::make_unique<StatsCollector>(type_, cls);
@@ -59,7 +61,7 @@ void StatsWeightCalculator::Reset()
}
}
float StatsWeightCalculator::CalculateItem(uint32 itemId)
float StatsWeightCalculator::CalculateItem(uint32 itemId, int32 randomPropertyIds)
{
ItemTemplate const* proto = &sObjectMgr->GetItemTemplateStore()->at(itemId);
@@ -70,6 +72,9 @@ float StatsWeightCalculator::CalculateItem(uint32 itemId)
collector_->CollectItemStats(proto);
if (randomPropertyIds != 0)
CalculateRandomProperty(randomPropertyIds, itemId);
if (enable_overflow_penalty_)
ApplyOverflowPenalty(player_);
@@ -116,6 +121,53 @@ float StatsWeightCalculator::CalculateEnchant(uint32 enchantId)
return weight_;
}
void StatsWeightCalculator::CalculateRandomProperty(int32 randomPropertyId, uint32 itemId)
{
if (randomPropertyId > 0)
{
ItemRandomPropertiesEntry const* item_rand = sItemRandomPropertiesStore.LookupEntry(randomPropertyId);
if (!item_rand)
{
return;
}
for (uint32 i = PROP_ENCHANTMENT_SLOT_0; i < MAX_ENCHANTMENT_SLOT; ++i)
{
uint32 enchantId = item_rand->Enchantment[i - PROP_ENCHANTMENT_SLOT_0];
SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(enchantId);
if (enchant)
collector_->CollectEnchantStats(enchant);
}
}
else
{
ItemRandomSuffixEntry const* item_rand = sItemRandomSuffixStore.LookupEntry(-randomPropertyId);
if (!item_rand)
{
return;
}
for (uint32 i = PROP_ENCHANTMENT_SLOT_0; i < MAX_ENCHANTMENT_SLOT; ++i)
{
uint32 enchantId = item_rand->Enchantment[i - PROP_ENCHANTMENT_SLOT_0];
SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(enchantId);
uint32 enchant_amount = 0;
for (int k = 0; k < MAX_ITEM_ENCHANTMENT_EFFECTS; ++k)
{
if (item_rand->Enchantment[k] == enchantId)
{
enchant_amount = uint32((item_rand->AllocationPct[k] * GenerateEnchSuffixFactor(itemId)) / 10000);
break;
}
}
if (enchant)
collector_->CollectEnchantStats(enchant, enchant_amount);
}
}
}
void StatsWeightCalculator::GenerateWeights(Player* player)
{
GenerateBasicWeights(player);
@@ -130,6 +182,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_ARMOR] += 0.001f;
stats_weights_[STATS_TYPE_BONUS] += 1.0f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 0.01f;
stats_weights_[STATS_TYPE_RANGED_DPS] += 0.01f;
if (cls == CLASS_HUNTER && (tab == HUNTER_TAB_BEASTMASTER || tab == HUNTER_TAB_SURVIVAL))
{
@@ -293,8 +346,8 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_CRIT] += 0.8f;
stats_weights_[STATS_TYPE_HASTE] += 1.0f;
}
else if ((cls == CLASS_PALADIN && tab == PALADIN_TAB_HOLY) || // holy
(cls == CLASS_SHAMAN && tab == SHAMAN_TAB_RESTORATION)) // heal
else if ((cls == CLASS_PALADIN && tab == PALADIN_TAB_HOLY) || // holy
(cls == CLASS_SHAMAN && tab == SHAMAN_TAB_RESTORATION)) // heal
{
stats_weights_[STATS_TYPE_INTELLECT] += 0.9f;
stats_weights_[STATS_TYPE_SPIRIT] += 0.15f;
@@ -303,7 +356,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_CRIT] += 0.6f;
stats_weights_[STATS_TYPE_HASTE] += 0.8f;
}
else if ((cls == CLASS_PRIEST && tab != PRIEST_TAB_SHADOW) || // discipline / holy
else if ((cls == CLASS_PRIEST && tab != PRIEST_TAB_SHADOW) || // discipline / holy
(cls == CLASS_DRUID && tab == DRUID_TAB_RESTORATION))
{
stats_weights_[STATS_TYPE_INTELLECT] += 0.8f;
@@ -464,9 +517,9 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
// {
// weight_ *= 1.0;
// }
// double hand
if (proto->Class == ITEM_CLASS_WEAPON)
{
// double hand
bool isDoubleHand = proto->Class == ITEM_CLASS_WEAPON &&
!(ITEM_SUBCLASS_MASK_SINGLE_HAND & (1 << proto->SubClass)) &&
!(ITEM_SUBCLASS_MASK_WEAPON_RANGED & (1 << proto->SubClass));
@@ -474,29 +527,37 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
if (isDoubleHand)
{
weight_ *= 0.5;
}
// spec without double hand
// enhancement, rogue, ice dk, unholy dk, shield tank, fury warrior without titan's grip but with duel wield
if (isDoubleHand &&
((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && player_->CanDualWield()) ||
(cls == CLASS_ROGUE) || (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_FROST) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanTitanGrip() && player_->CanDualWield()) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_PROTECTION) ||
(cls == CLASS_PALADIN && tab == PALADIN_TAB_PROTECTION)))
{
weight_ *= 0.1;
// spec without double hand
// enhancement, rogue, ice dk, unholy dk, shield tank, fury warrior without titan's grip but with duel wield
if (((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && player_->CanDualWield()) ||
(cls == CLASS_ROGUE) || (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_FROST) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanTitanGrip() &&
player_->CanDualWield()) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_PROTECTION) ||
(cls == CLASS_PALADIN && tab == PALADIN_TAB_PROTECTION)))
{
weight_ *= 0.1;
}
}
// spec with double hand
// fury without duel wield, arms, bear, retribution, blood dk
if (!isDoubleHand &&
((cls == CLASS_HUNTER && !player_->CanDualWield()) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanDualWield()) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_ARMS) || (cls == CLASS_DRUID && tab == DRUID_TAB_FERAL) ||
(cls == CLASS_PALADIN && tab == PALADIN_TAB_RETRIBUTION) ||
(cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_BLOOD) ||
(cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && !player_->CanDualWield())))
if (!isDoubleHand)
{
weight_ *= 0.1;
if ((cls == CLASS_HUNTER && !player_->CanDualWield()) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanDualWield()) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_ARMS) || (cls == CLASS_DRUID && tab == DRUID_TAB_FERAL) ||
(cls == CLASS_PALADIN && tab == PALADIN_TAB_RETRIBUTION) ||
(cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_BLOOD) ||
(cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && !player_->CanDualWield()))
{
weight_ *= 0.1;
}
// caster's main hand (cannot duel weapon but can equip two-hands stuff)
if (cls == CLASS_MAGE || cls == CLASS_PRIEST || cls == CLASS_WARLOCK || cls == CLASS_DRUID ||
(cls == CLASS_SHAMAN && !player_->CanDualWield()))
{
weight_ *= 0.65;
}
}
// fury with titan's grip
if ((!isDoubleHand || proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM ||
@@ -505,15 +566,18 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
{
weight_ *= 0.1;
}
if (cls == CLASS_HUNTER && proto->SubClass == ITEM_SUBCLASS_WEAPON_THROWN)
{
weight_ *= 0.1;
}
if (cls == CLASS_ROGUE && (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY) &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER)
if (lvl >= 10 && cls == CLASS_ROGUE && (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY) &&
proto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER)
{
weight_ *= 0.5;
weight_ *= 1.5;
}
if (cls == CLASS_ROGUE && player_->HasAura(13964) &&
(proto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD || proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE))
{
@@ -559,12 +623,13 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
if (hitOverflowType_ & CollectorType::SPELL)
{
hit_current = player->GetTotalAuraModifier(SPELL_AURA_MOD_SPELL_HIT_CHANCE);
hit_current += player->GetTotalAuraModifier(SPELL_AURA_MOD_INCREASES_SPELL_PCT_TO_HIT); // suppression (18176)
hit_current +=
player->GetTotalAuraModifier(SPELL_AURA_MOD_INCREASES_SPELL_PCT_TO_HIT); // suppression (18176)
hit_current += player->GetRatingBonusValue(CR_HIT_SPELL);
if (cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW && player->HasAura(15835)) // Shadow Focus
if (cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW && player->HasAura(15835)) // Shadow Focus
hit_current += 3;
if (cls == CLASS_MAGE && tab == MAGE_TAB_ARCANE && player->HasAura(12840)) // Arcane Focus
if (cls == CLASS_MAGE && tab == MAGE_TAB_ARCANE && player->HasAura(12840)) // Arcane Focus
hit_current += 3;
hit_overflow = SPELL_HIT_OVERFLOW;
@@ -593,7 +658,7 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
else
validPoints = 0;
}
collector_->stats[STATS_TYPE_HIT] = std::min(collector_->stats[STATS_TYPE_HIT], (int)validPoints);
collector_->stats[STATS_TYPE_HIT] = std::min(collector_->stats[STATS_TYPE_HIT], validPoints);
}
{
@@ -610,8 +675,7 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
else
validPoints = 0;
collector_->stats[STATS_TYPE_EXPERTISE] =
std::min(collector_->stats[STATS_TYPE_EXPERTISE], (int)validPoints);
collector_->stats[STATS_TYPE_EXPERTISE] = std::min(collector_->stats[STATS_TYPE_EXPERTISE], validPoints);
}
}
@@ -628,7 +692,7 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
else
validPoints = 0;
collector_->stats[STATS_TYPE_DEFENSE] = std::min(collector_->stats[STATS_TYPE_DEFENSE], (int)validPoints);
collector_->stats[STATS_TYPE_DEFENSE] = std::min(collector_->stats[STATS_TYPE_DEFENSE], validPoints);
}
}
@@ -647,7 +711,7 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
validPoints = 0;
collector_->stats[STATS_TYPE_ARMOR_PENETRATION] =
std::min(collector_->stats[STATS_TYPE_ARMOR_PENETRATION], (int)validPoints);
std::min(collector_->stats[STATS_TYPE_ARMOR_PENETRATION], validPoints);
}
}
}
@@ -657,7 +721,7 @@ void StatsWeightCalculator::ApplyWeightFinetune(Player* player)
{
if (type_ & (CollectorType::MELEE | CollectorType::RANGED))
{
float armor_penetration_current/*, armor_penetration_overflow*/; //not used, line marked for removal.
float armor_penetration_current /*, armor_penetration_overflow*/; // not used, line marked for removal.
armor_penetration_current = player->GetRatingBonusValue(CR_ARMOR_PENETRATION);
if (armor_penetration_current > 50)
stats_weights_[STATS_TYPE_ARMOR_PENETRATION] *= 1.2f;

View File

@@ -28,18 +28,19 @@ class StatsWeightCalculator
public:
StatsWeightCalculator(Player* player);
void Reset();
float CalculateItem(uint32 itemId);
float CalculateItem(uint32 itemId, int32 randomPropertyId = 0);
float CalculateEnchant(uint32 enchantId);
void SetOverflowPenalty(bool apply) { enable_overflow_penalty_ = apply; }
void SetItemSetBonus(bool apply) { enable_item_set_bonus_ = apply; }
void SetQualityBlend(bool apply) { enable_quality_blend_ = apply; }
private:
private:
void GenerateWeights(Player* player);
void GenerateBasicWeights(Player* player);
void GenerateAdditionalWeights(Player* player);
void CalculateRandomProperty(int32 randomPropertyId, uint32 itemId);
void CalculateItemSetMod(Player* player, ItemTemplate const* proto);
void CalculateSocketBonus(Player* player, ItemTemplate const* proto);
@@ -56,6 +57,7 @@ private:
CollectorType hitOverflowType_;
std::unique_ptr<StatsCollector> collector_;
uint8 cls;
uint8 lvl;
int tab;
bool enable_overflow_penalty_;
bool enable_item_set_bonus_;

View File

@@ -8,39 +8,89 @@
#include "ActionContext.h"
#include "ChatActionContext.h"
#include "ChatTriggerContext.h"
#include "DKAiObjectContext.h"
#include "DruidAiObjectContext.h"
#include "HunterAiObjectContext.h"
#include "MageAiObjectContext.h"
#include "PaladinAiObjectContext.h"
#include "Playerbots.h"
#include "RaidUlduarTriggerContext.h"
#include "PriestAiObjectContext.h"
#include "RaidUlduarActionContext.h"
#include "RaidUlduarTriggerContext.h"
#include "RogueAiObjectContext.h"
#include "ShamanAiObjectContext.h"
#include "SharedValueContext.h"
#include "StrategyContext.h"
#include "TriggerContext.h"
#include "ValueContext.h"
#include "WarlockAiObjectContext.h"
#include "WarriorAiObjectContext.h"
#include "WorldPacketActionContext.h"
#include "WorldPacketTriggerContext.h"
#include "RaidStrategyContext.h"
#include "RaidBwlActionContext.h"
#include "RaidBwlTriggerContext.h"
#include "RaidNaxxActionContext.h"
#include "RaidNaxxTriggerContext.h"
#include "RaidIccActionContext.h"
#include "RaidIccTriggerContext.h"
#include "RaidOsActionContext.h"
#include "RaidOsTriggerContext.h"
#include "RaidEoEActionContext.h"
#include "RaidVoATriggerContext.h"
#include "RaidOnyxiaActionContext.h"
#include "RaidOnyxiaTriggerContext.h"
#include "RaidVoAActionContext.h"
#include "RaidEoETriggerContext.h"
#include "RaidMcActionContext.h"
#include "RaidMcTriggerContext.h"
#include "RaidAq20ActionContext.h"
#include "RaidAq20TriggerContext.h"
#include "DungeonStrategyContext.h"
#include "WotlkDungeonActionContext.h"
#include "WotlkDungeonTriggerContext.h"
#include "dungeons/DungeonStrategyContext.h"
#include "dungeons/wotlk/WotlkDungeonActionContext.h"
#include "dungeons/wotlk/WotlkDungeonTriggerContext.h"
#include "raids/RaidStrategyContext.h"
#include "raids/aq20/RaidAq20ActionContext.h"
#include "raids/aq20/RaidAq20TriggerContext.h"
#include "raids/blackwinglair/RaidBwlActionContext.h"
#include "raids/blackwinglair/RaidBwlTriggerContext.h"
#include "raids/eyeofeternity/RaidEoEActionContext.h"
#include "raids/eyeofeternity/RaidEoETriggerContext.h"
#include "raids/icecrown/RaidIccActionContext.h"
#include "raids/icecrown/RaidIccTriggerContext.h"
#include "raids/moltencore/RaidMcActionContext.h"
#include "raids/moltencore/RaidMcTriggerContext.h"
#include "raids/naxxramas/RaidNaxxActionContext.h"
#include "raids/naxxramas/RaidNaxxTriggerContext.h"
#include "raids/obsidiansanctum/RaidOsActionContext.h"
#include "raids/obsidiansanctum/RaidOsTriggerContext.h"
#include "raids/onyxia/RaidOnyxiaActionContext.h"
#include "raids/onyxia/RaidOnyxiaTriggerContext.h"
#include "raids/vaultofarchavon/RaidVoAActionContext.h"
#include "raids/vaultofarchavon/RaidVoATriggerContext.h"
AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
SharedNamedObjectContextList<Strategy> AiObjectContext::sharedStrategyContexts;
SharedNamedObjectContextList<Action> AiObjectContext::sharedActionContexts;
SharedNamedObjectContextList<Trigger> AiObjectContext::sharedTriggerContexts;
SharedNamedObjectContextList<UntypedValue> AiObjectContext::sharedValueContexts;
AiObjectContext::AiObjectContext(PlayerbotAI* botAI, SharedNamedObjectContextList<Strategy>& sharedStrategyContext,
SharedNamedObjectContextList<Action>& sharedActionContext,
SharedNamedObjectContextList<Trigger>& sharedTriggerContext,
SharedNamedObjectContextList<UntypedValue>& sharedValueContext)
: PlayerbotAIAware(botAI),
strategyContexts(sharedStrategyContext),
actionContexts(sharedActionContext),
triggerContexts(sharedTriggerContext),
valueContexts(sharedValueContext)
{
}
void AiObjectContext::BuildAllSharedContexts()
{
AiObjectContext::BuildSharedContexts();
PriestAiObjectContext::BuildSharedContexts();
MageAiObjectContext::BuildSharedContexts();
WarlockAiObjectContext::BuildSharedContexts();
WarriorAiObjectContext::BuildSharedContexts();
ShamanAiObjectContext::BuildSharedContexts();
PaladinAiObjectContext::BuildSharedContexts();
DruidAiObjectContext::BuildSharedContexts();
HunterAiObjectContext::BuildSharedContexts();
RogueAiObjectContext::BuildSharedContexts();
DKAiObjectContext::BuildSharedContexts();
}
void AiObjectContext::BuildSharedContexts()
{
BuildSharedStrategyContexts(sharedStrategyContexts);
BuildSharedActionContexts(sharedActionContexts);
BuildSharedTriggerContexts(sharedTriggerContexts);
BuildSharedValueContexts(sharedValueContexts);
}
void AiObjectContext::BuildSharedStrategyContexts(SharedNamedObjectContextList<Strategy>& strategyContexts)
{
strategyContexts.Add(new StrategyContext());
strategyContexts.Add(new MovementStrategyContext());
@@ -48,7 +98,10 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
strategyContexts.Add(new QuestStrategyContext());
strategyContexts.Add(new RaidStrategyContext());
strategyContexts.Add(new DungeonStrategyContext());
}
void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList<Action>& actionContexts)
{
actionContexts.Add(new ActionContext());
actionContexts.Add(new ChatActionContext());
actionContexts.Add(new WorldPacketActionContext());
@@ -75,8 +128,12 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
actionContexts.Add(new WotlkDungeonUPActionContext());
actionContexts.Add(new WotlkDungeonCoSActionContext());
actionContexts.Add(new WotlkDungeonFoSActionContext());
actionContexts.Add(new WotlkDungeonPoSActionContext());
actionContexts.Add(new WotlkDungeonToCActionContext());
}
void AiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContextList<Trigger>& triggerContexts)
{
triggerContexts.Add(new TriggerContext());
triggerContexts.Add(new ChatTriggerContext());
triggerContexts.Add(new WorldPacketTriggerContext());
@@ -102,28 +159,14 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
triggerContexts.Add(new WotlkDungeonOccTriggerContext());
triggerContexts.Add(new WotlkDungeonUPTriggerContext());
triggerContexts.Add(new WotlkDungeonCoSTriggerContext());
triggerContexts.Add(new WotlkDungeonFosTriggerContext());
triggerContexts.Add(new WotlkDungeonFoSTriggerContext());
triggerContexts.Add(new WotlkDungeonPoSTriggerContext());
triggerContexts.Add(new WotlkDungeonToCTriggerContext());
}
void AiObjectContext::BuildSharedValueContexts(SharedNamedObjectContextList<UntypedValue>& valueContexts)
{
valueContexts.Add(new ValueContext());
valueContexts.Add(sSharedValueContext);
}
void AiObjectContext::Update()
{
strategyContexts.Update();
triggerContexts.Update();
actionContexts.Update();
valueContexts.Update();
}
void AiObjectContext::Reset()
{
strategyContexts.Reset();
triggerContexts.Reset();
actionContexts.Reset();
valueContexts.Reset();
}
std::vector<std::string> AiObjectContext::Save()
@@ -216,5 +259,3 @@ std::string const AiObjectContext::FormatValues()
return out.str();
}
void AiObjectContext::AddShared(NamedObjectContext<UntypedValue>* sharedValues) { valueContexts.Add(sharedValues); }

View File

@@ -19,10 +19,20 @@
class PlayerbotAI;
typedef Strategy* (*StrategyCreator)(PlayerbotAI* botAI);
typedef Action* (*ActionCreator)(PlayerbotAI* botAI);
typedef Trigger* (*TriggerCreator)(PlayerbotAI* botAI);
typedef UntypedValue* (*ValueCreator)(PlayerbotAI* botAI);
class AiObjectContext : public PlayerbotAIAware
{
public:
AiObjectContext(PlayerbotAI* botAI);
static BoolCalculatedValue* custom_glyphs(PlayerbotAI* ai); // Added for cutom glyphs
AiObjectContext(PlayerbotAI* botAI,
SharedNamedObjectContextList<Strategy>& sharedStrategyContext = sharedStrategyContexts,
SharedNamedObjectContextList<Action>& sharedActionContext = sharedActionContexts,
SharedNamedObjectContextList<Trigger>& sharedTriggerContext = sharedTriggerContexts,
SharedNamedObjectContextList<UntypedValue>& sharedValueContext = sharedValueContexts);
virtual ~AiObjectContext() {}
virtual Strategy* GetStrategy(std::string const name);
@@ -56,20 +66,30 @@ public:
std::set<std::string> GetSupportedActions();
std::string const FormatValues();
virtual void Update();
virtual void Reset();
virtual void AddShared(NamedObjectContext<UntypedValue>* sharedValues);
std::vector<std::string> Save();
void Load(std::vector<std::string> data);
std::vector<std::string> performanceStack;
static void BuildAllSharedContexts();
static void BuildSharedContexts();
static void BuildSharedStrategyContexts(SharedNamedObjectContextList<Strategy>& strategyContexts);
static void BuildSharedActionContexts(SharedNamedObjectContextList<Action>& actionContexts);
static void BuildSharedTriggerContexts(SharedNamedObjectContextList<Trigger>& triggerContexts);
static void BuildSharedValueContexts(SharedNamedObjectContextList<UntypedValue>& valueContexts);
protected:
NamedObjectContextList<Strategy> strategyContexts;
NamedObjectContextList<Action> actionContexts;
NamedObjectContextList<Trigger> triggerContexts;
NamedObjectContextList<UntypedValue> valueContexts;
private:
static SharedNamedObjectContextList<Strategy> sharedStrategyContexts;
static SharedNamedObjectContextList<Action> sharedActionContexts;
static SharedNamedObjectContextList<Trigger> sharedTriggerContexts;
static SharedNamedObjectContextList<UntypedValue> sharedValueContexts;
};
#endif

View File

@@ -11,6 +11,7 @@
#include "Playerbots.h"
#include "Queue.h"
#include "Strategy.h"
#include "Timer.h"
Engine::Engine(PlayerbotAI* botAI, AiObjectContext* factory) : PlayerbotAIAware(botAI), aiObjectContext(factory)
{
@@ -108,6 +109,8 @@ void Engine::Reset()
}
multipliers.clear();
actionNodeFactories.creators.clear();
}
void Engine::Init()
@@ -120,9 +123,10 @@ void Engine::Init()
strategyTypeMask |= strategy->GetType();
strategy->InitMultipliers(multipliers);
strategy->InitTriggers(triggers);
Event emptyEvent;
MultiplyAndPush(strategy->getDefaultActions(), 0.0f, false, emptyEvent, "default");
for (auto &iter : strategy->actionNodeFactories.creators)
{
actionNodeFactories.creators[iter.first] = iter.second;
}
}
if (testMode)
@@ -248,11 +252,9 @@ bool Engine::DoNextAction(Unit* unit, uint32 depth, bool minimal)
ActionNode* Engine::CreateActionNode(std::string const name)
{
for (std::map<std::string, Strategy*>::iterator i = strategies.begin(); i != strategies.end(); i++)
{
if (ActionNode* node = i->second->GetAction(name))
return node;
}
ActionNode* node = actionNodeFactories.GetContextObject(name, botAI);
if (node)
return node;
return new ActionNode(name,
/*P*/ nullptr,
@@ -432,6 +434,7 @@ bool Engine::HasStrategy(std::string const name) { return strategies.find(name)
void Engine::ProcessTriggers(bool minimal)
{
std::unordered_map<Trigger*, Event> fires;
uint32 now = getMSTime();
for (std::vector<TriggerNode*>::iterator i = triggers.begin(); i != triggers.end(); i++)
{
TriggerNode* node = *i;
@@ -451,7 +454,7 @@ void Engine::ProcessTriggers(bool minimal)
if (fires.find(trigger) != fires.end())
continue;
if (testMode || trigger->needCheck())
if (testMode || trigger->needCheck(now))
{
if (minimal && node->getFirstRelevance() < 100)
continue;

View File

@@ -114,6 +114,7 @@ protected:
float lastRelevance;
std::string lastAction;
uint32 strategyTypeMask;
NamedObjectFactoryList<ActionNode> actionNodeFactories;
};
#endif

View File

@@ -232,6 +232,20 @@ public:
}
};
class CollectItemsVisitor : public IterateItemsVisitor
{
public:
CollectItemsVisitor() : IterateItemsVisitor() {}
std::vector<Item*> items;
bool Visit(Item* item) override
{
items.push_back(item);
return true;
}
};
class ItemCountByQuality : public IterateItemsVisitor
{
public:

View File

@@ -29,7 +29,8 @@ public:
std::string const getQualifier() { return qualifier; }
static std::string const MultiQualify(std::vector<std::string> qualifiers, const std::string& separator, const std::string_view brackets = "{}");
static std::string const MultiQualify(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);
@@ -40,9 +41,9 @@ protected:
template <class T>
class NamedObjectFactory
{
protected:
typedef T* (*ActionCreator)(PlayerbotAI* botAI);
std::unordered_map<std::string, ActionCreator> creators;
public:
typedef T* (*ObjectCreator)(PlayerbotAI* botAI);
std::unordered_map<std::string, ObjectCreator> creators;
public:
T* create(std::string name, PlayerbotAI* botAI)
@@ -58,7 +59,7 @@ public:
if (creators.find(name) == creators.end())
return nullptr;
ActionCreator creator = creators[name];
ObjectCreator creator = creators[name];
if (!creator)
return nullptr;
@@ -73,7 +74,7 @@ public:
std::set<std::string> supports()
{
std::set<std::string> keys;
for (typename std::unordered_map<std::string, ActionCreator>::iterator it = creators.begin();
for (typename std::unordered_map<std::string, ObjectCreator>::iterator it = creators.begin();
it != creators.end(); it++)
keys.insert(it->first);
@@ -111,24 +112,6 @@ public:
created.clear();
}
void Update()
{
for (typename std::unordered_map<std::string, T*>::iterator i = created.begin(); i != created.end(); i++)
{
if (i->second)
i->second->Update();
}
}
void Reset()
{
for (typename std::unordered_map<std::string, T*>::iterator i = created.begin(); i != created.end(); i++)
{
if (i->second)
i->second->Reset();
}
}
bool IsShared() { return shared; }
bool IsSupportsSiblings() { return supportsSiblings; }
@@ -147,53 +130,93 @@ protected:
bool supportsSiblings;
};
template <class T>
class SharedNamedObjectContextList
{
public:
typedef T* (*ObjectCreator)(PlayerbotAI* botAI);
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++)
delete *i;
}
void Add(NamedObjectContext<T>* context)
{
contexts.push_back(context);
for (const auto& iter : context->creators)
{
creators[iter.first] = iter.second;
}
}
};
template <class T>
class NamedObjectContextList
{
public:
virtual ~NamedObjectContextList()
typedef T* (*ObjectCreator)(PlayerbotAI* botAI);
const std::unordered_map<std::string, ObjectCreator>& creators;
const std::vector<NamedObjectContext<T>*>& contexts;
std::unordered_map<std::string, T*> created;
NamedObjectContextList(const SharedNamedObjectContextList<T>& shared)
: creators(shared.creators), contexts(shared.contexts)
{
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
{
NamedObjectContext<T>* context = *i;
if (!context->IsShared())
delete context;
}
}
void Add(NamedObjectContext<T>* context) { contexts.push_back(context); }
~NamedObjectContextList()
{
for (typename std::unordered_map<std::string, T*>::iterator i = created.begin(); i != created.end(); i++)
{
if (i->second)
delete i->second;
}
created.clear();
}
T* create(std::string name, PlayerbotAI* botAI)
{
size_t found = name.find("::");
std::string qualifier;
if (found != std::string::npos)
{
qualifier = name.substr(found + 2);
name = name.substr(0, found);
}
if (creators.find(name) == creators.end())
return nullptr;
ObjectCreator creator = creators.at(name);
if (!creator)
return nullptr;
T* object = (*creator)(botAI);
Qualified* q = dynamic_cast<Qualified*>(object);
if (q && found != std::string::npos)
q->Qualify(qualifier);
return object;
}
T* GetContextObject(std::string const name, PlayerbotAI* botAI)
{
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
if (created.find(name) == created.end())
{
if (T* object = (*i)->create(name, botAI))
return object;
}
return nullptr;
}
void Update()
{
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
{
if (!(*i)->IsShared())
(*i)->Update();
}
}
void Reset()
{
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
{
(*i)->Reset();
if (T* object = create(name, botAI))
return created[name] = object;
}
return created[name];
}
std::set<std::string> GetSiblings(std::string const name)
{
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
for (auto i = contexts.begin(); i != contexts.end(); i++)
{
if (!(*i)->IsSupportsSiblings())
continue;
@@ -213,7 +236,7 @@ public:
std::set<std::string> supports()
{
std::set<std::string> result;
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
for (auto i = contexts.begin(); i != contexts.end(); i++)
{
std::set<std::string> supported = (*i)->supports();
@@ -227,46 +250,69 @@ public:
std::set<std::string> GetCreated()
{
std::set<std::string> result;
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
for (typename std::unordered_map<std::string, T*>::iterator i = created.begin(); i != created.end(); i++)
{
std::set<std::string> createdKeys = (*i)->GetCreated();
for (std::set<std::string>::iterator j = createdKeys.begin(); j != createdKeys.end(); j++)
result.insert(*j);
result.insert(i->first);
}
return result;
}
private:
std::vector<NamedObjectContext<T>*> contexts;
};
template <class T>
class NamedObjectFactoryList
{
public:
typedef T* (*ObjectCreator)(PlayerbotAI* botAI);
std::vector<NamedObjectFactory<T>*> factories;
std::unordered_map<std::string, ObjectCreator> creators;
virtual ~NamedObjectFactoryList()
{
for (typename std::list<NamedObjectFactory<T>*>::iterator i = factories.begin(); i != factories.end(); i++)
for (typename std::vector<NamedObjectFactory<T>*>::iterator i = factories.begin(); i != factories.end(); i++)
delete *i;
}
void Add(NamedObjectFactory<T>* context) { factories.push_front(context); }
T* GetContextObject(std::string const& name, PlayerbotAI* botAI)
T* create(std::string name, PlayerbotAI* botAI)
{
for (typename std::list<NamedObjectFactory<T>*>::iterator i = factories.begin(); i != factories.end(); i++)
size_t found = name.find("::");
std::string qualifier;
if (found != std::string::npos)
{
if (T* object = (*i)->create(name, botAI))
return object;
qualifier = name.substr(found + 2);
name = name.substr(0, found);
}
return nullptr;
if (creators.find(name) == creators.end())
return nullptr;
ObjectCreator creator = creators[name];
if (!creator)
return nullptr;
T* object = (*creator)(botAI);
Qualified* q = dynamic_cast<Qualified*>(object);
if (q && found != std::string::npos)
q->Qualify(qualifier);
return object;
}
private:
std::list<NamedObjectFactory<T>*> factories;
void Add(NamedObjectFactory<T>* context)
{
factories.push_back(context);
for (const auto& iter : context->creators)
{
creators[iter.first] = iter.second;
}
}
T* GetContextObject(std::string const name, PlayerbotAI* botAI)
{
if (T* object = create(name, botAI))
return object;
return nullptr;
}
};
#endif

View File

@@ -19,6 +19,7 @@ PassiveMultiplier::PassiveMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "pa
allowedActions.push_back("nc");
allowedActions.push_back("reset botAI");
allowedActions.push_back("check mount state");
allowedActions.push_back("lfg");
}
if (allowedParts.empty())

View File

@@ -41,6 +41,7 @@ enum StrategyType : uint32
// };
static float ACTION_IDLE = 0.0f;
static float ACTION_BG = 1.0f;
static float ACTION_DEFAULT = 5.0f;
static float ACTION_NORMAL = 10.0f;
static float ACTION_HIGH = 20.0f;
@@ -68,7 +69,7 @@ public:
void Update() {}
void Reset() {}
protected:
public:
NamedObjectFactoryList<ActionNode> actionNodeFactories;
};

View File

@@ -32,12 +32,11 @@ Value<Unit*>* Trigger::GetTargetValue() { return context->GetValue<Unit*>(GetTar
Unit* Trigger::GetTarget() { return GetTargetValue()->Get(); }
bool Trigger::needCheck()
bool Trigger::needCheck(uint32 now)
{
if (checkInterval < 2)
return true;
uint32 now = getMSTime();
if (!lastCheckTime || now - lastCheckTime >= checkInterval)
{
lastCheckTime = now;

View File

@@ -30,7 +30,7 @@ public:
virtual Value<Unit*>* GetTargetValue();
virtual std::string const GetTargetName() { return "self target"; }
bool needCheck();
bool needCheck(uint32 now);
protected:
int32 checkInterval;

View File

@@ -13,7 +13,7 @@ class PlayerbotAI;
class AcceptBgInvitationAction : public Action
{
public:
AcceptBgInvitationAction(PlayerbotAI* botAI) : Action(botAI, "accept bg invitatio") {}
AcceptBgInvitationAction(PlayerbotAI* botAI) : Action(botAI, "accept bg invitation") {}
bool Execute(Event event) override;
};

View File

@@ -22,8 +22,7 @@ bool AcceptInvitationAction::Execute(Event event)
std::string name;
packet >> flag >> name;
// Player* inviter = ObjectAccessor::FindPlayer(grp->GetLeaderGUID());
Player* inviter = ObjectAccessor::FindPlayerByName(name, true);
Player* inviter = ObjectAccessor::FindPlayer(grp->GetLeaderGUID());
if (!inviter)
return false;
@@ -36,11 +35,17 @@ bool AcceptInvitationAction::Execute(Event event)
return false;
}
if (bot->isAFK())
bot->ToggleAFK();
WorldPacket p;
uint32 roles_mask = 0;
p << roles_mask;
bot->GetSession()->HandleGroupAcceptOpcode(p);
if (!bot->GetGroup() || !bot->GetGroup()->IsMember(inviter->GetGUID()))
return false;
if (sRandomPlayerbotMgr->IsRandomBot(bot))
botAI->SetMaster(inviter);
// else

View File

@@ -38,6 +38,7 @@
#include "InviteToGroupAction.h"
#include "LeaveGroupAction.h"
#include "LootAction.h"
#include "LootRollAction.h"
#include "MoveToRpgTargetAction.h"
#include "MoveToTravelTargetAction.h"
#include "MovementActions.h"
@@ -63,6 +64,7 @@
#include "WorldBuffAction.h"
#include "XpGainAction.h"
#include "NewRpgAction.h"
#include "CancelChannelAction.h"
class PlayerbotAI;
@@ -189,10 +191,13 @@ public:
creators["buy tabard"] = &ActionContext::buy_tabard;
creators["guild manage nearby"] = &ActionContext::guild_manage_nearby;
creators["clean quest log"] = &ActionContext::clean_quest_log;
creators["roll"] = &ActionContext::roll_action;
creators["cancel channel"] = &ActionContext::cancel_channel;
// BG Tactics
creators["bg tactics"] = &ActionContext::bg_tactics;
creators["bg move to start"] = &ActionContext::bg_move_to_start;
creators["bg reset objective force"] = &ActionContext::bg_reset_objective_force;
creators["bg move to objective"] = &ActionContext::bg_move_to_objective;
creators["bg select objective"] = &ActionContext::bg_select_objective;
creators["bg check objective"] = &ActionContext::bg_check_objective;
@@ -245,10 +250,11 @@ public:
creators["new rpg status update"] = &ActionContext::new_rpg_status_update;
creators["new rpg go grind"] = &ActionContext::new_rpg_go_grind;
creators["new rpg go innkeeper"] = &ActionContext::new_rpg_go_innkeeper;
creators["new rpg move random"] = &ActionContext::new_rpg_move_random;
creators["new rpg move npc"] = &ActionContext::new_rpg_move_npc;
creators["new rpg go camp"] = &ActionContext::new_rpg_go_camp;
creators["new rpg wander random"] = &ActionContext::new_rpg_wander_random;
creators["new rpg wander npc"] = &ActionContext::new_rpg_wander_npc;
creators["new rpg do quest"] = &ActionContext::new_rpg_do_quest;
creators["new rpg travel flight"] = &ActionContext::new_rpg_travel_flight;
}
private:
@@ -296,6 +302,7 @@ private:
static Action* arcane_torrent(PlayerbotAI* botAI) { return new CastArcaneTorrentAction(botAI); }
static Action* mana_tap(PlayerbotAI* botAI) { return new CastManaTapAction(botAI); }
static Action* end_pull(PlayerbotAI* botAI) { return new ChangeCombatStrategyAction(botAI, "-pull"); }
static Action* cancel_channel(PlayerbotAI* botAI) { return new CancelChannelAction(botAI); }
static Action* emote(PlayerbotAI* botAI) { return new EmoteAction(botAI); }
static Action* talk(PlayerbotAI* botAI) { return new TalkAction(botAI); }
@@ -372,10 +379,12 @@ private:
static Action* buy_tabard(PlayerbotAI* botAI) { return new BuyTabardAction(botAI); }
static Action* guild_manage_nearby(PlayerbotAI* botAI) { return new GuildManageNearbyAction(botAI); }
static Action* clean_quest_log(PlayerbotAI* botAI) { return new CleanQuestLogAction(botAI); }
static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); }
// BG Tactics
static Action* bg_tactics(PlayerbotAI* botAI) { return new BGTactics(botAI); }
static Action* bg_move_to_start(PlayerbotAI* botAI) { return new BGTactics(botAI, "move to start"); }
static Action* bg_reset_objective_force(PlayerbotAI* botAI) { return new BGTactics(botAI, "reset objective force"); }
static Action* bg_move_to_objective(PlayerbotAI* botAI) { return new BGTactics(botAI, "move to objective"); }
static Action* bg_select_objective(PlayerbotAI* botAI) { return new BGTactics(botAI, "select objective"); }
static Action* bg_check_objective(PlayerbotAI* botAI) { return new BGTactics(botAI, "check objective"); }
@@ -428,10 +437,11 @@ private:
static Action* new_rpg_status_update(PlayerbotAI* ai) { return new NewRpgStatusUpdateAction(ai); }
static Action* new_rpg_go_grind(PlayerbotAI* ai) { return new NewRpgGoGrindAction(ai); }
static Action* new_rpg_go_innkeeper(PlayerbotAI* ai) { return new NewRpgGoInnKeeperAction(ai); }
static Action* new_rpg_move_random(PlayerbotAI* ai) { return new NewRpgMoveRandomAction(ai); }
static Action* new_rpg_move_npc(PlayerbotAI* ai) { return new NewRpgMoveNpcAction(ai); }
static Action* new_rpg_go_camp(PlayerbotAI* ai) { return new NewRpgGoCampAction(ai); }
static Action* new_rpg_wander_random(PlayerbotAI* ai) { return new NewRpgWanderRandomAction(ai); }
static Action* new_rpg_wander_npc(PlayerbotAI* ai) { return new NewRpgWanderNpcAction(ai); }
static Action* new_rpg_do_quest(PlayerbotAI* ai) { return new NewRpgDoQuestAction(ai); }
static Action* new_rpg_travel_flight(PlayerbotAI* ai) { return new NewRpgTravelFlightAction(ai); }
};
#endif

Some files were not shown because too many files have changed in this diff Show More