mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-10 20:07:37 +00:00
PLAYER_STARTED_MOVING / PLAYER_STOPPED_MOVING
This commit is contained in:
@@ -61,6 +61,8 @@ functions, just behavior the stock 1.12 engine didn't have. See the
|
||||
| `EQUIPMENT_SETS_CHANGED` | *(none)* |
|
||||
| `EQUIPMENT_SWAP_PENDING` | `setID` |
|
||||
| `EQUIPMENT_SWAP_FINISHED` | `success, setID` |
|
||||
| `PLAYER_STARTED_MOVING` | *(none)* |
|
||||
| `PLAYER_STOPPED_MOVING` | *(none)* |
|
||||
| `FACTION_STANDING_CHANGED` | `factionID, newStanding, repGained` |
|
||||
| `ITEM_DATA_LOAD_RESULT` | `itemID, success` |
|
||||
| `MODIFIER_STATE_CHANGED` | `keyName, down` |
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
-xc++
|
||||
-std=c++17
|
||||
-Isrc
|
||||
-Iexternal/minhook/include
|
||||
-DCLASSICAPI_VERSION_VALUE=1
|
||||
-D_CRT_SECURE_NO_WARNINGS
|
||||
--target=i686-pc-windows-msvc
|
||||
+35
@@ -159,6 +159,7 @@ build instructions.
|
||||
- [Events](#events)
|
||||
- [`C_EventUtils.IsEventValid(eventName)`](#c_eventutilsiseventvalideventname)
|
||||
- [`BAG_UPDATE_DELAYED` event](#bag_update_delayed-event)
|
||||
- [`PLAYER_STARTED_MOVING` / `PLAYER_STOPPED_MOVING` events](#player_started_moving--player_stopped_moving-events)
|
||||
- [Globals](#globals)
|
||||
- [`CLASSIC_API_VERSION`](#classic_api_version)
|
||||
- [`LE_EXPANSION_*`](#le_expansion_)
|
||||
@@ -3782,6 +3783,40 @@ event handlers.
|
||||
> currently won't trigger `BAG_UPDATE_DELAYED`. Player bag (0..4)
|
||||
> and bank (5..10) updates work normally — the 95% case.
|
||||
|
||||
### `PLAYER_STARTED_MOVING` / `PLAYER_STOPPED_MOVING` events
|
||||
|
||||
Fires (with no payload) on the transition where the player begins or
|
||||
ends translational movement, matching the modern events of the same
|
||||
name. Vanilla 1.12 doesn't expose any movement-state event — addons
|
||||
that need to know "is the player moving" otherwise poll
|
||||
`GetUnitSpeed("player")` or `IsFalling` on every `OnUpdate`.
|
||||
|
||||
```lua
|
||||
local f = CreateFrame("Frame")
|
||||
f:RegisterEvent("PLAYER_STARTED_MOVING")
|
||||
f:RegisterEvent("PLAYER_STOPPED_MOVING")
|
||||
f:SetScript("OnEvent", function()
|
||||
if event == "PLAYER_STARTED_MOVING" then ... end
|
||||
end)
|
||||
```
|
||||
|
||||
What counts as "moving" matches modern's behavior:
|
||||
|
||||
| Bit | Counts? | Note |
|
||||
|-----|---------|------|
|
||||
| `FORWARD` / `BACKWARD` / `STRAFE_LEFT` / `STRAFE_RIGHT` | yes | W/S/A/D press, mouse-click move |
|
||||
| `JUMPING` / `FALLING` / `FALLING_FAR` | yes | spacebar, walking off a ledge |
|
||||
| `TURN_LEFT` / `TURN_RIGHT` (turn-in-place) | no | matches modern (turning isn't moving) |
|
||||
| `WALK_MODE` (toggle), `ONTRANSPORT`, `LEVITATING`, `SWIMMING` alone | no | protocol-level flags, not active motion |
|
||||
|
||||
Implementation: subscribes to the shared `Tick::WorldTick` per-frame
|
||||
registry (single `MinHook` on `FUN_0066FD50` shared with
|
||||
`BAG_UPDATE_DELAYED`) and edge-detects on the moving-bits subset of
|
||||
the player's movement-flags word at `CGPlayer + 0x9E8`. State resets
|
||||
between zones — if the player pointer is unresolvable (loading
|
||||
screen, character select), the next valid tick won't fire a stale
|
||||
`STOPPED_MOVING`.
|
||||
|
||||
## Globals
|
||||
|
||||
### `CLASSIC_API_VERSION`
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ namespace Common {
|
||||
#define HOOK_FUNCTION(offset, hook, original) \
|
||||
{ \
|
||||
auto *target = reinterpret_cast<LPVOID>(offset); \
|
||||
if (MH_CreateHook(target, static_cast<LPVOID>(hook), \
|
||||
if (MH_CreateHook(target, reinterpret_cast<LPVOID>(hook), \
|
||||
reinterpret_cast<LPVOID *>(&original)) != MH_OK) \
|
||||
return FALSE; \
|
||||
if (MH_EnableHook(target) != MH_OK) \
|
||||
|
||||
+1
-1
@@ -88,7 +88,7 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) {
|
||||
return FALSE;
|
||||
|
||||
auto *target = reinterpret_cast<LPVOID>(Offsets::FUN_INVALID_FUNCTION_PTR_CHECK);
|
||||
if (MH_CreateHook(target, static_cast<LPVOID>(InvalidFunctionPtrCheck_h), nullptr) != MH_OK)
|
||||
if (MH_CreateHook(target, reinterpret_cast<LPVOID>(InvalidFunctionPtrCheck_h), nullptr) != MH_OK)
|
||||
return FALSE;
|
||||
if (MH_EnableHook(target) != MH_OK)
|
||||
return FALSE;
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
#include "MinHook.h"
|
||||
#include "Offsets.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
namespace Game {
|
||||
|
||||
namespace Lua {
|
||||
|
||||
@@ -1526,6 +1526,14 @@ enum Offsets {
|
||||
// 0x1000000 FLYING (no flying mounts in vanilla, but the bit
|
||||
// exists for druid Flight Form etc.)
|
||||
OFF_PLAYER_MOVEMENT_FLAGS = 0x9E8,
|
||||
MOVEFLAG_FORWARD = 0x1,
|
||||
MOVEFLAG_BACKWARD = 0x2,
|
||||
MOVEFLAG_STRAFE_LEFT = 0x4,
|
||||
MOVEFLAG_STRAFE_RIGHT = 0x8,
|
||||
// 0x10 / 0x20 are TURN_LEFT / TURN_RIGHT — turn-in-place, not
|
||||
// translational movement. Excluded from "moving" semantics
|
||||
// because modern `PLAYER_STARTED_MOVING` doesn't fire for them.
|
||||
MOVEFLAG_JUMPING = 0x1000,
|
||||
MOVEFLAG_FALLING = 0x2000,
|
||||
MOVEFLAG_FALLING_FAR = 0x4000,
|
||||
MOVEFLAG_SWIMMING = 0x200000,
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
namespace AddOns::Info {
|
||||
|
||||
+13
-15
@@ -18,14 +18,16 @@
|
||||
//
|
||||
// See TODO #67 for the full investigation history.
|
||||
//
|
||||
// Architecture: three hooks total.
|
||||
// Architecture: two MinHooks plus one tick subscription.
|
||||
//
|
||||
// 1. `FUN_004F91A0` — bag-slot diff loop. Hook just sets the
|
||||
// `g_pending` flag.
|
||||
// 2. `FUN_004F9370` — item→bag resolver (most common path). Hook
|
||||
// just sets the `g_pending` flag.
|
||||
// 3. `FUN_0066FD50` — engine's per-frame world-subsystem update.
|
||||
// Hook drains `g_pending` and fires DELAYED if set.
|
||||
// 3. `Tick::WorldTick::AutoSubscribe` — shared per-frame
|
||||
// subscription (the engine's world-subsystem update at
|
||||
// `FUN_0066FD50`, single-hooked in `src/tick/WorldTick.cpp`).
|
||||
// Our callback drains `g_pending` and fires DELAYED if set.
|
||||
//
|
||||
// All three targets are quiet (≤2 callers each, in code regions
|
||||
// other DLLs don't touch). The previous attempt at this feature
|
||||
@@ -52,6 +54,7 @@
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "event/Custom.h"
|
||||
#include "tick/WorldTick.h"
|
||||
|
||||
namespace Bag::UpdateDelayed {
|
||||
|
||||
@@ -86,14 +89,12 @@ void __stdcall BagItemToBag_h(int guidLo, int guidHi) {
|
||||
g_pending = true;
|
||||
}
|
||||
|
||||
// `FUN_0066FD50` — `__fastcall(int, int, int)` per-frame world tick.
|
||||
// We hook the TAIL: run the original first (so all of this frame's
|
||||
// world-tick work completes), then drain the pending flag and fire
|
||||
// DELAYED if any BAG_UPDATE happened this frame.
|
||||
using WorldTick_t = void(__fastcall *)(int arg1, int arg2, int arg3);
|
||||
WorldTick_t WorldTick_o = nullptr;
|
||||
void __fastcall WorldTick_h(int arg1, int arg2, int arg3) {
|
||||
WorldTick_o(arg1, arg2, arg3);
|
||||
// Per-frame drain. Runs at the tail of each world tick (subscribed
|
||||
// through `Tick::WorldTick`, which owns the single hook on
|
||||
// `FUN_0066FD50`). All this frame's BAG_UPDATEs have already fired
|
||||
// by the time we run; we just consume the pending flag and emit one
|
||||
// DELAYED if set.
|
||||
void OnWorldTick() {
|
||||
if (!g_pending)
|
||||
return;
|
||||
g_pending = false;
|
||||
@@ -112,9 +113,6 @@ static const Game::HookAutoRegister _hookItemToBag{
|
||||
reinterpret_cast<void *>(&BagItemToBag_h),
|
||||
reinterpret_cast<void **>(&BagItemToBag_o)};
|
||||
|
||||
static const Game::HookAutoRegister _hookWorldTick{
|
||||
Offsets::FUN_WORLD_TICK,
|
||||
reinterpret_cast<void *>(&WorldTick_h),
|
||||
reinterpret_cast<void **>(&WorldTick_o)};
|
||||
static const Tick::WorldTick::AutoSubscribe _tickSub{&OnWorldTick};
|
||||
|
||||
} // namespace Bag::UpdateDelayed
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "debug/Log.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
|
||||
@@ -13,12 +13,10 @@
|
||||
|
||||
#include "Data.h"
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "Storage.h"
|
||||
#include "event/Custom.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace EquipmentSet::Storage {
|
||||
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
#include "item/Location.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
namespace Item::Data {
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#include "item/Location.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string.h>
|
||||
|
||||
namespace Item::Equipment {
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU Lesser 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 Lesser General Public License for more details.
|
||||
|
||||
// `PLAYER_STARTED_MOVING` / `PLAYER_STOPPED_MOVING` — modern events
|
||||
// vanilla doesn't fire. Per-frame poll of the local player's
|
||||
// movement-flags word (same word `IsFalling`/`IsSwimming` read) and
|
||||
// edge-detect on the "translational motion" subset of bits. Fires
|
||||
// STARTED on `false→true`, STOPPED on `true→false`.
|
||||
//
|
||||
// The mask intentionally excludes TURN_LEFT / TURN_RIGHT (turn-in-
|
||||
// place doesn't count as moving on modern) and the protocol-level
|
||||
// flags like WALK_MODE, ONTRANSPORT, LEVITATING that don't reflect
|
||||
// active translation. Jumping and falling DO count — modern fires
|
||||
// STARTED_MOVING when you jump in place, and the airborne portion of
|
||||
// any jump/fall keeps the event "moving" until you touch down.
|
||||
//
|
||||
// We subscribe to the shared `Tick::WorldTick` registry rather than
|
||||
// installing our own MinHook on `FUN_WORLD_TICK` — MinHook permits
|
||||
// only one hook per target address, and `bag/UpdateDelayed.cpp`
|
||||
// also wants per-frame ticks.
|
||||
|
||||
#include "Offsets.h"
|
||||
#include "event/Custom.h"
|
||||
#include "tick/WorldTick.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Player::Movement {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char *kStartedMovingEvent = "PLAYER_STARTED_MOVING";
|
||||
constexpr const char *kStoppedMovingEvent = "PLAYER_STOPPED_MOVING";
|
||||
|
||||
constexpr uint32_t kMovingMask =
|
||||
Offsets::MOVEFLAG_FORWARD
|
||||
| Offsets::MOVEFLAG_BACKWARD
|
||||
| Offsets::MOVEFLAG_STRAFE_LEFT
|
||||
| Offsets::MOVEFLAG_STRAFE_RIGHT
|
||||
| Offsets::MOVEFLAG_JUMPING
|
||||
| Offsets::MOVEFLAG_FALLING
|
||||
| Offsets::MOVEFLAG_FALLING_FAR;
|
||||
|
||||
bool g_wasMoving = false;
|
||||
|
||||
using ResolveUnitToken_t = void *(__fastcall *)(const char *token);
|
||||
|
||||
const uint8_t *Player() {
|
||||
auto fn = reinterpret_cast<ResolveUnitToken_t>(Offsets::FUN_RESOLVE_UNIT_TOKEN);
|
||||
return static_cast<const uint8_t *>(fn("player"));
|
||||
}
|
||||
|
||||
void OnWorldTick() {
|
||||
auto *player = Player();
|
||||
if (player == nullptr) {
|
||||
// Pre-world / character select / loading screen. Reset
|
||||
// state so the next valid tick doesn't fire a spurious
|
||||
// STOPPED when the player pointer first comes back.
|
||||
g_wasMoving = false;
|
||||
return;
|
||||
}
|
||||
const uint32_t flags = *reinterpret_cast<const uint32_t *>(
|
||||
player + Offsets::OFF_PLAYER_MOVEMENT_FLAGS);
|
||||
const bool moving = (flags & kMovingMask) != 0;
|
||||
if (moving == g_wasMoving)
|
||||
return;
|
||||
g_wasMoving = moving;
|
||||
const int slot = Event::Custom::Lookup(
|
||||
moving ? kStartedMovingEvent : kStoppedMovingEvent);
|
||||
if (slot >= 0)
|
||||
Event::Custom::Fire_None(slot);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
static const Event::Custom::AutoReserve _reserveStarted{kStartedMovingEvent};
|
||||
static const Event::Custom::AutoReserve _reserveStopped{kStoppedMovingEvent};
|
||||
static const Tick::WorldTick::AutoSubscribe _tickSub{&OnWorldTick};
|
||||
|
||||
} // namespace Player::Movement
|
||||
@@ -12,7 +12,6 @@
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "event/Custom.h"
|
||||
#include "quest/Cache.h"
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU Lesser 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 Lesser General Public License for more details.
|
||||
|
||||
#include "WorldTick.h"
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
|
||||
namespace Tick::WorldTick {
|
||||
|
||||
namespace {
|
||||
|
||||
AutoSubscribe *g_head = nullptr;
|
||||
|
||||
using WorldTick_t = void(__fastcall *)(int, int, int);
|
||||
WorldTick_t WorldTick_o = nullptr;
|
||||
|
||||
void __fastcall WorldTick_h(int a, int b, int c) {
|
||||
WorldTick_o(a, b, c);
|
||||
for (auto *node = g_head; node != nullptr; node = node->next)
|
||||
node->cb();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AutoSubscribe::AutoSubscribe(Callback cb) : cb(cb), next(g_head) {
|
||||
g_head = this;
|
||||
}
|
||||
|
||||
static const Game::HookAutoRegister _hook{
|
||||
Offsets::FUN_WORLD_TICK,
|
||||
reinterpret_cast<void *>(&WorldTick_h),
|
||||
reinterpret_cast<void **>(&WorldTick_o)};
|
||||
|
||||
} // namespace Tick::WorldTick
|
||||
@@ -0,0 +1,42 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU Lesser 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 Lesser General Public License for more details.
|
||||
|
||||
#pragma once
|
||||
|
||||
// Shared per-frame tick subscription. The engine's world-subsystem
|
||||
// update at `FUN_WORLD_TICK` (`0x0066FD50`) is the canonical
|
||||
// once-per-frame hook target — single caller, quiet code region, no
|
||||
// known collisions with other Octo DLLs. MinHook only permits ONE
|
||||
// active hook per target address, so any feature that wants a
|
||||
// per-frame tick must go through this module instead of registering
|
||||
// its own `HookAutoRegister`.
|
||||
//
|
||||
// Subscribers receive a callback at the **tail** of each frame:
|
||||
// the engine's original world-tick runs first, then this module
|
||||
// walks the subscriber list. Order across subscribers is unspecified
|
||||
// (LIFO of static-init order across TUs), so subscribers must not
|
||||
// depend on each other.
|
||||
//
|
||||
// Module pattern, matching `HookAutoRegister` / `ModuleAutoRegister`:
|
||||
// declare a file-scope `static const Tick::WorldTick::AutoSubscribe`
|
||||
// in your module's TU. The constructor chains the callback onto the
|
||||
// internal list at static-init time, before `DllMain` runs.
|
||||
|
||||
namespace Tick::WorldTick {
|
||||
|
||||
using Callback = void (*)();
|
||||
|
||||
struct AutoSubscribe {
|
||||
explicit AutoSubscribe(Callback cb);
|
||||
Callback cb;
|
||||
AutoSubscribe *next;
|
||||
};
|
||||
|
||||
} // namespace Tick::WorldTick
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "Offsets.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
@@ -14,9 +14,6 @@
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
namespace Unit::Tooltip {
|
||||
|
||||
// `GameTooltip:SetUnitAura(unit, index, [filter])` — modern unified-aura
|
||||
|
||||
Reference in New Issue
Block a user