Files
ClassicAPI/src/group/MemberStats.cpp
T
Brues c54556f6a1 refactor: centralize engine field/global access via Game::Read/Ref/Ptr
Replace the raw `*reinterpret_cast<const T *>(base + Offsets::OFF_X)`
idiom (and its double-cast variant when base wasn't already a byte
pointer) with three typed accessors in Game.h:

  Game::Read<T>(base, offset)  field value read
  Game::Ref<T>(base, offset)   writable field lvalue
  Game::Ptr<T>(base, offset)   pointer to a field (arrays / out-params)
  Game::Read<T>(address) / Ref<T>(address)  fixed-VA globals

Pure mechanical refactor, zero behavior change: ~330 cast sites across
84 files collapse to 651 helper calls. Offsets stay in Offsets.h per the
single-source-of-truth rule — the helpers only remove the cast noise and
fold away the inner `reinterpret_cast<uint8_t*>(obj)` base casts.

Left as raw casts by design: FUN_* function-pointer casts, void*/char*
VA handles passed as arguments (caches, method registries), subobject-
anchor pointers fed to __thiscall helpers, `void *volatile` player-ptr
readiness gates (converting would strip volatile), and VA-as-array-base
captures that are subscripted later.
2026-08-19 11:01:19 -05:00

114 lines
4.4 KiB
C++

// This file is part of ClassicAPI.
//
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
// of the GNU General Public License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
#include "group/MemberStats.h"
#include "Game.h"
#include "Offsets.h"
namespace Group::MemberStats {
namespace {
// Both roster lookups are `__fastcall(u64 *guid) -> block*` (NULL on miss).
using Lookup_t = const uint8_t *(__fastcall *)(uint64_t *guid);
uint16_t U16(const uint8_t *p, int off) {
return Game::Read<uint16_t>(p, off);
}
} // namespace
Stats Lookup(uint64_t guid) {
Stats s;
if (guid == 0)
return s;
// Party stats block — the full record.
auto statsFn = reinterpret_cast<Lookup_t>(
static_cast<uintptr_t>(Offsets::FUN_GROUP_MEMBER_STATS_LOOKUP));
if (const uint8_t *e = statsFn(&guid)) {
s.valid = true;
s.full = true;
s.powerType = e[Offsets::OFF_GROUP_MEMBER_STATS_POWER_TYPE];
s.health = U16(e, Offsets::OFF_GROUP_MEMBER_STATS_HEALTH);
s.maxHealth = U16(e, Offsets::OFF_GROUP_MEMBER_STATS_MAX_HEALTH);
s.power = U16(e, Offsets::OFF_GROUP_MEMBER_STATS_POWER);
s.maxPower = U16(e, Offsets::OFF_GROUP_MEMBER_STATS_MAX_POWER);
s.level = U16(e, Offsets::OFF_GROUP_MEMBER_STATS_LEVEL);
s.areaId = U16(e, Offsets::OFF_GROUP_MEMBER_AREA_ID);
s.online = (e[Offsets::OFF_GROUP_MEMBER_STATUS_FLAGS] &
Offsets::GROUP_MEMBER_STATUS_ONLINE) != 0;
// The party stats block carries no dead / subgroup / rank fields; those
// are raid-only (dead comes from UnitIsDead on the live unit otherwise).
return s;
}
// Raid slot — same struct GetRaidRosterInfo reads.
auto slotFn = reinterpret_cast<Lookup_t>(
static_cast<uintptr_t>(Offsets::FUN_GROUP_MEMBER_SLOT_LOOKUP));
if (const uint8_t *e = slotFn(&guid)) {
s.valid = true;
s.full = false;
s.powerType = e[Offsets::OFF_RAID_SLOT_POWER_TYPE];
s.health = U16(e, Offsets::OFF_RAID_SLOT_HEALTH);
s.maxHealth = U16(e, Offsets::OFF_RAID_SLOT_MAX_HEALTH);
s.power = U16(e, Offsets::OFF_RAID_SLOT_POWER);
s.maxPower = U16(e, Offsets::OFF_RAID_SLOT_MAX_POWER);
s.level = U16(e, Offsets::OFF_RAID_SLOT_LEVEL);
s.areaId = U16(e, Offsets::OFF_RAID_SLOT_AREA_ID);
const uint32_t flags =
Game::Read<uint32_t>(e, Offsets::OFF_RAID_SLOT_FLAGS);
s.online = (flags & Offsets::RAID_SLOT_FLAG_ONLINE) != 0;
s.dead = (flags & Offsets::RAID_SLOT_FLAG_DEAD) != 0;
s.subgroup = static_cast<int>(Game::Read<uint32_t>(
e, Offsets::OFF_RAID_SLOT_SUBGROUP)) + 1;
s.rank = static_cast<int>(
Game::Read<uint32_t>(e, Offsets::OFF_RAID_SLOT_RANK));
return s;
}
return s;
}
const uint16_t *AuraArray(uint64_t guid) {
if (guid == 0)
return nullptr;
// Party stats block first — auras at +0x1a, but only for an online member
// (the engine's Script_UnitBuff bails on the party path when the online bit
// is clear).
auto statsFn = reinterpret_cast<Lookup_t>(
static_cast<uintptr_t>(Offsets::FUN_GROUP_MEMBER_STATS_LOOKUP));
if (const uint8_t *e = statsFn(&guid)) {
if ((e[Offsets::OFF_GROUP_MEMBER_STATUS_FLAGS] &
Offsets::GROUP_MEMBER_STATUS_ONLINE) == 0)
return nullptr;
return Game::Ptr<const uint16_t>(
e, Offsets::OFF_GROUP_MEMBER_STATS_AURAS);
}
// Raid slot — auras at +0x64, no online gate (mirrors the engine's raid
// branch, which reads the array unconditionally).
auto slotFn = reinterpret_cast<Lookup_t>(
static_cast<uintptr_t>(Offsets::FUN_GROUP_MEMBER_SLOT_LOOKUP));
if (const uint8_t *e = slotFn(&guid)) {
return Game::Ptr<const uint16_t>(
e, Offsets::OFF_RAID_SLOT_AURAS);
}
return nullptr;
}
} // namespace Group::MemberStats