mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-20 15:39:25 +02:00
# Pull Request Note: When I reference Aspect of the Hawk below, it also means Aspect of the Dragonhawk (the code will use Dragonhawk if the Hunter has it, Hawk if not, they share actions, triggers, and strategies). Hunter Aspects are currently bugged. All Hunters, regardless of spec or strategy, are hardcoded to use Aspect of the Hawk when mana is at 70%+ and Aspect of the Viper when mana drops to "lowMana" from the config (default is 15%), divided by 2. This means the following: - Hawk (bdps) and Viper (bmana) strategies are useless - Pack (bspeed) and Wild (rnature) strategies are applied, but bots will rapidly switch back and forth between Pack/Wild and Hawk/Viper, depending on strategy and mana level. This PR addresses the issues by doing the following: - Global Hawk strategy is removed. Now you need to set bdps for Hunters to use Hawk, but bdps remains the default Aspect strategy for all Hunters. - Dedicated Viper strategy is removed, leaving the global strategy. However, Viper will be used (when lowMana/2) ONLY if the bot is set to bdps. If the bot has the Wild or Pack strategy, they will not switch to Viper at all. I did this because I am assuming if you are using Wild or Pack, you need them for reasons other than to pump DPS. - The threshold to switch back to Hawk is lowered from 70% to 60%. The gap between lowMana/2 and 60% is now filled--if bdps is on, Hunters will switch to Hawk whenever above the Viper threshold, _except_ for when they have the Viper aura, in which case they will not switch to Hawk until 60% mana. This lets the Hunter build back mana before swapping back to Hawk (more like general player behavior) while still letting them swap from other Aspects to Hawk without needing to be all the way at 60% mana. - Gets rid of a weird condition in the Hawk trigger that would make it so that Hunters would switch to Hawk when at exactly 0 mana. I'm not sure what the point of that is. Also, I refactored the triggers a bit because I noticed there was some dead code in there. I didn't do a comprehensive refactor, but there was a lot of stuff that clearly didn't make sense even to my eyes, like back-to-back returns. I think there's more unnecessary code even just in the triggers, but I didn't want to get too into the weeds with this PR. --- ## Design Philosophy We prioritize **stability, performance, and predictability** over behavioral realism. Complex player-mimicking logic is intentionally limited due to its negative impact on scalability, maintainability, and long-term robustness. Excessive processing overhead can lead to server hiccups, increased CPU usage, and degraded performance for all participants. Because every action and decision tree is executed **per bot and per trigger**, even small increases in logic complexity can scale poorly and negatively affect both players and world (random) bots. Bots are not expected to behave perfectly, and perfect simulation of human decision-making is not a project goal. Increased behavioral realism often introduces disproportionate cost, reduced predictability, and significantly higher maintenance overhead. Every additional branch of logic increases long-term responsibility. All decision paths must be tested, validated, and maintained continuously as the system evolves. If advanced or AI-intensive behavior is introduced, the **default configuration must remain the lightweight decision model**. More complex behavior should only be available as an **explicit opt-in option**, clearly documented as having a measurable performance cost. Principles: - **Stability before intelligence** A stable system is always preferred over a smarter one. - **Performance is a shared resource** Any increase in bot cost affects all players and all bots. - **Simple logic scales better than smart logic** Predictable behavior under load is more valuable than perfect decisions. - **Complexity must justify itself** If a feature cannot clearly explain its cost, it should not exist. - **Defaults must be cheap** Expensive behavior must always be optional and clearly communicated. - **Bots should look reasonable, not perfect** The goal is believable behavior, not human simulation. Before submitting, confirm that this change aligns with those principles. --- ## Feature Evaluation Please answer the following: - Describe the **minimum logic** required to achieve the intended behavior? - Describe the **cheapest implementation** that produces an acceptable result? - Describe the **runtime cost** when this logic executes across many bots? I don't expect there to be any impact on costs, and if anything this PR removes some unneeded checks from triggers. --- ## How to Test the Changes - Step-by-step instructions to test the change - Any required setup (e.g. multiple players, bots, specific configuration) - Expected behavior and how to verify it The easiest way is to go shoot a dummy with Volley until low on mana and then toggle on selfbot. You can do this with various Aspects active to test. ## Complexity & Impact Does this change add new decision branches? - - [X] No - - [ ] Yes (**explain below**) Does this change increase per-bot or per-tick processing? - - [X] No - - [ ] Yes (**describe and justify impact**) Could this logic scale poorly under load? - - [X] No - - [ ] Yes (**explain why**) --- ## Defaults & Configuration Does this change modify default bot behavior? - - [ ] No - - [X] Yes (**explain why**) Described above. Default behavior is broken. If this introduces more advanced or AI-heavy logic: - - [X] Lightweight mode remains the default - - [ ] More complex behavior is optional and thereby configurable --- ## AI Assistance Was AI assistance (e.g. ChatGPT or similar tools) used while working on this change? - - [ ] No - - [X] Yes (**explain below**) I asked Claude some questions about the triggers to make sure I didn't screw anything up. If yes, please specify: - AI tool or model used (e.g. ChatGPT, GPT-4, Claude, etc.) - Purpose of usage (e.g. brainstorming, refactoring, documentation, code generation) - Which parts of the change were influenced or generated - Whether the result was manually reviewed and adapted AI assistance is allowed, but all submitted code must be fully understood, reviewed, and owned by the contributor. Any AI-influenced changes must be verified against existing CORE and PB logic. We expect contributors to be honest about what they do and do not understand. --- ## Final Checklist - - [X] Stability is not compromised - - [X] Performance impact is understood, tested, and acceptable - - [X] Added logic complexity is justified and explained - - [X] Documentation updated if needed --- ## Notes for Reviewers Anything that significantly improves realism at the cost of stability or performance should be carefully discussed before merging. --------- Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com> Co-authored-by: bash <hermensb@gmail.com> Co-authored-by: Revision <tkn963@gmail.com> Co-authored-by: kadeshar <kadeshar@gmail.com>
513 lines
12 KiB
C++
513 lines
12 KiB
C++
/*
|
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
|
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
|
*/
|
|
|
|
#ifndef _PLAYERBOT_HUNTERACTIONS_H
|
|
#define _PLAYERBOT_HUNTERACTIONS_H
|
|
|
|
#include "AiObject.h"
|
|
#include "Event.h"
|
|
#include "GenericSpellActions.h"
|
|
#include "Unit.h"
|
|
|
|
class PlayerbotAI;
|
|
class Unit;
|
|
|
|
// Buff and Out of Combat Spells
|
|
|
|
class CastTrueshotAuraAction : public CastBuffSpellAction
|
|
{
|
|
public:
|
|
CastTrueshotAuraAction(PlayerbotAI* botAI) :
|
|
CastBuffSpellAction(botAI, "trueshot aura") {}
|
|
};
|
|
|
|
class CastAspectOfTheDragonhawkAction : public CastBuffSpellAction
|
|
{
|
|
public:
|
|
CastAspectOfTheDragonhawkAction(PlayerbotAI* botAI) :
|
|
CastBuffSpellAction(botAI, "aspect of the dragonhawk") {}
|
|
};
|
|
|
|
class CastAspectOfTheHawkAction : public CastBuffSpellAction
|
|
{
|
|
public:
|
|
CastAspectOfTheHawkAction(PlayerbotAI* botAI) :
|
|
CastBuffSpellAction(botAI, "aspect of the hawk") {}
|
|
bool isUseful() override;
|
|
};
|
|
|
|
class CastAspectOfTheMonkeyAction : public CastBuffSpellAction
|
|
{
|
|
public:
|
|
CastAspectOfTheMonkeyAction(PlayerbotAI* botAI) :
|
|
CastBuffSpellAction(botAI, "aspect of the monkey") {}
|
|
};
|
|
|
|
class CastAspectOfTheWildAction : public CastBuffSpellAction
|
|
{
|
|
public:
|
|
CastAspectOfTheWildAction(PlayerbotAI* botAI) :
|
|
CastBuffSpellAction(botAI, "aspect of the wild") {}
|
|
};
|
|
|
|
class CastAspectOfTheCheetahAction : public CastBuffSpellAction
|
|
{
|
|
public:
|
|
CastAspectOfTheCheetahAction(PlayerbotAI* botAI) :
|
|
CastBuffSpellAction(botAI, "aspect of the cheetah") {}
|
|
};
|
|
|
|
class CastAspectOfThePackAction : public CastBuffSpellAction
|
|
{
|
|
public:
|
|
CastAspectOfThePackAction(PlayerbotAI* botAI) :
|
|
CastBuffSpellAction(botAI, "aspect of the pack") {}
|
|
};
|
|
|
|
class CastAspectOfTheViperAction : public CastBuffSpellAction
|
|
{
|
|
public:
|
|
CastAspectOfTheViperAction(PlayerbotAI* botAI) :
|
|
CastBuffSpellAction(botAI, "aspect of the viper") {}
|
|
};
|
|
|
|
// Cooldown Spells
|
|
|
|
class CastRapidFireAction : public CastBuffSpellAction
|
|
{
|
|
public:
|
|
CastRapidFireAction(PlayerbotAI* botAI) :
|
|
CastBuffSpellAction(botAI, "rapid fire") {}
|
|
};
|
|
|
|
class CastDeterrenceAction : public CastBuffSpellAction
|
|
{
|
|
public:
|
|
CastDeterrenceAction(PlayerbotAI* botAI) :
|
|
CastBuffSpellAction(botAI, "deterrence") {}
|
|
};
|
|
|
|
class CastReadinessAction : public CastBuffSpellAction
|
|
{
|
|
public:
|
|
CastReadinessAction(PlayerbotAI* botAI) :
|
|
CastBuffSpellAction(botAI, "readiness") {}
|
|
};
|
|
|
|
class CastDisengageAction : public CastSpellAction
|
|
{
|
|
public:
|
|
CastDisengageAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "disengage") {}
|
|
bool Execute(Event event) override;
|
|
bool isUseful() override;
|
|
};
|
|
|
|
// CC Spells
|
|
|
|
class CastScareBeastAction : public CastSpellAction
|
|
{
|
|
public:
|
|
CastScareBeastAction(PlayerbotAI* botAI) :
|
|
CastSpellAction(botAI, "scare beast") {}
|
|
};
|
|
|
|
class CastScareBeastCcAction : public CastSpellAction
|
|
{
|
|
public:
|
|
CastScareBeastCcAction(PlayerbotAI* botAI) :
|
|
CastSpellAction(botAI, "scare beast on cc") {}
|
|
Value<Unit*>* GetTargetValue() override;
|
|
bool Execute(Event event) override;
|
|
};
|
|
|
|
class CastFreezingTrap : public CastDebuffSpellAction
|
|
{
|
|
public:
|
|
CastFreezingTrap(PlayerbotAI* botAI) :
|
|
CastDebuffSpellAction(botAI, "freezing trap") {}
|
|
Value<Unit*>* GetTargetValue() override;
|
|
};
|
|
|
|
class CastWyvernStingAction : public CastDebuffSpellAction
|
|
{
|
|
public:
|
|
CastWyvernStingAction(PlayerbotAI* botAI) :
|
|
CastDebuffSpellAction(botAI, "wyvern sting", true) {}
|
|
};
|
|
|
|
class CastSilencingShotAction : public CastSpellAction
|
|
{
|
|
public:
|
|
CastSilencingShotAction(PlayerbotAI* botAI) :
|
|
CastSpellAction(botAI, "silencing shot") {}
|
|
};
|
|
|
|
class CastConcussiveShotAction : public CastSnareSpellAction
|
|
{
|
|
public:
|
|
CastConcussiveShotAction(PlayerbotAI* botAI) :
|
|
CastSnareSpellAction(botAI, "concussive shot") {}
|
|
};
|
|
|
|
class CastIntimidationAction : public CastBuffSpellAction
|
|
{
|
|
public:
|
|
CastIntimidationAction(PlayerbotAI* botAI) :
|
|
CastBuffSpellAction(botAI, "intimidation", false, 5000) {}
|
|
std::string const GetTargetName() override
|
|
{
|
|
return "pet target";
|
|
}
|
|
};
|
|
|
|
// Threat Spells
|
|
|
|
class CastDistractingShotAction : public CastSpellAction
|
|
{
|
|
public:
|
|
CastDistractingShotAction(PlayerbotAI* botAI) :
|
|
CastSpellAction(botAI, "distracting shot") {}
|
|
};
|
|
|
|
class CastMisdirectionOnMainTankAction : public BuffOnMainTankAction
|
|
{
|
|
public:
|
|
CastMisdirectionOnMainTankAction(PlayerbotAI* botAI) :
|
|
BuffOnMainTankAction(botAI, "misdirection", true) {}
|
|
};
|
|
|
|
class CastFeignDeathAction : public CastBuffSpellAction
|
|
{
|
|
public:
|
|
CastFeignDeathAction(PlayerbotAI* botAI) :
|
|
CastBuffSpellAction(botAI, "feign death") {}
|
|
};
|
|
|
|
// Pet Spells
|
|
|
|
class FeedPetAction : public Action
|
|
{
|
|
public:
|
|
FeedPetAction(PlayerbotAI* botAI) : Action(botAI, "feed pet") {}
|
|
bool Execute(Event event) override;
|
|
};
|
|
|
|
class CastCallPetAction : public CastBuffSpellAction
|
|
{
|
|
public:
|
|
CastCallPetAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "call pet") {}
|
|
};
|
|
|
|
class CastMendPetAction : public CastAuraSpellAction
|
|
{
|
|
public:
|
|
CastMendPetAction(PlayerbotAI* botAI) : CastAuraSpellAction(botAI, "mend pet") {}
|
|
std::string const GetTargetName() override
|
|
{
|
|
return "pet target";
|
|
}
|
|
};
|
|
|
|
class CastRevivePetAction : public CastBuffSpellAction
|
|
{
|
|
public:
|
|
CastRevivePetAction(PlayerbotAI* botAI) :
|
|
CastBuffSpellAction(botAI, "revive pet") {}
|
|
};
|
|
|
|
class CastKillCommandAction : public CastBuffSpellAction
|
|
{
|
|
public:
|
|
CastKillCommandAction(PlayerbotAI* botAI) :
|
|
CastBuffSpellAction(botAI, "kill command", false, 5000) {}
|
|
std::string const GetTargetName() override
|
|
{
|
|
return "pet target";
|
|
}
|
|
};
|
|
|
|
class CastBestialWrathAction : public CastBuffSpellAction
|
|
{
|
|
public:
|
|
CastBestialWrathAction(PlayerbotAI* botAI) :
|
|
CastBuffSpellAction(botAI, "bestial wrath", false, 5000) {}
|
|
std::string const GetTargetName() override
|
|
{
|
|
return "pet target";
|
|
}
|
|
};
|
|
|
|
// Direct Damage Spells
|
|
|
|
class CastAutoShotAction : public CastSpellAction
|
|
{
|
|
public:
|
|
CastAutoShotAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "auto shot") {}
|
|
ActionThreatType getThreatType() override
|
|
{
|
|
return ActionThreatType::None;
|
|
}
|
|
bool isUseful() override;
|
|
};
|
|
|
|
class CastArcaneShotAction : public CastSpellAction
|
|
{
|
|
public:
|
|
CastArcaneShotAction(PlayerbotAI* botAI) :
|
|
CastSpellAction(botAI, "arcane shot") {}
|
|
bool isUseful() override;
|
|
};
|
|
|
|
class CastAimedShotAction : public CastSpellAction
|
|
{
|
|
public:
|
|
CastAimedShotAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "aimed shot") {}
|
|
};
|
|
|
|
class CastChimeraShotAction : public CastSpellAction
|
|
{
|
|
public:
|
|
CastChimeraShotAction(PlayerbotAI* botAI) :
|
|
CastSpellAction(botAI, "chimera shot") {}
|
|
};
|
|
|
|
class CastSteadyShotAction : public CastSpellAction
|
|
{
|
|
public:
|
|
CastSteadyShotAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "steady shot") {}
|
|
};
|
|
|
|
class CastKillShotAction : public CastSpellAction
|
|
{
|
|
public:
|
|
CastKillShotAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "kill shot") {}
|
|
};
|
|
|
|
// DoT/Debuff Spells
|
|
|
|
class CastHuntersMarkAction : public CastDebuffSpellAction
|
|
{
|
|
public:
|
|
CastHuntersMarkAction(PlayerbotAI* botAI) :
|
|
CastDebuffSpellAction(botAI, "hunter's mark") {}
|
|
bool isUseful() override
|
|
{
|
|
// Bypass TTL check
|
|
return CastAuraSpellAction::isUseful();
|
|
}
|
|
};
|
|
|
|
class CastTranquilizingShotAction : public CastSpellAction
|
|
{
|
|
public:
|
|
CastTranquilizingShotAction(PlayerbotAI* botAI) :
|
|
CastSpellAction(botAI, "tranquilizing shot") {}
|
|
};
|
|
|
|
class CastViperStingAction : public CastDebuffSpellAction
|
|
{
|
|
public:
|
|
CastViperStingAction(PlayerbotAI* botAI) :
|
|
CastDebuffSpellAction(botAI, "viper sting", true) {}
|
|
bool isUseful() override;
|
|
};
|
|
|
|
class CastSerpentStingAction : public CastDebuffSpellAction
|
|
{
|
|
public:
|
|
CastSerpentStingAction(PlayerbotAI* botAI) :
|
|
CastDebuffSpellAction(botAI, "serpent sting", true) {}
|
|
bool isUseful() override
|
|
{
|
|
// Bypass TTL check
|
|
return CastAuraSpellAction::isUseful();
|
|
}
|
|
};
|
|
|
|
class CastScorpidStingAction : public CastDebuffSpellAction
|
|
{
|
|
public:
|
|
CastScorpidStingAction(PlayerbotAI* botAI) :
|
|
CastDebuffSpellAction(botAI, "scorpid sting", true) {}
|
|
bool isUseful() override
|
|
{
|
|
// Bypass TTL check
|
|
return CastAuraSpellAction::isUseful();
|
|
}
|
|
};
|
|
|
|
class CastSerpentStingOnAttackerAction : public CastDebuffSpellOnAttackerAction
|
|
{
|
|
public:
|
|
CastSerpentStingOnAttackerAction(PlayerbotAI* botAI)
|
|
: CastDebuffSpellOnAttackerAction(botAI, "serpent sting", true) {}
|
|
bool isUseful() override
|
|
{
|
|
// Bypass TTL check
|
|
return CastAuraSpellAction::isUseful();
|
|
}
|
|
};
|
|
|
|
class CastImmolationTrapAction : public CastSpellAction
|
|
{
|
|
public:
|
|
CastImmolationTrapAction(PlayerbotAI* botAI) :
|
|
CastSpellAction(botAI, "immolation trap") {}
|
|
bool isUseful() override;
|
|
};
|
|
|
|
class CastExplosiveTrapAction : public CastSpellAction
|
|
{
|
|
public:
|
|
CastExplosiveTrapAction(PlayerbotAI* botAI) :
|
|
CastSpellAction(botAI, "explosive trap") {}
|
|
};
|
|
|
|
class CastBlackArrowAction : public CastDebuffSpellAction
|
|
{
|
|
public:
|
|
CastBlackArrowAction(PlayerbotAI* botAI) :
|
|
CastDebuffSpellAction(botAI, "black arrow", true) {}
|
|
bool isUseful() override
|
|
{
|
|
if (botAI->HasStrategy("trap weave", BOT_STATE_COMBAT))
|
|
return false;
|
|
// Bypass TTL check
|
|
return CastAuraSpellAction::isUseful();
|
|
}
|
|
};
|
|
|
|
class CastExplosiveShotBaseAction : public CastDebuffSpellAction
|
|
{
|
|
public:
|
|
CastExplosiveShotBaseAction(PlayerbotAI* botAI)
|
|
: CastDebuffSpellAction(botAI, "explosive shot", true, 0.0f) {}
|
|
};
|
|
|
|
// Rank 4
|
|
class CastExplosiveShotRank4Action : public CastExplosiveShotBaseAction
|
|
{
|
|
public:
|
|
CastExplosiveShotRank4Action(PlayerbotAI* botAI) :
|
|
CastExplosiveShotBaseAction(botAI) {}
|
|
bool Execute(Event event) override
|
|
{
|
|
return botAI->CastSpell(60053, GetTarget());
|
|
}
|
|
bool isUseful() override
|
|
{
|
|
Unit* target = GetTarget();
|
|
if (!target)
|
|
return false;
|
|
|
|
return !target->HasAura(60053);
|
|
}
|
|
};
|
|
|
|
// Rank 3
|
|
class CastExplosiveShotRank3Action : public CastExplosiveShotBaseAction
|
|
{
|
|
public:
|
|
CastExplosiveShotRank3Action(PlayerbotAI* botAI) :
|
|
CastExplosiveShotBaseAction(botAI) {}
|
|
bool Execute(Event event) override
|
|
{
|
|
return botAI->CastSpell(60052, GetTarget());
|
|
}
|
|
bool isUseful() override
|
|
{
|
|
Unit* target = GetTarget();
|
|
if (!target)
|
|
return false;
|
|
|
|
return !target->HasAura(60052);
|
|
}
|
|
};
|
|
|
|
// Rank 2
|
|
class CastExplosiveShotRank2Action : public CastExplosiveShotBaseAction
|
|
{
|
|
public:
|
|
CastExplosiveShotRank2Action(PlayerbotAI* botAI) :
|
|
CastExplosiveShotBaseAction(botAI) {}
|
|
bool Execute(Event event) override
|
|
{
|
|
return botAI->CastSpell(60051, GetTarget());
|
|
}
|
|
bool isUseful() override
|
|
{
|
|
Unit* target = GetTarget();
|
|
if (!target)
|
|
return false;
|
|
|
|
return !target->HasAura(60051);
|
|
}
|
|
};
|
|
|
|
// Rank 1
|
|
class CastExplosiveShotRank1Action : public CastExplosiveShotBaseAction
|
|
{
|
|
public:
|
|
CastExplosiveShotRank1Action(PlayerbotAI* botAI) :
|
|
CastExplosiveShotBaseAction(botAI) {}
|
|
bool Execute(Event event) override
|
|
{
|
|
return botAI->CastSpell(53301, GetTarget());
|
|
}
|
|
bool isUseful() override
|
|
{
|
|
Unit* target = GetTarget();
|
|
if (!target)
|
|
return false;
|
|
|
|
return !target->HasAura(53301);
|
|
}
|
|
};
|
|
|
|
// Melee Spells
|
|
|
|
class CastWingClipAction : public CastSpellAction
|
|
{
|
|
public:
|
|
CastWingClipAction(PlayerbotAI* botAI) :
|
|
CastSpellAction(botAI, "wing clip") {}
|
|
bool isUseful() override;
|
|
std::vector<NextAction> getPrerequisites() override;
|
|
};
|
|
|
|
class CastRaptorStrikeAction : public CastSpellAction
|
|
{
|
|
public:
|
|
CastRaptorStrikeAction(PlayerbotAI* botAI) :
|
|
CastSpellAction(botAI, "raptor strike") {}
|
|
};
|
|
|
|
class CastMongooseBiteAction : public CastSpellAction
|
|
{
|
|
public:
|
|
CastMongooseBiteAction(PlayerbotAI* botAI) :
|
|
CastSpellAction(botAI, "mongoose bite") {}
|
|
};
|
|
|
|
// AoE Spells
|
|
|
|
class CastMultiShotAction : public CastSpellAction
|
|
{
|
|
public:
|
|
CastMultiShotAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "multi-shot") {}
|
|
};
|
|
|
|
class CastVolleyAction : public CastSpellAction
|
|
{
|
|
public:
|
|
CastVolleyAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "volley") {}
|
|
ActionThreatType getThreatType() override
|
|
{
|
|
return ActionThreatType::Aoe;
|
|
}
|
|
};
|
|
|
|
#endif
|