From 71be00602b395a8e1f66b4289d88f0de901d204c Mon Sep 17 00:00:00 2001 From: Kex Date: Thu, 12 Dec 2024 15:58:03 +0100 Subject: [PATCH] Chopping - Revise deletion of plants (#134) * Move deletion of loadtime entities to a game mode component and use entity IDs instead of positions * Replicate deleted entity IDs as ints * Use wrapper instead of ints for EntityID --- .../UserActions/ACE_Chopping_UserAction.c | 4 +- addons/core/Prefabs/MP/Modes/GameMode_Base.et | 7 + .../Prefabs/MP/Modes/GameMode_Base.et.meta | 17 ++ .../Containers/Backend/ACE_EditorStruct.c | 46 +++--- .../Components/ACE_LoadtimeEntityManager.c | 152 ++++++++++++++++++ .../Game/ACE_Core/GameMode/SCR_BaseGameMode.c | 48 ------ .../Game/ACE_Core/Global/ACE_EntityIdHelper.c | 26 +++ .../Game/ACE_Core/Global/ACE_HexTools.c | 66 ++++++++ .../ACE_Core/Player/SCR_PlayerController.c | 14 +- 9 files changed, 298 insertions(+), 82 deletions(-) create mode 100644 addons/core/Prefabs/MP/Modes/GameMode_Base.et create mode 100644 addons/core/Prefabs/MP/Modes/GameMode_Base.et.meta create mode 100644 addons/core/scripts/Game/ACE_Core/GameMode/Components/ACE_LoadtimeEntityManager.c delete mode 100644 addons/core/scripts/Game/ACE_Core/GameMode/SCR_BaseGameMode.c create mode 100644 addons/core/scripts/Game/ACE_Core/Global/ACE_EntityIdHelper.c create mode 100644 addons/core/scripts/Game/ACE_Core/Global/ACE_HexTools.c diff --git a/addons/chopping/scripts/Game/ACE_Chopping/UserActions/ACE_Chopping_UserAction.c b/addons/chopping/scripts/Game/ACE_Chopping/UserActions/ACE_Chopping_UserAction.c index 813b2d56..8165aff7 100644 --- a/addons/chopping/scripts/Game/ACE_Chopping/UserActions/ACE_Chopping_UserAction.c +++ b/addons/chopping/scripts/Game/ACE_Chopping/UserActions/ACE_Chopping_UserAction.c @@ -18,8 +18,8 @@ class ACE_Chopping_UserAction : ACE_ShovelUserAction if (!plant) return; - userCtrl.ACE_DeleteEntityAtPosition(plant.GetOrigin()); - SCR_EntityHelper.DeleteEntityAndChildren(helper); + userCtrl.ACE_RequestDeleteEntity(plant); + delete helper; } //------------------------------------------------------------------------------------------------ diff --git a/addons/core/Prefabs/MP/Modes/GameMode_Base.et b/addons/core/Prefabs/MP/Modes/GameMode_Base.et new file mode 100644 index 00000000..a6c5ca1a --- /dev/null +++ b/addons/core/Prefabs/MP/Modes/GameMode_Base.et @@ -0,0 +1,7 @@ +SCR_BaseGameMode { + ID "56B2B479C6B96951" + components { + ACE_LoadtimeEntityManager "{62FC19C3B5BE2C5F}" { + } + } +} \ No newline at end of file diff --git a/addons/core/Prefabs/MP/Modes/GameMode_Base.et.meta b/addons/core/Prefabs/MP/Modes/GameMode_Base.et.meta new file mode 100644 index 00000000..30f5b90f --- /dev/null +++ b/addons/core/Prefabs/MP/Modes/GameMode_Base.et.meta @@ -0,0 +1,17 @@ +MetaFileClass { + Name "{0F307326459A1395}Prefabs/MP/Modes/GameMode_Base.et" + Configurations { + EntityTemplateResourceClass PC { + } + EntityTemplateResourceClass XBOX_ONE : PC { + } + EntityTemplateResourceClass XBOX_SERIES : PC { + } + EntityTemplateResourceClass PS4 : PC { + } + EntityTemplateResourceClass PS5 : PC { + } + EntityTemplateResourceClass HEADLESS : PC { + } + } +} \ No newline at end of file diff --git a/addons/core/scripts/Game/ACE_Core/Editor/Containers/Backend/ACE_EditorStruct.c b/addons/core/scripts/Game/ACE_Core/Editor/Containers/Backend/ACE_EditorStruct.c index 0dba4db3..b721c9cc 100644 --- a/addons/core/scripts/Game/ACE_Core/Editor/Containers/Backend/ACE_EditorStruct.c +++ b/addons/core/scripts/Game/ACE_Core/Editor/Containers/Backend/ACE_EditorStruct.c @@ -3,13 +3,12 @@ //! Managed by SCR_DSSessionCallback. class ACE_EditorStruct : SCR_JsonApiStruct { - // SCR_JsonApiStruct does not support array of PoD, hence we use ACE_VectorStruct as wrapper - protected ref array m_aACE_DeletedEntityPositions = {}; + protected ref array m_aACE_DeletedEntityIDs = {}; //------------------------------------------------------------------------------------------------ void ACE_EditorStruct() { - RegV("m_aACE_DeletedEntityPositions"); + RegV("m_aACE_DeletedEntityIDs"); } //------------------------------------------------------------------------------------------------ @@ -17,9 +16,9 @@ class ACE_EditorStruct : SCR_JsonApiStruct override void Log() { Print("--- ACE_EditorStruct ------------------------"); - for (int i = 0, count = m_aACE_DeletedEntityPositions.Count(); i < count; i++) + for (int i = 0, count = m_aACE_DeletedEntityIDs.Count(); i < count; i++) { - Print("Removed entity position: " + m_aACE_DeletedEntityPositions[i]); + Print("Removed entity with ID: " + m_aACE_DeletedEntityIDs[i]); } Print("---------------------------------------------"); } @@ -28,40 +27,39 @@ class ACE_EditorStruct : SCR_JsonApiStruct //! Write world data into the struct. override bool Serialize() { - - SCR_BaseGameMode gameMode = SCR_BaseGameMode.Cast(GetGame().GetGameMode()); - if (!gameMode) + ACE_LoadtimeEntityManager manager = ACE_LoadtimeEntityManager.GetInstance(); + if (!manager) return false; - m_aACE_DeletedEntityPositions.Clear(); + array entityIDs = manager.GetDeletedEntityIDs(); + m_aACE_DeletedEntityIDs.Clear(); + m_aACE_DeletedEntityIDs.Reserve(entityIDs.Count()); - foreach (vector pos : gameMode.ACE_GetDeletedEntityPositions()) + foreach (EntityID entityID : entityIDs) { - ACE_VectorStruct posStruct = new ACE_VectorStruct(); - posStruct.SetVector(pos); - m_aACE_DeletedEntityPositions.Insert(posStruct); + m_aACE_DeletedEntityIDs.Insert(ACE_EntityIdHelper.ToString(entityID)); } - + return true; } //------------------------------------------------------------------------------------------------ - //! Read data from the struct and apply them in the world. + //! Read data from the struct and apply them to the world. override bool Deserialize() { - SCR_BaseGameMode gameMode = SCR_BaseGameMode.Cast(GetGame().GetGameMode()); - if (!gameMode) + ACE_LoadtimeEntityManager manager = ACE_LoadtimeEntityManager.GetInstance(); + if (!manager) return false; - array deletedEntityPositions = {}; + array entityIDs = {}; + entityIDs.Reserve(m_aACE_DeletedEntityIDs.Count()); - foreach (ACE_VectorStruct posStruct : m_aACE_DeletedEntityPositions) + foreach (string str : m_aACE_DeletedEntityIDs) { - deletedEntityPositions.Insert(posStruct.GetVector()); - }; - - gameMode.ACE_DeleteEntitiesAtPositionsGlobal(deletedEntityPositions); + entityIDs.Insert(ACE_EntityIdHelper.FromString(str)); + } + manager.DeleteEntitiesByIdGlobal(entityIDs); return true; } @@ -69,6 +67,6 @@ class ACE_EditorStruct : SCR_JsonApiStruct //! Clear cached data. override void ClearCache() { - m_aACE_DeletedEntityPositions.Clear(); + m_aACE_DeletedEntityIDs.Clear(); } } diff --git a/addons/core/scripts/Game/ACE_Core/GameMode/Components/ACE_LoadtimeEntityManager.c b/addons/core/scripts/Game/ACE_Core/GameMode/Components/ACE_LoadtimeEntityManager.c new file mode 100644 index 00000000..eaca4658 --- /dev/null +++ b/addons/core/scripts/Game/ACE_Core/GameMode/Components/ACE_LoadtimeEntityManager.c @@ -0,0 +1,152 @@ +//------------------------------------------------------------------------------------------------ +class ACE_LoadtimeEntityManagerClass : SCR_BaseGameModeComponentClass +{ +} + +//------------------------------------------------------------------------------------------------ +//! Methods for manipulating unreplicated loadtime entities +class ACE_LoadtimeEntityManager : SCR_BaseGameModeComponent +{ + // array can't be properly replicated (see https://feedback.bistudio.com/T186903) so we use a wrapper instead + [RplProp(onRplName: "DeleteInitialEntities")] + protected ref array m_aDeletedEntityIDs = {}; + + protected static ACE_LoadtimeEntityManager s_pInstance; + + //------------------------------------------------------------------------------------------------ + void ACE_LoadtimeEntityManager(IEntityComponentSource src, IEntity ent, IEntity parent) + { + s_pInstance = this; + } + + //------------------------------------------------------------------------------------------------ + static ACE_LoadtimeEntityManager GetInstance() + { + return s_pInstance; + } + + //------------------------------------------------------------------------------------------------ + //! Ensures that already deleted unreplicated entities are deleted for JIPs + void DeleteInitialEntities() + { + foreach (ACE_EntityIdWrapper idWrapper : m_aDeletedEntityIDs) + { + DeleteEntityByIdLocal(idWrapper.GetID()); + } + } + + //------------------------------------------------------------------------------------------------ + //! Deletes unreplicated entities by ID for all machines + //! Can only be called on the server + [RplRpc(RplChannel.Reliable, RplRcver.Server)] + void DeleteEntitiesByIdGlobal(notnull array entityIDs) + { + array newIDs = {}; + newIDs.Reserve(entityIDs.Count()); + m_aDeletedEntityIDs.Reserve(m_aDeletedEntityIDs.Count() + newIDs.Count()); + + foreach (EntityID entityID : entityIDs) + { + ACE_EntityIdWrapper idWrapper = ACE_EntityIdWrapper.CreateID(entityID); + newIDs.Insert(idWrapper); + m_aDeletedEntityIDs.Insert(idWrapper); + } + + Rpc(RpcDo_DeleteEntityByBitsBroadcast, newIDs); + RpcDo_DeleteEntityByBitsBroadcast(newIDs); + } + + //------------------------------------------------------------------------------------------------ + [RplRpc(RplChannel.Reliable, RplRcver.Broadcast)] + protected void RpcDo_DeleteEntityByBitsBroadcast(array newIDs) + { + foreach (ACE_EntityIdWrapper idWrapper : newIDs) + { + DeleteEntityByIdLocal(idWrapper.GetID()); + } + } + + //------------------------------------------------------------------------------------------------ + //! Deletes unreplicated entity by ID on a local machine + void DeleteEntityByIdLocal(EntityID entityID) + { + IEntity entity = GetGame().GetWorld().FindEntityByID(entityID); + if (entity) + delete entity; + } + + //------------------------------------------------------------------------------------------------ + //! Return all deleted unreplicated entity IDs + //! Can only be called on the server + array GetDeletedEntityIDs() + { + array entityIDs = {}; + entityIDs.Reserve(m_aDeletedEntityIDs.Count()); + + foreach (ACE_EntityIdWrapper idWrapper : m_aDeletedEntityIDs) + { + entityIDs.Insert(idWrapper.GetID()); + } + + return entityIDs; + } +} + +//------------------------------------------------------------------------------------------------ +//! Temporary workaround for https://feedback.bistudio.com/T186903 +class ACE_EntityIdWrapper : Managed +{ + static const int SNAPSHOT_SIZE_VALUE = 8; //--- EntityID is 64 bit integer + protected EntityID m_iID; + + //------------------------------------------------------------------------------------------------ + static ACE_EntityIdWrapper CreateID(EntityID id) + { + ACE_EntityIdWrapper instance = new ACE_EntityIdWrapper(); + instance.m_iID = id; + return instance; + } + + //------------------------------------------------------------------------------------------------ + EntityID GetID() + { + return m_iID; + } + + //------------------------------------------------------------------------------------------------ + static void Encode(SSnapSerializerBase snapshot, ScriptCtx hint, ScriptBitSerializer packet) + { + snapshot.Serialize(packet, SNAPSHOT_SIZE_VALUE); + } + + //------------------------------------------------------------------------------------------------ + static bool Decode(ScriptBitSerializer packet, ScriptCtx hint, SSnapSerializerBase snapshot) + { + return snapshot.Serialize(packet, SNAPSHOT_SIZE_VALUE); + } + + //------------------------------------------------------------------------------------------------ + static bool SnapCompare(SSnapSerializerBase lhs, SSnapSerializerBase rhs, ScriptCtx hint) + { + return lhs.CompareSnapshots(rhs, SNAPSHOT_SIZE_VALUE); + } + + //------------------------------------------------------------------------------------------------ + static bool PropCompare(ACE_EntityIdWrapper prop, SSnapSerializerBase snapshot, ScriptCtx hint) + { + return snapshot.Compare(prop.m_iID, SNAPSHOT_SIZE_VALUE); + } + + //------------------------------------------------------------------------------------------------ + static bool Extract(ACE_EntityIdWrapper prop, ScriptCtx hint, SSnapSerializerBase snapshot) + { + snapshot.SerializeBytes(prop.m_iID, SNAPSHOT_SIZE_VALUE); + return true; + } + + //------------------------------------------------------------------------------------------------ + static bool Inject(SSnapSerializerBase snapshot, ScriptCtx hint, ACE_EntityIdWrapper prop) + { + return Extract(prop, hint, snapshot); + } +} diff --git a/addons/core/scripts/Game/ACE_Core/GameMode/SCR_BaseGameMode.c b/addons/core/scripts/Game/ACE_Core/GameMode/SCR_BaseGameMode.c deleted file mode 100644 index 15bd1857..00000000 --- a/addons/core/scripts/Game/ACE_Core/GameMode/SCR_BaseGameMode.c +++ /dev/null @@ -1,48 +0,0 @@ -//------------------------------------------------------------------------------------------------ -//! Add methods for deleting unreplicated entities -modded class SCR_BaseGameMode : BaseGameMode -{ - [RplProp(onRplName: "ACE_DeleteInitialEntityPositions")] - protected ref array m_aACE_DeletedEntityPositions = {}; - - //------------------------------------------------------------------------------------------------ - //! Ensures that already deleted unreplicated entities are deleted for JIPs - void ACE_DeleteInitialEntityPositions() - { - ACE_DeleteEntitiesAtPositionsLocal(m_aACE_DeletedEntityPositions); - } - - //------------------------------------------------------------------------------------------------ - //! Deletes unreplicated entities by position for all machines - //! Can only be called on the server - [RplRpc(RplChannel.Reliable, RplRcver.Server)] - void ACE_DeleteEntitiesAtPositionsGlobal(array entityPositions) - { - m_aACE_DeletedEntityPositions.InsertAll(entityPositions); - ACE_DeleteEntitiesAtPositionsLocal(entityPositions); - Rpc(ACE_DeleteEntitiesAtPositionsLocal, entityPositions); - } - - //------------------------------------------------------------------------------------------------ - //! Deletes unreplicated entities by position on a local machine - [RplRpc(RplChannel.Reliable, RplRcver.Broadcast)] - void ACE_DeleteEntitiesAtPositionsLocal(array entityPositions) - { - ACE_QueryNearestEntity query = new ACE_QueryNearestEntity(0.01); - - foreach (vector pos : entityPositions) - { - IEntity entity = query.GetEntity(pos); - if (entity) - SCR_EntityHelper.DeleteEntityAndChildren(entity); - } - } - - //------------------------------------------------------------------------------------------------ - //! Return all deleted unreplicated entity positions - //! Can only be called on the server - array ACE_GetDeletedEntityPositions() - { - return m_aACE_DeletedEntityPositions; - } -} diff --git a/addons/core/scripts/Game/ACE_Core/Global/ACE_EntityIdHelper.c b/addons/core/scripts/Game/ACE_Core/Global/ACE_EntityIdHelper.c new file mode 100644 index 00000000..dcbf2f97 --- /dev/null +++ b/addons/core/scripts/Game/ACE_Core/Global/ACE_EntityIdHelper.c @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------------------------ +class ACE_EntityIdHelper +{ + //------------------------------------------------------------------------------------------------ + static array ToInt(EntityID id) + { + return ACE_HexTools.HexStringToInt(ToString(id)); + } + + //------------------------------------------------------------------------------------------------ + static EntityID FromString(string str) + { + array bits = ACE_HexTools.HexStringToInt(str); + if (bits.Count() < 2) + return EntityID.INVALID; + + return EntityID.FromInt(bits[0], bits[1]); + } + + //------------------------------------------------------------------------------------------------ + static string ToString(EntityID id) + { + // Drop the last three characters, which are " {}" + return id.ToString().Substring(0, 18); + } +} diff --git a/addons/core/scripts/Game/ACE_Core/Global/ACE_HexTools.c b/addons/core/scripts/Game/ACE_Core/Global/ACE_HexTools.c new file mode 100644 index 00000000..d52d0795 --- /dev/null +++ b/addons/core/scripts/Game/ACE_Core/Global/ACE_HexTools.c @@ -0,0 +1,66 @@ +//------------------------------------------------------------------------------------------------ +class ACE_HexTools +{ + protected static int s_iAscii0 = "0".ToAscii(); + protected static int s_iAscii9 = "9".ToAscii(); + protected static int s_iAsciiA = "A".ToAscii(); + protected static int s_iAsciiF = "F".ToAscii(); + protected static int s_iAsciiLoA = "a".ToAscii(); + protected static int s_iAsciiLoF = "f".ToAscii(); + + //------------------------------------------------------------------------------------------------ + //! Gets int values of hexadecimal string. Expected to be prefixed with "0x" + //! Ordered by most significant bits + static array HexStringToInt(string hexString) + { + if (hexString.Substring(0, 2) != "0x") + return {}; + + int numHexChars = hexString.Length() - 2; + if (!float.AlmostEqual(Math.Mod(numHexChars, 8), 0)) + return {}; + + hexString = hexString.Substring(2, numHexChars); + + int numBit32 = (hexString.Length()) / 8; + array result = {}; + result.Reserve(numBit32); + + for (int i_bit32 = 0; i_bit32 < numBit32; i_bit32++) + { + int bit32; + + for (int i_bit4 = 8 * i_bit32; i_bit4 < 8 * (i_bit32 + 1); i_bit4++) + { + int bit4 = HexCharToInt(hexString, index: i_bit4); + if (bit4 < 0) + return result; + + bit32 = 16 * bit32 + bit4; + } + + result.Insert(bit32); + } + + return result; + } + + //------------------------------------------------------------------------------------------------ + //! Gets int value of a character in a hexadecimal string + //! Returns -1 if parsing failed + //! \param index Index of the character, 0 by default. + static int HexCharToInt(string hexString, int index = 0) + { + int value = hexString.ToAscii(index); + if (value >= s_iAscii0 && value <= s_iAscii9) + value -= s_iAscii0; + else if (value >= s_iAsciiA && value <= s_iAsciiF) + value -= s_iAsciiA - 10; + else if (value >= s_iAsciiLoA && value <= s_iAsciiLoF) + value -= s_iAsciiLoA - 10; + else + return -1; + + return value; + } +} diff --git a/addons/core/scripts/Game/ACE_Core/Player/SCR_PlayerController.c b/addons/core/scripts/Game/ACE_Core/Player/SCR_PlayerController.c index 35a42a2c..68148d42 100644 --- a/addons/core/scripts/Game/ACE_Core/Player/SCR_PlayerController.c +++ b/addons/core/scripts/Game/ACE_Core/Player/SCR_PlayerController.c @@ -4,21 +4,19 @@ modded class SCR_PlayerController : PlayerController //------------------------------------------------------------------------------------------------ //! Request deletion of unreplicated entity from all machines //! Called from local player - void ACE_DeleteEntityAtPosition(vector pos) + void ACE_RequestDeleteEntity(IEntity entity) { - Rpc(RpcAsk_ACE_DeleteEntityAtPosition, pos); + Rpc(RpcAsk_ACE_DeleteEntityByBits, ACE_EntityIdHelper.ToInt(entity.GetID())); } //------------------------------------------------------------------------------------------------ - //! Request deletion of unreplicated entity from all machines - //! Called from server [RplRpc(RplChannel.Reliable, RplRcver.Server)] - void RpcAsk_ACE_DeleteEntityAtPosition(vector pos) + protected void RpcAsk_ACE_DeleteEntityByBits(array bits) { - SCR_BaseGameMode gameMode = SCR_BaseGameMode.Cast(GetGame().GetGameMode()); - if (!gameMode) + ACE_LoadtimeEntityManager manager = ACE_LoadtimeEntityManager.GetInstance(); + if (!manager) return; - gameMode.ACE_DeleteEntitiesAtPositionsGlobal({pos}); + manager.DeleteEntitiesByIdGlobal({EntityID.FromInt(bits[0], bits[1])}); } }