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 e0555caa56..b166866c37 100644 --- a/code/components/citizen-server-impl/include/state/SyncTrees_Five.h +++ b/code/components/citizen-server-impl/include/state/SyncTrees_Five.h @@ -446,6 +446,94 @@ struct NodeWrapper : public NodeBase } }; +struct ParseSerializer +{ + inline ParseSerializer(SyncParseState* state) + : state(state) + { + } + + template + bool Serialize(int size, T& data) + { + return state->buffer.Read(size, &data); + } + + bool Serialize(bool& data) + { + data = state->buffer.ReadBit(); + return true; + } + + bool Serialize(int size, float div, float& data) + { + data = state->buffer.ReadFloat(size, div); + return true; + } + + bool SerializeSigned(int size, float div, float& data) + { + data = state->buffer.ReadSignedFloat(size, div); + return true; + } + + static constexpr bool isReader = true; + SyncParseState* state; +}; + +struct UnparseSerializer +{ + inline UnparseSerializer(SyncUnparseState* state) + : state(state) + { + } + + template + bool Serialize(int size, T& data) + { + state->buffer.Write(size, data); + return true; + } + + bool Serialize(bool& data) + { + return state->buffer.WriteBit(data); + } + + bool Serialize(int size, float div, float& data) + { + state->buffer.WriteFloat(size, div, data); + return true; + } + + bool SerializeSigned(int size, float div, float& data) + { + state->buffer.WriteSignedFloat(size, div, data); + return true; + } + + static constexpr bool isReader = false; + SyncUnparseState* state; +}; + +template +struct GenericSerializeDataNode +{ + bool Parse(SyncParseState& state) + { + auto self = static_cast(this); + auto serializer = ParseSerializer{ &state }; + return self->Serialize(serializer); + } + + bool Unparse(SyncUnparseState& state) + { + auto self = static_cast(this); + auto serializer = UnparseSerializer{ &state }; + return self->Serialize(serializer); + } +}; + struct CVehicleCreationDataNode { uint32_t m_model; @@ -492,8 +580,9 @@ struct CVehicleCreationDataNode return true; } - bool Unparse(rl::MessageBuffer& buffer) + bool Unparse(sync::SyncUnparseState& state) { + auto& buffer = state.buffer; buffer.Write(32, m_model); buffer.Write(4, (uint8_t)m_popType); @@ -542,8 +631,9 @@ struct CAutomobileCreationDataNode return true; } - bool Unparse(rl::MessageBuffer& buffer) + bool Unparse(SyncUnparseState& state) { + rl::MessageBuffer& buffer = state.buffer; buffer.WriteBit(allDoorsClosed); if (!allDoorsClosed) @@ -559,8 +649,45 @@ struct CAutomobileCreationDataNode }; struct CGlobalFlagsDataNode { bool Parse(SyncParseState& state) { return true; } }; -struct CDynamicEntityGameStateDataNode { bool Parse(SyncParseState& state) { return true; } }; -struct CPhysicalGameStateDataNode { bool Parse(SyncParseState& state) { return true; } }; + +struct CDynamicEntityGameStateDataNode : GenericSerializeDataNode +{ + template + bool Serialize(Serializer& s) + { + return true; + } +}; + +struct CPhysicalGameStateDataNode : GenericSerializeDataNode +{ + bool isVisible; + bool flag2; + bool flag3; + bool flag4; + + int val1; + + template + bool Serialize(Serializer& s) + { + s.Serialize(isVisible); + s.Serialize(flag2); + s.Serialize(flag3); + s.Serialize(flag4); + + if (flag4) + { + s.Serialize(3, val1); + } + else + { + val1 = 0; + } + + return true; + } +}; struct CVehicleGameStateDataNode { @@ -829,8 +956,10 @@ struct CEntityScriptInfoDataNode return true; } - bool Unparse(rl::MessageBuffer& buffer) + bool Unparse(sync::SyncUnparseState& state) { + rl::MessageBuffer& buffer = state.buffer; + if (m_scriptHash) { buffer.WriteBit(true); @@ -1239,8 +1368,10 @@ struct CSectorDataNode return true; } - bool Unparse(rl::MessageBuffer& buffer) + bool Unparse(sync::SyncUnparseState& state) { + rl::MessageBuffer& buffer = state.buffer; + buffer.Write(10, m_sectorX); buffer.Write(10, m_sectorY); buffer.Write(6, m_sectorZ); @@ -1270,8 +1401,9 @@ struct CSectorPositionDataNode return true; } - bool Unparse(rl::MessageBuffer& buffer) + bool Unparse(sync::SyncUnparseState& state) { + rl::MessageBuffer& buffer = state.buffer; buffer.WriteFloat(12, 54.0f, m_posX); buffer.WriteFloat(12, 54.0f, m_posY); buffer.WriteFloat(12, 69.0f, m_posZ); @@ -1280,52 +1412,101 @@ struct CSectorPositionDataNode } }; -struct CPedCreationDataNode +struct CPedCreationDataNode : GenericSerializeDataNode { uint32_t m_model; ePopType m_popType; - bool Parse(SyncParseState& state) + bool isRespawnObjectId; + bool respawnFlaggedForRemoval; + + uint16_t randomSeed; + + uint32_t voiceHash; + + uint16_t vehicleId; + int vehicleSeat; + + uint32_t prop; + + bool isStanding; + int attributeDamageToPlayer; + + int maxHealth; + bool unkBool; + + template + bool Serialize(TSerializer& s) { - auto isRespawnObjectId = state.buffer.ReadBit(); - auto respawnFlaggedForRemoval = state.buffer.ReadBit(); + // false + s.Serialize(isRespawnObjectId); - auto popType = state.buffer.Read(4); - uint32_t model = state.buffer.Read(32); - m_model = model; + // false + s.Serialize(respawnFlaggedForRemoval); + // 7(?) + auto popType = (int)m_popType; + s.Serialize(4, popType); m_popType = (ePopType)popType; - auto randomSeed = state.buffer.Read(16); - auto inVehicle = state.buffer.ReadBit(); - auto unkVal = state.buffer.Read(32); + // model + s.Serialize(32, m_model); - uint16_t vehicleId = 0; - int vehicleSeat = 0; + // 6841 + s.Serialize(16, randomSeed); + + // false + bool inVehicle = vehicleId != 0; + s.Serialize(inVehicle); + + // NO_VOICE -> 0x87BFF09A + s.Serialize(32, voiceHash); if (inVehicle) { - vehicleId = state.buffer.Read(13); - vehicleSeat = state.buffer.Read(5); + s.Serialize(13, vehicleId); + s.Serialize(5, vehicleSeat); + } + else + { + vehicleId = 0; + vehicleSeat = 0; } - auto hasProp = state.buffer.ReadBit(); + // false + auto hasProp = prop != 0; + s.Serialize(hasProp); if (hasProp) { - auto prop = state.buffer.Read(32); + s.Serialize(32, prop); + } + else + { + prop = 0; } - auto isStanding = state.buffer.ReadBit(); - auto hasAttDamageToPlayer = state.buffer.ReadBit(); + // true + s.Serialize(isStanding); + + // false + auto hasAttDamageToPlayer = attributeDamageToPlayer >= 0; + s.Serialize(hasAttDamageToPlayer); if (hasAttDamageToPlayer) { - auto attributeDamageToPlayer = state.buffer.Read(5); + s.Serialize(5, attributeDamageToPlayer); + } + else + { + attributeDamageToPlayer = -1; } - auto maxHealth = state.buffer.Read(13); - auto unkBool = state.buffer.ReadBit(); + // 200 + s.Serialize(13, maxHealth); + + // false + s.Serialize(unkBool); return true; } @@ -1444,11 +1625,12 @@ struct CPedGameStateDataNode } }; -struct CEntityOrientationDataNode +struct CEntityOrientationDataNode : GenericSerializeDataNode { CEntityOrientationNodeData data; - bool Parse(SyncParseState& state) + template + bool Serialize(Serializer& s) { #if 0 auto rotX = state.buffer.ReadSigned(9) * 0.015625f; @@ -1459,10 +1641,10 @@ struct CEntityOrientationDataNode data.rotY = rotY; data.rotZ = rotZ; #else - data.quat.largest = state.buffer.Read(2); - data.quat.integer_a = state.buffer.Read(11); - data.quat.integer_b = state.buffer.Read(11); - data.quat.integer_c = state.buffer.Read(11); + s.Serialize(2, data.quat.largest); + s.Serialize(11, data.quat.integer_a); + s.Serialize(11, data.quat.integer_b); + s.Serialize(11, data.quat.integer_c); #endif return true; @@ -1565,6 +1747,23 @@ struct CHeliControlDataNode { bool Parse(SyncParseState& state) { return true; } struct CObjectCreationDataNode { uint32_t m_model; + bool m_dynamic; + + // #TODO: universal serializer + bool Unparse(SyncUnparseState& state) + { + state.buffer.Write(5, 1); + state.buffer.Write(32, m_model); + state.buffer.WriteBit(m_dynamic); + state.buffer.WriteBit(false); + state.buffer.WriteBit(false); + + state.buffer.WriteBit(false); + state.buffer.WriteBit(false); + state.buffer.WriteBit(false); + + return true; + } bool Parse(SyncParseState& state) { @@ -1649,27 +1848,28 @@ struct CObjectGameStateDataNode { bool Parse(SyncParseState& state) { return tru struct CObjectScriptGameStateDataNode { bool Parse(SyncParseState& state) { return true; } }; struct CPhysicalHealthDataNode { bool Parse(SyncParseState& state) { return true; } }; -struct CObjectSectorPosNode +struct CObjectSectorPosNode : GenericSerializeDataNode { - float m_sectorPosX; - float m_sectorPosY; - float m_sectorPosZ; + bool highRes; + float m_posX; + float m_posY; + float m_posZ; - bool Parse(SyncParseState& state) + template + bool Serialize(Serializer& s) { - bool highRes = state.buffer.ReadBit(); + s.Serialize(highRes); int bits = (highRes) ? 20 : 12; - auto posX = state.buffer.ReadFloat(bits, 54.0f); - auto posY = state.buffer.ReadFloat(bits, 54.0f); - auto posZ = state.buffer.ReadFloat(bits, 69.0f); + s.Serialize(bits, 54.0f, m_posX); + s.Serialize(bits, 54.0f, m_posY); + s.Serialize(bits, 69.0f, m_posZ); - m_sectorPosX = posX; - m_sectorPosY = posY; - m_sectorPosZ = posZ; - - state.entity->syncTree->CalculatePosition(); + if constexpr (typename Serializer::isReader) + { + s.state->entity->syncTree->CalculatePosition(); + } return true; } @@ -1787,17 +1987,15 @@ struct CPedMovementGroupDataNode { bool Parse(SyncParseState& state) { return tr struct CPedAIDataNode { bool Parse(SyncParseState& state) { return true; } }; struct CPedAppearanceDataNode { bool Parse(SyncParseState& state) { return true; } }; -struct CPedOrientationDataNode +struct CPedOrientationDataNode : GenericSerializeDataNode { CPedOrientationNodeData data; - bool Parse(SyncParseState& state) + template + bool Serialize(Serializer& s) { - auto currentHeading = state.buffer.ReadSignedFloat(8, 6.28318548f); - auto desiredHeading = state.buffer.ReadSignedFloat(8, 6.28318548f); - - data.currentHeading = currentHeading; - data.desiredHeading = desiredHeading; + s.SerializeSigned(8, 6.28318548f, data.currentHeading); + s.SerializeSigned(8, 6.28318548f, data.desiredHeading); return true; } @@ -1807,25 +2005,46 @@ struct CPedMovementDataNode { bool Parse(SyncParseState& state) { return true; } struct CPedTaskTreeDataNode { bool Parse(SyncParseState& state) { return true; } }; struct CPedTaskSpecificDataNode { bool Parse(SyncParseState& state) { return true; } }; -struct CPedSectorPosMapNode +struct CPedSectorPosMapNode : GenericSerializeDataNode { - float m_sectorPosX; - float m_sectorPosY; - float m_sectorPosZ; + float m_posX; + float m_posY; + float m_posZ; - bool Parse(SyncParseState& state) + bool isStandingOn; + bool isNM; + + uint16_t standingOn; + float standingOnOffset[3]; + + template + bool Serialize(TSerializer& s) { - auto posX = state.buffer.ReadFloat(12, 54.0f); - auto posY = state.buffer.ReadFloat(12, 54.0f); - auto posZ = state.buffer.ReadFloat(12, 69.0f); + s.Serialize(12, 54.0f, m_posX); + s.Serialize(12, 54.0f, m_posY); + s.Serialize(12, 69.0f, m_posZ); - m_sectorPosX = posX; - m_sectorPosY = posY; - m_sectorPosZ = posZ; + if constexpr (typename TSerializer::isReader) + { + s.state->entity->syncTree->CalculatePosition(); + } - state.entity->syncTree->CalculatePosition(); + bool hasExtraData = (isStandingOn || isNM); + s.Serialize(hasExtraData); + + if (hasExtraData) + { + s.Serialize(isNM); + s.Serialize(isStandingOn); - // more data follows + if (isStandingOn) + { + s.Serialize(13, standingOn); + s.SerializeSigned(12, 16.0f, standingOnOffset[0]); // Standing On Local Offset X + s.SerializeSigned(12, 16.0f, standingOnOffset[1]); // Standing On Local Offset Y + s.SerializeSigned(10, 4.0f, standingOnOffset[2]); // Standing On Local Offset Z + } + } return true; } @@ -2189,9 +2408,9 @@ struct CPlayerExtendedGameStateNode { bool Parse(SyncParseState& state) { return struct CPlayerSectorPosNode { - float m_sectorPosX; - float m_sectorPosY; - float m_sectorPosZ; + float m_posX; + float m_posY; + float m_posZ; uint16_t m_standingOnHandle; float m_standingOffsetX; @@ -2233,9 +2452,9 @@ struct CPlayerSectorPosNode auto posY = state.buffer.ReadFloat(12, 54.0f); auto posZ = state.buffer.ReadFloat(12, 69.0f); - m_sectorPosX = posX; - m_sectorPosY = posY; - m_sectorPosZ = posZ; + m_posX = posX; + m_posY = posY; + m_posZ = posZ; state.entity->syncTree->CalculatePosition(); @@ -2417,23 +2636,23 @@ struct SyncTree : public SyncTreeBase auto sectorPosX = (hasSpdn) ? secPosDataNode->m_posX : - (hasPspdn) ? playerSecPosDataNode->m_sectorPosX : - (hasOspdn) ? objectSecPosDataNode->m_sectorPosX : - (hasPspmdn) ? pedSecPosMapDataNode->m_sectorPosX : + (hasPspdn) ? playerSecPosDataNode->m_posX : + (hasOspdn) ? objectSecPosDataNode->m_posX : + (hasPspmdn) ? pedSecPosMapDataNode->m_posX : 0.0f; auto sectorPosY = (hasSpdn) ? secPosDataNode->m_posY : - (hasPspdn) ? playerSecPosDataNode->m_sectorPosY : - (hasOspdn) ? objectSecPosDataNode->m_sectorPosY : - (hasPspmdn) ? pedSecPosMapDataNode->m_sectorPosY : + (hasPspdn) ? playerSecPosDataNode->m_posY : + (hasOspdn) ? objectSecPosDataNode->m_posY : + (hasPspmdn) ? pedSecPosMapDataNode->m_posY : 0.0f; auto sectorPosZ = (hasSpdn) ? secPosDataNode->m_posZ : - (hasPspdn) ? playerSecPosDataNode->m_sectorPosZ : - (hasOspdn) ? objectSecPosDataNode->m_sectorPosZ : - (hasPspmdn) ? pedSecPosMapDataNode->m_sectorPosZ : + (hasPspdn) ? playerSecPosDataNode->m_posZ : + (hasOspdn) ? objectSecPosDataNode->m_posZ : + (hasPspmdn) ? pedSecPosMapDataNode->m_posZ : 0.0f; posOut[0] = ((sectorX - 512.0f) * 54.0f) + sectorPosX; diff --git a/code/components/citizen-server-impl/src/state/ServerRPC.cpp b/code/components/citizen-server-impl/src/state/ServerRPC.cpp index 6be2895d0f..d07d10ca68 100644 --- a/code/components/citizen-server-impl/src/state/ServerRPC.cpp +++ b/code/components/citizen-server-impl/src/state/ServerRPC.cpp @@ -123,6 +123,12 @@ static InitFunction initFunction([]() for (auto& native : rpcConfiguration->GetNatives()) { + // deprecated by ServerSetters + if (native->GetName() == "CREATE_PED" || native->GetName() == "CREATE_OBJECT_NO_OFFSET") + { + continue; + } + // RPC NATIVE fx::ScriptEngine::RegisterNativeHandler(native->GetName(), [=](fx::ScriptContext& ctx) { diff --git a/code/components/citizen-server-impl/src/state/ServerSetters.cpp b/code/components/citizen-server-impl/src/state/ServerSetters.cpp index 33e28ad563..fe8c6bd806 100644 --- a/code/components/citizen-server-impl/src/state/ServerSetters.cpp +++ b/code/components/citizen-server-impl/src/state/ServerSetters.cpp @@ -9,13 +9,18 @@ #include #include +#include +#include + namespace fx { +#pragma region helpers template void UnparseTo(TNode& node, TWrapper wrapper) { rl::MessageBuffer mb(wrapper->data.size()); - node.Unparse(mb); + sync::SyncUnparseState state{ mb }; + node.Unparse(state); memcpy(wrapper->data.data(), mb.GetBuffer().data(), mb.GetBuffer().size()); @@ -23,13 +28,65 @@ void UnparseTo(TNode& node, TWrapper wrapper) wrapper->node = node; } -std::shared_ptr MakeAutomobile(uint32_t model, float posX, float posY, float posZ, uint32_t resourceHash) +template +void SetupNode(const std::shared_ptr& tree, TFn fn) +{ + using TArgs = boost::function_types::parameter_types::type; + using TArg = boost::mpl::at_c::type; + using TNode = std::remove_reference_t; + + auto n = tree->GetNode(); + fn(n->node); + + UnparseTo(n->node, n); + + n->frameIndex = 12; + n->timestamp = msec().count(); +} + +template +void SetupPosition(const std::shared_ptr& tree, float posX, float posY, float posZ) +{ + int sectorX = int((posX / 54.0f) + 512.0f); + int sectorY = int((posY / 54.0f) + 512.0f); + int sectorZ = int((posZ + 1700.0f) / 69.0f); + + float sectorPosX = posX - ((sectorX - 512.0f) * 54.0f); + float sectorPosY = posY - ((sectorY - 512.0f) * 54.0f); + float sectorPosZ = posZ - ((sectorZ * 69.0f) - 1700.0f); + + SetupNode(tree, [sectorX, sectorY, sectorZ](TSectorNode& cdn) + { + cdn.m_sectorX = sectorX; + cdn.m_sectorY = sectorY; + cdn.m_sectorZ = sectorZ; + }); + + SetupNode(tree, [sectorPosX, sectorPosY, sectorPosZ](TPositionNode& cdn) + { + cdn.m_posX = sectorPosX; + cdn.m_posY = sectorPosY; + cdn.m_posZ = sectorPosZ; + }); +} + +template +void SetupHeading(const std::shared_ptr& tree, float heading) +{ + SetupNode(tree, [heading](sync::CEntityOrientationDataNode& node) + { + glm::quat q = glm::quat(glm::vec3(0.0f, 0.0f, heading * 0.01745329252f)); + node.data.quat.Load(q.x, q.y, q.z, q.w); + }); +} +#pragma endregion + +std::shared_ptr MakeAutomobile(uint32_t model, float posX, float posY, float posZ, uint32_t resourceHash, float heading = 0.0f) { auto tree = std::make_shared(); + SetupNode(tree, [model](sync::CVehicleCreationDataNode& cdn) { - auto n = tree->GetNode(); - auto& cdn = n->node; cdn.m_model = model; cdn.m_creationToken = msec().count(); cdn.m_needsToBeHotwired = false; @@ -39,64 +96,92 @@ std::shared_ptr MakeAutomobile(uint32_t model, float posX, f cdn.m_tyresDontBurst = false; cdn.m_vehicleStatus = 0; cdn.m_unk5 = false; - UnparseTo(cdn, n); - - n->frameIndex = 12; - n->timestamp = msec().count(); - } + }); + SetupNode(tree, [](sync::CAutomobileCreationDataNode& cdn) { - auto n = tree->GetNode(); - auto& cdn = n->node; cdn.allDoorsClosed = true; - UnparseTo(cdn, n); + }); - n->frameIndex = 12; - n->timestamp = msec().count(); - } + SetupPosition(tree, posX, posY, posZ); + SetupHeading(tree, heading); - int sectorX = int((posX / 54.0f) + 512.0f); - int sectorY = int((posY / 54.0f) + 512.0f); - int sectorZ = int((posZ + 1700.0f) / 69.0f); + SetupNode(tree, [resourceHash](sync::CEntityScriptInfoDataNode& cdn) + { + cdn.m_scriptHash = resourceHash; + cdn.m_timestamp = msec().count(); + }); - float sectorPosX = posX - ((sectorX - 512.0f) * 54.0f); - float sectorPosY = posY - ((sectorY - 512.0f) * 54.0f); - float sectorPosZ = posZ - ((sectorZ * 69.0f) - 1700.0f); + return tree; +} - { - auto n = tree->GetNode(); - auto& cdn = n->node; - cdn.m_sectorX = sectorX; - cdn.m_sectorY = sectorY; - cdn.m_sectorZ = sectorZ; - UnparseTo(cdn, n); +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(); - n->frameIndex = 12; - n->timestamp = msec().count(); - } + SetupNode(tree, [model](sync::CPedCreationDataNode& cdn) + { + cdn.m_model = model; + cdn.isRespawnObjectId = false; + cdn.respawnFlaggedForRemoval = false; + cdn.m_popType = sync::POPTYPE_MISSION; + cdn.randomSeed = rand(); + cdn.vehicleId = 0; + cdn.vehicleSeat = 0; + cdn.prop = 0; + cdn.voiceHash = HashString("NO_VOICE"); + cdn.isStanding = true; + cdn.attributeDamageToPlayer = -1; + cdn.maxHealth = 200; + cdn.unkBool = false; + }); + SetupNode(tree, [resourceHash](sync::CPedSectorPosMapNode& cdn) { - auto n = tree->GetNode(); - auto& cdn = n->node; - cdn.m_posX = sectorPosX; - cdn.m_posY = sectorPosY; - cdn.m_posZ = sectorPosZ; - UnparseTo(cdn, n); + cdn.isStandingOn = false; + cdn.isNM = false; + }); - n->frameIndex = 12; - n->timestamp = msec().count(); - } + SetupPosition(tree, posX, posY, posZ); + SetupNode(tree, [heading](sync::CPedOrientationDataNode& node) + { + node.data.currentHeading = heading * 0.01745329252f; + node.data.desiredHeading = heading * 0.01745329252f; + }); + + SetupNode(tree, [resourceHash](sync::CEntityScriptInfoDataNode& cdn) { - auto n = tree->GetNode(); - auto& cdn = n->node; cdn.m_scriptHash = resourceHash; cdn.m_timestamp = msec().count(); - UnparseTo(cdn, n); + }); - n->frameIndex = 12; - n->timestamp = msec().count(); - } + return tree; +} + +std::shared_ptr MakeObject(uint32_t model, float posX, float posY, float posZ, uint32_t resourceHash, bool dynamic, float heading = 0.0f) +{ + auto tree = std::make_shared(); + + SetupNode(tree, [model, dynamic](sync::CObjectCreationDataNode& cdn) + { + cdn.m_model = model; + cdn.m_dynamic = dynamic; + }); + + SetupNode(tree, [resourceHash](sync::CObjectSectorPosNode& cdn) + { + cdn.highRes = true; + }); + + SetupPosition(tree, posX, posY, posZ); + SetupHeading(tree, heading); + + SetupNode(tree, [resourceHash](sync::CEntityScriptInfoDataNode& cdn) + { + cdn.m_scriptHash = resourceHash; + cdn.m_timestamp = msec().count(); + }); return tree; } @@ -181,13 +266,61 @@ static InitFunction initFunction([]() } } - auto tree = MakeAutomobile(ctx.GetArgument(0), ctx.GetArgument(1), ctx.GetArgument(2), ctx.GetArgument(3), resourceHash); + auto tree = MakeAutomobile(ctx.GetArgument(0), ctx.GetArgument(1), ctx.GetArgument(2), ctx.GetArgument(3), resourceHash, ctx.GetArgument(4)); auto sgs = ref->GetComponent(); auto entity = sgs->CreateEntityFromTree(sync::NetObjEntityType::Automobile, tree); ctx.SetResult(sgs->MakeScriptHandle(entity)); }); + + fx::ScriptEngine::RegisterNativeHandler("CREATE_PED", [=](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()); + } + } + + auto tree = MakePed(ctx.GetArgument(1), ctx.GetArgument(2), ctx.GetArgument(3), ctx.GetArgument(4), resourceHash, ctx.GetArgument(5)); + + auto sgs = ref->GetComponent(); + auto entity = sgs->CreateEntityFromTree(sync::NetObjEntityType::Ped, tree); + + ctx.SetResult(sgs->MakeScriptHandle(entity)); + }); + + fx::ScriptEngine::RegisterNativeHandler("CREATE_OBJECT_NO_OFFSET", [=](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()); + } + } + + auto tree = MakeObject(ctx.GetArgument(0), ctx.GetArgument(1), ctx.GetArgument(2), ctx.GetArgument(3), resourceHash, ctx.GetArgument(6)); + + auto sgs = ref->GetComponent(); + auto entity = sgs->CreateEntityFromTree(sync::NetObjEntityType::Object, tree); + + ctx.SetResult(sgs->MakeScriptHandle(entity)); + }); }); }); }