From 155f6ac9cdabab8fb9026ca6dc47e8d3ee1c64cc Mon Sep 17 00:00:00 2001 From: Ehbw Date: Thu, 26 Dec 2024 20:04:27 +0000 Subject: [PATCH] feat(extra-natives/five): track switching --- .../extra-natives-five/src/TrackNatives.cpp | 219 +++++++++++++++++- ext/native-decls/RegisterTrackSwitch.md | 22 ++ ext/native-decls/SetTrackSwitchActive.md | 17 ++ 3 files changed, 248 insertions(+), 10 deletions(-) create mode 100644 ext/native-decls/RegisterTrackSwitch.md create mode 100644 ext/native-decls/SetTrackSwitchActive.md diff --git a/code/components/extra-natives-five/src/TrackNatives.cpp b/code/components/extra-natives-five/src/TrackNatives.cpp index 30493cbd0d..543e1d29e1 100644 --- a/code/components/extra-natives-five/src/TrackNatives.cpp +++ b/code/components/extra-natives-five/src/TrackNatives.cpp @@ -8,11 +8,14 @@ #include "StdInc.h" #include #include +#include + #include #include #include #include "ScriptWarnings.h" #include +#include static hook::cdecl_stub getTrainTrack([] { @@ -24,6 +27,11 @@ static float calculateDistance(const rage::Vector3& point1, const float& x, cons return (point1.x - x) * (point1.x - x) + (point1.y - y) * (point1.y - y) + (point1.z - z) * (point1.z - z); } +static float calculateDistance(const float& x, const float& y, const float& z, const float& x1, const float& y1, const float& z1) +{ + return (x - x1) * (x - x1) + (y - y1) * (y - y1) + (z - z1) * (z - z1); +} + static int32_t FindClosestTrack(rage::Vector3& position, int8_t* outTrack) { *outTrack = -1; @@ -58,6 +66,103 @@ static int32_t FindClosestTrack(rage::Vector3& position, int8_t* outTrack) return closestNode; } +struct CTrainJunction +{ + uint8_t onTrack; + int onNode; + + uint8_t newTrack; + int newNode; + + bool direction; + bool isActive; + + CTrainJunction(uint8_t track, int node, uint8_t newTrack, int newNode, bool direction) : onTrack(track), onNode(node), newTrack(newTrack), newNode(newNode), direction(direction), isActive(true) + { } +}; + +static std::vector g_trackSwaps; + +class CTrain : public CVehicle +{ +public: + inline static ptrdiff_t kTrackIndexOffset; + inline static ptrdiff_t kTrackNodeOffset; + inline static ptrdiff_t kTrainFlagsOffset; +public: + inline int8_t GetTrackIndex() + { + auto location = reinterpret_cast(this) + kTrackIndexOffset; + return *reinterpret_cast(location); + } + + inline uint32_t GetTrackNode() + { + auto location = reinterpret_cast(this) + kTrackNodeOffset; + return *reinterpret_cast(location); + } + + inline bool GetDirection() + { + auto location = reinterpret_cast(this) + kTrainFlagsOffset; + return (*(BYTE*)(this + kTrainFlagsOffset) & 8) != 0; + } + + inline void SetTrackIndex(int8_t trackIndex) + { + auto location = reinterpret_cast(this) + kTrackIndexOffset; + *(int8_t*)location = trackIndex; + } + + inline void SetTrackNode(uint32_t trackNode) + { + auto location = reinterpret_cast(this) + kTrackNodeOffset; + *(uint32_t*)location = trackNode; + } +}; + +static hook::cdecl_stub CTrain__SetTrainCoord([]() +{ + return hook::pattern("44 8B C2 48 83 C4 ? 5B").count(1).get(0).get(8); +}); + +static hook::cdecl_stub CTrain__GetBackwardCarriage([]() +{ + return hook::get_call(hook::get_pattern("E8 ? ? ? ? 48 39 03")); +}); + +static bool (*g_CTrain__Update)(CTrain*, float); +static bool CTrain__Update(CTrain* self, float unk) +{ + for (const auto& data : g_trackSwaps) + { + if (!data.isActive) + { + continue; + } + + if (self->GetTrackIndex() == data.onTrack + && self->GetTrackNode() == data.onNode + && self->GetDirection() == data.direction) + { + // Iterate through all carriages to ensure the new trackIndex and node are applied + CTrain* train; + for (train = self; train; train = CTrain__GetBackwardCarriage(train)) + { + train->SetTrackIndex(data.newTrack); + train->SetTrackNode(data.newNode); + } + + // Force the train to update coordinate + // The original update function call should smooth over the rest of the transition + CTrain__SetTrainCoord(self, data.newNode, -1); + } + } + + return g_CTrain__Update(self, unk); +} + + static HookFunction hookFunction([]() { MH_Initialize(); @@ -67,14 +172,30 @@ static HookFunction hookFunction([]() // Prevent game code from constantly setting the trains speed while in moving state if it has the "stopsAtStations" flag enabled from setting the train speed to the tracks max speed while moving. hook::nop(hook::get_pattern("F3 0F 10 75 ? 8B 55"), 5); + // Extend metro vehicle types check to all vehicles + hook::put(hook::get_pattern("83 BE ? ? ? ? ? 77 ? 44 21 65", 6), 0xF); + + { + CTrain::kTrackNodeOffset = *hook::get_pattern("E8 ? ? ? ? 40 8A F8 84 C0 75 ? 48 8B CB E8", -4); + CTrain::kTrackIndexOffset = *hook::get_pattern("88 87 ? ? ? ? 48 85 F6 75", 2); + CTrain::kTrainFlagsOffset = *hook::get_pattern("80 8B ? ? ? ? ? 8B 05 ? ? ? ? FF C8", 2); + } + + g_CTrain__Update = hook::trampoline(hook::get_call(hook::get_pattern("E8 ? ? ? ? 44 8A B5 ? ? ? ? 48 85 F6")), CTrain__Update); + + OnKillNetworkDone.Connect([]() + { + g_trackSwaps.clear(); + }); }); +template static rage::CTrainTrack* getAndCheckTrack(fx::ScriptContext& context, std::string_view nn) { - int trackIndex = context.GetArgument(0); + int trackIndex = context.GetArgument(ArgumentIndex); if (trackIndex < 0 || trackIndex > rage::CTrainTrack::kMaxTracks) { - trace("Invalid track index %i passed to %s\n", trackIndex, nn); + fx::scripting::Warningf("natives", "%s: Invalid track index %i", nn, trackIndex); context.SetResult(0); return NULL; } @@ -83,7 +204,7 @@ static rage::CTrainTrack* getAndCheckTrack(fx::ScriptContext& context, std::stri if (!track || track->m_hash == 0) { - trace("Track index %i passed to %s does not exist\n", trackIndex, nn); + fx::scripting::Warningf("natives", "%s: Track Index %i does not exist", nn, trackIndex); context.SetResult(0); return NULL; } @@ -112,7 +233,7 @@ static InitFunction initFunction([]() { int maxSpeed = context.CheckArgument(1); - if (rage::CTrainTrack* track = getAndCheckTrack(context, "SET_TRACK_MAX_SPEED")) + if (rage::CTrainTrack* track = getAndCheckTrack<0>(context, "SET_TRACK_MAX_SPEED")) { track->m_speed = maxSpeed; } @@ -120,7 +241,7 @@ static InitFunction initFunction([]() fx::ScriptEngine::RegisterNativeHandler("GET_TRACK_MAX_SPEED", [](fx::ScriptContext& context) { - if (rage::CTrainTrack* track = getAndCheckTrack(context, "GET_TRACK_MAX_SPEED")) + if (rage::CTrainTrack* track = getAndCheckTrack<0>(context, "GET_TRACK_MAX_SPEED")) { context.SetResult(track->m_speed); } @@ -128,7 +249,7 @@ static InitFunction initFunction([]() fx::ScriptEngine::RegisterNativeHandler("GET_TRACK_BRAKING_DISTANCE", [](fx::ScriptContext& context) { - if (rage::CTrainTrack* track = getAndCheckTrack(context, "GET_TRACK_BRAKING_DISTANCE")) + if (rage::CTrainTrack* track = getAndCheckTrack<0>(context, "GET_TRACK_BRAKING_DISTANCE")) { context.SetResult(track->m_brakeDistance); } @@ -137,7 +258,7 @@ static InitFunction initFunction([]() fx::ScriptEngine::RegisterNativeHandler("SET_TRACK_BRAKING_DISTANCE", [](fx::ScriptContext& context) { int brakeDistance = context.CheckArgument(1); - if (rage::CTrainTrack* track = getAndCheckTrack(context, "SET_TRACK_BRAKING_DISTANCE")) + if (rage::CTrainTrack* track = getAndCheckTrack<0>(context, "SET_TRACK_BRAKING_DISTANCE")) { track->m_brakeDistance = brakeDistance; } @@ -146,7 +267,7 @@ static InitFunction initFunction([]() fx::ScriptEngine::RegisterNativeHandler("SET_TRACK_ENABLED", [](fx::ScriptContext& context) { bool state = context.GetArgument(1); - if (rage::CTrainTrack* track = getAndCheckTrack(context, "SET_TRACK_ENABLED")) + if (rage::CTrainTrack* track = getAndCheckTrack<0>(context, "SET_TRACK_ENABLED")) { track->m_enabled = state; } @@ -154,7 +275,7 @@ static InitFunction initFunction([]() fx::ScriptEngine::RegisterNativeHandler("IS_TRACK_ENABLED", [](fx::ScriptContext& context) { - if (rage::CTrainTrack* track = getAndCheckTrack(context, "IS_TRACK_ENABLED")) + if (rage::CTrainTrack* track = getAndCheckTrack<0>(context, "IS_TRACK_ENABLED")) { context.SetResult(track->m_enabled); } @@ -162,9 +283,87 @@ static InitFunction initFunction([]() fx::ScriptEngine::RegisterNativeHandler("IS_TRACK_SWITCHED_OFF", [](fx::ScriptContext& context) { - if (rage::CTrainTrack* track = getAndCheckTrack(context, "IS_TRACK_SWITCHED_OFF")) + if (rage::CTrainTrack* track = getAndCheckTrack<0>(context, "IS_TRACK_SWITCHED_OFF")) { context.SetResult(track->m_disableAmbientTrains); } }); + + fx::ScriptEngine::RegisterNativeHandler("REGISTER_TRACK_SWITCH", [](fx::ScriptContext& context) + { + if (rage::CTrainTrack* track = getAndCheckTrack<0>(context, "REGISTER_TRACK_SWITCH")) + { + int trackNode = context.GetArgument(1); + int newTrackNode = context.GetArgument(3); + + if (track->m_nodeCount < trackNode) + { + fx::scripting::Warningf("natives", "REGISTER_TRACK_SWITCH: Invalid onTrackNode was passed (%i), should be from 0 to %i\n", trackNode, track->m_nodeCount); + return; + } + + // get the track we want to register the switch to + rage::CTrainTrack* newTrack = getAndCheckTrack<2>(context, "REGISTER_TRACK_SWITCH"); + + if (!newTrack) + { + fx::scripting::Warningf("natives", "REGISTER_TRACK_SWITCH: Track (%i) is invalid\n", context.GetArgument(2)); + return; + } + + if (newTrack->m_nodeCount < newTrackNode) + { + fx::scripting::Warningf("natives", "REGISTER_TRACK_SWITCH: Invalid toNode was passed (%i), should be from 0 to %i\n", newTrackNode, newTrack->m_nodeCount); + return; + } + + rage::CTrackNode onNode = track->m_nodes[trackNode]; + rage::CTrackNode toNode = newTrack->m_nodes[newTrackNode]; + + float dist = calculateDistance(onNode.m_x, onNode.m_y, onNode.m_z, toNode.m_x, toNode.m_y, toNode.m_z); + + if (dist > 15.0f) + { + fx::scripting::Warningf("natives", "REGISTER_TRACK_SWITCH: the specified track nodes must overlap each other\n"); + return; + } + + // Ensure we don't allow duplicates + uint8_t trackIndex = context.GetArgument(0); + uint8_t swapIndex = context.GetArgument(2); + + bool direction = context.GetArgument(4); + + for (const auto& data : g_trackSwaps) + { + if (data.direction == direction && + data.onTrack == trackIndex && + data.newTrack == swapIndex && + data.onNode == trackNode && + data.newNode == newTrackNode) + { + fx::scripting::Warningf("natives", "REGISTER_TRACK_SWITCH: Cannot register duplicate track switches"); + context.SetResult(0); + return; + } + } + + context.SetResult(g_trackSwaps.size()); + g_trackSwaps.push_back(CTrainJunction(trackIndex, trackNode, swapIndex, newTrackNode, direction)); + } + }); + + fx::ScriptEngine::RegisterNativeHandler("SET_TRACK_SWITCH_ACTIVE", [](fx::ScriptContext& context) + { + int index = context.GetArgument(0); + bool state = context.GetArgument(1); + + if (index >= g_trackSwaps.size() || index < 0) + { + fx::scripting::Warningf("natives", "SET_TRACK_SWITCH_ACTIVE: attempted to set state of an invalid track switch (%i)\n", index); + return; + } + + g_trackSwaps[index].isActive = state; + }); }); diff --git a/ext/native-decls/RegisterTrackSwitch.md b/ext/native-decls/RegisterTrackSwitch.md new file mode 100644 index 0000000000..9482c90114 --- /dev/null +++ b/ext/native-decls/RegisterTrackSwitch.md @@ -0,0 +1,22 @@ +--- +ns: CFX +apiset: client +game: gta5 +--- +## REGISTER_TRACK_SWITCH + +```c +int REGISTER_TRACK_SWITCH(int trackIndex, int trackNode, int newIndex, int newNode, bool direction); +``` + +Registers a track switch that when enabled will cause a train on the defined trackIndex, node and direction to change its current track index and begin traveling on the new node + +## Parameters +* **trackIndex**: The index a train should be on +* **trackNode**: The node a train should be on +* **newIndex**: The new index for a train to be placed on +* **newNode**: The new track node for a train to be placed on +* **direction**: The direction a train should be traveling for this switch + +## Return value +The track switch's handle \ No newline at end of file diff --git a/ext/native-decls/SetTrackSwitchActive.md b/ext/native-decls/SetTrackSwitchActive.md new file mode 100644 index 0000000000..e1d98f8613 --- /dev/null +++ b/ext/native-decls/SetTrackSwitchActive.md @@ -0,0 +1,17 @@ +--- +ns: CFX +apiset: client +game: gta5 +--- +## SET_TRACK_SWITCH_ACTIVE + +```c +void SET_TRACK_SWITCH_ACTIVE(int switchHandle, bool state); +``` + +Sets the state of a track switch. + +## Parameters +* **switchHandle**: The handle of the track switch. +* **state**: if the track should be enabled or disabled. +