mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
feat: parse SMSG_SET_FLAT/PCT_SPELL_MODIFIER and apply talent modifiers to spell tooltips
Implements SMSG_SET_FLAT_SPELL_MODIFIER and SMSG_SET_PCT_SPELL_MODIFIER (previously consumed silently). Parses per-group (uint8 groupIndex, uint8 SpellModOp, int32 value) tuples sent by the server after login and talent changes, and stores them in spellFlatMods_/spellPctMods_ maps keyed by (SpellModOp, groupIndex). Exposes getSpellFlatMod(op)/getSpellPctMod(op) accessors and a static applySpellMod() helper. Clears both maps on character login alongside spellCooldowns. Surfaces talent-modified mana cost and cast time in the spellbook tooltip via SpellModOp::Cost and SpellModOp::CastingTime lookups.
This commit is contained in:
parent
74d5984ee2
commit
e4fd4b4e6d
3 changed files with 120 additions and 8 deletions
|
|
@ -1491,6 +1491,84 @@ public:
|
|||
};
|
||||
const std::array<RuneSlot, 6>& getPlayerRunes() const { return playerRunes_; }
|
||||
|
||||
// Talent-driven spell modifiers (SMSG_SET_FLAT_SPELL_MODIFIER / SMSG_SET_PCT_SPELL_MODIFIER)
|
||||
// SpellModOp matches WotLK SpellModOp enum (server-side).
|
||||
enum class SpellModOp : uint8_t {
|
||||
Damage = 0,
|
||||
Duration = 1,
|
||||
Threat = 2,
|
||||
Effect1 = 3,
|
||||
Charges = 4,
|
||||
Range = 5,
|
||||
Radius = 6,
|
||||
CritChance = 7,
|
||||
AllEffects = 8,
|
||||
NotLoseCastingTime = 9,
|
||||
CastingTime = 10,
|
||||
Cooldown = 11,
|
||||
Effect2 = 12,
|
||||
IgnoreArmor = 13,
|
||||
Cost = 14,
|
||||
CritDamageBonus = 15,
|
||||
ResistMissChance = 16,
|
||||
JumpTargets = 17,
|
||||
ChanceOfSuccess = 18,
|
||||
ActivationTime = 19,
|
||||
Efficiency = 20,
|
||||
MultipleValue = 21,
|
||||
ResistDispelChance = 22,
|
||||
Effect3 = 23,
|
||||
BonusMultiplier = 24,
|
||||
ProcPerMinute = 25,
|
||||
ValueMultiplier = 26,
|
||||
ResistPushback = 27,
|
||||
MechanicDuration = 28,
|
||||
StartCooldown = 29,
|
||||
PeriodicBonus = 30,
|
||||
AttackPower = 31,
|
||||
};
|
||||
static constexpr int SPELL_MOD_OP_COUNT = 32;
|
||||
|
||||
// Key: (SpellModOp, groupIndex) — value: accumulated flat or pct modifier
|
||||
// pct values are stored in integer percent (e.g. -20 means -20% reduction).
|
||||
struct SpellModKey {
|
||||
SpellModOp op;
|
||||
uint8_t group;
|
||||
bool operator==(const SpellModKey& o) const {
|
||||
return op == o.op && group == o.group;
|
||||
}
|
||||
};
|
||||
struct SpellModKeyHash {
|
||||
std::size_t operator()(const SpellModKey& k) const {
|
||||
return std::hash<uint32_t>()(
|
||||
(static_cast<uint32_t>(static_cast<uint8_t>(k.op)) << 8) | k.group);
|
||||
}
|
||||
};
|
||||
|
||||
// Returns the sum of all flat modifiers for a given op across all groups.
|
||||
// (Callers that need per-group resolution can use getSpellFlatMods() directly.)
|
||||
int32_t getSpellFlatMod(SpellModOp op) const {
|
||||
int32_t total = 0;
|
||||
for (const auto& [k, v] : spellFlatMods_)
|
||||
if (k.op == op) total += v;
|
||||
return total;
|
||||
}
|
||||
// Returns the sum of all pct modifiers for a given op across all groups (in %).
|
||||
int32_t getSpellPctMod(SpellModOp op) const {
|
||||
int32_t total = 0;
|
||||
for (const auto& [k, v] : spellPctMods_)
|
||||
if (k.op == op) total += v;
|
||||
return total;
|
||||
}
|
||||
|
||||
// Convenience: apply flat+pct modifier to a base value.
|
||||
// result = (base + flatMod) * (1.0 + pctMod/100.0), clamped to >= 0.
|
||||
static int32_t applySpellMod(int32_t base, int32_t flat, int32_t pct) {
|
||||
int64_t v = static_cast<int64_t>(base) + flat;
|
||||
if (pct != 0) v = v + (v * pct + 50) / 100; // round half-up
|
||||
return static_cast<int32_t>(v < 0 ? 0 : v);
|
||||
}
|
||||
|
||||
struct FactionStandingInit {
|
||||
uint8_t flags = 0;
|
||||
int32_t standing = 0;
|
||||
|
|
@ -3100,6 +3178,11 @@ private:
|
|||
|
||||
// ---- WotLK Calendar: pending invite counter ----
|
||||
uint32_t calendarPendingInvites_ = 0; ///< Unacknowledged calendar invites (SMSG_CALENDAR_SEND_NUM_PENDING)
|
||||
|
||||
// ---- Spell modifiers (SMSG_SET_FLAT_SPELL_MODIFIER / SMSG_SET_PCT_SPELL_MODIFIER) ----
|
||||
// Keyed by (SpellModOp, groupIndex); cleared on logout/character change.
|
||||
std::unordered_map<SpellModKey, int32_t, SpellModKeyHash> spellFlatMods_;
|
||||
std::unordered_map<SpellModKey, int32_t, SpellModKeyHash> spellPctMods_;
|
||||
};
|
||||
|
||||
} // namespace game
|
||||
|
|
|
|||
|
|
@ -3756,12 +3756,29 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
}
|
||||
|
||||
case Opcode::SMSG_FEATURE_SYSTEM_STATUS:
|
||||
case Opcode::SMSG_SET_FLAT_SPELL_MODIFIER:
|
||||
case Opcode::SMSG_SET_PCT_SPELL_MODIFIER:
|
||||
// Different formats than SMSG_SPELL_DELAYED — consume and ignore
|
||||
packet.setReadPos(packet.getSize());
|
||||
break;
|
||||
|
||||
case Opcode::SMSG_SET_FLAT_SPELL_MODIFIER:
|
||||
case Opcode::SMSG_SET_PCT_SPELL_MODIFIER: {
|
||||
// WotLK format: one or more (uint8 groupIndex, uint8 modOp, int32 value) tuples
|
||||
// Each tuple is 6 bytes; iterate until packet is consumed.
|
||||
const bool isFlat = (*logicalOp == Opcode::SMSG_SET_FLAT_SPELL_MODIFIER);
|
||||
auto& modMap = isFlat ? spellFlatMods_ : spellPctMods_;
|
||||
while (packet.getSize() - packet.getReadPos() >= 6) {
|
||||
uint8_t groupIndex = packet.readUInt8();
|
||||
uint8_t modOpRaw = packet.readUInt8();
|
||||
int32_t value = static_cast<int32_t>(packet.readUInt32());
|
||||
if (groupIndex > 5 || modOpRaw >= SPELL_MOD_OP_COUNT) continue;
|
||||
SpellModKey key{ static_cast<SpellModOp>(modOpRaw), groupIndex };
|
||||
modMap[key] = value;
|
||||
LOG_DEBUG(isFlat ? "SMSG_SET_FLAT_SPELL_MODIFIER" : "SMSG_SET_PCT_SPELL_MODIFIER",
|
||||
": group=", (int)groupIndex, " op=", (int)modOpRaw, " value=", value);
|
||||
}
|
||||
packet.setReadPos(packet.getSize());
|
||||
break;
|
||||
}
|
||||
|
||||
case Opcode::SMSG_SPELL_DELAYED: {
|
||||
// WotLK: packed_guid (caster) + uint32 delayMs
|
||||
// TBC/Classic: uint64 (caster) + uint32 delayMs
|
||||
|
|
@ -7930,6 +7947,8 @@ void GameHandler::selectCharacter(uint64_t characterGuid) {
|
|||
std::fill(std::begin(playerStats_), std::end(playerStats_), -1);
|
||||
knownSpells.clear();
|
||||
spellCooldowns.clear();
|
||||
spellFlatMods_.clear();
|
||||
spellPctMods_.clear();
|
||||
actionBar = {};
|
||||
playerAuras.clear();
|
||||
targetAuras.clear();
|
||||
|
|
|
|||
|
|
@ -525,7 +525,7 @@ void SpellbookScreen::renderSpellTooltip(const SpellInfo* info, game::GameHandle
|
|||
|
||||
// Resource cost + cast time on same row (WoW style)
|
||||
if (!info->isPassive()) {
|
||||
// Left: resource cost
|
||||
// Left: resource cost (with talent flat/pct modifier applied)
|
||||
char costBuf[64] = "";
|
||||
if (info->manaCost > 0) {
|
||||
const char* powerName = "Mana";
|
||||
|
|
@ -535,16 +535,26 @@ void SpellbookScreen::renderSpellTooltip(const SpellInfo* info, game::GameHandle
|
|||
case 4: powerName = "Focus"; break;
|
||||
default: break;
|
||||
}
|
||||
std::snprintf(costBuf, sizeof(costBuf), "%u %s", info->manaCost, powerName);
|
||||
// Apply SMSG_SET_FLAT/PCT_SPELL_MODIFIER Cost modifier (SpellModOp::Cost = 14)
|
||||
int32_t flatCost = gameHandler.getSpellFlatMod(game::GameHandler::SpellModOp::Cost);
|
||||
int32_t pctCost = gameHandler.getSpellPctMod(game::GameHandler::SpellModOp::Cost);
|
||||
uint32_t displayCost = static_cast<uint32_t>(
|
||||
game::GameHandler::applySpellMod(static_cast<int32_t>(info->manaCost), flatCost, pctCost));
|
||||
std::snprintf(costBuf, sizeof(costBuf), "%u %s", displayCost, powerName);
|
||||
}
|
||||
|
||||
// Right: cast time
|
||||
// Right: cast time (with talent CastingTime modifier applied)
|
||||
char castBuf[32] = "";
|
||||
if (info->castTimeMs == 0) {
|
||||
std::snprintf(castBuf, sizeof(castBuf), "Instant cast");
|
||||
} else {
|
||||
float secs = info->castTimeMs / 1000.0f;
|
||||
std::snprintf(castBuf, sizeof(castBuf), "%.1f sec cast", secs);
|
||||
// Apply SpellModOp::CastingTime (10) modifiers
|
||||
int32_t flatCT = gameHandler.getSpellFlatMod(game::GameHandler::SpellModOp::CastingTime);
|
||||
int32_t pctCT = gameHandler.getSpellPctMod(game::GameHandler::SpellModOp::CastingTime);
|
||||
int32_t modCT = game::GameHandler::applySpellMod(
|
||||
static_cast<int32_t>(info->castTimeMs), flatCT, pctCT);
|
||||
float secs = static_cast<float>(modCT) / 1000.0f;
|
||||
std::snprintf(castBuf, sizeof(castBuf), "%.1f sec cast", secs > 0.0f ? secs : 0.0f);
|
||||
}
|
||||
|
||||
if (costBuf[0] || castBuf[0]) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue