// Experimental opt-in for an existing island scenario. Native multiplayer // replication must be qualified before recommending this route on live servers. enum ERR_TerrainTakeoverStage { INACTIVE, SETTLING, DRAINING, RUNNING, FAILED } class ERR_TerrainTakeoverRules { static bool TimedOut(float elapsed) { return elapsed >= 10; } static bool MayDrain(float elapsed, bool sameMode) { return elapsed >= 0.5 && sameMode; } static bool MayCreate(float elapsed, bool empty) { return elapsed >= 0.5 && empty && !TimedOut(elapsed); } static bool SameAdmission(bool connected, bool sameController, bool identityWasPresent, bool sameIdentity) { return connected && sameController && (!identityWasPresent || sameIdentity); } } class ERR_TerrainTakeoverAdmission { int Connection; SCR_PlayerController Controller; UUID Identity; } modded class SCR_EntityCatalogManagerComponent { // Vanilla's singleton is not explicitly reset when its owner is removed. static void ERR_ReleaseTerrainCatalog(IEntity owner) { if (s_Instance && s_Instance.GetOwner() == owner) s_Instance = null; } } modded class SCR_BaseGameMode { override void OnPlayerAuditSuccess(int iPlayerID) { if (GetGame().ERR_QueueTerrainAdmission(this, iPlayerID)) return; super.OnPlayerAuditSuccess(iPlayerID); } protected override void OnPlayerDisconnected(int playerId, KickCauseCode cause, int timeout) { GetGame().ERR_ForgetTerrainAdmission(playerId); super.OnPlayerDisconnected(playerId, cause, timeout); } } modded class ERR_GameMode { [RplProp()] protected bool m_ERRTerrainTakenOver; bool ERR_TerrainTakenOver() { return m_ERRTerrainTakenOver; } void ERR_StartTerrainTakeover(World world) { m_ERRTerrainTakenOver = true; Replication.BumpMe(); OnWorldPostProcess(world); OnGameStart(); } } modded class ArmaReforgerScripted { protected ERR_TerrainTakeoverStage m_ERRTerrainStage; protected float m_ERRTerrainElapsed; protected BaseWorld m_ERRTerrainWorld; protected SCR_BaseGameMode m_ERRTerrainOriginal; protected ref array m_ERRTerrainAdmissions = {}; protected ref array m_ERRTerrainAirfields = {}; protected ref array m_ERRTerrainMarkerSlots = {}; void ERR_ForgetTerrainAdmission(int connection) { for (int i = m_ERRTerrainAdmissions.Count() - 1; i >= 0; i--) if (m_ERRTerrainAdmissions[i].Connection == connection) m_ERRTerrainAdmissions.Remove(i); } bool ERR_QueueTerrainAdmission(SCR_BaseGameMode mode, int connection) { if (m_ERRTerrainStage != ERR_TerrainTakeoverStage.SETTLING && m_ERRTerrainStage != ERR_TerrainTakeoverStage.DRAINING) return false; if (mode != m_ERRTerrainOriginal || ERR_GameMode.Cast(mode) || !Replication.IsServer()) return false; ERR_ForgetTerrainAdmission(connection); SCR_PlayerController controller = SCR_PlayerController.Cast(GetPlayerManager().GetPlayerController(connection)); if (!controller || !GetPlayerManager().IsPlayerConnected(connection)) return true; ERR_TerrainTakeoverAdmission admission = new ERR_TerrainTakeoverAdmission(); admission.Connection = connection; admission.Controller = controller; admission.Identity = SCR_PlayerIdentityUtils.GetPlayerIdentityId(connection); m_ERRTerrainAdmissions.Insert(admission); PrintFormat("Reintroduced [terrain takeover]: held native admission connection=%1", connection); return true; } protected void ERR_ResetTerrainTakeover() { m_ERRTerrainStage = ERR_TerrainTakeoverStage.INACTIVE; m_ERRTerrainElapsed = 0; m_ERRTerrainWorld = null; m_ERRTerrainOriginal = null; m_ERRTerrainAdmissions.Clear(); m_ERRTerrainAirfields.Clear(); m_ERRTerrainMarkerSlots.Clear(); } protected void ERR_FailTerrainTakeover(string reason) { m_ERRTerrainStage = ERR_TerrainTakeoverStage.FAILED; m_ERRTerrainAdmissions.Clear(); PrintFormat("Reintroduced [terrain takeover failed]: %1", reason, LogLevel.ERROR); } protected bool ERR_PreflightTerrainTakeover() { if (!m_ERRTerrainOriginal || !GetWorldEntity() || !GetAIWorld() || !GetAIWorld().GetNavmeshWorldComponent("Soldiers")) { ERR_FailTerrainTakeover("Select a plain scenario with SCR_BaseGameMode and a Soldiers navmesh; original scenario retained."); return false; } Resource prefab = Resource.Load("{8FB1559AE3F29722}Prefabs/ERR/TerrainConnector.et"); if (!prefab || !prefab.IsValid()) { ERR_FailTerrainTakeover("Escape bootstrap prefab is missing; original scenario retained."); return false; } vector low, high; GetWorldEntity().GetWorldBounds(low, high); if (!ERR_TerrainSites.Bounds(low, high) || !ERR_TerrainTakeoverSettings.ParseAirfields(ERR_TerrainTakeoverSettings.Airfields(), m_ERRTerrainAirfields)) { ERR_FailTerrainTakeover("Invalid terrain size or m_sERR_Airfields; use up to 16 X Z pairs separated by semicolons. Original scenario retained."); return false; } foreach (vector centre : m_ERRTerrainAirfields) { if (centre[0] < low[0] || centre[0] > high[0] || centre[2] < low[2] || centre[2] > high[2] || ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetWorld(), centre)) { ERR_FailTerrainTakeover("An airport centre is outside this terrain or over water; original scenario retained."); return false; } } for (int slot = 0; slot < 16; slot++) if (!GetWorld().FindEntityByName("ERR_Airfield_" + slot.ToString())) m_ERRTerrainMarkerSlots.Insert(slot); if (m_ERRTerrainMarkerSlots.Count() < m_ERRTerrainAirfields.Count()) { ERR_FailTerrainTakeover("Not enough unused ERR_Airfield marker names for configured centres; original scenario retained."); return false; } return true; } protected override void OnWorldPostProcess(World world) { // Do not opt an existing authored Escape mission into replacement. if (ERR_TerrainTakeoverSettings.Enabled() && !ERR_GameMode.Get() && Replication.IsServer()) { ERR_ResetTerrainTakeover(); m_ERRTerrainWorld = world; m_ERRTerrainOriginal = SCR_BaseGameMode.Cast(GetGameMode()); if (ERR_PreflightTerrainTakeover()) { m_ERRTerrainStage = ERR_TerrainTakeoverStage.SETTLING; PrintFormat("Reintroduced [terrain takeover]: enabled; configured airports=%1", m_ERRTerrainAirfields.Count()); } } super.OnWorldPostProcess(world); } override void OnUpdate(BaseWorld world, float timeslice) { super.OnUpdate(world, timeslice); if (m_ERRTerrainStage != ERR_TerrainTakeoverStage.SETTLING && m_ERRTerrainStage != ERR_TerrainTakeoverStage.DRAINING) return; if (world != m_ERRTerrainWorld || GetWorld() != m_ERRTerrainWorld || !Replication.IsServer()) { ERR_ResetTerrainTakeover(); return; } m_ERRTerrainElapsed += timeslice; if (ERR_TerrainTakeoverRules.TimedOut(m_ERRTerrainElapsed)) { ERR_FailTerrainTakeover("Native manager registrations did not settle within 10 seconds; restart the scenario."); return; } if (m_ERRTerrainStage == ERR_TerrainTakeoverStage.SETTLING) { if (!m_ERRTerrainOriginal || GetGameMode() != m_ERRTerrainOriginal) { ERR_FailTerrainTakeover("Original mode changed during startup; takeover cancelled."); return; } if (ERR_TerrainTakeoverRules.MayDrain(m_ERRTerrainElapsed, true)) ERR_DrainTerrainMode(); } else if (ERR_TerrainTakeoverRules.MayCreate(m_ERRTerrainElapsed, ERR_TerrainRegistrationsEmpty())) ERR_CreateTerrainMode(); } protected void ERR_DrainTerrainMode() { Print("Reintroduced [terrain takeover]: stopping original scenario"); m_ERRTerrainOriginal.OnGameEnd(); if (m_DataCollectorComponent && m_DataCollectorComponent.GetOwner() == m_ERRTerrainOriginal) m_DataCollectorComponent = null; SCR_EntityCatalogManagerComponent.ERR_ReleaseTerrainCatalog(m_ERRTerrainOriginal); // Capture first, delete after: a manager may be a child of the old mode. array managers = {m_ERRTerrainOriginal, GetLoadoutManager(), GetFactionManager(), GetPerceptionManager(), GetChat()}; ChimeraWorld chimera = ChimeraWorld.CastFrom(GetWorld()); if (chimera) managers.Insert(chimera.GetRadioManager()); foreach (IEntity manager : managers) if (manager) SCR_EntityHelper.DeleteEntityAndChildren(manager); m_ERRTerrainStage = ERR_TerrainTakeoverStage.DRAINING; m_ERRTerrainElapsed = 0; } protected bool ERR_TerrainRegistrationsEmpty() { ChimeraWorld chimera = ChimeraWorld.CastFrom(GetWorld()); return !GetGameMode() && !GetLoadoutManager() && !GetFactionManager() && !GetPerceptionManager() && !GetChat() && (!chimera || !chimera.GetRadioManager()) && !SCR_EntityCatalogManagerComponent.GetInstance() && !m_DataCollectorComponent; } protected void ERR_CreateTerrainMode() { Print("Reintroduced [terrain takeover]: native registrations cleared"); for (int i = 0; i < m_ERRTerrainAirfields.Count(); i++) { EntitySpawnParams parameters = new EntitySpawnParams(); parameters.TransformMode = ETransformMode.WORLD; parameters.Transform[3] = m_ERRTerrainAirfields[i]; IEntity marker = SpawnEntity(GenericEntity, GetWorld(), parameters); if (!marker) { ERR_FailTerrainTakeover("Could not create configured airport marker; restart the scenario."); return; } marker.SetName("ERR_Airfield_" + m_ERRTerrainMarkerSlots[i].ToString()); } ERR_GameMode mode = ERR_GameMode.Cast(SpawnEntityPrefab(Resource.Load("{8FB1559AE3F29722}Prefabs/ERR/TerrainConnector.et"), GetWorld())); if (!mode || GetGameMode() != mode) { ERR_FailTerrainTakeover("Escape game mode could not register; restart the scenario."); return; } m_ERRTerrainStage = ERR_TerrainTakeoverStage.RUNNING; mode.ERR_StartTerrainTakeover(GetWorld()); foreach (ERR_TerrainTakeoverAdmission admission : m_ERRTerrainAdmissions) { int connection = admission.Connection; bool connected = GetPlayerManager().IsPlayerConnected(connection); bool sameController = admission.Controller && GetPlayerManager().GetPlayerController(connection) == admission.Controller; UUID identity = SCR_PlayerIdentityUtils.GetPlayerIdentityId(connection); if (ERR_TerrainTakeoverRules.SameAdmission(connected, sameController, !admission.Identity.IsNull(), identity == admission.Identity)) mode.OnPlayerAuditSuccess(connection); else PrintFormat("Reintroduced [terrain takeover]: discarded stale admission connection=%1", connection); } m_ERRTerrainAdmissions.Clear(); m_ERRTerrainOriginal = null; Print("Reintroduced [terrain takeover]: Escape started on selected scenario"); } override protected void OnBeforeWorldCleanup() { ERR_ResetTerrainTakeover(); super.OnBeforeWorldCleanup(); } } modded class SCR_SpawnRequestComponent { override SCR_SpawnHandlerComponent GetHandlerComponent() { ERR_GameMode mode = ERR_GameMode.Get(); if (mode && mode.ERR_TerrainTakenOver()) { // Controllers that arrived before handover retain the removed handler. // Resolve directly from the current owner, avoiding any old singleton. SCR_RespawnSystemComponent system = SCR_RespawnSystemComponent.Cast(mode.FindComponent(SCR_RespawnSystemComponent)); if (system) return SCR_SpawnHandlerComponent.Cast(system.FindComponent(GetHandlerType())); return null; } return super.GetHandlerComponent(); } }