Add Unending Breath to Warlock NonCombat Strat (#2074)

# Pull Request

Adds actions and triggers for Warlock class to cast Unending Breath when
swimming, following the existing implementation for Shaman Water
Breathing.

---

## Feature Evaluation

Add triggers for Warlock noncombat strategy for Unending Breath on self
and party.
Triggers should only be active while swimming.
Minimal runtime cost on Warlock bots trigger processing.

---

## How to Test the Changes

- Bring a Warlock bot into water
- It should cast Unending Breath on itself and anyone in the party

## Complexity & Impact

- Does this change add new decision branches?
    - [ ] No
    - [x] Yes (**explain below**)
It adds triggers to Warlock to decide when to cast Unending Breath on
self or party members.

- Does this change increase per-bot or per-tick processing?
    - [ ] No
    - [x] Yes (**describe and justify impact**)
Minimal additional processing for Warlock triggers, same as already
existing triggers for Shaman.

- Could this logic scale poorly under load?
    - [x] No
    - [ ] Yes (**explain why**)

---

## Defaults & Configuration

- Does this change modify default bot behavior?
    - [x] No
    - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:

- [ ] 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**)

Claude was used to explore the codebase to find similar implementations
that already existed.

---

## 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: bashermens <31279994+hermensbas@users.noreply.github.com>
This commit is contained in:
dillyns
2026-02-08 12:36:27 -05:00
committed by GitHub
parent cfb2ed4bf3
commit c9c936d5c1
5 changed files with 46 additions and 0 deletions

View File

@@ -41,6 +41,18 @@ public:
std::string const GetTargetName() override { return "pet target"; }
};
class CastUnendingBreathAction : public CastBuffSpellAction
{
public:
CastUnendingBreathAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "unending breath") {}
};
class CastUnendingBreathOnPartyAction : public BuffOnPartyAction
{
public:
CastUnendingBreathOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "unending breath") {}
};
class CreateSoulShardAction : public Action
{
public:

View File

@@ -85,6 +85,8 @@ void GenericWarlockNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& tr
triggers.push_back(new TriggerNode("too many soul shards", { NextAction("destroy soul shard", 60.0f) }));
triggers.push_back(new TriggerNode("soul link", { NextAction("soul link", 28.0f) }));
triggers.push_back(new TriggerNode("demon armor", { NextAction("fel armor", 27.0f) }));
triggers.push_back(new TriggerNode("unending breath", { NextAction("unending breath", 12.0f) }));
triggers.push_back(new TriggerNode("unending breath on party", { NextAction("unending breath on party", 11.0f) }));
triggers.push_back(new TriggerNode("no healthstone", { NextAction("create healthstone", 26.0f) }));
triggers.push_back(new TriggerNode("no soulstone", { NextAction("create soulstone", 25.0f) }));
triggers.push_back(new TriggerNode("life tap", { NextAction("life tap", 23.0f) }));

View File

@@ -79,6 +79,16 @@ bool SoulLinkTrigger::IsActive()
return !botAI->HasAura("soul link", target);
}
bool UnendingBreathTrigger::IsActive()
{
return BuffTrigger::IsActive() && AI_VALUE2(bool, "swimming", "self target");
}
bool UnendingBreathOnPartyTrigger::IsActive()
{
return BuffOnPartyTrigger::IsActive() && AI_VALUE2(bool, "swimming", "self target");
}
bool DemonicEmpowermentTrigger::IsActive()
{
Pet* pet = bot->GetPet();

View File

@@ -32,6 +32,20 @@ public:
bool IsActive() override;
};
class UnendingBreathTrigger : public BuffTrigger
{
public:
UnendingBreathTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "unending breath", 5 * 2000) {}
bool IsActive() override;
};
class UnendingBreathOnPartyTrigger : public BuffOnPartyTrigger
{
public:
UnendingBreathOnPartyTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "unending breath on party", 2 * 2000) {}
bool IsActive() override;
};
class OutOfSoulShardsTrigger : public Trigger
{
public:

View File

@@ -143,6 +143,8 @@ public:
creators["shadow trance"] = &WarlockTriggerFactoryInternal::shadow_trance;
creators["demon armor"] = &WarlockTriggerFactoryInternal::demon_armor;
creators["soul link"] = &WarlockTriggerFactoryInternal::soul_link;
creators["unending breath"] = &WarlockTriggerFactoryInternal::unending_breath;
creators["unending breath on party"] = &WarlockTriggerFactoryInternal::unending_breath_on_party;
creators["no soul shard"] = &WarlockTriggerFactoryInternal::no_soul_shard;
creators["too many soul shards"] = &WarlockTriggerFactoryInternal::too_many_soul_shards;
creators["no healthstone"] = &WarlockTriggerFactoryInternal::HasHealthstone;
@@ -189,6 +191,8 @@ private:
static Trigger* shadow_trance(PlayerbotAI* botAI) { return new ShadowTranceTrigger(botAI); }
static Trigger* demon_armor(PlayerbotAI* botAI) { return new DemonArmorTrigger(botAI); }
static Trigger* soul_link(PlayerbotAI* botAI) { return new SoulLinkTrigger(botAI); }
static Trigger* unending_breath(PlayerbotAI* botAI) { return new UnendingBreathTrigger(botAI); }
static Trigger* unending_breath_on_party(PlayerbotAI* botAI) { return new UnendingBreathOnPartyTrigger(botAI); }
static Trigger* no_soul_shard(PlayerbotAI* botAI) { return new OutOfSoulShardsTrigger(botAI); }
static Trigger* too_many_soul_shards(PlayerbotAI* botAI) { return new TooManySoulShardsTrigger(botAI); }
static Trigger* HasHealthstone(PlayerbotAI* botAI) { return new HasHealthstoneTrigger(botAI); }
@@ -240,6 +244,8 @@ public:
creators["demon armor"] = &WarlockAiObjectContextInternal::demon_armor;
creators["demon skin"] = &WarlockAiObjectContextInternal::demon_skin;
creators["soul link"] = &WarlockAiObjectContextInternal::soul_link;
creators["unending breath"] = &WarlockAiObjectContextInternal::unending_breath;
creators["unending breath on party"] = &WarlockAiObjectContextInternal::unending_breath_on_party;
creators["create soul shard"] = &WarlockAiObjectContextInternal::create_soul_shard;
creators["destroy soul shard"] = &WarlockAiObjectContextInternal::destroy_soul_shard;
creators["create healthstone"] = &WarlockAiObjectContextInternal::create_healthstone;
@@ -313,6 +319,8 @@ private:
static Action* demon_armor(PlayerbotAI* botAI) { return new CastDemonArmorAction(botAI); }
static Action* demon_skin(PlayerbotAI* botAI) { return new CastDemonSkinAction(botAI); }
static Action* soul_link(PlayerbotAI* botAI) { return new CastSoulLinkAction(botAI); }
static Action* unending_breath(PlayerbotAI* botAI) { return new CastUnendingBreathAction(botAI); }
static Action* unending_breath_on_party(PlayerbotAI* botAI) { return new CastUnendingBreathOnPartyAction(botAI); }
static Action* create_soul_shard(PlayerbotAI* botAI) { return new CreateSoulShardAction(botAI); }
static Action* destroy_soul_shard(PlayerbotAI* botAI) { return new DestroySoulShardAction(botAI); }
static Action* create_healthstone(PlayerbotAI* botAI) { return new CastCreateHealthstoneAction(botAI); }