From 88726823a50d45dffbfaab7c5424c0433353e76c Mon Sep 17 00:00:00 2001 From: Ehbw Date: Sun, 31 Dec 2023 17:27:38 +0000 Subject: [PATCH] feat(gamestate/server): CREATE_TRAIN(_CARRIAGE) command --- .../citizen-server-impl/component.json | 3 +- .../include/parser/Parser.h | 53 ++++ .../include/parser/TrainParser.h | 108 ++++++++ .../include/state/ServerGameState.h | 24 +- .../include/state/SyncTrees_Five.h | 13 +- .../src/state/ServerGameState.cpp | 8 +- .../src/state/ServerGameState_Scripting.cpp | 68 +++++- .../src/state/ServerSetters.cpp | 230 ++++++++++++++++++ .../gta-net-five/src/CloneObjectManager.cpp | 36 +++ ext/native-decls/server/CreateTrain.md | 33 +++ .../server/CreateTrainCarriage.md | 43 ++++ 11 files changed, 589 insertions(+), 30 deletions(-) create mode 100644 code/components/citizen-server-impl/include/parser/Parser.h create mode 100644 code/components/citizen-server-impl/include/parser/TrainParser.h create mode 100644 ext/native-decls/server/CreateTrain.md create mode 100644 ext/native-decls/server/CreateTrainCarriage.md diff --git a/code/components/citizen-server-impl/component.json b/code/components/citizen-server-impl/component.json index 843c9a6445..4b0f95e6fe 100644 --- a/code/components/citizen-server-impl/component.json +++ b/code/components/citizen-server-impl/component.json @@ -27,7 +27,8 @@ "vendor:folly", "vendor:concurrentqueue", "vendor:xenium", - "vendor:rocksdb" + "vendor:rocksdb", + "vendor:tinyxml2-dll" ], "provides": [] } diff --git a/code/components/citizen-server-impl/include/parser/Parser.h b/code/components/citizen-server-impl/include/parser/Parser.h new file mode 100644 index 0000000000..7722170d2b --- /dev/null +++ b/code/components/citizen-server-impl/include/parser/Parser.h @@ -0,0 +1,53 @@ +#pragma once + +#include "StdInc.h" +#include + +#include +#include +#include + +namespace fx::data { + template + class Parser + { + protected: + T m_data; + public: + inline void LoadFile(fwRefContainer resource, std::string_view file) + { + const std::string& rootPath = resource->GetPath(); + + if (rootPath.empty()) + { + return; + } + + fwRefContainer stream = vfs::OpenRead(rootPath + "/" + std::string{ file }); + + if (!stream.GetRef()) + { + return; + } + + auto data = stream->ReadToEnd(); + tinyxml2::XMLDocument document; + tinyxml2::XMLError error = document.Parse(reinterpret_cast(data.data()), data.size()); + + if (error == tinyxml2::XML_SUCCESS) + { + ParseFile(document); + } + }; + + virtual void ParseFile(tinyxml2::XMLDocument&) + { + + } + + T GetData() + { + return m_data; + }; + }; +} diff --git a/code/components/citizen-server-impl/include/parser/TrainParser.h b/code/components/citizen-server-impl/include/parser/TrainParser.h new file mode 100644 index 0000000000..60e3172f56 --- /dev/null +++ b/code/components/citizen-server-impl/include/parser/TrainParser.h @@ -0,0 +1,108 @@ +#pragma once + +#include "Parser.h" + +namespace fx::data +{ + struct CTrainCarriageInfo + { + const char* m_modelName; + + uint32_t m_maxPedsPerCarriage; + bool m_flipModelDir; + bool m_doInteriorLights; + bool m_carriageVertOffset; + uint32_t m_repeatCount; + }; + + struct CTrainConfig + { + const char* m_name; + float m_populateTrainDist; + bool m_announceStations; + bool m_doorsBeep; + bool m_carriagesHang; + bool m_carriagesSwing; + bool m_carriagesUseEvenTrackSpacing; + bool m_linkTracksWithAdjacentStations; + bool m_noRandomSpawns; + float m_carriageGap; + + std::vector m_carriages; + }; + + struct CTrainTrack + { + const char* m_trainConfigName; + bool isPingPongTrack; + bool stopsAtStations; + bool MPStopsAtStations; + uint32_t speed; + uint32_t breakingDist; + }; +} + +namespace fx +{ + class CTrainConfigParser : public fwRefCountable, public data::Parser> + { + void ParseFile(tinyxml2::XMLDocument& document) override + { + tinyxml2::XMLElement* configs = document.FirstChildElement("train_configs"); + + if (!configs) + { + return; + } + + std::vector trainConfigs; + tinyxml2::XMLElement* config = configs->FirstChildElement("train_config"); + + while (config) + { + data::CTrainConfig trainConfig; + trainConfig.m_name = config->Attribute("name"); + trainConfig.m_populateTrainDist = config->IntAttribute("populate_train_dist", 0); + trainConfig.m_announceStations = config->BoolAttribute("announce_stations", false); + trainConfig.m_doorsBeep = config->BoolAttribute("doors_beep", false); + trainConfig.m_carriagesHang = config->BoolAttribute("carriages_hang", false); + trainConfig.m_carriagesSwing = config->BoolAttribute("carriages_swing", false); + trainConfig.m_linkTracksWithAdjacentStations = config->BoolAttribute("link_tracks_with_adjacent_stations", true); + trainConfig.m_noRandomSpawns = config->BoolAttribute("no_random_spawn", true); + trainConfig.m_carriageGap = config->FloatAttribute("carriage_gap", 0.1); + + tinyxml2::XMLElement* carriage = config->FirstChildElement("carriage"); + while (carriage) + { + data::CTrainCarriageInfo carriageInfo{}; + carriageInfo.m_modelName = config->Attribute("model_name"); + carriageInfo.m_maxPedsPerCarriage = config->IntAttribute("max_peds_per_carriage", 0); + carriageInfo.m_flipModelDir = config->BoolAttribute("flip_model_dir", false); + carriageInfo.m_doInteriorLights = config->BoolAttribute("do_interior_lights", false); + carriageInfo.m_carriageVertOffset = config->FloatAttribute("carriage_vert_offset", 1.0); + carriageInfo.m_repeatCount = config->IntAttribute("repeat_count", 1); + + trainConfig.m_carriages.push_back(carriageInfo); + + carriage = carriage->NextSiblingElement("carriage"); + } + + trainConfigs.push_back(trainConfig); + config = config->NextSiblingElement("train_config"); + } + + m_data = trainConfigs; + } + }; + + class CTrainTrackParser : public data::Parser + { + void ParseFile(tinyxml2::XMLDocument& document) + { + + } + }; +} + +DECLARE_INSTANCE_TYPE(fx::CTrainConfigParser); +DECLARE_INSTANCE_TYPE(fx::CTrainTrackParser); diff --git a/code/components/citizen-server-impl/include/state/ServerGameState.h b/code/components/citizen-server-impl/include/state/ServerGameState.h index d68ec1ea21..e9b42d5d56 100644 --- a/code/components/citizen-server-impl/include/state/ServerGameState.h +++ b/code/components/citizen-server-impl/include/state/ServerGameState.h @@ -547,18 +547,18 @@ struct CTrainGameStateDataNodeData bool isEngine; bool isCaboose; - bool unk12; + bool isMissionTrain; bool direction; - bool unk14; + bool hasPassengerCarriages; bool renderDerailed; // 2372 { - bool unk198; //unk198 - bool highPrecisionBlending; //unk224 - bool hasNoThreadId; //unk199 + bool allowRemovalByPopulation; + bool highPrecisionBlending; + bool shouldStopAtStations; // } bool forceDoorsOpen; @@ -648,17 +648,6 @@ enum ePopType POPTYPE_TOOL }; -//TODO: Probably should be moved out of fx::sync namespace -struct scrVector -{ - float x; - int pad; - float y; - int pad2; - float z; - int pad3; -}; - struct SyncTreeBase { public: @@ -1124,6 +1113,9 @@ struct ScriptGuid } }; +auto GetTrain(fx::ServerGameState* sgs, uint32_t objectId) -> fx::sync::SyncEntityPtr; +auto GetNextTrain(fx::ServerGameState* sgs, const fx::sync::SyncEntityPtr& entity) -> fx::sync::SyncEntityPtr; + struct EntityCreationState { // TODO: allow resending in case the target client disappears diff --git a/code/components/citizen-server-impl/include/state/SyncTrees_Five.h b/code/components/citizen-server-impl/include/state/SyncTrees_Five.h index 94b56d6738..72aaa07cd8 100644 --- a/code/components/citizen-server-impl/include/state/SyncTrees_Five.h +++ b/code/components/citizen-server-impl/include/state/SyncTrees_Five.h @@ -2627,22 +2627,23 @@ struct CTrainGameStateDataNode : GenericSerializeDataNode #include +#include #include @@ -839,7 +840,8 @@ void sync::SyncCommandList::Execute(const fx::ClientSharedPtr& client) } #ifdef STATE_FIVE -static auto GetTrain(fx::ServerGameState* sgs, uint32_t objectId) -> fx::sync::SyncEntityPtr + +auto GetTrain(fx::ServerGameState* sgs, uint32_t objectId) -> fx::sync::SyncEntityPtr { if (objectId != 0) { @@ -854,7 +856,7 @@ static auto GetTrain(fx::ServerGameState* sgs, uint32_t objectId) -> fx::sync::S return {}; }; -static auto GetNextTrain(fx::ServerGameState* sgs, const fx::sync::SyncEntityPtr& entity) -> fx::sync::SyncEntityPtr +auto GetNextTrain(fx::ServerGameState* sgs, const fx::sync::SyncEntityPtr& entity) -> fx::sync::SyncEntityPtr { if (auto trainState = entity->syncTree->GetTrainState()) { @@ -6940,8 +6942,10 @@ static InitFunction initFunction([]() g_oneSyncWorkaround763185 = instance->AddVariable("onesync_workaround763185", ConVar_None, false); fwRefContainer sgs = new fx::ServerGameState(); + fwRefContainer trainConfigParser = new fx::CTrainConfigParser(); instance->SetComponent(sgs); instance->SetComponent(sgs); + instance->SetComponent(trainConfigParser); instance->GetComponent()->OnSyncTick.Connect([=]() { diff --git a/code/components/citizen-server-impl/src/state/ServerGameState_Scripting.cpp b/code/components/citizen-server-impl/src/state/ServerGameState_Scripting.cpp index b69d8fdad2..a8b84d4e09 100644 --- a/code/components/citizen-server-impl/src/state/ServerGameState_Scripting.cpp +++ b/code/components/citizen-server-impl/src/state/ServerGameState_Scripting.cpp @@ -2,7 +2,8 @@ #include #include -#include +#include +#include #include #include @@ -10,7 +11,7 @@ #include #include #include - + namespace fx { void DisownEntityScript(const fx::sync::SyncEntityPtr& entity); @@ -1094,9 +1095,47 @@ static void Init() fx::ScriptEngine::RegisterNativeHandler("DELETE_ENTITY", makeEntityFunction([](fx::ScriptContext& context, const fx::sync::SyncEntityPtr& entity) { - auto resourceManager = fx::ResourceManager::GetCurrent(); - auto instance = resourceManager->GetComponent()->Get(); - auto gameState = instance->GetComponent(); + auto resourceManager = fx::ResourceManager::GetCurrent(); + auto instance = resourceManager->GetComponent()->Get(); + auto gameState = instance->GetComponent(); + +#ifdef STATE_FIVE + // If the entity is a server-owned train, loop through all the train carriages and delete + if (entity->IsOwnedByServerScript() && entity->type == fx::sync::NetObjEntityType::Train) + { + if (auto trainState = entity->syncTree->GetTrainState()) + { + auto deleteCarriages = [gameState](const fx::sync::SyncEntityPtr& engine) + { + for (auto link = GetNextTrain(gameState.GetRef(), engine); link; link = GetNextTrain(gameState.GetRef(), link)) + { + if (link->handle != engine->handle) + { + gameState->DeleteEntity(link); + } + } + + //Clean up the engine afterwards + gameState->DeleteEntity(engine); + }; + + if (trainState->isEngine) + { + deleteCarriages(entity); + return 0; + } + + if (trainState->engineCarriage && trainState->engineCarriage != entity->handle) + { + if (auto engine = GetTrain(gameState.GetRef(), trainState->engineCarriage)) + { + deleteCarriages(engine); + return 0; + } + } + } + } +#endif gameState->DeleteEntity(entity); @@ -1446,6 +1485,25 @@ static void Init() return train ? train->carriageIndex : -1; })); + + fx::ScriptEngine::RegisterNativeHandler("LOAD_TRAIN_CONFIG_FROM_PATH", [](fx::ScriptContext& context) + { + auto resourceManager = fx::ResourceManager::GetCurrent(); + + fwRefContainer resource = resourceManager->GetResource(context.CheckArgument(0)); + + if (!resource.GetRef()) + { + return; + } + + std::string path = context.CheckArgument(1); + + // get the owning server instance + auto instance = resourceManager->GetComponent()->Get(); + + instance->GetComponent()->LoadFile(resource, path); + }); fx::ScriptEngine::RegisterNativeHandler("GET_PLAYER_FAKE_WANTED_LEVEL", MakePlayerEntityFunction([](fx::ScriptContext& context, const fx::sync::SyncEntityPtr& entity) { diff --git a/code/components/citizen-server-impl/src/state/ServerSetters.cpp b/code/components/citizen-server-impl/src/state/ServerSetters.cpp index 268cde134b..fc97706e40 100644 --- a/code/components/citizen-server-impl/src/state/ServerSetters.cpp +++ b/code/components/citizen-server-impl/src/state/ServerSetters.cpp @@ -5,13 +5,16 @@ #include #include +#include #include +#include #include #include #include #include +#include namespace fx { @@ -156,6 +159,42 @@ std::shared_ptr MakeVehicle(uint32_t model, float posX, floa return tree; } +template +std::shared_ptr MakeTrain(uint32_t model, float posX, float posY, float posZ, float heading, uint32_t resourceHash, bool isEngine, bool direction, bool stopAtStation, float speed, uint32_t trackid, uint32_t trainConfigIndex) +{ + auto tree = std::static_pointer_cast(MakeVehicle(model, posX, posY, posZ, heading, resourceHash)); + + SetupNode(tree, [isEngine, speed, direction, trackid, trainConfigIndex, stopAtStation](sync::CTrainGameStateDataNode& cdn) + { + cdn.data.linkedToBackwardId = 0; + cdn.data.linkedToForwardId = 0; + cdn.data.distanceFromEngine = 0.0f; + + cdn.data.trainState = 3; + + cdn.data.isEngine = isEngine; + cdn.data.cruiseSpeed = speed; + cdn.data.direction = direction; + cdn.data.trackId = trackid; + cdn.data.trainConfigIndex = trainConfigIndex; + + cdn.data.isCaboose = true; + + cdn.data.isMissionTrain = true; + cdn.data.hasPassengerCarriages = true; + cdn.data.renderDerailed = false; + + if (Is2372()) + { + cdn.data.allowRemovalByPopulation = false; + cdn.data.shouldStopAtStations = stopAtStation; + cdn.data.highPrecisionBlending = false; + } + }); + + return tree; +} + std::shared_ptr MakePed(uint32_t model, float posX, float posY, float posZ, uint32_t resourceHash, float heading = 0.0f) { auto tree = std::make_shared(); @@ -294,6 +333,38 @@ void DisownEntityScript(const fx::sync::SyncEntityPtr& entity) } } +static auto GetTrainCarriageCount(fx::ServerGameState* sgs, const fx::sync::SyncEntityPtr& engine) -> int +{ + if (engine->type != fx::sync::NetObjEntityType::Train) + { + return -1; + } + + int carriageCount = 0; + for (auto link = GetNextTrain(sgs, engine); link; link = GetNextTrain(sgs, link)) + { + carriageCount++; + } + + return carriageCount; +}; + +static auto GetTrainCabooseCarriage(fx::ServerGameState* sgs, const fx::sync::SyncEntityPtr& engine) -> fx::sync::SyncEntityPtr +{ + fx::sync::SyncEntityPtr caboose = engine; + for (auto link = GetNextTrain(sgs, engine); link; link = GetNextTrain(sgs, link)) + { + auto state = link->syncTree->GetTrainState(); + + if (state->linkedToBackwardId == 0 || state->isCaboose) + { + caboose = link; + break; + } + } + return caboose; +} + static InitFunction initFunction([]() { fx::ServerInstanceBase::OnServerCreate.Connect([](fx::ServerInstanceBase* ref) @@ -447,6 +518,165 @@ static InitFunction initFunction([]() ctx.SetResult(sgs->MakeScriptHandle(entity)); }); + fx::ScriptEngine::RegisterNativeHandler("CREATE_TRAIN", [=](fx::ScriptContext& ctx) + { + uint32_t resourceHash = 0; + + fx::OMPtr runtime; + + if (FX_SUCCEEDED(fx::GetCurrentScriptRuntime(&runtime))) + { + fx::Resource* resource = reinterpret_cast(runtime->GetParentObject()); + + if (resource) + { + resourceHash = HashString(resource->GetName().c_str()); + } + } + + uint32_t modelHash = ctx.GetArgument(0); + float x = ctx.GetArgument(1); + float y = ctx.GetArgument(2); + float z = ctx.GetArgument(3); + bool direction = ctx.GetArgument(4); + bool stopAtStations = ctx.GetArgument(5); + float speed = ctx.GetArgument(6); + uint32_t trackId = ctx.GetArgument(7); + uint32_t trainConfigIndex = ctx.CheckArgument(8); + + //Don't create train if an invalid trackid has been provided. + if (trackId != 0 && trackId != 3) + { + throw std::runtime_error(va("Tried to spawn train on invalid track id: %u", trackId)); + + ctx.SetResult(0); + return; + } + + auto& trainConfigs = ref->GetComponent(); + + //Don't create train if an invalid trainConfigIndex has been provided, otherwise the client crashes. + if (trainConfigIndex > (trainConfigs->GetData().empty() ? (uint32_t)(Is3095() ? 27 : (Is2802() ? 26 : (Is2372() ? 25 : 24))) : trainConfigs->GetData().size())) + { + throw std::runtime_error(va("Tried to spawn train with invalid train config index: %u", trainConfigIndex)); + + ctx.SetResult(0); + return; + } + + + auto tree = MakeTrain(modelHash, x, y, z, 0.0f, resourceHash, true, direction, stopAtStations, speed, trackId, trainConfigIndex); + auto& sgs = ref->GetComponent(); + auto entity = sgs->CreateEntityFromTree(sync::NetObjEntityType::Train, tree); + + //Edit CTrainGameStateData node to assign engineCarriage to the newly created objectID + SetupNode(tree, [entity](sync::CTrainGameStateDataNode& cdn) + { + cdn.data.engineCarriage = entity->handle; + }); + + ctx.SetResult(sgs->MakeScriptHandle(entity)); + }); + + fx::ScriptEngine::RegisterNativeHandler("CREATE_TRAIN_CARRIAGE", [=](fx::ScriptContext& ctx) + { + // get the current resource manager + auto resourceManager = fx::ResourceManager::GetCurrent(); + + // get the owning server instance + auto instance = resourceManager->GetComponent()->Get(); + + // get the server's game state + auto gameState = instance->GetComponent(); + + // parse the client ID + auto id = ctx.GetArgument(0); + + if (!id) + { + ctx.SetResult(0); + return; + } + + auto entity = gameState->GetEntity(id); + + //Make sure entity exists. + if (!entity) + { + throw std::runtime_error(va("Tried to access invalid entity: %d", id)); + + ctx.SetResult(0); + return; + } + + auto trainState = entity->syncTree->GetTrainState(); + + // Make sure entity is a train + if (entity->type != fx::sync::NetObjEntityType::Train) + { + throw std::runtime_error(va("Entity is not a train: %d", id)); + + ctx.SetResult(0); + return; + } + + // Make sure entity is the engine carriage + if (!trainState || !trainState->isEngine) + { + throw std::runtime_error(va("Tried to attach train carriage to invalid train entity: %d", id)); + + ctx.SetResult(0); + return; + } + + uint32_t resourceHash = 0; + fx::OMPtr runtime; + + if (FX_SUCCEEDED(fx::GetCurrentScriptRuntime(&runtime))) + { + fx::Resource* resource = reinterpret_cast(runtime->GetParentObject()); + + if (resource) + { + resourceHash = HashString(resource->GetName().c_str()); + } + } + + uint32_t modelHash = ctx.GetArgument(1); + float distanceFromEngine = ctx.GetArgument(2); + + float position[3]; + entity->syncTree->GetPosition(position); + + auto tree = MakeTrain(modelHash, position[0], position[1], position[2], 0.0f, resourceHash, false, trainState->direction, trainState->shouldStopAtStations, trainState->cruiseSpeed, trainState->trackId, trainState->trainConfigIndex); + auto& sgs = ref->GetComponent(); + + if (tree) + { + SetupNode(tree, [sgs, entity, trainState, distanceFromEngine](sync::CTrainGameStateDataNode& cdn){ + cdn.data.engineCarriage = entity->handle; + cdn.data.linkedToForwardId = GetTrainCabooseCarriage(sgs.GetRef(), entity)->handle; + cdn.data.carriageIndex = GetTrainCarriageCount(sgs.GetRef(), entity) + 1; + // If the train direction is forward the distanceFromEngine has to be negative + cdn.data.distanceFromEngine = trainState->direction ? -distanceFromEngine : +distanceFromEngine; + cdn.data.isEngine = false; + cdn.data.isCaboose = true; + }); + } + + auto carriageEntity = sgs->CreateEntityFromTree(fx::sync::NetObjEntityType::Train, tree); + auto caboose = GetTrainCabooseCarriage(sgs.GetRef(), entity); + + auto trainSyncTree = std::static_pointer_cast(caboose->syncTree); + SetupNode(trainSyncTree, [carriageEntity](sync::CTrainGameStateDataNode& cdn) + { + cdn.data.linkedToBackwardId = carriageEntity->handle; + cdn.data.isCaboose = false; + }); + + ctx.SetResult(sgs->MakeScriptHandle(carriageEntity)); + }); + fx::ScriptEngine::RegisterNativeHandler("CREATE_OBJECT_NO_OFFSET", [=](fx::ScriptContext& ctx) { uint32_t resourceHash = 0; diff --git a/code/components/gta-net-five/src/CloneObjectManager.cpp b/code/components/gta-net-five/src/CloneObjectManager.cpp index 98da61de9a..dcc7453673 100644 --- a/code/components/gta-net-five/src/CloneObjectManager.cpp +++ b/code/components/gta-net-five/src/CloneObjectManager.cpp @@ -1,6 +1,13 @@ #include #include +//Required for server-spawned train blending +#ifdef GTA_FIVE +#include +#include +#include +#endif + #include #include @@ -80,6 +87,15 @@ static void netObjectMgrBase__DestroyNetworkObject(rage::netObjectMgr* manager, delete object; } } + + +#ifdef GTA_FIVE +static int TrainTrackNodeIndexOffset; +static hook::cdecl_stub _SetTrainCoord([]() +{ + return hook::pattern("41 B1 01 48 8B D9 45 8A C1 C6 44 24 ? ? FF 90 ? ? ? ? 83 CA FF").count(1).get(0).get(0x22); +}); +#endif static void(*g_orig_netObjectMgrBase__ChangeOwner)(rage::netObjectMgr*, rage::netObject*, CNetGamePlayer*, int); @@ -96,6 +112,24 @@ static void netObjectMgrBase__ChangeOwner(rage::netObjectMgr* manager, rage::net object->PostMigrate(migrationType); CloneObjectMgr->ChangeOwner(object, oldOwnerId, targetPlayer, migrationType); +#ifdef GTA_FIVE + //Check that the entity is a train and that we own the entity + if (object->objectType == (uint16_t)NetObjEntityType::Train && targetPlayer->physicalPlayerIndex() == 128) + { + //Make sure that the gameObject isn't a nullptr + if (auto pVehicle = object->GetGameObject()) + { + //Only force blend if the train track node is 0, as this is the only time we need to correct position + //this does not have any affect on trains that are actually at track node 0 + if ((int)*(int*)((char*)pVehicle + TrainTrackNodeIndexOffset) == 0) + { + _SetTrainCoord((CVehicle*)pVehicle, -1, -1); + // Force blend + object->GetBlender()->m_30(); + } + } + } +#endif } static rage::netObject* (*g_orig_netObjectMgrBase__GetNetworkObject)(rage::netObjectMgr* manager, uint16_t id, bool evenIfDeleting); @@ -148,6 +182,8 @@ static HookFunction hookFunction([]() MH_Initialize(); #if GTA_FIVE + //Taken from extra-natives-five/VehicleExtraNatives.cpp + TrainTrackNodeIndexOffset = *hook::get_pattern("E8 ? ? ? ? 40 8A F8 84 C0 75 ? 48 8B CB E8", -4); MH_CreateHook(hook::get_pattern("48 8B F2 0F B7 52 0A 41 B0 01", -0x19), netObjectMgrBase__RegisterNetworkObject, (void**)&g_orig_netObjectMgrBase__RegisterNetworkObject); // MH_CreateHook(hook::get_pattern("8A 42 4C 45 33 FF 48 8B DA C0 E8 02", -0x21), netObjectMgrBase__DestroyNetworkObject, (void**)&g_orig_netObjectMgrBase__DestroyNetworkObject); // MH_CreateHook(hook::get_pattern("44 8A 62 4B 33 DB 41 8B E9", -0x20), netObjectMgrBase__ChangeOwner, (void**)&g_orig_netObjectMgrBase__ChangeOwner); // diff --git a/ext/native-decls/server/CreateTrain.md b/ext/native-decls/server/CreateTrain.md new file mode 100644 index 0000000000..59101ca219 --- /dev/null +++ b/ext/native-decls/server/CreateTrain.md @@ -0,0 +1,33 @@ +--- +ns: CFX +apiset: server +--- +## CREATE_TRAIN + +```c +Vehicle CREATE_TRAIN(Hash modelHash, float x, float y, float z, bool direction, bool stopsAtStations, float cruiseSpeed, int trackId, int trainConfigIndex); +``` + +Creates a functioning train using 'server setter' logic (like CREATE_VEHICLE_SERVER_SETTER and CREATE_AUTOMOBILE). + +## Parameters +* **modelHash**: The model of train to spawn. +* **x**: Spawn coordinate X component. +* **y**: Spawn coordinate Y component. +* **z**: Spawn coordinate Z component. +* **direction**: The direction in which the train will go (true = forward, false = backward) +* **stopsAtStations**: Should the train stop at stations on the track (>b2372) +* **cruiseSpeed**: The desired speed for the train to achieve (maximum 30.0f) +* **trackId**: The track the train should follow (0 = Main Track, 3 = Metro track) +* **trainConfigIndex**: The train variation id, found in trains.xml + +## Return value +A script handle for the train. + +## Examples +```lua +-- Coordinates of the first node (0) in track 3 +local coordinates = vec3(193.196, -603.836, 16.7565) +local metro = CreateTrain(`metrotrain`, coordinates, true, true, 12.0, 3, 25) +print(GetTrainCarriageIndex(metro)) -- should return 0 +``` \ No newline at end of file diff --git a/ext/native-decls/server/CreateTrainCarriage.md b/ext/native-decls/server/CreateTrainCarriage.md new file mode 100644 index 0000000000..62cf630a65 --- /dev/null +++ b/ext/native-decls/server/CreateTrainCarriage.md @@ -0,0 +1,43 @@ +--- +ns: CFX +apiset: server +--- +## CREATE_TRAIN_CARRIAGE + +```c +Vehicle CREATE_TRAIN_CARRIAGE(Vehicle train, Hash modelHash, float distanceFromEngine); +``` + +Creates and attaches train carriage to the target train. + +## Parameters +* **train**: The target train. +* **modelHash**: The model of train to spawn. +* **distanceFromEngine**: how far the carriage should be from the engine carriage, maximum 1000.0f + +## Return value +A script handle for the train carriage. + +## Examples +```lua +--[[ + To work out distanceFromEngine: + First Carriage: + lengthOfCarriage = (modelBoundBoxMaxY - modelBoundBoxMinY) + distanceFromEngine = lengthOfCarriage + carriageGap (found in trains.xml) + + Each Additional Carriage: add distance to previous carriage distanceFromEngine + + Example with metrotrain, trainconfigIndex 26 (b2802) + lengthOfCarriage = (5.611063 - -5.667999) + distanceFromEngine = lengthOfCarriage + -0.5 = 10.7791 +]] + +-- Coordinates of the first node (0) in track 3 +local coordinates = vec3(193.196, -603.836, 16.7565) +local metro = CreateTrain(`metrotrain`, coordinates, true, true, 12.0, 3, 25) + +-- This carriage would be flipped as in trains.xml 'flip_model_dir' is true for the second carriage +local metroCarriage = CreateTrainCarriage(metro, `metrotrain`, 10.7791) +print(GetTrainCarriageIndex(metroCarriage)) -- Will return 1. +``` \ No newline at end of file