Skip to content

Commit 29aafe8

Browse files
committed
turtle: move combo-finisher & armor-healing custom data out of stock
Two pieces of hardcoded Turtle-specific data were embedded in stock modules; relocate them to src/turtle/ behind extension points so the stock code carries only generic mechanism. - aura/ComboDuration: add an AutoDanglingResolver extension point; the stock module keeps the dangling-row detection + scaling formula, and Turtle's reworked-Rip base/max values move to src/turtle/ComboDuration.cpp. Not gated on Turtle::Detected() — the dangling-row condition stays the gate (gate on the bug, not the realm), preserving prior behavior exactly. - spell/BonusDamage: add a Spell::HealingAura resolver extension point; the stock GetSpellBonusHealing decoder keeps auras 135 (flat) and 175 (xSpirit), and Turtle's Ironclad aura 199 (xArmor) moves to src/turtle/HealingAura.cpp, gated on Turtle::Detected() (an aura index is a generic slot that could collide on another server). Behavior is unchanged on Turtle; both extractions are pure relocation.
1 parent 461f15a commit 29aafe8

7 files changed

Lines changed: 244 additions & 39 deletions

File tree

src/aura/ComboDuration.cpp

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -132,30 +132,19 @@ const Override *FindOverride(uint32_t spellId) {
132132
return nullptr;
133133
}
134134

135-
// Known values for spells whose duration row DANGLES — a non-zero
136-
// DurationIndex pointing at a SpellDuration row the client doesn't have.
137-
// Consulted ONLY when that exact condition holds, so the gate is the bug
138-
// itself, not a server fingerprint: on a stock client every one of these
139-
// resolves to a valid row and this table is never reached, and if the
140-
// broken client data is ever fixed the DBC row takes over automatically.
141-
//
142-
// Turtle's reworked Rip is the known case: all six ranks point at
143-
// SpellDuration row 87, which Turtle added on the SERVER only
144-
// ({8000, 0, 18000} — live server data shows 10/12/14/16/18s at 1..5 CP,
145-
// i.e. 8s + 2s per combo point); their client patch never shipped the
146-
// row (the client table jumps 86 -> 105). Verified the only dangling
147-
// duration index in the entire client Spell.dbc.
148-
constexpr Override kDanglingRowKnown[] = {
149-
{1079, 8000, 18000}, {9492, 8000, 18000}, {9493, 8000, 18000},
150-
{9752, 8000, 18000}, {9894, 8000, 18000}, {9896, 8000, 18000},
151-
};
152-
153-
const Override *FindDanglingRowKnown(uint32_t spellId) {
154-
for (const auto &k : kDanglingRowKnown) {
155-
if (k.spellId == spellId)
156-
return &k;
157-
}
158-
return nullptr;
135+
// Resolver chain for dangling-duration-row spells (client DBC missing the
136+
// row). The KNOWN VALUES themselves are server-custom data and live in
137+
// their own module (e.g. `src/turtle/ComboDuration.cpp` for Turtle's
138+
// reworked Rip); this stock module only owns the mechanism and the
139+
// dangling-condition gate. Chained at static-init by `AutoDanglingResolver`.
140+
AutoDanglingResolver *g_danglingResolvers = nullptr;
141+
142+
bool ResolveDangling(uint32_t spellId, int32_t *baseMs, int32_t *maxMs) {
143+
for (AutoDanglingResolver *r = g_danglingResolvers; r != nullptr;
144+
r = r->next)
145+
if (r->fn(spellId, baseMs, maxMs))
146+
return true;
147+
return false;
159148
}
160149

161150
// `C_UnitAuras.RegisterComboDuration(spellID, baseSeconds, maxSeconds)`
@@ -203,6 +192,11 @@ const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
203192

204193
} // namespace
205194

