// Client bootstrap for the explicitly selected takeover prefab. The native game // mode pointer already refers to the incoming entity inside its constructor, so // capture original managers during the original mode's initialization instead. class ERR_TerrainTakeoverModeClass : ERR_GameModeClass {} class ERR_TerrainTakeoverMode : ERR_GameMode { void ERR_TerrainTakeoverMode(IEntitySource src, IEntity parent) { if (!GetGame() || Replication.IsServer()) return; GetGame().ERR_BeginClientTerrainTakeover(this); } } modded class ArmaReforgerScripted { protected ref array m_ERRClientOldManagers = {}; protected bool m_ERRClientTerrainTakeover; bool ERR_IsClientTerrainTakeover() { return m_ERRClientTerrainTakeover; } protected SCR_EntityCatalogManagerComponent m_ERROldCatalog; protected EntityID m_ERROldLoadoutId; protected SCR_AdditionalGameModeSettingsComponent m_ERROldSettings; protected SCR_PerceivedFactionManagerComponent m_ERROldPerceivedFaction; protected SCR_PlayerListedAdminManagerComponent m_ERROldAdmins; protected SCR_IdentityManagerComponent m_ERROldIdentity; void ERR_ClientCaptureOriginal(SCR_BaseGameMode mode) { if (Replication.IsServer() || ERR_GameMode.Cast(mode)) return; m_ERROldCatalog = SCR_EntityCatalogManagerComponent.GetInstance(); if (GetLoadoutManager()) m_ERROldLoadoutId = GetLoadoutManager().GetID(); m_ERROldSettings = SCR_AdditionalGameModeSettingsComponent.GetInstance(); m_ERROldPerceivedFaction = SCR_PerceivedFactionManagerComponent.GetInstance(); m_ERROldAdmins = SCR_PlayerListedAdminManagerComponent.GetInstance(); m_ERROldIdentity = SCR_IdentityManagerComponent.GetInstance(); m_ERRClientOldManagers = {mode, GetLoadoutManager(), GetFactionManager(), GetPerceptionManager(), GetChat()}; } override void UnregisterLoadoutManager(SCR_LoadoutManager instance) { if (m_ERRClientTerrainTakeover && instance && instance.GetID() == m_ERROldLoadoutId && instance != GetLoadoutManager()) { Print("Reintroduced [terrain client]: completed original loadout unregister"); return; } super.UnregisterLoadoutManager(instance); } override protected void OnBeforeWorldCleanup() { m_ERRClientTerrainTakeover = false; m_ERRClientOldManagers.Clear(); m_ERROldCatalog = null; m_ERROldSettings = null; m_ERROldPerceivedFaction = null; m_ERROldAdmins = null; m_ERROldIdentity = null; m_ERROldLoadoutId = EntityID.INVALID; super.OnBeforeWorldCleanup(); } void ERR_BeginClientTerrainTakeover(ERR_TerrainTakeoverMode incoming) { if (m_ERRClientOldManagers.IsEmpty()) return; m_ERRClientTerrainTakeover = true; SCR_BaseGameMode mode = SCR_BaseGameMode.Cast(m_ERRClientOldManagers[0]); PrintFormat("Reintroduced [terrain client]: drain retained mode=%1 incoming=%2", mode, incoming); if (mode) mode.OnGameEnd(); // Owner entities can already be invalid while native destruction is deferred. // Compare the captured component itself, never clear a newer singleton. SCR_EntityCatalogManagerComponent.ERR_ReleaseClientCatalog(m_ERROldCatalog); m_DataCollectorComponent = null; SCR_AdditionalGameModeSettingsComponent.ERR_ReleaseClientTerrain(m_ERROldSettings); SCR_PerceivedFactionManagerComponent.ERR_ReleaseClientTerrain(m_ERROldPerceivedFaction); SCR_PlayerListedAdminManagerComponent.ERR_ReleaseClientTerrain(m_ERROldAdmins); SCR_IdentityManagerComponent.ERR_ReleaseClientTerrain(m_ERROldIdentity); foreach (IEntity manager : m_ERRClientOldManagers) if (manager && manager != incoming) delete manager; m_ERRClientOldManagers.Clear(); PrintFormat("Reintroduced [terrain client]: drained native mode=%1 factions=%2 loadouts=%3", GetGameMode(), GetFactionManager(), GetLoadoutManager()); } } modded class SCR_EntityCatalogManagerComponent { static void ERR_ReleaseClientCatalog(SCR_EntityCatalogManagerComponent expected) { if (s_Instance == expected) s_Instance = null; } protected bool m_ERRWaitingForFaction; void ERR_CompleteFactionInit() { if (!m_ERRWaitingForFaction || !GetGame().GetFactionManager()) return; m_ERRWaitingForFaction = false; ClearEventMask(GetOwner(), EntityEvent.FRAME); super.EOnInit(GetOwner()); Print("Reintroduced [terrain client]: catalog completed during faction initialization"); } override void EOnInit(IEntity owner) { if (!Replication.IsServer() && ERR_TerrainTakeoverMode.Cast(owner) && !GetGame().GetFactionManager()) { m_ERRWaitingForFaction = true; SetEventMask(owner, EntityEvent.FRAME); return; } super.EOnInit(owner); } override void EOnFrame(IEntity owner, float timeSlice) { if (!m_ERRWaitingForFaction || !GetGame().GetFactionManager()) return; m_ERRWaitingForFaction = false; ClearEventMask(owner, EntityEvent.FRAME); super.EOnInit(owner); Print("Reintroduced [terrain client]: catalog initialized after factions"); } } modded class SCR_DelegateFactionManagerComponent { override void OnFactionsInit(array factions) { if (!Replication.IsServer() && GetGame().ERR_IsClientTerrainTakeover()) { SCR_EntityCatalogManagerComponent catalog = SCR_EntityCatalogManagerComponent.GetInstance(); if (catalog) catalog.ERR_CompleteFactionInit(); SCR_IdentityManagerComponent identity = SCR_IdentityManagerComponent.GetInstance(); if (identity) identity.ERR_CompleteTerrainFactionInit(); // The server replicates these delegates. During the initial snapshot the // vanilla Runtime() guard is false and would create duplicates on the client. return; } super.OnFactionsInit(factions); } } // Both peers consume the same extra index before the vanilla payload. Vanilla // RplLoad dereferences its faction before applying its own serialized index; // runtime takeover delegates must bind first. Ordinary missions only consume // the prefix and retain their normal initialization. modded class SCR_EditableFactionComponent { override bool RplSave(ScriptBitWriter writer) { writer.WriteInt(m_iFactionIndex); return super.RplSave(writer); } override bool RplLoad(ScriptBitReader reader) { int initialIndex; if (!reader.ReadInt(initialIndex)) return false; if (GetGame().ERR_IsClientTerrainTakeover() && !m_ScrFaction) { SetFactionIndexBroadcast(initialIndex); if (!m_ScrFaction) return false; } return super.RplLoad(reader); } } modded class SCR_BaseGameMode { override void EOnInit(IEntity owner) { super.EOnInit(owner); GetGame().ERR_ClientCaptureOriginal(this); } } modded class SCR_AdditionalGameModeSettingsComponent { static void ERR_ReleaseClientTerrain(SCR_AdditionalGameModeSettingsComponent expected) { if (s_Instance == expected) s_Instance = null; } } modded class SCR_PerceivedFactionManagerComponent { static void ERR_ReleaseClientTerrain(SCR_PerceivedFactionManagerComponent expected) { if (s_Instance == expected) s_Instance = null; } } modded class SCR_PlayerListedAdminManagerComponent { static void ERR_ReleaseClientTerrain(SCR_PlayerListedAdminManagerComponent expected) { if (m_Instance == expected) m_Instance = null; } } modded class SCR_IdentityManagerComponent { protected bool m_ERRWaitingForTerrainFaction; override void EOnInit(IEntity owner) { if (!Replication.IsServer() && GetGame().ERR_IsClientTerrainTakeover() && ERR_TerrainTakeoverMode.Cast(owner) && !GetGame().GetFactionManager()) { m_ERRWaitingForTerrainFaction = true; return; } super.EOnInit(owner); } void ERR_CompleteTerrainFactionInit() { if (!m_ERRWaitingForTerrainFaction || !GetGame().GetFactionManager()) return; m_ERRWaitingForTerrainFaction = false; super.EOnInit(GetOwner()); } static void ERR_ReleaseClientTerrain(SCR_IdentityManagerComponent expected) { if (m_sInstance == expected) m_sInstance = null; } }