Fractured: Paragon core hooks, mod-paragon, mod-ale, Docker build cap
- Track mod-paragon and mod-ale (un-ignore modules in .gitignore). - Ship docker-compose.override.yml with CMAKE_EXTRA_OPTIONS for LuaJIT (mod-ale). - Dockerfile: CBUILD_PARALLEL default to limit OOM under Docker/WSL2. - Core: CLASS_PARAGON sticky combo points (DetachComboTarget), selection rebind, Spell::CheckPower rune path for multi-resource Paragon. - spell_dk_death_rune: IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_ABILITY) for Blood of the North / Reaping / DRM on Paragon. - Remove temporary Paragon CheckPower logging. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11501,6 +11501,25 @@ void Player::SetSelection(ObjectGuid guid)
|
||||
|
||||
if (NeedSendSpectatorData())
|
||||
ArenaSpectator::SendCommand_GUID(FindMap(), GetGUID(), "TRG", guid);
|
||||
|
||||
// mod-paragon: sticky combo points must follow the player when targets
|
||||
// change. The client filters SMSG_UPDATE_COMBO_POINTS by the embedded
|
||||
// target GUID — without this re-bind the dots vanish off the new target
|
||||
// frame on each swap even though Unit::m_comboPoints is intact. Mirrors
|
||||
// the rebind path inside Unit::AddComboPoints. Applies to every class
|
||||
// that participates in the sticky-CP pool (Paragon + Rogue + Druid),
|
||||
// gated behind the same `Paragon.StickyComboPoints` config switch.
|
||||
if (sConfigMgr->GetOption<bool>("Paragon.StickyComboPoints", true)
|
||||
&& GetComboPoints() > 0
|
||||
&& !guid.IsEmpty())
|
||||
{
|
||||
uint8 const cls = getClass();
|
||||
if (cls == CLASS_PARAGON || cls == CLASS_ROGUE || cls == CLASS_DRUID)
|
||||
{
|
||||
if (Unit* newTarget = ObjectAccessor::GetUnit(*this, guid))
|
||||
RebindComboTarget(newTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Player::SetGroup(Group* group, int8 subgroup)
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "CellImpl.h"
|
||||
#include "CharacterCache.h"
|
||||
#include "CharmInfo.h"
|
||||
#include "Config.h"
|
||||
#include "Chat.h"
|
||||
#include "ChatPackets.h"
|
||||
#include "ChatTextBuilder.h"
|
||||
@@ -13224,6 +13225,37 @@ void Unit::RestoreDisplayId()
|
||||
SetDisplayId(GetNativeDisplayId());
|
||||
}
|
||||
|
||||
// mod-paragon: returns true if this unit is a player whose combo-point pool
|
||||
// should persist across target swaps. Originally Paragon-only; widened to
|
||||
// cover the natively combo-point-using classes (Rogue, Druid) so the same
|
||||
// "stored CP pool" UX applies to them. Gate retains the existing config key
|
||||
// `Paragon.StickyComboPoints` for backward compatibility.
|
||||
static bool IsStickyComboPointsClass(Unit const* unit)
|
||||
{
|
||||
if (!unit || !unit->IsPlayer())
|
||||
return false;
|
||||
if (!sConfigMgr->GetOption<bool>("Paragon.StickyComboPoints", true))
|
||||
return false;
|
||||
uint8 cls = unit->ToPlayer()->getClass();
|
||||
return cls == CLASS_PARAGON || cls == CLASS_ROGUE || cls == CLASS_DRUID;
|
||||
}
|
||||
|
||||
uint8 Unit::GetComboPoints(Unit const* who) const
|
||||
{
|
||||
if (IsStickyComboPointsClass(this))
|
||||
return m_comboPoints;
|
||||
|
||||
return (who && m_comboTarget != who) ? 0 : m_comboPoints;
|
||||
}
|
||||
|
||||
uint8 Unit::GetComboPoints(ObjectGuid const& guid) const
|
||||
{
|
||||
if (IsStickyComboPointsClass(this))
|
||||
return m_comboPoints;
|
||||
|
||||
return (m_comboTarget && m_comboTarget->GetGUID() == guid) ? m_comboPoints : 0;
|
||||
}
|
||||
|
||||
void Unit::AddComboPoints(Unit* target, int8 count)
|
||||
{
|
||||
if (!count)
|
||||
@@ -13231,6 +13263,32 @@ void Unit::AddComboPoints(Unit* target, int8 count)
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsStickyComboPointsClass(this))
|
||||
{
|
||||
if (target)
|
||||
{
|
||||
if (target != m_comboTarget)
|
||||
{
|
||||
if (m_comboTarget)
|
||||
m_comboTarget->RemoveComboPointHolder(this);
|
||||
|
||||
m_comboTarget = target;
|
||||
target->AddComboPointHolder(this);
|
||||
}
|
||||
}
|
||||
|
||||
m_comboPoints = std::max<int8>(std::min<int8>(m_comboPoints + count, 5), 0);
|
||||
|
||||
if (!m_comboPoints && m_comboTarget)
|
||||
{
|
||||
m_comboTarget->RemoveComboPointHolder(this);
|
||||
m_comboTarget = nullptr;
|
||||
}
|
||||
|
||||
SendComboPoints();
|
||||
return;
|
||||
}
|
||||
|
||||
if (target && target != m_comboTarget)
|
||||
{
|
||||
if (m_comboTarget)
|
||||
@@ -13250,6 +13308,20 @@ void Unit::AddComboPoints(Unit* target, int8 count)
|
||||
SendComboPoints();
|
||||
}
|
||||
|
||||
void Unit::RebindComboTarget(Unit* newTarget)
|
||||
{
|
||||
if (!newTarget || newTarget == m_comboTarget || m_comboPoints <= 0)
|
||||
return;
|
||||
|
||||
if (m_comboTarget)
|
||||
m_comboTarget->RemoveComboPointHolder(this);
|
||||
|
||||
m_comboTarget = newTarget;
|
||||
newTarget->AddComboPointHolder(this);
|
||||
|
||||
SendComboPoints();
|
||||
}
|
||||
|
||||
void Unit::ClearComboPoints()
|
||||
{
|
||||
if (!m_comboTarget)
|
||||
@@ -13267,6 +13339,26 @@ void Unit::ClearComboPoints()
|
||||
m_comboTarget = nullptr;
|
||||
}
|
||||
|
||||
void Unit::DetachComboTarget()
|
||||
{
|
||||
// mod-paragon: sticky-CP holder cleanup. Used when the target unit is
|
||||
// dying / despawning but we want to keep the holder's m_comboPoints
|
||||
// intact so they can finish on a fresh target. Pool re-binds via
|
||||
// Unit::RebindComboTarget on the next SetSelection (or via the next
|
||||
// AddComboPoints if the player generates more before tabbing).
|
||||
//
|
||||
// This intentionally does NOT touch SPELL_AURA_RETAIN_COMBO_POINTS and
|
||||
// does NOT call SendComboPoints — sticky-CP classes drive their target-
|
||||
// frame paint client-side (ComboFrame.lua's simulator for Paragon, the
|
||||
// sticky cache for Rogue/Druid), and a fake "binding to nullguid" packet
|
||||
// would just confuse the engine.
|
||||
if (m_comboTarget)
|
||||
{
|
||||
m_comboTarget->RemoveComboPointHolder(this);
|
||||
m_comboTarget = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::SendComboPoints()
|
||||
{
|
||||
if (m_cleanupDone)
|
||||
@@ -13309,7 +13401,21 @@ void Unit::ClearComboPointHolders()
|
||||
{
|
||||
while (!m_ComboPointHolders.empty())
|
||||
{
|
||||
(*m_ComboPointHolders.begin())->ClearComboPoints(); // this also removes it from m_comboPointHolders
|
||||
Unit* holder = *m_ComboPointHolders.begin();
|
||||
// mod-paragon: this is the *target* dying / despawning, iterating
|
||||
// every player who has CPs anchored to it. Stock behavior wipes the
|
||||
// holder's pool — that's the literal opposite of what
|
||||
// Paragon.StickyComboPoints promises. For sticky-CP classes
|
||||
// (Paragon / Rogue / Druid) detach the binding only and keep the
|
||||
// count, so the next finisher on a fresh target still has fuel.
|
||||
if (IsStickyComboPointsClass(holder))
|
||||
{
|
||||
holder->DetachComboTarget(); // also removes holder from m_ComboPointHolders
|
||||
}
|
||||
else
|
||||
{
|
||||
holder->ClearComboPoints(); // this also removes it from m_comboPointHolders
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1011,8 +1011,8 @@ public:
|
||||
void AddExtraAttacks(uint32 count);
|
||||
|
||||
// Combot points system
|
||||
[[nodiscard]] uint8 GetComboPoints(Unit const* who = nullptr) const { return (who && m_comboTarget != who) ? 0 : m_comboPoints; }
|
||||
[[nodiscard]] uint8 GetComboPoints(ObjectGuid const& guid) const { return (m_comboTarget && m_comboTarget->GetGUID() == guid) ? m_comboPoints : 0; }
|
||||
[[nodiscard]] uint8 GetComboPoints(Unit const* who = nullptr) const;
|
||||
[[nodiscard]] uint8 GetComboPoints(ObjectGuid const& guid) const;
|
||||
[[nodiscard]] Unit* GetComboTarget() const { return m_comboTarget; }
|
||||
[[nodiscard]] ObjectGuid const GetComboTargetGUID() const { return m_comboTarget ? m_comboTarget->GetGUID() : ObjectGuid::Empty; }
|
||||
|
||||
@@ -1020,6 +1020,18 @@ public:
|
||||
void AddComboPoints(int8 count) { AddComboPoints(nullptr, count); }
|
||||
void ClearComboPoints();
|
||||
|
||||
// mod-paragon: re-anchor an existing combo-point pool to a different
|
||||
// target without changing the count, then push SMSG_UPDATE_COMBO_POINTS.
|
||||
// Used by Player::SetSelection so sticky combo points keep displaying on
|
||||
// the new target frame after a target swap.
|
||||
void RebindComboTarget(Unit* newTarget);
|
||||
|
||||
// mod-paragon: sticky-CP holder cleanup when the *target* dies / despawns.
|
||||
// Detaches m_comboTarget (and removes self from the dying unit's holder
|
||||
// set) but intentionally leaves m_comboPoints intact, so the next
|
||||
// RebindComboTarget on a fresh target still has a pool to anchor.
|
||||
void DetachComboTarget();
|
||||
|
||||
void AddComboPointHolder(Unit* unit) { m_ComboPointHolders.insert(unit); }
|
||||
void RemoveComboPointHolder(Unit* unit) { m_ComboPointHolders.erase(unit); }
|
||||
void ClearComboPointHolders();
|
||||
|
||||
@@ -7148,6 +7148,13 @@ SpellCastResult Spell::CheckPower()
|
||||
SpellCastResult failReason = CheckRuneCost(m_spellInfo->RuneCostID);
|
||||
if (failReason != SPELL_CAST_OK)
|
||||
return failReason;
|
||||
|
||||
// Rune spells: real availability is in Player::m_runes (per-slot cooldowns),
|
||||
// validated above. Do not compare UNIT_FIELD_POWER7 (POWER_RUNE) to
|
||||
// ManaCost — that field is mainly a client sync/display value (often 0
|
||||
// for non-DK primaries) and produces SPELL_FAILED_NO_POWER ("not enough
|
||||
// runes") even when CheckRuneCost passes (e.g. Paragon multi-resource).
|
||||
return SPELL_CAST_OK;
|
||||
}
|
||||
|
||||
// Check power amount
|
||||
|
||||
@@ -2643,7 +2643,12 @@ class spell_dk_death_rune : public AuraScript
|
||||
|
||||
bool Load() override
|
||||
{
|
||||
return GetUnitOwner()->IsPlayer() && GetUnitOwner()->ToPlayer()->getClass() == CLASS_DEATH_KNIGHT;
|
||||
// mod-paragon: Paragon claims CLASS_DEATH_KNIGHT for CLASS_CONTEXT_ABILITY
|
||||
// (Player::IsClass hook) so DK rune state + talents apply; raw getClass()
|
||||
// would skip this script and Blood of the North / Reaping / Death Rune
|
||||
// Mastery would never convert runes server-side.
|
||||
Player* player = GetUnitOwner()->ToPlayer();
|
||||
return player && player->IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_ABILITY);
|
||||
}
|
||||
|
||||
bool CheckProc(ProcEventInfo& eventInfo)
|
||||
@@ -2652,11 +2657,7 @@ class spell_dk_death_rune : public AuraScript
|
||||
if (!caster || !caster->IsPlayer())
|
||||
return false;
|
||||
|
||||
Player* player = caster->ToPlayer();
|
||||
if (player->getClass() != CLASS_DEATH_KNIGHT)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
return caster->ToPlayer()->IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_ABILITY);
|
||||
}
|
||||
|
||||
void HandleProc(ProcEventInfo& eventInfo)
|
||||
|
||||
Reference in New Issue
Block a user