diff --git a/code/client/CMakeLists.txt b/code/client/CMakeLists.txt index 2562631..fde4a1b 100644 --- a/code/client/CMakeLists.txt +++ b/code/client/CMakeLists.txt @@ -9,6 +9,7 @@ set(HOGWARTSMP_CLIENT_FILES src/core/hooks/localplayer.cpp src/core/hooks/pak_mount.cpp src/core/hooks/player_controller.cpp + src/core/hooks/population.cpp src/core/hooks/render_device.cpp src/core/hooks/world.cpp src/core/modules/human.cpp diff --git a/code/client/src/core/game_layout.h b/code/client/src/core/game_layout.h index f514480..48e2187 100644 --- a/code/client/src/core/game_layout.h +++ b/code/client/src/core/game_layout.h @@ -68,6 +68,9 @@ namespace HogwartsMP::Game { Aob ulocalplayerCtor; // ULocalPlayer::ULocalPlayer (call site) Aob apcCtor; // APlayerController::APlayerController (call site) + // --- Ambient population control --- + Aob populationManagerTick; // FUN_14316dd90 — no-op to disable all ambient NPC fleshing + Offsets offsets; }; @@ -116,6 +119,10 @@ namespace HogwartsMP::Game { {"LocalPlayer/ULocalPlayer::ULocalPlayer", "E9 ? ? ? ? C3 66 66 66 2E 0F 1F 84 00 00 00 00 00 48 8D 64 24 D8 41 54 F7 1C 24", true}, {"LocalPlayer/APlayerController::APlayerController", "E9 ? ? ? ? C3 85 C0 3C 88", true}, + // Ambient population — PopulationManagerTick (FUN_14316dd90). No-op disables ambient NPCs. + // `80 B9 ? ? 00 00 00` = cmp byte[rcx+0x964],0 (the pause gate). RVA 0x316DD90 is the fallback. + {"Population/PopulationManagerTick", "48 89 5C 24 10 57 48 83 EC 40 80 B9 64 09 00 00 00 48 8B FA 48 8B D9", false}, + // Offsets {/*PersistentLevel*/ 0x30, /*OwningGameInstance*/ 0x330, /*LocalPlayers*/ 0x38}, }; @@ -155,6 +162,9 @@ namespace HogwartsMP::Game { {"LocalPlayer/ULocalPlayer::ULocalPlayer", "E9 ? ? ? ? C3 66 66 66 2E 0F 1F 84 00 00 00 00 00 48 8D 64 24 D8 41 54 F7 1C 24", true}, {"LocalPlayer/APlayerController::APlayerController", "E9 ? ? ? ? C3 85 C0 3C 88", true}, + // Ambient population — not independently verified for this older build; marked optional. + {"Population/PopulationManagerTick", "48 89 5C 24 10 57 48 83 EC 40 80 B9 64 09 00 00 00 48 8B FA 48 8B D9", true}, + {/*PersistentLevel*/ 0x30, /*OwningGameInstance*/ 0x320, /*LocalPlayers*/ 0x38}, }; diff --git a/code/client/src/core/hooks/population.cpp b/code/client/src/core/hooks/population.cpp new file mode 100644 index 0000000..2f388b4 --- /dev/null +++ b/code/client/src/core/hooks/population.cpp @@ -0,0 +1,66 @@ +#include + +#include +#include +#include + +#include + +#include +#include + +#include "../aob_scan.h" +#include "population.h" + +// The whole tiered ambient crowd (BP_Tier3_Character_C / Tier4_Actor / students) is driven by +// UPopulationManager's per-frame tick, FUN_14316dd90, which ticks its 4 async population jobs. +// void __fastcall(UPopulationManager* this, FTickCtx*). Address via the game_layout AOB, RVA fallback. + +namespace HogwartsMP::Core::Hooks { + namespace { + constexpr uintptr_t kPopTick_RVA = 0x316DD90; // FUN_14316dd90 fallback (Steam build 20773316) + + using PopTick_t = void(__fastcall *)(void *, void *); + PopTick_t s_orig = nullptr; + + // Default OFF (no-op the tick from boot) => areas load EMPTY. + // Server owners can flip to ON for vanilla via SetAmbientPopulation. + std::atomic s_enabled{false}; // true = vanilla population; false = no ambient NPCs + + auto Log() { + return Framework::Logging::GetLogger("NpcLab"); + } + + void __fastcall PopTick_Hook(void *mgr, void *tickCtx) { + if (!s_enabled.load(std::memory_order_relaxed)) { + return; // skip the population tick entirely -> no flesh + } + s_orig(mgr, tickCtx); + } + } // namespace + + void SetAmbientPopulation(bool enabled) { + s_enabled.store(enabled, std::memory_order_relaxed); + Log()->info("AmbientPopulation: {}", enabled ? "ON (vanilla NPCs)" : "OFF (no ambient NPCs)"); + } + + void ToggleAmbientPopulation() { + SetAmbientPopulation(!s_enabled.load(std::memory_order_relaxed)); + } + + bool IsAmbientPopulationEnabled() { + return s_enabled.load(std::memory_order_relaxed); + } +} // namespace HogwartsMP::Core::Hooks + +static InitFunction init([]() { + using namespace HogwartsMP::Core::Hooks; + auto *target = HogwartsMP::Core::AobFirst(HogwartsMP::Game::gLayout.populationManagerTick); + if (!target) { + // AOB miss/ambiguous — fall back to the pinned RVA for the known build. + target = reinterpret_cast(reinterpret_cast(GetModuleHandle(nullptr)) + kPopTick_RVA); + } + const MH_STATUS st = MH_CreateHook(target, reinterpret_cast(&PopTick_Hook), reinterpret_cast(&s_orig)); + Framework::Logging::GetLogger("NpcLab")->info("AmbientPopulation: hooked PopulationManagerTick @ 0x{:x} status {} (default {})", + reinterpret_cast(target), static_cast(st), IsAmbientPopulationEnabled() ? "ON" : "OFF"); +}, "AmbientPopulation"); diff --git a/code/client/src/core/hooks/population.h b/code/client/src/core/hooks/population.h new file mode 100644 index 0000000..020d608 --- /dev/null +++ b/code/client/src/core/hooks/population.h @@ -0,0 +1,13 @@ +#pragma once + +// Ambient population control. Toggles the game's whole ambient-NPC ("Hobo"/Tier crowd) system on/off by +// no-oping UPopulationManager's per-frame tick. With it OFF, the manager never requests flesh, so +// NPCs are never spawned. + +namespace HogwartsMP::Core::Hooks { + // enabled=true => vanilla ambient population; false => no ambient NPCs. Default ON. + // Exposed so a console command / scripting builtin can let server owners flip it. + void SetAmbientPopulation(bool enabled); + void ToggleAmbientPopulation(); + bool IsAmbientPopulationEnabled(); +} // namespace HogwartsMP::Core::Hooks diff --git a/code/client/src/core/states/session_connected.cpp b/code/client/src/core/states/session_connected.cpp index 32ee696..78a3899 100644 --- a/code/client/src/core/states/session_connected.cpp +++ b/code/client/src/core/states/session_connected.cpp @@ -4,6 +4,7 @@ #include #include "core/application.h" +#include "core/hooks/population.h" namespace HogwartsMP::Core::States { SessionConnectedState::SessionConnectedState() {} diff --git a/code/client/src/core/states/session_offline_debug.cpp b/code/client/src/core/states/session_offline_debug.cpp index dd12baf..b549a10 100644 --- a/code/client/src/core/states/session_offline_debug.cpp +++ b/code/client/src/core/states/session_offline_debug.cpp @@ -5,6 +5,7 @@ #include #include "../application.h" +#include "../hooks/population.h" namespace HogwartsMP::Core::States { SessionOfflineDebugState::SessionOfflineDebugState() {}