195+
AutoDanglingResolver::AutoDanglingResolver(DanglingResolver f)
196+
: fn(f), next(g_danglingResolvers) {
197+
g_danglingResolvers = this;
198+
}
199+
206200
uint32_t TryComboScaledMs(const uint8_t *spellRecord, uint32_t spellId) {
207201
if (spellRecord == nullptr || spellId == 0)
208202
return 0;
@@ -227,12 +221,11 @@ uint32_t TryComboScaledMs(const uint8_t *spellRecord, uint32_t spellId) {
227221
maxMs = *reinterpret_cast<const int32_t *>(row + 0xC);
228222
} else if (idx != 0) {
229223
// Dangling duration index — the client data is broken for this
230-
// spell. Fall back to the built-in known values (Turtle Rip).
231-
const Override *k = FindDanglingRowKnown(spellId);
232-
if (k == nullptr)
224+
// spell. Defer to a registered resolver (the known values are
225+
// server-custom data owned by a platform module, e.g.
226+
// src/turtle for Turtle's reworked Rip).
227+
if (!ResolveDangling(spellId, &baseMs, &maxMs))
233228
return 0;
234-
baseMs = k->baseMs;
235-
maxMs = k->maxMs;
236229
} else {
237230
return 0; // no duration row at all
238231
}

src/aura/ComboDuration.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,22 @@ namespace Aura::ComboDuration {
4747
// duration path".
4848
uint32_t TryComboScaledMs(const uint8_t *spellRecord, uint32_t spellId);
4949

50+
// Extension point for finishers whose SpellDuration row DANGLES — a
51+
// non-zero DurationIndex pointing at a row the client DBC doesn't have.
52+
// A resolver returns true and fills *baseMs/*maxMs if it knows the spell,
53+
// false otherwise. Consulted by `TryComboScaledMs` ONLY in that dangling
54+
// condition (after any Lua-registered override), so the activation gate is
55+
// the client-data bug itself, not a server fingerprint — the deliberate
56+
// design choice (a realm-sniffing approach was rejected). Server-custom
57+
// data (e.g. Turtle's reworked Rip) registers its values here instead of
58+
// living in this stock module: declare a file-scope
59+
// `static const Aura::ComboDuration::AutoDanglingResolver`.
60+
using DanglingResolver = bool (*)(uint32_t spellId, int32_t *baseMs,
61+
int32_t *maxMs);
62+
struct AutoDanglingResolver {
63+
explicit AutoDanglingResolver(DanglingResolver fn);
64+
DanglingResolver fn;
65+
AutoDanglingResolver *next;
66+
};
67+
5068
} // namespace Aura::ComboDuration

src/spell/BonusDamage.cpp

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
#include "item/Location.h"
6464
#include "item/StatAccum.h"
6565
#include "player/StatSignal.h"
66+
#include "spell/HealingAura.h"
6667
#include "tick/WorldTick.h"
6768
#include "unit/Identity.h"
6869

@@ -139,18 +140,19 @@ long ItemFlatHealing(const uint8_t *cgItem) {
139140

140141
// --- Buff / talent healing auras (flat + Spirit/Armor conversions) ---
141142
// One decoder for every healing aura a buff or passive talent can carry,
142-
// mirroring the server's SpellBaseHealingBonusDone:
143+
// mirroring the server's SpellBaseHealingBonusDone. Stock vanilla auras are
144+
// handled inline:
143145
// 135 MOD_HEALING_DONE → flat += amount
144146
// 175 MOD_SPELL_HEALING_OF_STAT_PERCENT → += Spirit × amount / 100 (Spiritual Guidance)
145-
// 199 MOD_SPELL_HEALING_OF_ARMOR_PERCENT → += Armor × amount / 100 (Turtle Ironclad)
146-
// Amounts/percents are read straight from Spell.dbc — generic (any talent/buff
147-
// using these auras, incl. Turtle customs) and robust (no client-vs-server
148-
// value delta). `requirePassive` gates the bitmap walk to always-on talents,
149-
// so a *known but not-currently-cast* buff (Divine Spirit) isn't counted as
150-
// on; active buffs pass `requirePassive == false`.
147+
// Any other aura index is deferred to `Spell::HealingAura::Resolve`, the
148+
// extension point where server-custom healing auras live (e.g. Turtle's
149+
// Ironclad, aura 199 = × Armor — see src/turtle/HealingAura.cpp). Amounts/
150+
// percents are read straight from Spell.dbc — generic and robust (no
151+
// client-vs-server value delta). `requirePassive` gates the bitmap walk to
152+
// always-on talents, so a *known but not-currently-cast* buff (Divine
153+
// Spirit) isn't counted as on; active buffs pass `requirePassive == false`.
151154
constexpr int kAuraModHealingDone = 135;
152155
constexpr int kAuraModHealingOfStatPercent = 175; // × Spirit
153-
constexpr int kAuraModHealingOfArmorPercent = 199; // × Armor (Turtle Ironclad)
154156

155157
void AddSpellHealingAuras(long &total, int spellID, int32_t spirit,
156158
int32_t armor, bool requirePassive) {
@@ -177,8 +179,8 @@ void AddSpellHealingAuras(long &total, int spellID, int32_t spirit,
177179
total += amount;
178180
else if (aura[i] == kAuraModHealingOfStatPercent)
179181
total += static_cast<long>(spirit) * amount / 100;
180-
else if (aura[i] == kAuraModHealingOfArmorPercent)
181-
total += static_cast<long>(armor) * amount / 100;
182+
else
183+
total += Spell::HealingAura::Resolve(aura[i], amount, spirit, armor);
182184
}
183185
}
184186

src/spell/HealingAura.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// This file is part of ClassicAPI.
2+
//
3+
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
4+
// of the GNU General Public License as published by the Free Software Foundation, either
5+
// version 3 of the License, or (at your option) any later version.
6+
//
7+
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
8+
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
9+
// PURPOSE. See the GNU General Public License for more details.
10+
//
11+
// You should have received a copy of the GNU General Public License along with
12+
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
13+
14+
#include "spell/HealingAura.h"
15+
16+
namespace Spell::HealingAura {
17+
18+
namespace {
19+
20+
// Chained at static-init by each `AutoResolver` constructor, before
21+
// `DllMain`. Zero-initialized ahead of any dynamic init.
22+
AutoResolver *g_resolvers = nullptr;
23+
24+
} // namespace
25+
26+
AutoResolver::AutoResolver(Resolver f) : fn(f), next(g_resolvers) {
27+
g_resolvers = this;
28+
}
29+
30+
long Resolve(int auraIndex, long amount, int32_t spirit, int32_t armor) {
31+
for (AutoResolver *r = g_resolvers; r != nullptr; r = r->next)
32+
if (const long v = r->fn(auraIndex, amount, spirit, armor))
33+
return v;
34+
return 0;
35+
}
36+
37+
} // namespace Spell::HealingAura

src/spell/HealingAura.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// This file is part of ClassicAPI.
2+
//
3+
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
4+
// of the GNU General Public License as published by the Free Software Foundation, either
5+
// version 3 of the License, or (at your option) any later version.
6+
//
7+
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
8+
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
9+
// PURPOSE. See the GNU General Public License for more details.
10+
//
11+
// You should have received a copy of the GNU General Public License along with
12+
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
13+
14+
#pragma once
15+
16+
#include <cstdint>
17+
18+
// Extension point for non-stock healing-aura semantics feeding
19+
// `GetSpellBonusHealing`. The stock vanilla healing auras — 135
20+
// (MOD_HEALING_DONE, flat) and 175 (MOD_SPELL_HEALING_OF_STAT_PERCENT,
21+
// ×Spirit) — are decoded inline in `spell/BonusDamage.cpp`. Server-custom
22+
// aura effects (e.g. Turtle's Ironclad, aura 199 = ×Armor) register a
23+
// resolver here so their meaning lives outside the stock decoder.
24+
namespace Spell::HealingAura {
25+
26+
// Flat healing contribution for a single Spell.dbc aura effect: the aura
27+
// index plus its computed amount (base + dice), and the player's Spirit /
28+
// Armor for stat-scaled auras. Returns 0 for aura indices no resolver
29+
// recognizes. Consulted by the stock decoder for any effect that isn't a
30+
// stock healing aura.
31+
long Resolve(int auraIndex, long amount, int32_t spirit, int32_t armor);
32+
33+
// A resolver maps one aura effect to its flat healing contribution, or
34+
// returns 0 if it doesn't recognize the aura index. Declare a file-scope
35+
// `static const Spell::HealingAura::AutoResolver` in a platform module to
36+
// register one at static-init time.
37+
using Resolver = long (*)(int auraIndex, long amount, int32_t spirit,
38+
int32_t armor);
39+
struct AutoResolver {
40+
explicit AutoResolver(Resolver fn);
41+
Resolver fn;
42+
AutoResolver *next;
43+
};
44+
45+
} // namespace Spell::HealingAura

src/turtle/ComboDuration.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// This file is part of ClassicAPI.
2+
//
3+
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
4+
// of the GNU General Public License as published by the Free Software Foundation, either
5+
// version 3 of the License, or (at your option) any later version.
6+
//
7+
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
8+
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
9+
// PURPOSE. See the GNU General Public License for more details.
10+
//
11+
// You should have received a copy of the GNU General Public License along with
12+
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
13+
14+
// Turtle WoW reworked Rip — known combo-finisher durations for a spell
15+
// whose client DBC row is missing. All six Rip ranks point at
16+
// SpellDuration row 87, which Turtle added on the SERVER only
17+
// ({8000, 0, 18000} — live data shows 10/12/14/16/18s at 1..5 CP, i.e.
18+
// 8s + 2s per combo point); the client patch never shipped the row (the
19+
// client table jumps 86 → 105), so the index dangles. Verified the only
20+
// dangling duration index in the entire client Spell.dbc.
21+
//
22+
// This supplies the values through `Aura::ComboDuration`'s dangling-row
23+
// resolver so the Turtle-specific data lives here, not in the stock
24+
// module. Unlike the other `src/turtle/` modules it does NOT gate on
25+
// `Turtle::Detected()`: the activation gate is the dangling-row condition
26+
// in `Aura::ComboDuration` (a client-data bug unique to this Rip), which
27+
// is the deliberate design — gate on the bug, not the realm. On a stock
28+
// client Rip resolves to a valid row and this resolver is never consulted;
29+
// if Turtle ever ships the missing client row, the DBC takes over and this
30+
// goes dormant automatically.
31+
32+
#include "aura/ComboDuration.h"
33+
34+
#include <cstdint>
35+
36+
namespace Turtle::ComboDuration {
37+
38+
namespace {
39+
40+
struct Known {
41+
uint32_t spellId;
42+
int32_t baseMs;
43+
int32_t maxMs;
44+
};
45+
46+
constexpr Known kRip[] = {
47+
{1079, 8000, 18000}, {9492, 8000, 18000}, {9493, 8000, 18000},
48+
{9752, 8000, 18000}, {9894, 8000, 18000}, {9896, 8000, 18000},
49+
};
50+
51+
bool Resolve(uint32_t spellId, int32_t *baseMs, int32_t *maxMs) {
52+
for (const auto &k : kRip) {
53+
if (k.spellId == spellId) {
54+
*baseMs = k.baseMs;
55+
*maxMs = k.maxMs;
56+
return true;
57+
}
58+
}
59+
return false;
60+
}
61+
62+
const Aura::ComboDuration::AutoDanglingResolver _register{&Resolve};
63+
64+
} // namespace
65+
66+
} // namespace Turtle::ComboDuration

src/turtle/HealingAura.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// This file is part of ClassicAPI.
2+
//
3+
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
4+
// of the GNU General Public License as published by the Free Software Foundation, either
5+
// version 3 of the License, or (at your option) any later version.
6+
//
7+
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
8+
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
9+
// PURPOSE. See the GNU General Public License for more details.
10+
//
11+
// You should have received a copy of the GNU General Public License along with
12+
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
13+
14+
// Turtle WoW custom healing auras. Aura 199
15+
// (MOD_SPELL_HEALING_OF_ARMOR_PERCENT, "Ironclad") adds
16+
// `Armor × amount / 100` to the player's spell healing bonus — not a stock
17+
// vanilla aura index. Plugs into `Spell::HealingAura`'s resolver chain so
18+
// the Turtle-specific aura semantic lives here, not in the stock
19+
// GetSpellBonusHealing decoder. Gated on Turtle detection (the aura index
20+
// is a generic slot number that could mean something else on another
21+
// server).
22+
23+
#include "spell/HealingAura.h"
24+
#include "turtle/Detect.h"
25+
26+
#include <cstdint>
27+
28+
namespace Turtle::HealingAura {
29+
30+
namespace {
31+
32+
constexpr int kAuraModHealingOfArmorPercent = 199; // × Armor (Ironclad)
33+
34+
long Resolve(int auraIndex, long amount, int32_t /*spirit*/, int32_t armor) {
35+
if (!Turtle::Detected() || auraIndex != kAuraModHealingOfArmorPercent)
36+
return 0;
37+
return static_cast<long>(armor) * amount / 100;
38+
}
39+
40+
const Spell::HealingAura::AutoResolver _register{&Resolve};
41+
42+
} // namespace
43+
44+
} // namespace Turtle::HealingAura

0 commit comments

Comments
 (0)