Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
// A support station's cleanup must not require a still-living item catalog.
|
||||
// Remember the subscriptions made by successful native initialization, rather
|
||||
// than rerunning startup validation after that catalog has been destroyed.
|
||||
modded class SCR_BaseItemSupportStationComponent
|
||||
{
|
||||
protected bool m_ERRItemStationScoped;
|
||||
protected bool m_ERRItemStationInitializing;
|
||||
protected bool m_ERRItemStationInitialized;
|
||||
protected bool m_ERRItemStationDeleted;
|
||||
protected SCR_ResourceComponent m_ERRItemStationResource;
|
||||
protected SCR_SupportStationManagerComponent m_ERRItemStationManager;
|
||||
|
||||
override void OnPostInit(IEntity owner)
|
||||
{
|
||||
m_ERRItemStationScoped = !SCR_Global.IsEditMode() && (ERR_GameMode.Get() || ERR_TerrainTakeoverSettings.Enabled());
|
||||
super.OnPostInit(owner);
|
||||
}
|
||||
protected override void DelayedInit(IEntity owner)
|
||||
{
|
||||
if (m_ERRItemStationDeleted) return;
|
||||
// A preloader/preview is not a playable world. Its transient equipment may
|
||||
// be created before any game mode/catalog exists and immediately discarded.
|
||||
if (m_ERRItemStationScoped && owner && GetGame() && owner.GetWorld() != GetGame().GetWorld() && !SCR_EntityCatalogManagerComponent.GetInstance()) return;
|
||||
m_ERRItemStationInitializing = m_ERRItemStationScoped;
|
||||
super.DelayedInit(owner);
|
||||
m_ERRItemStationInitializing = false;
|
||||
if (m_ERRItemStationInitialized && UsesRange()) m_ERRItemStationManager = SCR_SupportStationManagerComponent.GetInstance();
|
||||
}
|
||||
protected override void AddRemoveOnDamageStateChanged(IEntity owner, bool subscribe)
|
||||
{
|
||||
super.AddRemoveOnDamageStateChanged(owner, subscribe);
|
||||
// This hook is reached only after the complete native virtual validation.
|
||||
if (!subscribe || !m_ERRItemStationInitializing) return;
|
||||
m_ERRItemStationInitialized = true;
|
||||
if (m_ResourceConsumer || m_ResourceGenerator) m_ERRItemStationResource = GetResourceComponent();
|
||||
}
|
||||
override void OnDelete(IEntity owner)
|
||||
{
|
||||
if (!m_ERRItemStationScoped || SCR_Global.IsEditMode())
|
||||
{
|
||||
super.OnDelete(owner);
|
||||
return;
|
||||
}
|
||||
if (m_ERRItemStationDeleted) return;
|
||||
m_ERRItemStationDeleted = true;
|
||||
if (GetGame()) GetGame().GetCallqueue().Remove(DelayedInit);
|
||||
if (m_ERRItemStationInitialized)
|
||||
{
|
||||
AddRemoveOnDamageStateChanged(owner, false);
|
||||
// Use the managers this station joined, never a new round's singleton.
|
||||
if (m_ERRItemStationManager) m_ERRItemStationManager.RemoveSupportStation(this);
|
||||
if (m_ERRItemStationResource) m_ERRItemStationResource.TEMP_GetOnInteractorReplicated().Remove(TEMP_OnInteractorReplicated);
|
||||
}
|
||||
m_ERRItemStationManager = null;
|
||||
m_ERRItemStationResource = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
// The editor core survives a game-mode replacement, but vanilla attaches its
|
||||
// player lifecycle/role listeners only once, during OnGameStart. Move those
|
||||
// listeners without restarting the core or replacing per-player editors.
|
||||
[BaseContainerProps(configRoot: true)]
|
||||
modded class SCR_EditorManagerCore
|
||||
{
|
||||
protected ERR_GameMode m_ERREditorRetryMode;
|
||||
protected bool m_ERREditorRetryQueued;
|
||||
protected bool m_ERREditorRetryNeeded;
|
||||
protected int m_ERREditorRetryAttempts;
|
||||
|
||||
// A retained controller may still carry the server's temporary identity while
|
||||
// a connected restart restores its network owner. Never give it an editor yet.
|
||||
protected bool ERR_EditorConnectionReady(int playerID)
|
||||
{
|
||||
PlayerManager players = GetGame().GetPlayerManager();
|
||||
PlayerController controller = players.GetPlayerController(playerID);
|
||||
if (!players.IsPlayerConnected(playerID) || !controller) return false;
|
||||
RplIdentity identity = controller.GetRplIdentity();
|
||||
if (identity == RplIdentity.Invalid()) return false;
|
||||
if (RplSession.Mode() == RplMode.Dedicated && identity == RplIdentity.Local()) return false;
|
||||
array<SCR_EditorManagerEntity> editors = {};
|
||||
GetEditorEntities(editors);
|
||||
foreach (SCR_EditorManagerEntity editor : editors)
|
||||
{
|
||||
int otherID = editor.GetPlayerID();
|
||||
if (otherID == playerID) continue;
|
||||
PlayerController other = players.GetPlayerController(otherID);
|
||||
if (other && other.GetRplIdentity() == identity) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override SCR_EditorManagerEntity CreateEditorManager(int playerID)
|
||||
{
|
||||
// Keep vanilla behavior outside the replaced terrain mode.
|
||||
ERR_TerrainTakeoverMode mode = ERR_TerrainTakeoverMode.Cast(GetGame().GetGameMode());
|
||||
if (!Replication.IsServer() || !mode) return super.CreateEditorManager(playerID);
|
||||
SCR_EditorManagerEntity existing = GetEditorManager(playerID);
|
||||
if (existing) return existing;
|
||||
if (!ERR_EditorConnectionReady(playerID))
|
||||
{
|
||||
if (!m_ERREditorRetryQueued && m_ERREditorRetryAttempts == 0)
|
||||
PrintFormat("Reintroduced [terrain editor]: waiting for distinct network ownership; player=%1", playerID);
|
||||
ERR_QueueTerrainEditorRetry(mode);
|
||||
return null;
|
||||
}
|
||||
return super.CreateEditorManager(playerID);
|
||||
}
|
||||
|
||||
protected void ERR_QueueTerrainEditorRetry(ERR_GameMode mode)
|
||||
{
|
||||
m_ERREditorRetryNeeded = true;
|
||||
if (m_ERREditorRetryQueued || m_ERREditorRetryAttempts >= 80) return;
|
||||
m_ERREditorRetryMode = mode;
|
||||
m_ERREditorRetryQueued = true;
|
||||
GetGame().GetCallqueue().CallLater(ERR_RetryTerrainEditors, 250, false);
|
||||
}
|
||||
protected void ERR_RetryTerrainEditors()
|
||||
{
|
||||
m_ERREditorRetryQueued = false;
|
||||
if (!m_ERREditorRetryMode || m_ERREditorRetryMode != ERR_GameMode.Get()) { ERR_StopTerrainEditorRetry(); return; }
|
||||
m_ERREditorRetryAttempts++;
|
||||
m_ERREditorRetryNeeded = false;
|
||||
ERR_ReconcileTerrainEditors(m_ERREditorRetryMode, true);
|
||||
if (m_ERREditorRetryNeeded && m_ERREditorRetryAttempts == 80)
|
||||
Print("Reintroduced [terrain editor]: ownership repair window ended; future player registration can still create its editor", LogLevel.WARNING);
|
||||
if (!m_ERREditorRetryNeeded || m_ERREditorRetryAttempts >= 80) ERR_StopTerrainEditorRetry();
|
||||
}
|
||||
protected void ERR_StopTerrainEditorRetry()
|
||||
{
|
||||
if (GetGame()) GetGame().GetCallqueue().Remove(ERR_RetryTerrainEditors);
|
||||
m_ERREditorRetryMode = null;
|
||||
m_ERREditorRetryNeeded = false;
|
||||
m_ERREditorRetryQueued = false;
|
||||
m_ERREditorRetryAttempts = 0;
|
||||
}
|
||||
override void OnGameEnd()
|
||||
{
|
||||
ERR_StopTerrainEditorRetry();
|
||||
super.OnGameEnd();
|
||||
}
|
||||
|
||||
// Registered-player events can precede takeover (notably on #restart).
|
||||
// Restore only missing managers, and let vanilla derive modes from actual roles.
|
||||
void ERR_ReconcileTerrainEditors(ERR_GameMode mode, bool missingOnly = false)
|
||||
{
|
||||
if (!Replication.IsServer() || !mode || mode != ERR_GameMode.Get() || !mode.ERR_TerrainTakenOver()) return;
|
||||
PlayerManager players = GetGame().GetPlayerManager();
|
||||
array<int> ids = {}; players.GetPlayers(ids);
|
||||
foreach (int id : ids)
|
||||
{
|
||||
if (!players.IsPlayerConnected(id) || !players.GetPlayerController(id)) continue;
|
||||
SCR_EditorManagerEntity editor = GetEditorManager(id);
|
||||
if (editor && missingOnly) continue;
|
||||
if (!editor)
|
||||
{
|
||||
OnPlayerRegistered(id);
|
||||
editor = GetEditorManager(id);
|
||||
}
|
||||
if (!editor) continue; // Ownership is still settling; the bounded retry owns repair.
|
||||
editor.RecreateEditorModes();
|
||||
IEntity body = players.GetPlayerControlledEntity(id);
|
||||
if (body && ERR_Actors.Alive(body)) OnPlayerSpawn(id, body);
|
||||
PrintFormat("Reintroduced [terrain editor]: player=%1 admin=%2 limited=%3 canOpen=%4", id, SCR_Global.IsAdmin(id), editor.IsLimited(), editor.CanOpen());
|
||||
}
|
||||
}
|
||||
void ERR_SetTerrainModeEvents(SCR_BaseGameMode mode, bool attach)
|
||||
{
|
||||
if (!mode || !Replication.IsServer()) return;
|
||||
ERR_StopTerrainEditorRetry();
|
||||
mode.GetOnPlayerRegistered().Remove(OnPlayerRegistered);
|
||||
mode.GetOnPostCompPlayerDisconnected().Remove(OnPlayerDisconnected);
|
||||
mode.GetOnPlayerSpawned().Remove(OnPlayerSpawn);
|
||||
mode.GetOnPlayerKilled().Remove(OnPlayerKilled);
|
||||
mode.GetOnPlayerDeleted().Remove(OnPlayerDeleted);
|
||||
mode.GetOnPlayerRoleChange().Remove(OnPlayerRoleChange);
|
||||
if (!attach) return;
|
||||
mode.GetOnPlayerRegistered().Insert(OnPlayerRegistered);
|
||||
mode.GetOnPostCompPlayerDisconnected().Insert(OnPlayerDisconnected);
|
||||
mode.GetOnPlayerSpawned().Insert(OnPlayerSpawn);
|
||||
mode.GetOnPlayerKilled().Insert(OnPlayerKilled);
|
||||
mode.GetOnPlayerDeleted().Insert(OnPlayerDeleted);
|
||||
mode.GetOnPlayerRoleChange().Insert(OnPlayerRoleChange);
|
||||
Print("Reintroduced [terrain takeover]: Game Master player and admin events attached to Escape");
|
||||
}
|
||||
}
|
||||
|
||||
// Vanilla caches are tied to the original game mode. Release its exact
|
||||
// components before takeover. Some vanilla destructors unconditionally clear
|
||||
// their static cache later; resolve those getters from the current takeover
|
||||
// owner so delayed destruction cannot invalidate the replacement.
|
||||
modded class SCR_ArsenalManagerComponent
|
||||
{
|
||||
static void ERR_ReleaseTerrainSingleton(SCR_ArsenalManagerComponent expected)
|
||||
{
|
||||
if (s_Instance == expected) s_Instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
modded class SCR_GroupsManagerComponent
|
||||
{
|
||||
static void ERR_ReleaseTerrainSingleton(SCR_GroupsManagerComponent expected)
|
||||
{
|
||||
if (s_Instance == expected) s_Instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
modded class SCR_SupportStationManagerComponent
|
||||
{
|
||||
static void ERR_ReleaseTerrainSingleton(SCR_SupportStationManagerComponent expected)
|
||||
{
|
||||
if (s_Instance == expected) s_Instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
modded class SCR_TaskFinishHistoryManagerComponent
|
||||
{
|
||||
static void ERR_ReleaseTerrainSingleton(SCR_TaskFinishHistoryManagerComponent expected)
|
||||
{
|
||||
if (s_Instance == expected) s_Instance = null;
|
||||
}
|
||||
override static SCR_TaskFinishHistoryManagerComponent GetInstance()
|
||||
{
|
||||
ERR_TerrainTakeoverMode mode;
|
||||
if (GetGame()) mode = ERR_TerrainTakeoverMode.Cast(GetGame().GetGameMode());
|
||||
if (mode) return SCR_TaskFinishHistoryManagerComponent.Cast(mode.FindComponent(SCR_TaskFinishHistoryManagerComponent));
|
||||
return super.GetInstance();
|
||||
}
|
||||
}
|
||||
|
||||
modded class SCR_TaskManagerUIComponent
|
||||
{
|
||||
static void ERR_ReleaseTerrainSingleton(SCR_TaskManagerUIComponent expected)
|
||||
{
|
||||
if (s_Instance == expected) s_Instance = null;
|
||||
}
|
||||
override static SCR_TaskManagerUIComponent GetInstance()
|
||||
{
|
||||
ERR_TerrainTakeoverMode mode;
|
||||
if (GetGame()) mode = ERR_TerrainTakeoverMode.Cast(GetGame().GetGameMode());
|
||||
if (mode) return SCR_TaskManagerUIComponent.Cast(mode.FindComponent(SCR_TaskManagerUIComponent));
|
||||
return super.GetInstance();
|
||||
}
|
||||
}
|
||||
|
||||
modded class SCR_TextsTaskManagerComponent
|
||||
{
|
||||
static void ERR_ReleaseTerrainSingleton(SCR_TextsTaskManagerComponent expected)
|
||||
{
|
||||
if (s_Instance == expected) s_Instance = null;
|
||||
}
|
||||
override static SCR_TextsTaskManagerComponent GetInstance()
|
||||
{
|
||||
ERR_TerrainTakeoverMode mode;
|
||||
if (GetGame()) mode = ERR_TerrainTakeoverMode.Cast(GetGame().GetGameMode());
|
||||
if (mode) return SCR_TextsTaskManagerComponent.Cast(mode.FindComponent(SCR_TextsTaskManagerComponent));
|
||||
return super.GetInstance();
|
||||
}
|
||||
}
|
||||
|
||||
modded class SCR_MapMarkerManagerComponent
|
||||
{
|
||||
override static SCR_MapMarkerManagerComponent GetInstance()
|
||||
{
|
||||
ERR_TerrainTakeoverMode mode;
|
||||
if (GetGame()) mode = ERR_TerrainTakeoverMode.Cast(GetGame().GetGameMode());
|
||||
if (mode) return SCR_MapMarkerManagerComponent.Cast(mode.FindComponent(SCR_MapMarkerManagerComponent));
|
||||
return super.GetInstance();
|
||||
}
|
||||
}
|
||||
|
||||
modded class SCR_RespawnSystemComponent
|
||||
{
|
||||
override static SCR_RespawnSystemComponent GetInstance()
|
||||
{
|
||||
ERR_TerrainTakeoverMode mode;
|
||||
if (GetGame()) mode = ERR_TerrainTakeoverMode.Cast(GetGame().GetGameMode());
|
||||
if (mode) return SCR_RespawnSystemComponent.Cast(mode.FindComponent(SCR_RespawnSystemComponent));
|
||||
return super.GetInstance();
|
||||
}
|
||||
}
|
||||
|
||||
// World systems survive mode replacement too. Vanilla caches this component
|
||||
// once in OnInit; rebinding also wakes a system disabled by the original mode.
|
||||
modded class DeployMenuSystem
|
||||
{
|
||||
void ERR_BindEscapeRespawn(SCR_RespawnSystemComponent respawn)
|
||||
{
|
||||
ERR_GameMode mode = ERR_GameMode.Get();
|
||||
if (!mode || !respawn || respawn != SCR_RespawnSystemComponent.Cast(mode.FindComponent(SCR_RespawnSystemComponent))) return;
|
||||
if (m_RespawnSystem != respawn)
|
||||
{
|
||||
m_RespawnSystem = respawn;
|
||||
m_bReady = false;
|
||||
Print("Reintroduced [loading]: respawn animation and preparation display attached to Escape");
|
||||
}
|
||||
Enable(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Standalone global nodes otherwise have no delivery-order relationship. During
|
||||
// a connected restart a faction delegate can arrive before its new mode and
|
||||
// faction manager. Express that dependency to replication before publishing the
|
||||
// newly spawned hierarchy, rather than relying on an arbitrary frame delay.
|
||||
class ERR_TerrainTakeoverReplication
|
||||
{
|
||||
static void CollectDelegates(SCR_DelegateFactionManagerComponent manager, array<IEntity> entities)
|
||||
{
|
||||
if (!manager) return;
|
||||
map<Faction, SCR_EditableFactionComponent> delegates = new map<Faction, SCR_EditableFactionComponent>();
|
||||
manager.GetFactionDelegates(delegates);
|
||||
for (int i = 0; i < delegates.Count(); i++)
|
||||
{
|
||||
SCR_EditableFactionComponent delegate = delegates.GetElement(i);
|
||||
if (delegate && delegate.GetOwner() && !entities.Contains(delegate.GetOwner())) entities.Insert(delegate.GetOwner());
|
||||
}
|
||||
}
|
||||
protected static bool Parent(IEntity entity, IEntity parent)
|
||||
{
|
||||
RplComponent childRpl = SCR_EntityHelper.GetEntityRplComponent(entity);
|
||||
RplComponent parentRpl = SCR_EntityHelper.GetEntityRplComponent(parent);
|
||||
if (!childRpl || !parentRpl) return false;
|
||||
RplNode childNode = childRpl.GetNode();
|
||||
RplNode parentNode = parentRpl.GetNode();
|
||||
if (!childNode || !parentNode || childNode == parentNode) return false;
|
||||
if (childNode.GetParent() != parentNode) childNode.SetParent(parentNode);
|
||||
return childNode.GetParent() == parentNode;
|
||||
}
|
||||
static bool Link(ERR_GameMode mode)
|
||||
{
|
||||
IEntity factions = GetGame().GetFactionManager();
|
||||
if (!factions || !Parent(factions, mode)) return false;
|
||||
if (!Parent(GetGame().GetLoadoutManager(), mode) || !Parent(GetGame().GetChat(), mode)) return false;
|
||||
SCR_DelegateFactionManagerComponent manager = SCR_DelegateFactionManagerComponent.GetInstance();
|
||||
if (!manager) return false;
|
||||
map<Faction, SCR_EditableFactionComponent> delegates = new map<Faction, SCR_EditableFactionComponent>();
|
||||
manager.GetFactionDelegates(delegates);
|
||||
for (int i = 0; i < delegates.Count(); i++)
|
||||
{
|
||||
SCR_EditableFactionComponent delegate = delegates.GetElement(i);
|
||||
if (!delegate || !Parent(delegate.GetOwner(), factions)) return false;
|
||||
}
|
||||
return !delegates.IsEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"reason": "complete", "exit": -15}
|
||||
@@ -0,0 +1,9 @@
|
||||
18:52:32.020 SCRIPT : Module: Game; loaded 6732x files; 13519x classes; used 33487K of static memory; defines: "PLATFORM_LINUX,ENF_ENABLE_VHC_OVERRIDE,ENF_ENABLE_RPLCOMPONENT_IN_GAMECODE,ENABLE_BASE_DESTRUCTION,BUDGET_OPTIMIZATION_CHECKS,DEBUG,RESPAWN_TIMER_COMPONENT_DEBUG,DEBUG_CONFIGURABLE_DIALOGS,DISABLE_WEAPON_SWITCHING,SCRIPTED_AIM_MODIFIER_DEBUG,WCS_DEFINES_ARMAMENTS,WCS_DEFINES_SPACECORE"; CRC32: c38afc14
|
||||
18:53:51.354 SCRIPT : ERR CATALOG DIAG PASS: real transient kit spawned before delayed initialization
|
||||
18:53:51.355 SCRIPT : ERR CATALOG DIAG PASS: real item support station spawned
|
||||
18:53:53.377 SCRIPT : ERR CATALOG DIAG PASS: native catalog and damage subscription initialized
|
||||
18:53:53.377 SCRIPT : ERR CATALOG DIAG PASS: real resource callback subscribed before cleanup
|
||||
18:53:53.377 SCRIPT : ERR CATALOG DIAG PASS: catalog loss cleanup detaches damage once across duplicate delete callbacks
|
||||
18:53:53.377 SCRIPT : ERR CATALOG DIAG PASS: native resource callback detached even on non-range kit
|
||||
18:53:53.377 SCRIPT : ERR CATALOG DIAG PASS: global catalog unchanged by kit-local fault injection
|
||||
18:53:55.410 SCRIPT : ERR CATALOG DIAG COMPLETE failures=0
|
||||
@@ -0,0 +1 @@
|
||||
{"reason": "complete", "exit": -15}
|
||||
@@ -0,0 +1,13 @@
|
||||
18:50:30.660 SCRIPT : Module: Game; loaded 6731x files; 13518x classes; used 33486K of static memory; defines: "PLATFORM_LINUX,ENF_ENABLE_VHC_OVERRIDE,ENF_ENABLE_RPLCOMPONENT_IN_GAMECODE,ENABLE_BASE_DESTRUCTION,BUDGET_OPTIMIZATION_CHECKS,DEBUG,RESPAWN_TIMER_COMPONENT_DEBUG,DEBUG_CONFIGURABLE_DIALOGS,DISABLE_WEAPON_SWITCHING,SCRIPTED_AIM_MODIFIER_DEBUG,WCS_DEFINES_ARMAMENTS,WCS_DEFINES_SPACECORE"; CRC32: abda9269
|
||||
18:51:45.330 SCRIPT : ERR CATALOG DIAG PASS: real transient kit spawned before delayed initialization
|
||||
18:51:45.330 SCRIPT (E): 'SCR_BaseResupplySupportStationComponent' needs a entity catalog manager!
|
||||
18:51:45.330 SCRIPT : ERR CATALOG DIAG PASS: real item support station spawned
|
||||
18:51:47.329 SCRIPT : ERR CATALOG DIAG PASS: native catalog and damage subscription initialized
|
||||
18:51:47.329 SCRIPT : ERR CATALOG DIAG PASS: real resource callback subscribed before cleanup
|
||||
18:51:47.329 SCRIPT (E): 'SCR_BaseResupplySupportStationComponent' needs a entity catalog manager!
|
||||
18:51:47.329 SCRIPT (E): 'SCR_BaseResupplySupportStationComponent' needs a entity catalog manager!
|
||||
18:51:47.329 SCRIPT : ERR CATALOG DIAG FAIL: catalog loss cleanup detaches damage once across duplicate delete callbacks
|
||||
18:51:47.329 SCRIPT : ERR CATALOG DIAG FAIL: native resource callback detached even on non-range kit
|
||||
18:51:47.329 SCRIPT : ERR CATALOG DIAG PASS: global catalog unchanged by kit-local fault injection
|
||||
18:51:47.329 SCRIPT (E): 'SCR_BaseResupplySupportStationComponent' needs a entity catalog manager!
|
||||
18:51:49.362 SCRIPT : ERR CATALOG DIAG COMPLETE failures=2
|
||||
@@ -0,0 +1,91 @@
|
||||
// Private diagnostic injected only into the frozen test addon.
|
||||
modded class SCR_BaseItemSupportStationComponent
|
||||
{
|
||||
protected int m_ERRFixtureAdds;
|
||||
protected int m_ERRFixtureRemoves;
|
||||
protected int m_ERRFixtureResourceCalls;
|
||||
protected override void AddRemoveOnDamageStateChanged(IEntity owner, bool subscribe)
|
||||
{
|
||||
super.AddRemoveOnDamageStateChanged(owner, subscribe);
|
||||
if (subscribe) m_ERRFixtureAdds++;
|
||||
else m_ERRFixtureRemoves++;
|
||||
}
|
||||
protected override void TEMP_OnInteractorReplicated()
|
||||
{
|
||||
m_ERRFixtureResourceCalls++;
|
||||
super.TEMP_OnInteractorReplicated();
|
||||
}
|
||||
void ERR_FixtureCatalogLoss() { m_EntityCatalogManager = null; }
|
||||
void ERR_FixtureResourceEvent()
|
||||
{
|
||||
SCR_ResourceComponent resource = GetResourceComponent();
|
||||
if (resource) resource.TEMP_GetOnInteractorReplicated().Invoke();
|
||||
}
|
||||
bool ERR_FixtureReady() { return m_EntityCatalogManager != null && m_ERRFixtureAdds == 1; }
|
||||
int ERR_FixtureRemoves() { return m_ERRFixtureRemoves; }
|
||||
int ERR_FixtureCalls() { return m_ERRFixtureResourceCalls; }
|
||||
}
|
||||
modded class ArmaReforgerScripted
|
||||
{
|
||||
protected int m_ERRFixtureStage;
|
||||
protected float m_ERRFixtureWait;
|
||||
protected IEntity m_ERRFixtureKit;
|
||||
protected SCR_BaseItemSupportStationComponent m_ERRFixtureStation;
|
||||
protected int m_ERRFixtureFailures;
|
||||
protected void ERR_FixtureCheck(bool pass, string label)
|
||||
{
|
||||
if (!pass) m_ERRFixtureFailures++;
|
||||
if (pass) Print("ERR CATALOG DIAG PASS: " + label);
|
||||
else Print("ERR CATALOG DIAG FAIL: " + label);
|
||||
}
|
||||
protected IEntity ERR_FixtureSpawnKit()
|
||||
{
|
||||
EntitySpawnParams params = new EntitySpawnParams(); params.TransformMode = ETransformMode.WORLD;
|
||||
params.Transform[3] = "6000 200 7000";
|
||||
return SpawnEntityPrefab(Resource.Load("{AE578EEA4244D41F}Prefabs/Items/Equipment/Kits/MedicalKit_01/MedicalKit_01_US.et"), GetWorld(), params);
|
||||
}
|
||||
override void OnUpdate(BaseWorld world, float timeslice)
|
||||
{
|
||||
super.OnUpdate(world, timeslice);
|
||||
if (m_ERRFixtureStage == 3 || !ERR_GameMode.Get() || !ERR_GameMode.Get().Active()) return;
|
||||
m_ERRFixtureWait += timeslice;
|
||||
if (m_ERRFixtureWait < 2) return;
|
||||
m_ERRFixtureWait = 0;
|
||||
if (m_ERRFixtureStage == 0)
|
||||
{
|
||||
IEntity transientKit = ERR_FixtureSpawnKit();
|
||||
ERR_FixtureCheck(transientKit != null, "real transient kit spawned before delayed initialization");
|
||||
if (transientKit) SCR_EntityHelper.DeleteEntityAndChildren(transientKit);
|
||||
m_ERRFixtureKit = ERR_FixtureSpawnKit();
|
||||
if (m_ERRFixtureKit) m_ERRFixtureStation = SCR_BaseItemSupportStationComponent.Cast(m_ERRFixtureKit.FindComponent(SCR_BaseItemSupportStationComponent));
|
||||
ERR_FixtureCheck(m_ERRFixtureStation != null, "real item support station spawned");
|
||||
m_ERRFixtureStage = 1;
|
||||
return;
|
||||
}
|
||||
if (m_ERRFixtureStage == 1)
|
||||
{
|
||||
if (m_ERRFixtureStation)
|
||||
{
|
||||
ERR_FixtureCheck(m_ERRFixtureStation.ERR_FixtureReady(), "native catalog and damage subscription initialized");
|
||||
int beforeCalls = m_ERRFixtureStation.ERR_FixtureCalls();
|
||||
m_ERRFixtureStation.ERR_FixtureResourceEvent();
|
||||
int calls = m_ERRFixtureStation.ERR_FixtureCalls();
|
||||
ERR_FixtureCheck(calls == beforeCalls + 1, "real resource callback subscribed before cleanup");
|
||||
SCR_EntityCatalogManagerComponent globalCatalog = SCR_EntityCatalogManagerComponent.GetInstance();
|
||||
m_ERRFixtureStation.ERR_FixtureCatalogLoss();
|
||||
m_ERRFixtureStation.OnDelete(m_ERRFixtureKit);
|
||||
m_ERRFixtureStation.OnDelete(m_ERRFixtureKit);
|
||||
ERR_FixtureCheck(m_ERRFixtureStation.ERR_FixtureRemoves() == 1, "catalog loss cleanup detaches damage once across duplicate delete callbacks");
|
||||
m_ERRFixtureStation.ERR_FixtureResourceEvent();
|
||||
ERR_FixtureCheck(m_ERRFixtureStation.ERR_FixtureCalls() == calls, "native resource callback detached even on non-range kit");
|
||||
ERR_FixtureCheck(globalCatalog && globalCatalog == SCR_EntityCatalogManagerComponent.GetInstance(), "global catalog unchanged by kit-local fault injection");
|
||||
}
|
||||
if (m_ERRFixtureKit) SCR_EntityHelper.DeleteEntityAndChildren(m_ERRFixtureKit);
|
||||
m_ERRFixtureKit = null; m_ERRFixtureStation = null;
|
||||
m_ERRFixtureStage = 2;
|
||||
return;
|
||||
}
|
||||
m_ERRFixtureStage = 3;
|
||||
PrintFormat("ERR CATALOG DIAG COMPLETE failures=%1", m_ERRFixtureFailures);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"date": "2026-10-07",
|
||||
"engine": "1.8.0.13",
|
||||
"source_only": true,
|
||||
"live_server_changes": false,
|
||||
"fixes": [
|
||||
"Idempotent editor creation and bounded ownership readiness retry, scoped to terrain takeover",
|
||||
"Catalog-independent item-station cleanup; never-initialized items cancel queued initialization; catalog-less off-world preloads skip gameplay initialization"
|
||||
],
|
||||
"tests": [
|
||||
"test_terrain_editor.py",
|
||||
"test_terrain_editor_registration.py",
|
||||
"test_terrain_takeover.py",
|
||||
"test_item_station_lifecycle.py",
|
||||
"test_item_station_teardown.py"
|
||||
],
|
||||
"native": {
|
||||
"item-lifecycle-unfixed": {
|
||||
"passes": 5,
|
||||
"failed_assertions": 2,
|
||||
"script_errors": 4,
|
||||
"console_sha256": "27bebba0db63a2dd601173b9c23e69a690a71924dd739d8020dc878a391eceeb",
|
||||
"completion": "18:51:49.362 SCRIPT : ERR CATALOG DIAG COMPLETE failures=2"
|
||||
},
|
||||
"item-lifecycle-fixed": {
|
||||
"passes": 7,
|
||||
"failed_assertions": 0,
|
||||
"script_errors": 0,
|
||||
"console_sha256": "9c448ef71c7d64b1d549a7271f4e01f8baf37d07fa68d64e5ce494918e15292f",
|
||||
"completion": "18:53:55.410 SCRIPT : ERR CATALOG DIAG COMPLETE failures=0"
|
||||
}
|
||||
},
|
||||
"limits": [
|
||||
"No two-player connected restart was executed locally; editor ordering is covered by production-method regression tests and native script compilation",
|
||||
"Kit-local expired catalog was fault-injected; the shared catalog remained unchanged",
|
||||
"Unrelated backend heartbeat/resource warnings and zero-radius random-point errors are outside these fixes"
|
||||
],
|
||||
"source_sha256": {
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverManagers.c": "0f75141c603cdc8b5bc8804e21df10514167dbbb1770f9f64e18fd30c6fe2d13",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ItemStationLifecycle.c": "0feee55cc5ddf13e00fe73ae3926ec2cadd5ba861d793a939164287ccbe1861d"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user