Files
EscapistsReintroduced/docs/evidence/terrain-lookup-loop-2026-10-04/fixed/presentation.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

220 lines
9.4 KiB
Plaintext

// Resolve from the scenario header even before a world exists: the backend asks
// for its mission name during startup. Child scenario filenames are not terrains.
class ERR_TerrainDisplayName
{
// GetWorldFile() returns a path, often $Addon:Worlds/..., not a GUID resource.
// ResourceName.GetPath() asks the resource system to resolve it and logs a
// zero-GUID warning on every call. Compare/display paths without that lookup.
static string Path(string world)
{
if (world.Length() >= 18 && world.Substring(0, 1) == "{" && world.Substring(17, 1) == "}")
return world.Substring(18, world.Length() - 18);
return world;
}
static string Label(string world)
{
if (world.IsEmpty()) return "";
string name = FilePath.StripPath(Path(world));
int extension = name.IndexOf(".");
if (extension >= 0) name = name.Substring(0, extension);
// Packed worlds may hide their parent container before native world loading.
// Strip conventional scenario wrappers for that early callback fallback.
string lower = name; lower.ToLower();
foreach (string prefix : {"gm_", "err_", "conflict_", "combatops_", "campaign_"})
{
if (lower.IndexOf(prefix) != 0) continue;
name = name.Substring(prefix.Length(), name.Length() - prefix.Length());
lower = name; lower.ToLower(); break;
}
foreach (string suffix : {"_test", "_gm", "_exploration", "_conflict", "_combatops"})
{
int start = lower.Length() - suffix.Length();
if (start < 0 || lower.Substring(start, suffix.Length()) != suffix) continue;
name = name.Substring(0, start); lower = name; lower.ToLower(); break;
}
if (lower == "eden") return "Everon";
name.Replace("_", " "); name.Replace("\n", " "); name.Replace("\r", " ");
name = name.Trim();
if (name.Length() > 80) name = name.Substring(0, 80);
if (!name.IsEmpty()) { string first = name.Substring(0, 1); first.ToUpper(); name = first + name.Substring(1, name.Length() - 1); }
return name;
}
static string Resolve(ResourceName world)
{
array<ResourceName> seen = {};
for (int depth = 0; depth < 16 && !world.IsEmpty(); depth++)
{
if (seen.Contains(world)) break;
seen.Insert(world);
Resource resource = BaseContainerTools.LoadContainer(world);
if (!resource || !resource.IsValid()) break;
BaseContainer container = resource.GetResource().ToBaseContainer();
if (!container) break;
ResourceName parent;
if (!container.Get("Parent", parent) || parent.IsEmpty() || seen.Contains(parent)) break;
world = parent;
}
return Label(world);
}
}
// Scenario selection still loads the terrain's header. Advertise the configured
// Escape mode through the native DS name callback, then update local metadata
// only when the authority actually starts Escape or its bootstrap reaches a client.
modded class ArmaReforgerScripted
{
protected ref SCR_MissionHeader m_ERRPresentedTerrainHeader;
protected string m_ERROriginalTerrainTitle;
protected string m_ERROriginalTerrainGameMode;
protected string m_ERRAppliedTerrainTitle;
protected string m_ERRTitleWorld;
protected string m_ERRTerrainLabel;
protected BaseWorld m_ERRPresentedTerrainWorld;
protected bool m_ERRTitleLoaded;
protected float m_ERRTerrainPresentationWait;
string ERR_TerrainMissionTitle(SCR_MissionHeader header)
{
string world = header.GetWorldResourceName();
string loadedWorld = GetWorldFile();
// After the actual Escape bootstrap, the running world wins over a stale
// source header. Before that, do not label an incoming scenario with the
// outgoing world's terrain. A saved next-round selection is never consulted.
bool confirmed = m_ERRPresentedTerrainHeader == header && m_ERRPresentedTerrainWorld && m_ERRPresentedTerrainWorld == GetWorld();
if (confirmed && !loadedWorld.IsEmpty()) world = loadedWorld;
bool loaded = GetWorld() && GetWorld().GetNumSubScenes() > 0 && !world.IsEmpty() && ERR_TerrainDisplayName.Path(loadedWorld) == ERR_TerrainDisplayName.Path(world);
if (loaded)
world = GetWorld().GetSubSceneName(0);
if (world != m_ERRTitleWorld || loaded != m_ERRTitleLoaded)
{
m_ERRTitleWorld = world; m_ERRTitleLoaded = loaded;
m_ERRTerrainLabel = ERR_TerrainDisplayName.Resolve(world);
}
string title = "Co10 Escape Reintroduced";
if (!m_ERRTerrainLabel.IsEmpty()) title += " - " + m_ERRTerrainLabel;
return title;
}
override string GetMissionName()
{
SCR_MissionHeader header = SCR_MissionHeader.Cast(GetMissionHeader());
// Our own presentation has already set GameMode=Escape. It still needs to
// follow the running terrain when the root scene becomes available later.
if (header && header == m_ERRPresentedTerrainHeader)
{
ERR_RefreshTerrainPresentation();
return header.m_sName;
}
// The engine requests this before the dedicated world has loaded. Preserve
// authored Escape names and explicit opt-outs; do not change the scenario ID.
if (Replication.IsServer() && header && header.m_sGameMode != "Escape" && m_ERRTerrainStage != ERR_TerrainTakeoverStage.FAILED && ERR_TerrainTakeoverSettings.Enabled())
return ERR_TerrainMissionTitle(header);
return super.GetMissionName();
}
void ERR_PresentTerrainTakeover()
{
SCR_MissionHeader header = SCR_MissionHeader.Cast(GetMissionHeader());
if (!header || SCR_Global.IsEditMode()) return;
if (m_ERRPresentedTerrainHeader == header && m_ERRPresentedTerrainWorld == GetWorld())
{ ERR_RefreshTerrainPresentation(); return; }
ERR_RestoreTerrainPresentation();
m_ERRPresentedTerrainHeader = header;
m_ERRPresentedTerrainWorld = GetWorld();
m_ERROriginalTerrainTitle = header.m_sName;
m_ERROriginalTerrainGameMode = header.m_sGameMode;
m_ERRAppliedTerrainTitle = ERR_TerrainMissionTitle(header);
header.m_sName = m_ERRAppliedTerrainTitle;
header.m_sGameMode = "Escape";
PrintFormat("Reintroduced [terrain presentation]: mission=%1; source scenario=%2", header.m_sName, header.GetHeaderResourceName());
}
void ERR_RefreshTerrainPresentation()
{
SCR_MissionHeader header = SCR_MissionHeader.Cast(GetMissionHeader());
if (!header || header != m_ERRPresentedTerrainHeader || SCR_Global.IsEditMode()) return;
if (m_ERRPresentedTerrainWorld != GetWorld() || header.m_sName != m_ERRAppliedTerrainTitle) return;
string title = ERR_TerrainMissionTitle(header);
if (title == m_ERRAppliedTerrainTitle) return;
m_ERRAppliedTerrainTitle = title; header.m_sName = title;
PrintFormat("Reintroduced [terrain presentation]: refreshed mission=%1; loaded world=%2", title, GetWorldFile());
}
string ERR_TerrainLoadingTitle(SCR_MissionHeader requested)
{
if (!requested) return "";
SCR_MissionHeader header = SCR_MissionHeader.Cast(GetMissionHeader());
// The loading screen can hold a separate copy of the same scenario header.
// Never replace another incoming scenario's title with the outgoing world.
if (!header || header != m_ERRPresentedTerrainHeader || m_ERRPresentedTerrainWorld != GetWorld()) return requested.m_sName;
if (requested != header && (requested.GetHeaderResourceName().IsEmpty() || requested.GetHeaderResourceName() != header.GetHeaderResourceName())) return requested.m_sName;
ERR_RefreshTerrainPresentation();
return header.m_sName;
}
override void OnUpdate(BaseWorld world, float timeslice)
{
super.OnUpdate(world, timeslice);
if (!m_ERRPresentedTerrainHeader) return;
m_ERRTerrainPresentationWait += timeslice;
if (m_ERRTerrainPresentationWait < 0.5) return;
m_ERRTerrainPresentationWait = 0;
ERR_RefreshTerrainPresentation();
}
protected void ERR_RestoreTerrainPresentation()
{
if (m_ERRPresentedTerrainHeader)
{
// A later change by another system belongs to that system.
if (m_ERRPresentedTerrainHeader.m_sName == m_ERRAppliedTerrainTitle) m_ERRPresentedTerrainHeader.m_sName = m_ERROriginalTerrainTitle;
if (m_ERRPresentedTerrainHeader.m_sGameMode == "Escape") m_ERRPresentedTerrainHeader.m_sGameMode = m_ERROriginalTerrainGameMode;
}
m_ERRPresentedTerrainHeader = null;
m_ERRPresentedTerrainWorld = null;
m_ERROriginalTerrainTitle = "";
m_ERROriginalTerrainGameMode = "";
m_ERRAppliedTerrainTitle = "";
}
override protected void OnBeforeWorldCleanup()
{
ERR_RestoreTerrainPresentation();
m_ERRTitleWorld = ""; m_ERRTerrainLabel = ""; m_ERRTitleLoaded = false; m_ERRTerrainPresentationWait = 0;
super.OnBeforeWorldCleanup();
}
}
// Vanilla copies the header title once, before the replicated Escape bootstrap
// may arrive. Refresh only the title; do not recreate loading widgets/hints.
modded class SCR_LoadingScreenComponent
{
protected ref SCR_MissionHeader m_ERRLoadingTerrainHeader;
protected string m_ERRLoadingTerrainTitle;
protected float m_ERRLoadingTerrainWait;
override void OnMissionDataRetrieved(SCR_MissionHeader header)
{
super.OnMissionDataRetrieved(header);
m_ERRLoadingTerrainHeader = header;
m_ERRLoadingTerrainTitle = ""; m_ERRLoadingTerrainWait = 0;
ERR_RefreshLoadingTerrainTitle();
}
protected void ERR_RefreshLoadingTerrainTitle()
{
if (!m_ERRLoadingTerrainHeader || !m_Widgets || !m_Widgets.m_wTitle || !GetGame()) return;
string title = GetGame().ERR_TerrainLoadingTitle(m_ERRLoadingTerrainHeader);
if (title == m_ERRLoadingTerrainTitle) return;
m_ERRLoadingTerrainTitle = title;
m_Widgets.m_wTitle.SetText(title);
}
override void Update(float timeSlice, float progress = 0, float minDurationRatio = 0)
{
super.Update(timeSlice, progress, minDurationRatio);
m_ERRLoadingTerrainWait += timeSlice;
if (m_ERRLoadingTerrainWait < 0.5) return;
m_ERRLoadingTerrainWait = 0;
ERR_RefreshLoadingTerrainTitle();
}
}