Files
ClassicAPI/src/faction/StandingChanged.cpp
T
Brues d9102bae3b Relicense source headers from LGPL to GPL v3
The repo was internally inconsistent: the LICENSE file is GPL v3 but every
src/ header carried the LGPL v3 notice and the README said "LGPL v3 or
later". LGPL was also the wrong fit — this is an injected mod plus a
bundled addon, not a library other code links against, so LGPL's linking
exception buys nothing.

It's also required: src/voicechat/Tts.cpp is ported from awesome_wotlk's
VoiceChat.cpp, which is GPL v3, and a derivative of GPL code cannot be
relicensed under more permissive terms. Dependencies stay compatible —
MinHook (BSD-2) and the nampower-mirrored packet shapes (BSD) are
GPL-compatible, and SAPI is covered by the system-library exception.

Swept "GNU Lesser General Public License" -> "GNU General Public License"
across all 257 src/ headers and changed the README license line to match
the GPL v3 LICENSE file. Comment-only; no build impact.
2026-06-29 16:38:16 -05:00

114 lines
4.6 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/>.
// FACTION_STANDING_CHANGED — fires once per reputation change with
// `(factionID, newStanding, repGained)`. Polyfills modern WoW's event
// of the same name (modern fires `(factionID, newStanding)`); we add
// `repGained` as a third arg because the engine has the delta on hand
// in EDX and addons commonly need it for "+X reputation" displays
// without re-deriving it from the chat string. Vanilla 1.12 only
// exposes the localized `CHAT_MSG_COMBAT_FACTION_CHANGE` text, which
// addons currently scrape and reverse-resolve back to IDs.
//
// We hook the engine's per-rep-change notify dispatcher at
// `0x0062C5F0`. That function is the single chokepoint that fires the
// chat event after a successful standing update: it's only called when
// the value actually changed AND the engine's `notify` flag was set
// (i.e. from the per-update SMSG handler, not the bulk init at login).
// This matches modern WoW's semantics — FACTION_STANDING_CHANGED does
// not fire for initial faction sync.
//
// The hook target's calling convention is `__fastcall(ecx = factionID,
// edx = signedDelta)`. `delta` is forwarded as the `repGained` event
// arg (positive on gain, negative on loss). To produce the
// `newStanding` payload, we read back the live total via
// `FUN_REPUTATION_GET_STANDING(factionID)` — the setter has already
// written the new delta into the per-slot storage by the time our
// hook fires.
#include "StandingChanged.h"
#include "Game.h"
#include "Offsets.h"
#include "event/Custom.h"
namespace Faction::StandingChanged {
namespace {
using GetStanding_t = int(__fastcall *)(int factionID);
constexpr const char *kEventName = "FACTION_STANDING_CHANGED";
const Event::Custom::AutoReserve _reserve{kEventName};
// In-flight snapshot of the current rep change. Set on hook entry,
// cleared on hook exit. WoW's main thread is the only one that runs
// the rep-update SMSG handler and Lua callbacks, so a plain file
// static is sufficient — no TLS needed.
LastChange g_last{};
int __fastcall Script_C_Reputation_GetLastStandingChange(void *L) {
if (!g_last.valid)
return 0;
Game::Lua::PushNumber(L, static_cast<double>(g_last.factionID));
Game::Lua::PushNumber(L, static_cast<double>(g_last.newStanding));
Game::Lua::PushNumber(L, static_cast<double>(g_last.delta));
return 3;
}
void RegisterLuaFunctions() {
Game::Lua::RegisterTableFunction("C_Reputation", "GetLastStandingChange",
&Script_C_Reputation_GetLastStandingChange);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
} // namespace
FireNotify_t FireNotify_o = nullptr;
const LastChange &Last() { return g_last; }
void __fastcall FireNotify_h(int factionID, int delta) {
// The engine's setter at 0x004D6330 has already written the new
// standing into the per-slot storage by the time we get here, so
// `FUN_REPUTATION_GET_STANDING` returns the post-change total. We
// capture it before forwarding so anything reading
// `g_last` from inside the engine's chat dispatch (and from our
// own `FACTION_STANDING_CHANGED` observers below) sees a
// consistent snapshot.
auto getStanding = reinterpret_cast<GetStanding_t>(
Offsets::FUN_REPUTATION_GET_STANDING);
const int newStanding = getStanding(factionID);
g_last = {true, factionID, newStanding, delta};
// Forward to the engine first so its CHAT_MSG_COMBAT_FACTION_CHANGE
// fires before our FACTION_STANDING_CHANGED — preserves ordering
// any addon relying on the chat event firing first would expect.
FireNotify_o(factionID, delta);
const int slot = Event::Custom::Lookup(kEventName);
if (slot >= 0)
Event::Custom::Fire(slot, "%d%d%d", factionID, newStanding, delta);
g_last = {};
}
static const Game::HookAutoRegister _hookreg{
Offsets::FUN_REPUTATION_FIRE_NOTIFY,
reinterpret_cast<void *>(&FireNotify_h),
reinterpret_cast<void **>(&FireNotify_o)};
} // namespace Faction::StandingChanged