diff --git a/include/RE/A/Actor.h b/include/RE/A/Actor.h index d273a0d2..1af3af1a 100644 --- a/include/RE/A/Actor.h +++ b/include/RE/A/Actor.h @@ -1,5 +1,6 @@ #pragma once +#include "ActorMover.h" #include "RE/A/ACTOR_VISIBILITY_MASK.h" #include "RE/A/AIProcess.h" #include "RE/A/AITimeStamp.h" @@ -420,6 +421,17 @@ namespace RE return func(this); } + /// Check if this actor is standing on a walkable navmesh triangle. + /// Uses the pathfinding system to find the navmesh triangle at the actor's + /// current position and checks the walkable flag (bit 0) on it. + /// Returns false if the actor is not on any valid navmesh surface. + [[nodiscard]] bool IsOnNavmesh() + { + using func_t = std::uint8_t(Actor*); + static REL::Relocation func{ ID::Actor::IsOnNavmesh }; + return func(this) != 0; + } + bool IsPathValid() { using func_t = decltype(&Actor::IsPathValid); @@ -524,6 +536,102 @@ namespace RE return func(this, a_angle); } + // Set pathfinding goal: mover (this) walks to target's position + void SetPathfindingGoalPos(Actor* a_target, float a_speed = FLT_MIN, uint64_t a_flags = 0) + { + if (!REX::FModule::IsRuntimeNG()) return; + using func_t = void(Actor*, Actor*, float, uint64_t); + static REL::Relocation func{ REL::ID(2230246) }; + return func(this, a_target, a_speed, a_flags); + } + + // Set pathfinding goal to a target reference + void SetPathfindingGoal(TESObjectREFR* a_target, float a_speed, void* a_avoidNodes = nullptr) + { + if (!REX::FModule::IsRuntimeNG()) return; + using func_t = void(Actor*, TESObjectREFR*, float, void*); + static REL::Relocation func{ REL::ID(2230247) }; + return func(this, a_target, a_speed, a_avoidNodes); + } + + // Force AI system to re-evaluate the current package (guard check → dispatches to AIProcess) + void EvaluatePackage() + { + if (!REX::FModule::IsRuntimeNG()) return; + using func_t = void(Actor*); + static REL::Relocation func{ REL::ID(2229802) }; + func(this); + } + + // Set pathfinding goal to a specific world position (NiPoint3), NOT a target actor or ref. + // This is the core function for "make actor walk to this coordinate". + // Internally routes through ActorMover → BSPathingRequest → MovementController event sink. + // @param a_pos Target position in world coordinates + // @param a_speed Movement speed (pass FLT_MIN for auto-calc) + // @return true if path request was submitted + bool SetPathfindingGoalPos(const NiPoint3& a_pos, float a_speed = FLT_MIN) + { + if (!REX::FModule::IsRuntimeNG()) return false; + using func_t = bool(Actor*, const NiPoint3&, float); + static REL::Relocation func{ REL::ID(2230256) }; + return func(this, a_pos, a_speed); + } + + // Clear current pathfinding request. Cancels any active path. + void ClearPath(BSTSmartPointer& a_request) + { + if (!REX::FModule::IsRuntimeNG()) return; + using func_t = void(Actor*, BSTSmartPointer&); + static REL::Relocation func{ REL::ID(2230253) }; + return func(this, a_request); + } + + // Set pathfinding goal to a target reference (with full BSPathingAvoidNodeArray support). + // Unlike the simpler SetPathfindingGoal overload, this routes through the + // ActorMover's SetPathfindingGoal method directly. + void SetPathfindingGoal(TESObjectREFR* a_target, float a_speed, BSPathingAvoidNodeArray* a_avoidNodes) + { + if (!REX::FModule::IsRuntimeNG()) return; + using func_t = void(Actor*, TESObjectREFR*, float, BSPathingAvoidNodeArray*); + static REL::Relocation func{ REL::ID(2230248) }; + return func(this, a_target, a_speed, a_avoidNodes); + } + + // Release the MovementControllerNPC at actor+0x318. + void ReleaseMovementController() + { + if (!REX::FModule::IsRuntimeNG()) return; + using func_t = void(Actor*); + static REL::Relocation func{ REL::ID(2230236) }; + return func(this); + } + + // Ensure the MovementControllerNPC at actor+0x318 exists. + // GetMovementControllerNPC (ID 2230235) auto-creates it if null. + void GetOrCreateMovementController() + { + using func_t = void(Actor*); + static REL::Relocation func{ ID::Actor::GetMovementControllerNPC }; + return func(this); + } + + // Resolve ActorMover from actor, handling mount indirection via actor+0x308. + RE::ActorMover* GetOrCreateActorMover() + { + using func_t = RE::ActorMover*(Actor*); + static REL::Relocation func{ ID::Actor::GetActorMoverResolver }; + return func(this); + } + + // Atomic teleport: updates pos, NIF world transform tree, Havok rigid body, anim graph, and cell attachment + bool MoveTo(float a_x, float a_y, float a_z, float a_yawRad) + { + if (!REX::FModule::IsRuntimeNG()) return false; + using func_t = bool(Actor*, float, float, float, float); + static REL::Relocation func{ REL::ID(2229713) }; + return func(this, a_x, a_y, a_z, a_yawRad); + } + void TrespassAlarm(TESObjectREFR* a_refr, TESForm* a_owner, std::int32_t a_crime) { using func_t = decltype(&Actor::TrespassAlarm); diff --git a/include/RE/A/ActorMover.h b/include/RE/A/ActorMover.h new file mode 100644 index 00000000..1347a508 --- /dev/null +++ b/include/RE/A/ActorMover.h @@ -0,0 +1,295 @@ +#pragma once + +#include "RE/B/BSPointerHandle.h" +#include "RE/M/MemoryManager.h" +#include "RE/N/NiPoint3.h" +#include "RE/T/TESObjectREFR.h" +#include "REL/Relocation.h" +#include "REX/FModule.h" + +namespace RE +{ + class Actor; + + struct BSPathingAvoidNodeArray; + struct MovementMessage; + + /// Manages an actor's pathfinding requests and movement system integration. + /// + /// ActorMover is the bridge between high-level AI pathfinding requests and the + /// low-level PathfindingCore / MovementController system. Every Actor that is + /// pathfinding has an ActorMover at Actor+0x308, created via Actor::CreateActorMover(). + /// + /// Size: 0x10 + /// +0x00: vfptr (vtable at VTABLE::ActorMover, REL::ID(334518)) + /// +0x08: Actor* actor (owning actor back-reference) + /// + class ActorMover + { + public: + inline static constexpr auto RTTI{ RTTI::ActorMover }; + inline static constexpr auto VTABLE{ VTABLE::ActorMover }; + + struct PathingRequest; + + // 00: virtual destructor + virtual ~ActorMover(); // 00 + + // get owning actor + Actor* GetActor() const { return actor; } + + // ==== Pathing query ==== + + /// Check if this actor has an active pathing request. + /// Returns true when a PathingRequest exists in the AIProcess event sink. + /// @param a_request [out] Receives a reference-counted pointer to the active request. + /// @return true if currently pathing + bool IsActorPathing(BSTSmartPointer& a_request) + { + using func_t = bool(ActorMover*, BSTSmartPointer&); + static REL::Relocation func{ REL::ID(2234289) }; + return func(this, a_request); + } + + /// Check if currently pathing (handles special fly-state cases). + /// Also creates a new PathingRequest if the actor's fly state is valid. + /// This is the main IsPathing check that the per-frame update uses. + bool IsPathing(BSTSmartPointer& a_request) + { + using func_t = bool(ActorMover*, BSTSmartPointer&); + static REL::Relocation func{ REL::ID(2234290) }; + return func(this, a_request); + } + + // ==== Pathfinding goal setters ==== + + /// Set pathfinding goal to a target reference with speed override. + /// NOTE: This ID is listed as `SetPathfindingGoal` in IDs.h but both + /// overloads (with/without overrideSpeed) are handled by this function. + void SetPathfindingGoal(TESObjectREFR* a_target, float a_speed, BSPathingAvoidNodeArray* a_avoidNodes) + { + using func_t = void(ActorMover*, TESObjectREFR*, float, BSPathingAvoidNodeArray*); + static REL::Relocation func{ ID::ActorMover::SetPathfindingGoal }; + return func(this, a_target, a_speed, a_avoidNodes); + } + + /// Set pathfinding goal to a target reference with explicit speed override. + /// USE THIS FOR MOVEMENT + void SetPathfindingGoal(TESObjectREFR* a_target, float a_speed, float a_overrideSpeed, BSPathingAvoidNodeArray* a_avoidNodes) + { + using func_t = void(ActorMover*, TESObjectREFR*, float, float, BSPathingAvoidNodeArray*); + static REL::Relocation func{ REL::ID(2234293) }; + return func(this, a_target, a_speed, a_overrideSpeed, a_avoidNodes); + } + + /// Set pathfinding goal to a position. + /// a_speed: FLT_MIN sentinel = auto-calculate from actor's movement controller + /// NOTE: IDs.h mislabels this ID as ClearPath - RVA 0xDC0F30 is actually SetPathfindingGoalPos. + void SetPathfindingGoalPos(NiPoint3* a_pos, float a_speed, float a_tolerance, float a_speedMult) + { + using func_t = void(ActorMover*, NiPoint3*, float, float, float); + static REL::Relocation func{ REL::ID(2234298) }; + return func(this, a_pos, a_speed, a_tolerance, a_speedMult); + } + + /// Set pathfinding goal to a position (alternate variant). + void SetPathfindingGoalPos(NiPoint3* a_pos, std::uint32_t a_flags, float a_speed1, float a_speed2, + std::uint32_t a_tolerance) + { + using func_t = void(ActorMover*, NiPoint3*, std::uint32_t, float, float, std::uint32_t); + static REL::Relocation func{ REL::ID(2234299) }; + return func(this, a_pos, a_flags, a_speed1, a_speed2, a_tolerance); + } + + /// Set pathfinding goal to another Actor's position via PathfindingCore. + /// This reads target->data.location and target->parentCell, then calls + /// PathfindingCore::RequestPath to create a BSPathingRequest (enables MOVEMENT). + void SetPathfindingGoalPos_Inner(Actor* a_target, float a_speed, uint64_t a_flags) + { + using func_t = void(ActorMover*, Actor*, float, uint64_t); + static REL::Relocation func{ REL::ID(2234291) }; + return func(this, a_target, a_speed, a_flags); + } + + /// Set pathfinding goal targeting an actor handle or form ID. + void SetPathfindingGoal(Actor* a_target, float a_speed, float a_tolerance) + { + using func_t = void(ActorMover*, Actor*, float, float); + static REL::Relocation func{ REL::ID(2234300) }; + return func(this, a_target, a_speed, a_tolerance); + } + + // ==== Extended pathing ==== + + /// Set pathfinding goal via explicit parameter struct. + /// ID 2234294 - Listed as SetPathfindingGoalEx in IDs.h + /// @param a_params Pointer to extended pathfinding params (type unknown) + /// @param a_out Receives result/output + /// @return true on success + // TODO: FIXME + bool SetPathfindingGoalEx(TESObjectREFR* a_targetHandle, void* a_params) + { + using func_t = bool(ActorMover*, TESObjectREFR*, void*); + static REL::Relocation func{ ID::ActorMover::SetPathfindingGoalEx }; + return func(this, a_targetHandle, a_params); + } + + // ==== Path lifecycle ==== + + /// Clear the current pathing request through the event sink. + /// Returns true if a request was successfully cleared. + bool ClearPath() + { + using func_t = bool(ActorMover*); + static REL::Relocation func{ REL::ID(2234295) }; + return func(this); + } + + /// Per-frame pathing request processing and submission. + /// Called each frame from Actor::Update to advance the pathing state machine. + void UpdatePathing(float a_speedMult) + { + using func_t = void(ActorMover*, float); + static REL::Relocation func{ REL::ID(2234296) }; + return func(this, a_speedMult); + } + + /// Immediately cancel any current path and request a new path computation. + /// Called by many subsystems (combat, package, movement, etc.) when the + /// actor's desired destination changes. + void RequestPath() + { + using func_t = void(ActorMover*); + static REL::Relocation func{ REL::ID(2234297) }; + return func(this); + } + + /// Stop all actor movement by sending a MovementMessage type Stop (0). + void StopMoving() + { + using func_t = void(ActorMover*); + static REL::Relocation func{ REL::ID(2234302) }; + return func(this); + } + + /// Check if the current path is still valid. + /// Returns false if path has been invalidated (e.g., navmesh cut). + bool IsPathValid() + { + using func_t = bool(ActorMover*); + static REL::Relocation func{ REL::ID(2234301) }; + return func(this); + } + + // ==== Extended pathing: MovementController event sink interface ==== + // These methods access the AIProcess event sink directly rather than + // routing through PathingRequest smart pointers. + + /// Set pathfinding goal with heading/speed. Computes heading angle from + /// actor's current heading and schedules a BSPathingRequest. + /// @param a_heading Target heading angle + /// @param a_speed Movement speed + std::uint64_t SetPathfindingGoal(float a_heading, float a_speed) + { + using func_t = std::uint64_t(ActorMover*, float, float); + static REL::Relocation func{ REL::ID(2234303) }; + return func(this, a_heading, a_speed); + } + + /// Set pathfinding goal to a NiPoint3 position via MovementController. + /// This is the lower-level version that bypasses PathingRequest and + /// directly schedules a BSPathingRequest through the event sink. + /// @param a_pos Target position + /// @param a_speed Movement speed + bool SetPathfindingGoalPos(const NiPoint3& a_pos, float a_speed) + { + using func_t = bool(ActorMover*, const NiPoint3&, float); + static REL::Relocation func{ REL::ID(2234304) }; + return func(this, a_pos, a_speed); + } + + /// Check whether the current destination matches, and only send a new + /// pathfinding request if it changed. Route via DAT_143dc3ac0 event sink. + bool SetPathfindingGoalCheckMoveOnly(std::uint32_t a_flags, void* a_dest, float a_speed) + { + using func_t = bool(ActorMover*, std::uint32_t, void*, float); + static REL::Relocation func{ REL::ID(2234305) }; + return func(this, a_flags, a_dest, a_speed); + } + + /// Same as SetPathfindingGoalCheckMoveOnly but takes a NiPoint3 destination. + bool SetPathfindingGoalPosCheckMoveOnly(const NiPoint3& a_pos, void* a_dest, float a_speed) + { + using func_t = bool(ActorMover*, const NiPoint3&, void*, float); + static REL::Relocation func{ REL::ID(2234306) }; + return func(this, a_pos, a_dest, a_speed); + } + + /// Check if the pathing request is complete via the MovementController + /// event sink (DAT_143dc3ac0 vtable+0x48). + std::uint8_t IsPathingRequestComplete() + { + using func_t = std::uint8_t(ActorMover*); + static REL::Relocation func{ REL::ID(2234307) }; + return func(this); + } + + /// Check path status via MovementController event sink (vtable+0x50). + /// Returns 1 if path is active, 0 otherwise. + std::uint8_t CheckPathStatus() + { + using func_t = std::uint8_t(ActorMover*); + static REL::Relocation func{ REL::ID(2234308) }; + return func(this); + } + + /// Compare current path destination with expected destination. + /// @param a_expected Expected destination to compare against + /// @param a_outStatus [out] Receives additional status info + /// @return true if destinations match + bool ComparePathDestination(void* a_expected, std::uint8_t* a_outStatus) + { + using func_t = bool(ActorMover*, void*, std::uint8_t*); + static REL::Relocation func{ REL::ID(2234309) }; + return func(this, a_expected, a_outStatus); + } + + /// Compare and resend path if destination doesn't match. + bool CompareAndResendPath(void* a_expected, std::uint8_t* a_outStatus) + { + using func_t = bool(ActorMover*, void*, std::uint8_t*); + static REL::Relocation func{ REL::ID(2234310) }; + return func(this, a_expected, a_outStatus); + } + + /// Get current pathing state from MovementController event sink (vtable+0x68). + std::uint8_t GetPathingState() + { + using func_t = std::uint8_t(ActorMover*); + static REL::Relocation func{ REL::ID(2234311) }; + return func(this); + } + + /// Check if actor is pathing via the MovementController (not PathingRequest). + /// This is the low-level IsPathing that Actor::IsPathing uses internally. + /// Returns true only if pathing is active through the event sink. + bool IsPathingViaMovementController() + { + using func_t = bool(ActorMover*); + static REL::Relocation func{ REL::ID(2234312) }; + return func(this); + } + + /// Extended IsPathing check that also validates current destination type. + bool IsPathingViaMovementControllerChecked() + { + using func_t = bool(ActorMover*); + static REL::Relocation func{ REL::ID(2234313) }; + return func(this); + } + + // members + Actor* actor; // 08 - owning actor back-reference + }; + static_assert(sizeof(ActorMover) == 0x10); +} diff --git a/include/RE/B/BSPathingRequest.h b/include/RE/B/BSPathingRequest.h new file mode 100644 index 00000000..650e6ca0 --- /dev/null +++ b/include/RE/B/BSPathingRequest.h @@ -0,0 +1,61 @@ +#pragma once + +// Unstable, doesn't work 9/10 times, trying to use it WILL break your will to live. + +#include "RE/M/MemoryManager.h" +#include "RE/N/NiPoint3.h" + +namespace RE +{ + class __declspec(novtable) BSPathingRequest + { + public: + static constexpr auto VTABLE{ VTABLE::BSPathingRequest }; + + /// Flags byte at +0xDC (set by FUN_140c5ab50 from fly state / walk check). + enum class Flag : std::uint8_t + { + kNone = 0, + kOverrideSpeed = 1 << 2 // Bit 2: override speed value present at +0x54 + }; + + BSPathingRequest() { REX::EMPLACE_VTABLE(this); } + + F4_HEAP_REDEFINE_NEW(BSPathingRequest); + + static BSPathingRequest* Create(const NiPoint3& a_destination) + { + auto* request = new BSPathingRequest(); + request->destination = a_destination; + request->refCount = 1; + return request; + } + + /// Check if the override speed flag is set (bit 2 at +0x83 or +0xDC). + bool HasOverrideSpeed() const { return (flags & static_cast(Flag::kOverrideSpeed)) != 0; } + + /// Get the override speed value at +0x54 (only valid when HasOverrideSpeed() is true). + float GetOverrideSpeed() const { return overrideSpeed; } + + // members + void* vtable; // 00 + char pad08[0x12]; // 08-19 + char typeChar; // 1A + char pad1B[0x05]; // 1B-1F + void* allocator; // 20 + std::int32_t refCount; // 28 + char pad2C[0x1C]; // 2C-47 + std::uint64_t targetData; // 48 - Target data (formID likely) + char pad50[0x04]; // 50-53 + float overrideSpeed; // 54 - Override speed (conditional, only valid when flags bit 2 set) + char pad58[0x2B]; // 58-82 + std::uint8_t flags83; // 83 - Flag byte, bit 2 = override speed present + char pad84[0x34]; // 84-B7 + void* pathStore; // B8 + char padC0[0x10]; // C0-CF + NiPoint3 destination; // D0 + std::uint8_t flags; // DC - Flag byte (from FUN_140c5ab50: fly state / walk check) + char padDD[0x63]; // DD-13F (total 0x140) + }; + static_assert(sizeof(BSPathingRequest) == 0x140); +} diff --git a/include/RE/B/BSPathingRequestRotate.h b/include/RE/B/BSPathingRequestRotate.h new file mode 100644 index 00000000..c8b05a12 --- /dev/null +++ b/include/RE/B/BSPathingRequestRotate.h @@ -0,0 +1,51 @@ +#pragma once + +// Unstable, doesn't work 9/10 times, trying to use it WILL break your will to live. + +#include "RE/B/BSPathingRequest.h" +#include "RE/M/MemoryManager.h" +#include "REL/Relocation.h" + +namespace RE +{ + /// A specialized BSPathingRequest that adds a rotate flag. + /// + /// Created by constructor FUN_1407b6420 (RVA 0x7B6420, ID 2172304). + /// Used by the PathBuilderRotatePath path builder for rotation-based + /// movement commands (e.g., from FUN_140dc16f0). + /// + /// Differences from base BSPathingRequest: + /// - Different vtable (RVA 0x02514158) + /// - Sets flag bit 0x200 at +0x1B (as uint32) + /// - Processed by PathBuilderRotatePath instead of default path builder + /// + /// Size: 0x140 (same as BSPathingRequest, no additional members) + class __declspec(novtable) BSPathingRequestRotate : + public BSPathingRequest // 00 + { + public: + static constexpr auto RTTI{ RTTI::PathingRequestRotate }; + static constexpr auto VTABLE{ VTABLE::PathingRequestRotate }; + + BSPathingRequestRotate() + { + REX::EMPLACE_VTABLE(this); // override vtable to PathingRequestRotate + auto flags = reinterpret_cast(reinterpret_cast(this) + 0x1B); + *flags |= 0x200; // set rotate flag + } + + ~BSPathingRequestRotate() override = default; // 00 + + F4_HEAP_REDEFINE_NEW(BSPathingRequestRotate); + + /// Check if the rotate flag is set. + [[nodiscard]] bool HasRotateFlag() const + { + auto flags = reinterpret_cast(reinterpret_cast(this) + 0x1B); + return (*flags & 0x200) != 0; + } + + // No additional member fields - inherits full BSPathingRequest (0x140 bytes) layout. + }; + static_assert(sizeof(BSPathingRequestRotate) == 0x140); +} diff --git a/include/RE/Fallout.h b/include/RE/Fallout.h index 358e6fc8..4d7c926a 100644 --- a/include/RE/Fallout.h +++ b/include/RE/Fallout.h @@ -331,6 +331,7 @@ #include "RE/B/BSPathingAvoidNode.h" #include "RE/B/BSPathingFaceTarget.h" #include "RE/B/BSPathingGoal.h" + #include "RE/B/BSPathingLocation.h" #include "RE/B/BSPathingStart.h" #include "RE/B/BSPointerAllocator.h" @@ -871,6 +872,14 @@ #include "RE/I/IMovementInterface.h" #include "RE/I/IMovementPlayerControls.h" #include "RE/I/IMovementPlayerControlsFilter.h" +#include "RE/I/IMovementDirectControl.h" +#include "RE/I/IMovementParameters.h" +#include "RE/I/IMovementPathEvaluator.h" +#include "RE/I/IMovementPathManager.h" +#include "RE/I/IMovementPlannerDirectControl.h" +#include "RE/I/IMovementQueryState.h" +#include "RE/I/IMovementSetGoal.h" +#include "RE/I/IMovementSetSprinting.h" #include "RE/I/IMovementState.h" #include "RE/I/INPUT_DEVICE.h" #include "RE/I/INPUT_DEVICE_LIGHT_STATE.h" @@ -976,6 +985,7 @@ #include "RE/M/MoveData.h" #include "RE/M/Movement.h" #include "RE/M/MovementCorrection.h" +#include "RE/M/MovementMessage.h" #include "RE/M/MovementCorrectionData.h" #include "RE/M/MovementData.h" #include "RE/M/MovementLargeDelta.h" diff --git a/include/RE/I/IAnimationGraphManagerHolder.h b/include/RE/I/IAnimationGraphManagerHolder.h index e8eaa801..23782e31 100644 --- a/include/RE/I/IAnimationGraphManagerHolder.h +++ b/include/RE/I/IAnimationGraphManagerHolder.h @@ -53,6 +53,14 @@ namespace RE return func(this, a_variable, a_var); }; + bool NotifyAnimationGraph(const BSFixedString& a_eventName) + { + if (!REX::FModule::IsRuntimeNG()) return false; + using func_t = bool(IAnimationGraphManagerHolder*, const BSFixedString&); + static REL::Relocation func{ REL::ID(2214564) }; + return func(this, a_eventName); + } + bool SetGraphVariableFloat(const BSFixedString& a_variable, float a_var) { using func_t = decltype(&IAnimationGraphManagerHolder::SetGraphVariableFloat); diff --git a/include/RE/I/IMovementDirectControl.h b/include/RE/I/IMovementDirectControl.h new file mode 100644 index 00000000..738b2765 --- /dev/null +++ b/include/RE/I/IMovementDirectControl.h @@ -0,0 +1,69 @@ +#pragma once + +// Unstable, doesn't work 9/10 times, trying to use it WILL break your will to live. + +#include "RE/I/IMovementInterface.h" +#include "RE/N/NiPoint3.h" + +namespace RE +{ + /// 28-byte goal structure passed to IMovementDirectControl::SetPositionGoal. + /// Used by ActorMover::SetPathfindingGoalPos for quick "move to position" commands. + struct MovementGoalDirect + { + std::uint32_t type; // 00 - 2 = move_to_pos, 0 = stop, 1 = heading + float posX; // 04 + float posY; // 08 + float posZ; // 0C + std::uint32_t zero; // 10 - Always 0 + float speed; // 14 + float speedDup; // 18 - Duplicate of speed + }; + static_assert(sizeof(MovementGoalDirect) == 0x1C); + + /// 28-byte goal structure passed to IMovementDirectControl::SetHeadingGoal. + /// Used by FUN_140dc1330 for heading-based movement. + struct MovementHeadingGoal + { + std::uint32_t type; // 00 - 0 = stop, 1 = rotate to heading + float headingX; // 04 - FLT_MAX sentinel (or heading vector) + float headingY; // 08 - FLT_MAX + float headingZ; // 0C - FLT_MAX + std::uint32_t zero; // 10 - Always 0 + float speed; // 14 - FLT_MAX + float speedDup; // 18 - FLT_MAX + }; + static_assert(sizeof(MovementHeadingGoal) == 0x1C); + + /// Interface for direct control of actor movement. + /// Embedded at MovementControllerNPC+0x138. + /// + /// Resolved via QueryInterface with GUID DAT_143dc3af8. + /// Shares the same vtable as IMovementSetGoal but dispatches + /// different behavior based on the `this` pointer offset. + /// + /// Vtable layout (shared with IMovementSetGoal): + /// +0x00: Destructor + /// +0x08: SetPositionGoal(const MovementGoalDirect&) - takes position/heading struct + /// +0x10: SetHeadingGoal(const MovementHeadingGoal&) - takes heading-based struct + class __declspec(novtable) IMovementDirectControl : + public IMovementInterface // 00 + { + public: + static constexpr auto RTTI{ RTTI::IMovementDirectControl }; + static constexpr auto VTABLE{ VTABLE::IMovementDirectControl }; + + // add + /// Set a position-based movement goal. + /// When called through MC+0x138, interprets the parameter as a direct + /// struct (MovementGoalDirect) rather than a BSFixedString*. + /// @param a_goal The goal structure containing type, position, and speed + virtual void SetPositionGoal(const MovementGoalDirect& a_goal) = 0; // 01 + + /// Set a heading-based movement goal. + /// When called through MC+0x138, handles heading/rotation commands. + /// @param a_goal The heading goal structure + virtual void SetHeadingGoal(const MovementHeadingGoal& a_goal) = 0; // 02 + }; + static_assert(sizeof(IMovementDirectControl) == 0x8); +} diff --git a/include/RE/I/IMovementParameters.h b/include/RE/I/IMovementParameters.h new file mode 100644 index 00000000..fb1b1c0c --- /dev/null +++ b/include/RE/I/IMovementParameters.h @@ -0,0 +1,48 @@ +#pragma once + +// Unstable, doesn't work 9/10 times, trying to use it WILL break your will to live. + +#include "RE/I/IMovementInterface.h" + +namespace RE +{ + /// Movement parameters used when submitting pathing requests. + /// + /// Size: 0x30 bytes + /// VTABLE: IMovementParameters (REL::ID(1136103)) + /// + /// Created in ActorMover::UpdatePathing and stored in the BSPathingRequest. + /// Contains speed multipliers and movement constants. + /// + /// Layout: + /// +0x00: vfptr (IMovementParameters vtable) + /// +0x08: refCount (int32, BSTSmartPointer ref count) + /// +0x0C: field_0C (int32, initialized to 0) + /// +0x10: speedMult (float) + /// +0x14: speedMultSq (float, speedMult * speedMult) + /// +0x18: speed (float) + /// +0x1C: constant2 (float, always 2.0f) + /// +0x20: constantField20 (float, from DAT_142f2a8f8) + /// +0x24: constantField24 (float, from DAT_142f2a8fc) + /// +0x28: constantField28 (float, from DAT_142f2a900) + class __declspec(novtable) IMovementParameters + { + public: + static constexpr auto RTTI{ RTTI::IMovementParameters }; + static constexpr auto VTABLE{ VTABLE::IMovementParameters }; + + virtual ~IMovementParameters() = default; // 00 + + // members + std::int32_t refCount; // 08 - BSTSmartPointer reference count + std::int32_t field_0C; // 0C - initialized to 0 + float speedMult; // 10 - speed multiplier + float speedMultSq; // 14 - speedMult * speedMult + float speed; // 18 - movement speed + float constant2; // 1C - always 2.0f + float constantField20; // 20 - from DAT_142f2a8f8 + float constantField24; // 24 - from DAT_142f2a8fc + float constantField28; // 28 - from DAT_142f2a900 + }; + static_assert(sizeof(IMovementParameters) == 0x30); +} diff --git a/include/RE/I/IMovementPathEvaluator.h b/include/RE/I/IMovementPathEvaluator.h new file mode 100644 index 00000000..6e5f7bae --- /dev/null +++ b/include/RE/I/IMovementPathEvaluator.h @@ -0,0 +1,29 @@ +#pragma once + +#include "RE/I/IMovementInterface.h" + +namespace RE +{ + /// Interface for path evaluation from the movement controller. + /// Embedded at MovementControllerNPC+0x130. + /// + /// Note: This may be the same as or related to IMovementPathManager + /// (both reside at MC+0x130). The path manager interface with + /// SubmitPath/ClearPath/RequestPath is described in IMovementPathManager.h. + /// + /// Full virtual function layout is not yet known. + // Note: IMovementPathEvaluator has no standalone RTTI or VTABLE entry. + // This may be because IMovementPathManager (at the same offset +0x130) + // IS the path evaluator. Both names are used in the RE doc for MC+0x130. + // Use IMovementPathManager for path submission/clear/request operations. + class __declspec(novtable) IMovementPathEvaluator : + public IMovementInterface // 00 + { + public: + // No standalone RTTI or VTABLE entry found + // Consider using IMovementPathManager for path-related operations + + // TODO: Add virtual functions as they are discovered + }; + static_assert(sizeof(IMovementPathEvaluator) == 0x8); +} diff --git a/include/RE/I/IMovementPathManager.h b/include/RE/I/IMovementPathManager.h new file mode 100644 index 00000000..6f7d89f4 --- /dev/null +++ b/include/RE/I/IMovementPathManager.h @@ -0,0 +1,46 @@ +#pragma once + +// Unstable, doesn't work 9/10 times, trying to use it WILL break your will to live. + +#include "RE/B/BSTSmartPointer.h" +#include "RE/I/IMovementInterface.h" + +namespace RE +{ + class BSPathingRequest; + + /// Interface for path submission, clearing, and re-requesting. + /// + /// Obtained from MovementControllerNPC via QueryInterface with GUID DAT_143dc3ab8. + /// Resides at MovementControllerNPC+0x130. + /// + /// Vtable layout: + /// +0x00: Destructor + /// +0x08: SubmitPath(BSTSmartPointer*) + /// +0x10: (unknown) + /// +0x18: ClearPath + /// +0x20: RequestPath + // Note: IMovementPathManager has no standalone RTTI or VTABLE entry. + // It is obtained from MovementControllerNPC via QueryInterface(DAT_143dc3ab8) + // and resides at MC+0x130. + class __declspec(novtable) IMovementPathManager : + public IMovementInterface // 00 + { + public: + // No standalone RTTI or VTABLE entry - resolved at runtime via QueryInterface. + + // add + /// Submit a pathing request to the path manager for processing. + /// @param a_request Pointer to a BSTSmartPointer containing the request + virtual void SubmitPath(BSTSmartPointer* a_request) = 0; // 01 + + // 02: Unknown + + /// Clear the current pathing request. + virtual void ClearPath() = 0; // 03 + + /// Cancel current path and re-request path computation. + virtual void RequestPath() = 0; // 04 + }; + static_assert(sizeof(IMovementPathManager) == 0x8); +} diff --git a/include/RE/I/IMovementPlannerAgent.h b/include/RE/I/IMovementPlannerAgent.h new file mode 100644 index 00000000..a0323b50 --- /dev/null +++ b/include/RE/I/IMovementPlannerAgent.h @@ -0,0 +1,39 @@ +#pragma once + +// Unstable, doesn't work 9/10 times, trying to use it WILL break your will to live. + +#include "RE/I/IMovementInterface.h" + +namespace RE +{ + /// Pure virtual interface for all movement planner agent types. + /// + /// Provides ~14 virtual function slots that are shared across all + /// MovementPlannerAgent subclasses. The exact function signatures + /// are not yet fully identified. + class __declspec(novtable) IMovementPlannerAgent : + public IMovementInterface // 00 + { + public: + static constexpr auto RTTI{ RTTI::IMovementPlannerAgent }; + static constexpr auto VTABLE{ VTABLE::IMovementPlannerAgent }; + + // virtual ~IMovementPlannerAgent() = default; // 00 - inherited from IMovementInterface + + // add + /// [03] - Common function shared by all planner agents + virtual void Func03() = 0; // 03 - RVA 0x141E85E70 (common to all planners) + + /// [04] - Set goal type for this agent + virtual void SetGoalType() = 0; // 04 + + // [05] through [13] - Additional update/process functions + + /// [14] - Data pointer function + virtual void Func14() = 0; // 14 + + /// [15] - Final virtual + virtual void Func15() = 0; // 15 + }; + static_assert(sizeof(IMovementPlannerAgent) == 0x8); +} diff --git a/include/RE/I/IMovementPlannerDirectControl.h b/include/RE/I/IMovementPlannerDirectControl.h new file mode 100644 index 00000000..00d46109 --- /dev/null +++ b/include/RE/I/IMovementPlannerDirectControl.h @@ -0,0 +1,21 @@ +#pragma once + +#include "RE/I/IMovementInterface.h" + +namespace RE +{ + /// Interface for planner-level direct control of actor movement. + /// Embedded at MovementControllerNPC+0x140. + /// + /// Full virtual function layout is not yet known. + class __declspec(novtable) IMovementPlannerDirectControl : + public IMovementInterface // 00 + { + public: + static constexpr auto RTTI{ RTTI::IMovementPlannerDirectControl }; + static constexpr auto VTABLE{ VTABLE::IMovementPlannerDirectControl }; + + // TODO: Add virtual functions as they are discovered + }; + static_assert(sizeof(IMovementPlannerDirectControl) == 0x8); +} diff --git a/include/RE/I/IMovementQueryPathingState.h b/include/RE/I/IMovementQueryPathingState.h new file mode 100644 index 00000000..0e1fbcd0 --- /dev/null +++ b/include/RE/I/IMovementQueryPathingState.h @@ -0,0 +1,38 @@ +#pragma once + +#include "RE/B/BSFixedString.h" +#include "RE/I/IMovementInterface.h" + +namespace RE +{ + /// Interface for querying pathing state from the movement controller. + /// Embedded at MovementControllerNPC+0x128. + /// + /// Shares the same vtable as IMovementSetGoal but dispatches different + /// behavior based on the `this` pointer offset. + /// + /// Vtable layout (shared with IMovementSetGoal): + /// +0x00: Destructor + /// +0x08: SetGoal(BSFixedString*, data) - same as IMovementSetGoal's SetGoal + /// +0x10: QueryPathingState - specific to this interface + class __declspec(novtable) IMovementQueryPathingState : + public IMovementInterface // 00 + { + public: + static constexpr auto RTTI{ RTTI::IMovementQueryPathingState }; + static constexpr auto VTABLE{ VTABLE::IMovementQueryPathingState }; + + // add + /// Store a goal entry in the pathing state system. + /// Behaves identically to IMovementSetGoal::SetGoal when called + /// through this interface pointer. + /// @param a_name Goal name string + /// @param a_data Goal data payload + /// @return 1 on success + virtual std::int32_t SetGoal(BSFixedString* a_name, std::uint64_t a_data) = 0; // 01 + + /// Query the current pathing state information. + virtual void QueryPathingState() = 0; // 02 + }; + static_assert(sizeof(IMovementQueryPathingState) == 0x8); +} diff --git a/include/RE/I/IMovementQueryState.h b/include/RE/I/IMovementQueryState.h new file mode 100644 index 00000000..3e98e24b --- /dev/null +++ b/include/RE/I/IMovementQueryState.h @@ -0,0 +1,23 @@ +#pragma once + +// Unstable, doesn't work 9/10 times, trying to use it WILL break your will to live. + +#include "RE/I/IMovementInterface.h" + +namespace RE +{ + /// Interface for querying pathing state from the movement controller. + /// Embedded at MovementControllerNPC+0x128. + /// + /// Full virtual function layout is not yet known. + class __declspec(novtable) IMovementQueryState : + public IMovementInterface // 00 + { + public: + static constexpr auto RTTI{ RTTI::IMovementQueryState }; + static constexpr auto VTABLE{ VTABLE::IMovementQueryState }; + + // TODO: Add virtual functions as they are discovered + }; + static_assert(sizeof(IMovementQueryState) == 0x8); +} diff --git a/include/RE/I/IMovementSetGoal.h b/include/RE/I/IMovementSetGoal.h new file mode 100644 index 00000000..b3ef4358 --- /dev/null +++ b/include/RE/I/IMovementSetGoal.h @@ -0,0 +1,35 @@ +#pragma once + +// Unstable, doesn't work 9/10 times, trying to use it WILL break your will to live. + +#include "RE/B/BSFixedString.h" +#include "RE/I/IMovementInterface.h" + +namespace RE +{ + class __declspec(novtable) IMovementSetGoal : + public IMovementInterface // 00 + { + public: + static constexpr auto RTTI{ RTTI::IMovementSetGoal }; + static constexpr auto VTABLE{ VTABLE::IMovementSetGoal }; + + // Add + /// Stores a goal entry in the BSFixedString array at this+0x48. + /// Looks up existing entry by name, updates data if found, appends if not. + /// Uses spinlock at this+0x108. + /// @param a_name Goal name string + /// @param a_data Goal data payload + /// @return 1 on success + virtual std::int32_t SetGoal(BSFixedString* a_name, std::uint64_t a_data) = 0; // 01 + + /// Requests path computation for a previously stored goal. + /// Scans the BSFixedString array for matching name. + /// If found with count==1: clears entire array. + /// If found with count>1: removes the matching entry, shifts rest. + /// Uses spinlock at this+0x108. + /// @param a_name Goal name to request path for + virtual void RequestPath(BSFixedString* a_name) = 0; // 02 + }; + static_assert(sizeof(IMovementSetGoal) == 0x8); +} diff --git a/include/RE/I/IMovementSetSprinting.h b/include/RE/I/IMovementSetSprinting.h new file mode 100644 index 00000000..7d82a4d9 --- /dev/null +++ b/include/RE/I/IMovementSetSprinting.h @@ -0,0 +1,21 @@ +#pragma once + +#include "RE/I/IMovementInterface.h" + +namespace RE +{ + /// Interface for setting sprint state on the movement controller. + /// Embedded at MovementControllerNPC+0x148. + /// + /// Full virtual function layout is not yet known. + class __declspec(novtable) IMovementSetSprinting : + public IMovementInterface // 00 + { + public: + static constexpr auto RTTI{ RTTI::IMovementSetSprinting }; + static constexpr auto VTABLE{ VTABLE::IMovementSetSprinting }; + + // TODO: Add virtual functions as they are discovered + }; + static_assert(sizeof(IMovementSetSprinting) == 0x8); +} diff --git a/include/RE/IDs.h b/include/RE/IDs.h index 64b6eb3d..43a7e808 100644 --- a/include/RE/IDs.h +++ b/include/RE/IDs.h @@ -13,6 +13,7 @@ namespace RE::ID inline constexpr REL::VariantID CanUseIdle{ 1223707, 2229592 }; inline constexpr REL::VariantID ClearAttackStates{ 1525555, 2229773 }; inline constexpr REL::VariantID EndInterruptPackage{ 575188, 2229892 }; + inline constexpr REL::VariantID EvaluatePackage{ 0, 2229802 }; inline constexpr REL::VariantID ExitCover{ 770035, 2231166 }; inline constexpr REL::VariantID GetAimVector{ 554863, 2230378 }; inline constexpr REL::VariantID GetClosestBone{ 1180004, 2230051 }; @@ -35,6 +36,7 @@ namespace RE::ID inline constexpr REL::VariantID IsCrippled{ 1238666, 2230998 }; inline constexpr REL::VariantID IsFollowing{ 629579, 2230013 }; inline constexpr REL::VariantID IsJumping{ 1041558, 2229640 }; + inline constexpr REL::VariantID IsOnNavmesh{ 0, 2230282 }; inline constexpr REL::VariantID IsPathValid{ 1522194, 2230279 }; inline constexpr REL::VariantID IsPathing{ 989661, 2234312 }; inline constexpr REL::VariantID IsPathingComplete{ 817283, 2230274 }; @@ -68,6 +70,64 @@ namespace RE::ID inline constexpr REL::VariantID StopInteractingQuick{ 129904, 2231227 }; inline constexpr REL::VariantID CalculateDetectionFormula{ 589441, 2230213 }; inline constexpr REL::VariantID DoHitMe{ 881215, 2231148 }; + inline constexpr REL::VariantID GetActorMover{ 1329664, 2230242 }; + inline constexpr REL::VariantID GetActorMoverResolver{ 0, 2230241 }; // ActorMover* resolve(Actor*), handles mount indirection via actor+0x308 + inline constexpr REL::VariantID GetMovementControllerNPC{ 1398256, 2230235 }; + inline constexpr REL::VariantID CreateActorMover{ 369824, 2230491 }; + inline constexpr REL::VariantID DestroyActorMover{ 0, 2230492 }; + inline constexpr REL::VariantID ReleaseMovementControllerNPC{ 0, 2230236 }; + inline constexpr REL::VariantID SetPathfindingGoal_REFR{ 0, 2230248 }; // void(Actor*, TESObjectREFR*, float, BSPathingAvoidNodeArray*) + inline constexpr REL::VariantID SetPathfindingGoal_Checked{ 0, 2230259 }; // bool(Actor*, undefined4, longlong*, float) + inline constexpr REL::VariantID SetPathfindingGoalPos_NiPoint3{ 0, 2230256 }; // bool(Actor*, NiPoint3*, float) + inline constexpr REL::VariantID SetPathfindingGoalPos_Checked{ 0, 2230258 }; // bool(Actor*, NiPoint3*, longlong*, float) + inline constexpr REL::VariantID ClearPath{ 0, 2230253 }; // void(Actor*, BSTSmartPointer&) + // SetPathfindingGoalPos with full TargetData struct (8 params). This is the version + // called by AI package dispatch (65+ callers via FUN_140c9bca0, RVA 0xC9BCA0). + // Distinct from SetPathfindingGoalPos_NiPoint3 (ID 2230256, RVA 0xC9B590). + inline constexpr REL::VariantID SetPathfindingGoalPos_TargetData{ 0, 2230266 }; + // Per-frame pathing update dispatcher: calls ActorMover::UpdatePathing (ID 2234296) + // via CALL at offset +0xD9 from entry. RVA 0xC9B9F0 + inline constexpr REL::VariantID DispatchUpdatePathing{ 0, 2230262 }; + } + + namespace ActorMover + { + inline constexpr REL::VariantID ctor{ 4155763, 2234288 }; + inline constexpr REL::VariantID dtor{ 12252, 4485111 }; + inline constexpr REL::VariantID IsPathing{ 4090716, 2234290 }; + inline constexpr REL::VariantID ClearPath{ 4660091, 2234295 }; // NOTE: IDs.h previously had 2234298 here - is actually SetPathfindingGoalPos + inline constexpr REL::VariantID SetPathfindingGoal{ 142993, 2234292 }; + inline constexpr REL::VariantID SetPathfindingGoalEx{ 1113466, 2234294 }; + inline constexpr REL::VariantID IsActorPathing{ 0, 2234289 }; // bool(ActorMover*, BSTSmartPointer&) + inline constexpr REL::VariantID SetPathfindingGoalPos_Inner{ 0, 2234291 }; // void(ActorMover*, Actor*, float, uint64_t) + inline constexpr REL::VariantID SetPathfindingGoal2{ 0, 2234293 }; // void(ActorMover*, TESObjectREFR*, float, float, BSPathingAvoidNodeArray*) + inline constexpr REL::VariantID UpdatePathing{ 0, 2234296 }; // void(ActorMover*, float) + inline constexpr REL::VariantID RequestPath{ 0, 2234297 }; // void(ActorMover*) + inline constexpr REL::VariantID SetPathfindingGoalPos{ 0, 2234299 }; // void(ActorMover*, NiPoint3*, uint32_t, float, float, uint32_t) + inline constexpr REL::VariantID SetPathfindingGoalActor{ 0, 2234300 }; // void(ActorMover*, Actor*, float, float) + inline constexpr REL::VariantID IsPathValid{ 0, 2234301 }; // bool(ActorMover*) + inline constexpr REL::VariantID StopMoving{ 0, 2234302 }; // void(ActorMover*) + inline constexpr REL::VariantID SetPathfindingPos_Hdg{ 0, 2234303 }; // ulonglong(ActorMover*, float heading, float speed) + inline constexpr REL::VariantID SetPathfindingPos_NiPoint3{ 0, 2234304 }; // bool(ActorMover*, NiPoint3*, float speed) + inline constexpr REL::VariantID SetPathfindingGoal_CheckMoveOnly{ 0, 2234305 }; // bool(ActorMover*, uint, void*, float) + inline constexpr REL::VariantID SetPathfindingGoalPos_CheckMoveOnly{ 0, 2234306 }; // bool(ActorMover*, NiPoint3*, void*, float) + inline constexpr REL::VariantID PathingRequest_IsComplete{ 0, 2234307 }; // uint8_t(ActorMover*) + inline constexpr REL::VariantID PathingRequest_CheckPathStatus{ 0, 2234308 }; // uint8_t(ActorMover*) + inline constexpr REL::VariantID ComparePathDestination{ 0, 2234309 }; // bool(ActorMover*, void*, uint8_t*) + inline constexpr REL::VariantID CompareAndResendPath{ 0, 2234310 }; // bool(ActorMover*, void*, uint8_t*) + inline constexpr REL::VariantID GetPathingState{ 0, 2234311 }; // uint8_t(ActorMover*) + inline constexpr REL::VariantID IsPathing_MC{ 0, 2234312 }; // bool(ActorMover*) via MovementController event sink + inline constexpr REL::VariantID IsPathing_MC_CheckType{ 0, 2234313 }; // bool(ActorMover*) + inline constexpr REL::VariantID Activate3D{ 0, 2234375 }; // void(MovementControllerNPC*) - Activate for 3D-loaded actors + inline constexpr REL::VariantID Activate3DNotLoaded{ 0, 2234376 }; // void(MovementControllerNPC*) - Activate when 3D not yet loaded + } + + namespace IMovementSetGoal + { + // Concrete implementation on MovementControllerNPC. + // Every path submission (SetGoal/RrequestPath) converges through these. + inline constexpr REL::VariantID SetGoal{ 0, 2301642 }; + inline constexpr REL::VariantID RequestPath{ 0, 2301645 }; } namespace ActorEquipManager @@ -113,9 +173,9 @@ namespace RE::ID inline constexpr REL::VariantID SetCurrentAmmo{ 795983, 2232302 }; inline constexpr REL::VariantID SetCommandType{ 1555789, 2231826 }; inline constexpr REL::VariantID SetEquippedItem{ 1200276, 2231627 }; - inline constexpr REL::VariantID SetupSpecialIdle{ 1446774, 2231704 }; + inline constexpr REL::VariantID SetupSpecialIdle{ 1446774, 2231705 }; inline constexpr REL::VariantID SetWeaponBonesCulled{ 397172, 2232535 }; - inline constexpr REL::VariantID StopCurrentIdle{ 434460, 2231705 }; + inline constexpr REL::VariantID StopCurrentIdle{ 434460, 2231706 }; inline constexpr REL::VariantID SetRunOncePackage{ 155445, 2232344 }; inline constexpr REL::VariantID AddToProcedureIndexRunning{ 134486, 2718412 }; inline constexpr REL::VariantID ComputeLastTimeProcessed{ 941571, 2231541 }; @@ -1538,6 +1598,10 @@ namespace RE::ID inline constexpr REL::VariantID Activate{ 829033, 2271861 }; } + namespace NiAVObject + { + } + namespace NiMatrix3 { inline constexpr REL::VariantID ToEulerAnglesXYZ{ 34114, 2269806 }; diff --git a/include/RE/M/MovementAgentPathFollowerVirtual.h b/include/RE/M/MovementAgentPathFollowerVirtual.h new file mode 100644 index 00000000..0afbadb1 --- /dev/null +++ b/include/RE/M/MovementAgentPathFollowerVirtual.h @@ -0,0 +1,39 @@ +#pragma once + +// Unstable, doesn't work 9/10 times, trying to use it WILL break your will to live. + +#include "RE/I/IMovementPlannerAgent.h" +#include "RE/M/MemoryManager.h" +#include "RE/M/MovementPlannerAgent.h" +#include "REL/Relocation.h" + +namespace RE +{ + /// A MovementPlannerAgent variant that connects to the Havok path follower system. + /// + /// Registered via AutoRegisterMovementAgentCreator. + /// Reads from the Havok path follower state and feeds it into the movement system. + /// + /// Vtable layout (16 entries, RVA 0x02899040): + /// +0x00: Destructor + /// +0x70 (vtable[14]): Returns pointer to Havok bhkPathFollower + class __declspec(novtable) MovementAgentPathFollowerVirtual : + public MovementPlannerAgent // 00 + { + public: + static constexpr auto RTTI{ RTTI::MovementAgentPathFollowerVirtual }; + static constexpr auto VTABLE{ VTABLE::MovementAgentPathFollowerVirtual }; + + MovementAgentPathFollowerVirtual() { REX::EMPLACE_VTABLE(this); } + + ~MovementAgentPathFollowerVirtual() override = default; // 00 + + F4_HEAP_REDEFINE_NEW(MovementAgentPathFollowerVirtual); + + // members + // Inherits MovementPlannerAgent layout (~0x40 bytes) + // Full member layout is not yet known beyond the vtable overrides. + char pad40[0x00]; // Placeholder for any additional members + }; + static_assert(sizeof(MovementAgentPathFollowerVirtual) == 0x40); +} diff --git a/include/RE/M/MovementArbiter.h b/include/RE/M/MovementArbiter.h new file mode 100644 index 00000000..8277beb2 --- /dev/null +++ b/include/RE/M/MovementArbiter.h @@ -0,0 +1,30 @@ +#pragma once + +#include "RE/M/MemoryManager.h" + +namespace RE +{ + /// Base class for all movement arbiter types. + /// + /// Arbiters are registered via AutoRegisterMovementArbiterCreator + /// and managed through BSTCreateFactoryManager. They handle various + /// aspects of pathing and movement arbitration. + /// + /// Subclasses include: + /// - MovementPathManagerArbiter (path submission/clear) + /// - MovementPlannerArbiter (agent registration & orchestration) + /// - MovementHandlerArbiter (movement handler dispatch) + /// - MovementTweenerArbiter (tweener interpolation) + /// - MovementPostUpdateArbiter (post-update tasks) + class __declspec(novtable) MovementArbiter + { + public: + static constexpr auto RTTI{ RTTI::MovementArbiter }; + static constexpr auto VTABLE{ VTABLE::MovementArbiter }; + + virtual ~MovementArbiter() = default; // 00 + + F4_HEAP_REDEFINE_NEW(MovementArbiter); + }; + static_assert(sizeof(MovementArbiter) == 0x8); +} diff --git a/include/RE/M/MovementControllerNPC.h b/include/RE/M/MovementControllerNPC.h new file mode 100644 index 00000000..81756070 --- /dev/null +++ b/include/RE/M/MovementControllerNPC.h @@ -0,0 +1,261 @@ +#pragma once + +// Unstable, doesn't work 9/10 times, trying to use it WILL break your will to live. + +#include "RE/B/BSFixedString.h" +#include "RE/B/BSSpinLock.h" +#include "RE/I/IMovementDirectControl.h" +#include "RE/I/IMovementPathEvaluator.h" +#include "RE/I/IMovementPathManager.h" +#include "RE/I/IMovementPlannerDirectControl.h" +#include "RE/I/IMovementQueryState.h" +#include "RE/I/IMovementSetGoal.h" +#include "RE/I/IMovementSetSprinting.h" +#include "RE/M/MemoryManager.h" +#include "REL/Relocation.h" + +namespace RE +{ + class Actor; + + /// Controls an actor's movement at the Havok/physics level. + /// + /// MovementControllerNPC bridges the gap between pathfinding requests + /// (via ActorMover) and the actual Havok character controller movement. + /// Every NPC with an AIProcess has one at Actor+0x318 (BSTSmartPointer). + /// + /// Size: 0x1A8 + /// +0x000: vfptr (MovementControllerNPC vtable) + /// +0x008: RefCount (int32, BSTSmartPointer ref count) + /// +0x050: Interface table (array of {BSFixedString, uint64_t} pairs) - used by QueryInterface + /// +0x100: Interface count (int32) + /// +0x108: SpinLock for interface table access + /// +0x120: IMovementSetGoal interface vtable + /// +0x128: IMovementQueryState interface vtable + /// +0x130: IMovementPathEvaluator / IMovementPathManager vtable + /// +0x138: IMovementDirectControl interface vtable + /// +0x140: IMovementPlannerDirectControl interface vtable + /// +0x148: IMovementSetSprinting interface vtable + /// +0x150: Update spinlock counter + /// +0x154: Update recursion counter + /// +0x158: Pending goal queue head + /// +0x160: Pending goal count + /// +0x170: Active goal queue head + /// +0x178: Active goal count + /// +0x188: Handle pointer (cleaned up in dtor) + /// +0x190: Actor* back-pointer + /// +0x198: State (init to 2) + /// +0x19C: Sub-state (0=idle, 1=active, 2=reset, 3=disable) + /// +0x1A0: MoveMode (uint32, low byte: 0=walk, 1=run) + /// +0x1A4: Activation flags (uint16) + /// +0x1A6: Active flag (uint8) + /// + /// Vtable layout: + /// [0]: Destructor + /// [1]: FUN_141E44880 (SetGoal/SetPositionGoal dispatcher) + /// [2]: FUN_141E44A10 (RequestPath cancel?) + /// [3]: QueryInterface (FUN_141E44940, RVA 0x1E44940, ID 2301643) + /// [4]: FUN_141E44AE0 (RequestPath by name) + /// [5]: FUN_141E44480 (interface cleanup) + /// [6]: FUN_140DC6C20 (PostInit) + /// [7]: FUN_141E44530 (SetGoal/SubmitGoal) + /// [8]: FUN_141E44780 (ProcessGoal?) + /// + /// NOTE: The class is NOT declared with virtual functions here because the + /// vtable is set by the game's constructor at runtime. Use the raw memory + /// offsets for field access. + /// + /// The 6 sub-interfaces at +0x120 through +0x148 all share the same vtable + /// as the primary class but dispatch differently based on `this` offset. + /// + class MovementControllerNPC + { + public: + inline static constexpr auto RTTI{ RTTI::MovementControllerNPC }; + inline static constexpr auto VTABLE{ VTABLE::MovementControllerNPC }; + + /// State enum for +0x198. + enum class State : std::int32_t + { + kUninitialized = 0, + kInitialized = 2 // Value set by constructor + }; + + /// Sub-state enum for +0x19C. + enum class SubState : std::int32_t + { + kIdle = 0, + kActive = 1, + kReset = 2, + kDisable = 3 + }; + + /// Move mode enum for +0x1A0 (low byte). + enum class MoveMode : std::uint8_t + { + kWalk = 0, + kRun = 1 + }; + + // === QueryInterface (vtable[3], RVA 0x1E44940, ID 2301643) === + + /// Query the movement controller for a named interface. + /// Searches the BSFixedString-keyed table at +0x50 (count at +0x100). + /// Returns the interface slot pointer (e.g. this+0x120, this+0x130, etc.) + /// or nullptr if the GUID is not found. + /// + /// Available GUIDs: + /// DAT_143dc3ac0 (IMovementSetGoal) → MC+0x120 + /// DAT_143dc3ab8 (IMovementPathManager) → MC+0x130 + /// DAT_143dc3af8 (IMovementDirectControl) → MC+0x138 + /// + /// @param a_guid The GUID BSFixedString to look up + /// @return Pointer to the interface vtable slot, or nullptr + void* QueryInterface(BSFixedString* a_guid) + { + using func_t = void*(MovementControllerNPC*, BSFixedString*); + static REL::Relocation func{ REL::ID(2301643) }; + return func(this, a_guid); + } + + // === Interface accessors (offset-based reads from the sub-interface vtables) === + + /// Get the IMovementSetGoal interface pointer (at +0x120). + /// NOTE: Returns the ADDRESS of the vtable slot (this+0x120), NOT the + /// vtable pointer value stored at that slot. The slot address serves as + /// the "interface object" because the first 8 bytes at that address ARE + /// the vtable pointer (as set by the MC constructor). The previous + /// implementation dereferenced the slot and returned the raw vtable + /// address, which would cause virtual function calls to read the destructor + /// function address as the vtable pointer and then index into random code. + [[nodiscard]] IMovementSetGoal* GetIMovementSetGoal() const + { + return reinterpret_cast( + const_cast(reinterpret_cast(this) + 0x120)); + } + + /// Get the IMovementQueryState interface pointer (at +0x128). + [[nodiscard]] IMovementQueryState* GetIMovementQueryState() const + { + return reinterpret_cast( + const_cast(reinterpret_cast(this) + 0x128)); + } + + /// Get the IMovementPathManager interface pointer (at +0x130). + /// Note: This offset corresponds to IMovementPathEvaluator in the layout, + /// but QueryInterface returns IMovementPathManager here. + [[nodiscard]] IMovementPathManager* GetIMovementPathManager() const + { + return reinterpret_cast( + const_cast(reinterpret_cast(this) + 0x130)); + } + + /// Get the IMovementDirectControl interface pointer (at +0x138). + [[nodiscard]] IMovementDirectControl* GetIMovementDirectControl() const + { + return reinterpret_cast( + const_cast(reinterpret_cast(this) + 0x138)); + } + + /// Get the IMovementPlannerDirectControl interface pointer (at +0x140). + [[nodiscard]] IMovementPlannerDirectControl* GetIMovementPlannerDirectControl() const + { + return reinterpret_cast( + const_cast(reinterpret_cast(this) + 0x140)); + } + + /// Get the IMovementSetSprinting interface pointer (at +0x148). + [[nodiscard]] IMovementSetSprinting* GetIMovementSetSprinting() const + { + return reinterpret_cast( + const_cast(reinterpret_cast(this) + 0x148)); + } + + // === Key accessors (offset-based reads) === + + /// Returns the owning Actor, or nullptr if not set. + [[nodiscard]] Actor* GetActor() const { return *reinterpret_cast(reinterpret_cast(this) + 0x190); } + + /// Returns the current state. Initialized to 2. + [[nodiscard]] std::int32_t GetState() const { return *reinterpret_cast(reinterpret_cast(this) + 0x198); } + + /// Returns the sub-state (0=idle, 1=active, 2=reset, 3=disable). + [[nodiscard]] std::int32_t GetSubState() const { return *reinterpret_cast(reinterpret_cast(this) + 0x19C); } + + /// Returns the current move mode (0=walk, 1=run). + [[nodiscard]] std::uint8_t GetMoveMode() const { return *reinterpret_cast(reinterpret_cast(this) + 0x1A0); } + + /// Returns whether the controller is active. + [[nodiscard]] bool IsActive() const { return *reinterpret_cast(reinterpret_cast(this) + 0x1A7) != 0; } + + /// Returns the activation flags at +0x1A4. + [[nodiscard]] std::uint16_t GetActivationFlags() const { return *reinterpret_cast(reinterpret_cast(this) + 0x1A4); } + + /// Returns whether the interface vtables are all set (initialization check). + [[nodiscard]] bool IsInitialized() const + { + auto base = reinterpret_cast(this); + return *reinterpret_cast(base + 0x120) != nullptr && + *reinterpret_cast(base + 0x128) != nullptr && + *reinterpret_cast(base + 0x130) != nullptr && + *reinterpret_cast(base + 0x138) != nullptr && + *reinterpret_cast(base + 0x140) != nullptr && + *reinterpret_cast(base + 0x148) != nullptr && + GetActor() != nullptr; + } + + /// Returns the number of pending goals in the queue. + [[nodiscard]] std::int32_t GetPendingGoalCount() const { return *reinterpret_cast(reinterpret_cast(this) + 0x160); } + + /// Returns the number of active goals. + [[nodiscard]] std::int32_t GetActiveGoalCount() const { return *reinterpret_cast(reinterpret_cast(this) + 0x178); } + + /// Activate the MovementControllerNPC so it processes pathing requests. + /// Both the update loop and queue processing require an active controller. + /// Call after spawn and after GetOrCreateMovementController. + /// ID 2234375 - Activates for 3D-loaded actors. + /// ID 2234376 - Activates when 3D not yet loaded. + static void Activate(RE::MovementControllerNPC* mc) + { + using func_t = void(RE::MovementControllerNPC*); + static REL::Relocation func{ ID::ActorMover::Activate3D }; + return func(mc); + } + + // === Member fields (for direct offset-based access) === + // Note: declared here for documentation. Most fields are accessed + // via the accessor methods above rather than directly. + // + // +0x000: void* vfptr + // +0x008: char pad08[0x48] - ref count, internal state up to +0x50 + // +0x050: void* interfaceTable - BSFixedString-keyed interface table array + // +0x100: std::int32_t interfaceCount - number of interfaces + // +0x108: BSSpinLock interfaceLock - spinlock for interface table + // +0x110: char pad110[0x10] - up to +0x120 + // +0x120: void* imovementSetGoal_vtable + // +0x128: void* imovementQueryState_vtable + // +0x130: void* imovementPathManager_vtable + // +0x138: void* imovementDirectControl_vtable + // +0x140: void* imovementPlannerDirectControl_vtable + // +0x148: void* imovementSetSprinting_vtable + // +0x150: std::uint32_t updateSpinLockCount + // +0x154: std::uint32_t updateRecursionCount + // +0x158: void* pendingGoalQueueHead + // +0x160: std::int32_t pendingGoalCount + // +0x164: char pad164[0x0C] - pad to +0x170 + // +0x170: void* activeGoalQueueHead + // +0x178: std::int32_t activeGoalCount + // +0x17C: char pad17C[0x0C] - pad to +0x188 + // +0x188: void* handlePtr + // +0x190: Actor* actor + // +0x198: std::int32_t state + // +0x19C: std::int32_t subState + // +0x1A0: std::uint32_t moveMode + // +0x1A4: std::uint16_t activationFlags + // +0x1A6: std::uint8_t activeFlag + // +0x1A7: char pad1A7[0x01] - pad to +0x1A8 + }; + // NOTE: No static_assert for size here because the class has no declared + // data members (it's a transparent offset-based access type). The runtime + // size is 0x1A8 bytes, allocated by the game's constructor. +} diff --git a/include/RE/M/MovementMessage.h b/include/RE/M/MovementMessage.h new file mode 100644 index 00000000..977d1bc3 --- /dev/null +++ b/include/RE/M/MovementMessage.h @@ -0,0 +1,27 @@ +#pragma once + +namespace RE +{ + /// Movement message type enum. + /// Used for inter-system movement commands between + /// ActorMover, MovementControllerNPC, and the event sink. + enum class MovementMessageType : std::uint32_t + { + kStop = 0, // Stop all movement + kNewPath = 1, // New path available (MovementMessageNewPath) + kActorCollision = 2, // Actor collision event (MovementMessageActorCollision) + // TODO: Additional message types unknown + }; + + /// Movement message structure. + /// + /// Used for inter-system movement command communication. + /// Full structure layout is not yet known. + struct MovementMessage + { + MovementMessageType type; // 00 - message type + std::uint32_t pad04; // 04 + void* data; // 08 - message data (type-dependent) + }; + static_assert(sizeof(MovementMessage) == 0x10); +} diff --git a/include/RE/M/MovementPathManagerArbiter.h b/include/RE/M/MovementPathManagerArbiter.h new file mode 100644 index 00000000..5e700adb --- /dev/null +++ b/include/RE/M/MovementPathManagerArbiter.h @@ -0,0 +1,84 @@ +#pragma once + +// Unstable, doesn't work 9/10 times, trying to use it WILL break your will to live. + +#include "RE/B/BSFixedString.h" +#include "RE/B/BSSpinLock.h" +#include "RE/B/BSTSmartPointer.h" +#include "RE/I/IMovementQueryPathingState.h" +#include "RE/I/IMovementSetGoal.h" +#include "RE/M/MemoryManager.h" +#include "RE/M/MovementArbiter.h" +#include "REL/Relocation.h" + +namespace RE +{ + class BSPathingRequest; + class MovementControllerNPC; + + /// Manages path submission, clearing, and re-requesting. + /// + /// Registered via AutoRegisterMovementArbiterCreator. + /// Created during MovementControllerNPC::Activate alongside other arbiters. + /// Handles the IMovementPathManager, IMovementSetGoal, and IMovementQueryPathingState + /// interfaces via embedded sub-objects. + /// + /// Size: 0x80 + /// +0x00: vfptr (primary vtable, RVA 0x02895228) + /// +0x18: Embedded IMovementSetGoal interface vtable + /// +0x20: Embedded IMovementQueryPathingState interface vtable + /// +0x28: BSFixedString[2] array (0x20 bytes to +0x48) + /// +0x40: Back-pointer to MovementControllerNPC + /// +0x48: Pad / count + /// +0x50: BSTSmartPointer (path request) + /// +0x58: BSTSmartPointer (0xB0 byte allocation) + /// +0x60: BSTSmartPointer (0x38 byte allocation) + /// +0x68: DWORD array (0x18 bytes to +0x80) + class __declspec(novtable) MovementPathManagerArbiter : + public MovementArbiter // 00 + { + public: + static constexpr auto RTTI{ RTTI::MovementPathManagerArbiter }; + static constexpr auto VTABLE{ VTABLE::MovementPathManagerArbiter }; + + MovementPathManagerArbiter() { REX::EMPLACE_VTABLE(this); } + + ~MovementPathManagerArbiter() override = default; // 00 + + F4_HEAP_REDEFINE_NEW(MovementPathManagerArbiter); + + // Embedded interface accessors (offset-based reads) + /// Get the embedded IMovementSetGoal interface (at +0x18). + [[nodiscard]] IMovementSetGoal* GetSetGoal() + { + return reinterpret_cast(reinterpret_cast(this) + 0x18); + } + + /// Get the embedded IMovementQueryPathingState interface (at +0x20). + [[nodiscard]] IMovementQueryPathingState* GetQueryPathingState() + { + return reinterpret_cast(reinterpret_cast(this) + 0x20); + } + + /// Get the MovementControllerNPC back-pointer (at +0x40). + [[nodiscard]] MovementControllerNPC* GetParentMC() const + { + return parentMC; + } + + // members + // +0x00: vfptr (inherited from MovementArbiter, RVA 0x02895228) + // +0x08: internal data + char pad08[0x10]; // 08-17 + void* imovementSetGoal_vtable; // 18 - Embedded IMovementSetGoal vtable + void* imovementQueryPathingState_vtable; // 20 - Embedded IMovementQueryPathingState vtable + BSFixedString fixedStringArray[2]; // 28 - 2-entry BSFixedString array (0x20 bytes) + MovementControllerNPC* parentMC; // 40 - Back-pointer to MovementControllerNPC (used for QI) + char pad48[0x08]; // 48 - Pad / count area + BSTSmartPointer smartPtr1; // 50 - Pathing request reference + BSTSmartPointer smartPtr2; // 58 - BSPathingRequest (0xB0 byte allocation) + BSTSmartPointer smartPtr3; // 60 - Generic (0x38 byte allocation) + std::uint32_t dwordArray[6]; // 68 - DWORD array (0x18 bytes to end) + }; + static_assert(sizeof(MovementPathManagerArbiter) == 0x80); +} diff --git a/include/RE/M/MovementPlannerAgent.h b/include/RE/M/MovementPlannerAgent.h new file mode 100644 index 00000000..2b98afab --- /dev/null +++ b/include/RE/M/MovementPlannerAgent.h @@ -0,0 +1,53 @@ +#pragma once + +// Unstable, doesn't work 9/10 times, trying to use it WILL break your will to live. + +#include "RE/I/IMovementInterface.h" +#include "RE/I/IMovementPlannerAgent.h" +#include "RE/M/MemoryManager.h" +#include "REL/Relocation.h" + +namespace RE +{ + /// Base class for all movement planner agent types. + /// + /// Registered via AutoRegisterMovementAgentCreator and managed + /// through BSTCreateFactoryManager. Provides 16 virtual function slots + /// shared across all planner agent variants. + /// + /// Subclasses include: + /// - MovementPlannerAgentDirectControl + /// - MovementPlannerAgentWarp + /// - MovementPlannerAgentNavmeshBounds + /// - MovementPlannerAgentHorseControlsLoadScrapper + /// - MovementPlannerAgentKeepOffsetLoadScrapper + /// - MovementAgentPathFollowerVirtual + /// + /// Size: ~0x40 + class __declspec(novtable) MovementPlannerAgent : + public IMovementPlannerAgent // 00 + { + public: + static constexpr auto RTTI{ RTTI::MovementPlannerAgent }; + static constexpr auto VTABLE{ VTABLE::MovementPlannerAgent }; + + MovementPlannerAgent() { REX::EMPLACE_VTABLE(this); } + + ~MovementPlannerAgent() override = default; // 00 + + F4_HEAP_REDEFINE_NEW(MovementPlannerAgent); + + // Override pure virtuals from IMovementPlannerAgent with base implementations: + // [03] - FUN_141e85e70 - common to all planners + // [04] - FUN_141e7fad0 - SetGoalType (overridden by DirectControl) + // [05-13] - Various update/process functions + // [14] - Data pointer function + // [15] - Final virtual + + // members + // Full member layout is not yet known. + // The size is approximately 0x40 based on factory template parameter. + char pad08[0x38]; // 08-3F + }; + static_assert(sizeof(MovementPlannerAgent) == 0x40); +} diff --git a/include/RE/M/MovementPlannerArbiter.h b/include/RE/M/MovementPlannerArbiter.h new file mode 100644 index 00000000..c70dd641 --- /dev/null +++ b/include/RE/M/MovementPlannerArbiter.h @@ -0,0 +1,59 @@ +#pragma once + +// Unstable, doesn't work 9/10 times, trying to use it WILL break your will to live. + +#include "RE/B/BSTArray.h" +#include "RE/B/BSTSmartPointer.h" +#include "RE/I/IMovementPlannerAgent.h" +#include "RE/M/MemoryManager.h" +#include "RE/M/MovementArbiter.h" +#include "REL/Relocation.h" + +namespace RE +{ + /// Manages MovementPlannerAgent instances during movement planning. + /// + /// Created during MovementControllerNPC::Activate via the setup function + /// FUN_141e44c60 (ID 2301646). Maintains two arrays of planner agents: + /// one for active agents and one for registered/lookup agents. + /// + /// Registered via AutoRegisterMovementArbiterCreator. + /// + /// Size: ~0x80 + /// +0x00: vfptr (primary vtable, RVA 0x028987E0, 32 entries) + /// +0x10: allocatorFlag (int32, initialized to 0x80000000) + /// +0x18: First agent array (BSTArray>) + /// +0x30: allocatorFlag for second array + /// +0x38: Second agent array (BSTArray>) + /// +0x68: Int array managed by FUN_141659160-style DWORD array + class __declspec(novtable) MovementPlannerArbiter : + public MovementArbiter // 00 + { + public: + static constexpr auto RTTI{ RTTI::MovementPlannerArbiter }; + static constexpr auto VTABLE{ VTABLE::MovementPlannerArbiter }; + + MovementPlannerArbiter() { REX::EMPLACE_VTABLE(this); } + + ~MovementPlannerArbiter() override = default; // 00 + + F4_HEAP_REDEFINE_NEW(MovementPlannerArbiter); + + // Setup function that registers planner agents. + // FUN_141e44c60 (RVA 0x1E44C60, ID 2301646) + + // members + // +0x00: vfptr (inherited from MovementArbiter, RVA 0x028987E0) + // +0x08: base class data (from MovementArbiter) + std::int32_t allocatorFlag1; // 10 - initialized to 0x80000000 + char pad14[0x04]; // 14-17 + BSTArray> agentArray1; // 18 - First agent array (active/registered) + std::int32_t allocatorFlag2; // 30 - allocator flag for second array + char pad34[0x04]; // 34-37 + BSTArray> agentArray2; // 38 - Second agent array (lookup) + // +0x50-0x67: internal data / more fields + char pad50[0x18]; // 50-67 + std::uint32_t intArray[6]; // 68 - DWORD array (0x18 bytes to +0x80) + }; + static_assert(sizeof(MovementPlannerArbiter) == 0x80); +} diff --git a/include/RE/N/NiAVObject.h b/include/RE/N/NiAVObject.h index ff8f3de4..2b6853b9 100644 --- a/include/RE/N/NiAVObject.h +++ b/include/RE/N/NiAVObject.h @@ -72,6 +72,17 @@ namespace RE void CullNode(bool a_cull); void Update(NiUpdateData& a_data); + // Set the Havok motion type on this object's collision body. + // type: 0=Dynamic, 2=Keyframed, 4=Static + // force: force update even if body is not yet added + void SetMotionType(std::uint32_t a_type, bool a_force) + { + if (!REX::FModule::IsRuntimeNG()) return; + using func_t = void(NiAVObject*, std::uint32_t, bool); + static REL::Relocation func{ REL::ID(2277724) }; + return func(this, a_type, a_force); + } + // members NiNode* parent{ nullptr }; // 027 NiTransform local; // 030 diff --git a/include/RE/T/TESDataHandler.h b/include/RE/T/TESDataHandler.h index ee54e9c6..fc94290a 100644 --- a/include/RE/T/TESDataHandler.h +++ b/include/RE/T/TESDataHandler.h @@ -1,5 +1,6 @@ #pragma once +#include "RE/B/BSFixedString.h" #include "RE/B/BSPointerHandle.h" #include "RE/B/BSSimpleList.h" #include "RE/B/BSTArray.h"