Files
EscapistsReintroduced/docs/evidence/terrain-takeover-feature-2026-10-01/client-experiment-21/ERR_TerrainTakeoverClient.c.txt
T
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

211 lines
7.6 KiB
Plaintext

// 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<IEntity> 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<Faction> 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;
}
}