Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(extra-natives/five): track switching #3044

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 213 additions & 10 deletions code/components/extra-natives-five/src/TrackNatives.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@
#include "StdInc.h"
#include <ScriptEngine.h>
#include <Hooking.h>
#include <Hooking.Stubs.h>

#include <limits>
#include <MinHook.h>
#include <rageVectors.h>
#include "ScriptWarnings.h"
#include <Train.h>
#include <GameInit.h>

static hook::cdecl_stub<rage::CTrainTrack* (uint32_t)> getTrainTrack([]
{
Expand All @@ -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;
Expand Down Expand Up @@ -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<CTrainJunction> 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<uint8_t*>(this) + kTrackIndexOffset;
return *reinterpret_cast<int8_t*>(location);
}

inline uint32_t GetTrackNode()
{
auto location = reinterpret_cast<uint8_t*>(this) + kTrackNodeOffset;
return *reinterpret_cast<uint32_t*>(location);
}

inline bool GetDirection()
{
auto location = reinterpret_cast<uint8_t*>(this) + kTrainFlagsOffset;
return (*(BYTE*)(this + kTrainFlagsOffset) & 8) != 0;
}

inline void SetTrackIndex(int8_t trackIndex)
{
auto location = reinterpret_cast<uint8_t*>(this) + kTrackIndexOffset;
*(int8_t*)location = trackIndex;
}

inline void SetTrackNode(uint32_t trackNode)
{
auto location = reinterpret_cast<uint8_t*>(this) + kTrackNodeOffset;
*(uint32_t*)location = trackNode;
}
};

static hook::cdecl_stub<void(CVehicle*, int, int)> CTrain__SetTrainCoord([]()
{
return hook::pattern("44 8B C2 48 83 C4 ? 5B").count(1).get(0).get<void>(8);
});

static hook::cdecl_stub<CTrain* (CTrain*)> 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();
Expand All @@ -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<uint8_t>(hook::get_pattern("83 BE ? ? ? ? ? 77 ? 44 21 65", 6), 0xF);

{
CTrain::kTrackNodeOffset = *hook::get_pattern<uint32_t>("E8 ? ? ? ? 40 8A F8 84 C0 75 ? 48 8B CB E8", -4);
CTrain::kTrackIndexOffset = *hook::get_pattern<uint32_t>("88 87 ? ? ? ? 48 85 F6 75", 2);
CTrain::kTrainFlagsOffset = *hook::get_pattern<uint32_t>("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<int ArgumentIndex>
static rage::CTrainTrack* getAndCheckTrack(fx::ScriptContext& context, std::string_view nn)
{
int trackIndex = context.GetArgument<int>(0);
int trackIndex = context.GetArgument<int>(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;
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -112,23 +233,23 @@ static InitFunction initFunction([]()
{
int maxSpeed = context.CheckArgument<int>(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;
}
});

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<int>(track->m_speed);
}
});

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<int>(track->m_brakeDistance);
}
Expand All @@ -137,7 +258,7 @@ static InitFunction initFunction([]()
fx::ScriptEngine::RegisterNativeHandler("SET_TRACK_BRAKING_DISTANCE", [](fx::ScriptContext& context)
{
int brakeDistance = context.CheckArgument<int>(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;
}
Expand All @@ -146,25 +267,107 @@ static InitFunction initFunction([]()
fx::ScriptEngine::RegisterNativeHandler("SET_TRACK_ENABLED", [](fx::ScriptContext& context)
{
bool state = context.GetArgument<bool>(1);
if (rage::CTrainTrack* track = getAndCheckTrack(context, "SET_TRACK_ENABLED"))
if (rage::CTrainTrack* track = getAndCheckTrack<0>(context, "SET_TRACK_ENABLED"))
{
track->m_enabled = state;
}
});

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<bool>(track->m_enabled);
}
});

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<bool>(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<int>(1);
int newTrackNode = context.GetArgument<int>(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);
context.SetResult<int>(-1);
return;
Ehbw marked this conversation as resolved.
Show resolved Hide resolved
}

// 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<uint8_t>(2));
context.SetResult<int>(-1);
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);
Comment on lines +316 to +318
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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);
if (newTrack->m_nodeCount - 1 < newTrackNode)
{
fx::scripting::Warningf("natives", "REGISTER_TRACK_SWITCH: Invalid toNode was passed (%i), should be from 0 to %i\n", newTrackNode, newTrack->m_nodeCount - 1);

Stops Out Of Bounds stuff

context.SetResult<int>(-1);
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");
context.SetResult<int>(-1);
return;
}

// Ensure we don't allow duplicates
uint8_t trackIndex = context.GetArgument<uint8_t>(0);
uint8_t swapIndex = context.GetArgument<uint8_t>(2);

bool direction = context.GetArgument<bool>(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<int>(-1);
return;
}
}

context.SetResult<int>(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<int>(0);
bool state = context.GetArgument<bool>(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;
});
});
22 changes: 22 additions & 0 deletions ext/native-decls/RegisterTrackSwitch.md
Original file line number Diff line number Diff line change
@@ -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 or -1 if invalid.
17 changes: 17 additions & 0 deletions ext/native-decls/SetTrackSwitchActive.md
Original file line number Diff line number Diff line change
@@ -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.

Loading