Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
211 lines
8.9 KiB
Plaintext
211 lines
8.9 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
|
|
{
|
|
static string Label(ResourceName world)
|
|
{
|
|
if (world.IsEmpty()) return "";
|
|
string name = FilePath.StripPath(world.GetPath());
|
|
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 ResourceName m_ERRTitleWorld;
|
|
protected string m_ERRTerrainLabel;
|
|
protected BaseWorld m_ERRPresentedTerrainWorld;
|
|
protected bool m_ERRTitleLoaded;
|
|
protected float m_ERRTerrainPresentationWait;
|
|
|
|
string ERR_TerrainMissionTitle(SCR_MissionHeader header)
|
|
{
|
|
ResourceName world = header.GetWorldResourceName();
|
|
ResourceName 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() && loadedWorld.GetPath() == world.GetPath();
|
|
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();
|
|
}
|
|
}
|