Advance standalone admission, finite artillery and native validation

This commit is contained in:
2026-09-19 09:02:31 +02:00
parent af5146a317
commit 40a474934c
532 changed files with 56456 additions and 199 deletions
@@ -1,3 +1,26 @@
// Discovery markers are authored objective information, not player annotations.
// Native local insertion overwrites owner ID before OnCreateMarker; clear it at
// that boundary so native edit/drag cannot move a discovered objective.
class ERR_DiscoveryMarker : SCR_MapMarkerBase
{
void ERR_DiscoveryMarker() { SetCanBeRemovedByOwner(false); }
override void OnCreateMarker(bool skipProfanityFilter = false)
{
SetMarkerOwnerID(-1);
// Text is scenario-authored, never user input. Avoid delayed UGC callbacks
// overwriting a newer discovery label after the location becomes identified.
super.OnCreateMarker(true);
}
override void SetCustomText(string text)
{
super.SetCustomText(text);
// Native SetCustomText updates data only; an already-open map needs its
// existing widget updated too. Closed maps use the saved text on creation.
SCR_MapMarkerWidgetComponent widget = GetMarkerComponent();
if (GetRootWidget() && widget) widget.SetText(text, true);
}
}
// Read replicated discovery locally. A server-wide marker list never leaks
// locations which have not been recovered through gameplay.
modded class ERR_LocationComponent
@@ -17,15 +40,6 @@ modded class ERR_LocationComponent
if (!GetGame().GetPlayerController() || Knowledge() <= 0 || (Knowledge() == m_ERRShown && Label() == m_ERRShownLabel)) return;
SCR_MapMarkerManagerComponent manager = SCR_MapMarkerManagerComponent.GetInstance();
if (!manager || !manager.GetMarkerConfig()) return;
if (!m_ERRMarker)
{
m_ERRMarker = new SCR_MapMarkerBase();
m_ERRMarker.SetType(SCR_EMapMarkerType.PLACED_CUSTOM);
vector p = Position();
m_ERRMarker.SetWorldPos(Math.Round(p[0]), Math.Round(p[2]));
m_ERRMarker.SetMarkerConfigID(0);
manager.InsertStaticMarker(m_ERRMarker, true);
}
string label = "Unknown location";
if (Knowledge() == 2)
{
@@ -36,6 +50,16 @@ modded class ERR_LocationComponent
else if (Kind() == ERR_LocationKind.PRISON) label = "Prison";
else label = Id();
}
if (!m_ERRMarker)
{
m_ERRMarker = new ERR_DiscoveryMarker();
m_ERRMarker.SetType(SCR_EMapMarkerType.PLACED_CUSTOM);
vector p = Position();
m_ERRMarker.SetWorldPos(Math.Round(p[0]), Math.Round(p[2]));
m_ERRMarker.SetMarkerConfigID(0);
m_ERRMarker.SetCustomText(label);
manager.InsertStaticMarker(m_ERRMarker, true);
}
m_ERRMarker.SetCustomText(label);
m_ERRShown = Knowledge();
m_ERRShownLabel = Label();
@@ -0,0 +1,107 @@
// A round-local roster identity is not a Bohemia/platform identity. Only the
// real native admission callback may select it for the sole offline controller.
class ERR_LocalAdmissionRules
{
static bool Context(bool server, int sessionMode, int count, bool connected, bool sameLocal, bool sameManager, int connection, int controllerId)
{
return server && sessionMode == RplMode.None && count == 1 && connected && sameLocal && sameManager && connection > 0 && controllerId == connection;
}
}
class ERR_LocalAdmission
{
protected ERR_GameMode m_Mode;
protected SCR_PlayerController m_Controller;
protected int m_Connection;
protected UUID m_Key;
protected bool m_Selected;
protected bool m_OfflineMode;
protected bool m_LocalKey;
protected bool m_Failed;
protected bool m_Stopped;
protected bool m_RefusalLogged;
void ERR_LocalAdmission(ERR_GameMode mode) { m_Mode = mode; m_OfflineMode = RplSession.Mode() == RplMode.None; }
bool OfflineMode() { return m_OfflineMode; }
bool Selected() { return m_Selected; }
bool LocalKey() { return m_LocalKey; }
protected bool Context(ERR_GameMode mode, int connection, SCR_PlayerController controller)
{
if (!GetGame()) return false;
PlayerManager manager = GetGame().GetPlayerManager();
bool ownedMode = mode && mode == m_Mode && mode == ERR_GameMode.Get();
int count = -1;
bool connected, sameManager;
if (manager)
{
count = manager.GetPlayerCount();
connected = manager.IsPlayerConnected(connection);
sameManager = controller && manager.GetPlayerController(connection) == controller;
}
bool sameLocal = controller && controller == GetGame().GetPlayerController();
int controllerId;
if (controller) controllerId = controller.GetPlayerId();
bool valid = ownedMode && manager && controller && ERR_LocalAdmissionRules.Context(Replication.IsServer(), RplSession.Mode(), count, connected, sameLocal, sameManager, connection, controllerId);
if (!valid && !m_RefusalLogged)
{
string project;
if (System.GetCLIParam("errOrdinaryScenario", project) && (project == "core" || project == "chat"))
{
m_RefusalLogged = true;
PrintFormat("ERR ORDINARY LOCAL CONTEXT: server=%1 sessionMode=%2 count=%3 connected=%4 sameLocal=%5 sameManager=%6 positiveMatchingId=%7 ownedMode=%8 controllerPresent=%9", Replication.IsServer(), RplSession.Mode(), count, connected, sameLocal, sameManager, connection > 0 && controllerId == connection, ownedMode, controller != null);
}
}
return valid;
}
UUID Resolve(ERR_GameMode mode, int connection, SCR_PlayerController controller, UUID nativeIdentity)
{
// This helper is inert for modes created as Listen/Dedicated, even after Stop.
if (!m_OfflineMode) return nativeIdentity;
if (m_Failed || m_Stopped) return UUID.NULL_UUID;
if (m_Selected)
{
// A late backend value or name change never grants another local life.
if (mode != m_Mode || connection != m_Connection || controller != m_Controller || !Validate()) { m_Failed = true; return UUID.NULL_UUID; }
return m_Key;
}
if (!Context(mode, connection, controller)) { m_Failed = true; return UUID.NULL_UUID; }
m_Controller = controller; m_Connection = connection; m_Key = nativeIdentity;
m_Selected = true;
if (m_Key.IsNull()) { m_Key = UUID.GenV4(); m_LocalKey = true; }
if (m_Key.IsNull()) { m_Failed = true; return UUID.NULL_UUID; }
if (m_LocalKey) Print("Rereintroduced [admission]: sole offline controller uses a generated local-round roster key; backend identity and permissions unchanged.");
return m_Key;
}
bool Validate()
{
if (!m_OfflineMode) return true;
if (m_Failed || m_Stopped) return false;
if (!m_Selected) return true;
if (!Context(m_Mode, m_Connection, m_Controller)) m_Failed = true;
return !m_Failed;
}
bool Disconnected(int connection)
{
if (!m_Selected || m_Stopped || connection != m_Connection) return false;
m_Failed = true;
return true;
}
void Stop()
{
m_Stopped = true;
m_Controller = null;
m_Key = UUID.NULL_UUID;
// Keep selection/failure latched: this object cannot admit a replacement.
}
}
modded class ERR_GameMode
{
void ERR_FailLocalAdmission()
{
if (!Replication.IsServer()) return;
string reason = "The offline player context changed. Start a new scenario to establish a fresh session.";
if (Phase() == ERR_Phase.PREPARING) { FailPreparation(reason); return; }
if (!Active()) return;
m_EndReason = reason;
Transition(ERR_Phase.DEFEAT);
}
}
@@ -17,11 +17,14 @@ modded class ERR_GameMode
protected ref ERR_NativeCasualtyServiceProbe m_ERRCasualtyServiceProbe;
protected ref ERR_NativePrisonerProbe m_ERRPrisonerProbe;
protected ref ERR_NativeGuardCancelProbe m_ERRGuardCancelProbe;
protected ref ERR_NativeLocalAdmissionProbe m_ERRLocalAdmissionProbe;
protected ref ERR_NativePoweredFlightProbe m_ERRPoweredFlightProbe;
protected ref ERR_NativeObjectiveLossProbe m_ERRObjectiveLossProbe;
protected ref ERR_NativeMedicalProbe m_ERRBaseMedicalProbe;
protected ref ERR_NativeCasualtyLoadProbe m_ERRCasualtyLoadProbe;
protected ref ERR_NativeEnvironmentProbe m_ERRBaseEnvironmentProbe;
protected bool m_ERRCatalogChecked;
protected bool m_ERRDiscoveryMapChecked;
protected ref ERR_NativePatrolProbe m_ERRPatrolEast;
protected ref ERR_NativePatrolProbe m_ERRPatrolWest;
protected int m_ERRBaseStep;
@@ -79,12 +82,15 @@ modded class ERR_GameMode
}
protected void ERR_StopHealthProbes()
{
ERR_NativeDiscoveryMapProbe.Stop();
if (m_ERRPatrolEast) m_ERRPatrolEast.Stop();
if (m_ERRPatrolWest) m_ERRPatrolWest.Stop();
if (m_ERRBaseEnvironmentRestartProbe) m_ERRBaseEnvironmentRestartProbe.Stop();
if (m_ERRBaseEnvironmentProbe) m_ERRBaseEnvironmentProbe.Stop();
if (m_ERRPrisonerProbe) m_ERRPrisonerProbe.Stop();
if (m_ERRGuardCancelProbe) m_ERRGuardCancelProbe.Stop();
if (m_ERRLocalAdmissionProbe) m_ERRLocalAdmissionProbe.Stop();
if (m_ERRPoweredFlightProbe) m_ERRPoweredFlightProbe.Stop();
if (m_ERRCasualtyServiceProbe) m_ERRCasualtyServiceProbe.Stop();
if (m_ERRObjectiveLossProbe) m_ERRObjectiveLossProbe.Stop();
if (m_ERRBaseMedicalProbe) m_ERRBaseMedicalProbe.Stop();
@@ -156,6 +162,20 @@ modded class ERR_GameMode
{
if (!ERR_MissionRules.Terminal(current)) return;
GetGame().GetCallqueue().Remove(ERR_BaseTick);
if (m_ERRExtractionCase == "powered-flight")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1802, "native powered-flight fixture finished before explicit test reset")) return;
ERR_StopHealthProbes();
Print("ERR SMOKE COMPLETE: native pilot-input powered takeoff, hover, contact landing and shutdown; isolated mechanism fixture reset");
return;
}
if (m_ERRExtractionCase == "local-admission")
{
// Completion is emitted by the calling tick after synchronous teardown and
// its post-transition invariants; a terminal event alone is insufficient.
ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1700, "local admission fixture received its production fail-closed transition");
return;
}
if (m_ERRExtractionCase == "early-helicopter-loss")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 201 && Players().HasSurvivor() && Extraction().State() == ERR_ExtractionState.LOST, "lost helicopter ends prison phase despite a living prisoner")) return;
@@ -285,6 +305,25 @@ modded class ERR_GameMode
Print("ERR SMOKE COMPLETE: expected startup-failure");
return;
}
if (m_ERRBaseStep == 1800)
{
int result = m_ERRPoweredFlightProbe.Tick(now);
if (result < 0) { ERR_BaseCheck(false, "native powered-flight feasibility fixture"); return; }
if (result == 1) { m_ERRBaseStep = 1802; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 1700)
{
int result = m_ERRLocalAdmissionProbe.Tick(now);
if (result < 0) { ERR_StopHealthProbes(); GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1)
{
m_ERRBaseStep = 1702;
GetGame().GetCallqueue().Remove(ERR_BaseTick);
Print("ERR SMOKE COMPLETE: native local admission callback replay and fail-closed method injection; not multiplayer evidence");
}
return;
}
if (m_ERRBaseStep == 1400)
{
int result = m_ERRCasualtyServiceProbe.Tick(now);
@@ -423,6 +462,25 @@ modded class ERR_GameMode
if (!ERR_NativeFactionCatalogProbe.Run(this)) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
m_ERRCatalogChecked = true;
}
if (!m_ERRDiscoveryMapChecked)
{
if (!ERR_NativeDiscoveryMapProbe.Run()) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
m_ERRDiscoveryMapChecked = true;
}
if (m_ERRExtractionCase == "powered-flight")
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRPoweredFlightProbe = new ERR_NativePoweredFlightProbe(this);
m_ERRBaseDeadline = now + 250000;
m_ERRBaseStep = 1800;
return;
}
if (m_ERRExtractionCase == "local-admission")
{
m_ERRLocalAdmissionProbe = new ERR_NativeLocalAdmissionProbe(this);
m_ERRBaseStep = 1700;
return;
}
if (m_ERRExtractionCase == "casualty-service")
{
m_ERRCasualtyServiceProbe = new ERR_NativeCasualtyServiceProbe(this);
@@ -0,0 +1,140 @@
#ifdef WORKBENCH
// Opt-in hidden native-widget comparison; no discovery, inventory or mission
// state is changed, and no map menu or player input is fabricated.
// Separate owned subclasses preserve the vanilla base RPC serializer identity.
// Their small widget adapters are repeated to compare the unchanged native
// setter against the real successor subclass without modding a replicated type.
class ERR_NativePlainMapMarker : SCR_MapMarkerBase
{
bool ERR_ProbeAttachMapWidget()
{
ERR_ProbeDetachMapWidget();
SCR_MapMarkerManagerComponent manager = SCR_MapMarkerManagerComponent.GetInstance();
if (!manager || !manager.GetMarkerConfig()) return false;
m_ConfigEntry = manager.GetMarkerConfig().GetMarkerEntryConfigByType(GetType());
if (!m_ConfigEntry) return false;
m_wRoot = GetGame().GetWorkspace().CreateWidgets(m_ConfigEntry.GetMarkerLayout());
if (!m_wRoot) return false;
m_wRoot.SetVisible(false);
m_MarkerWidgetComp = SCR_MapMarkerWidgetComponent.Cast(m_wRoot.FindHandler(SCR_MapMarkerWidgetComponent));
if (!m_MarkerWidgetComp) return false;
m_MarkerWidgetComp.SetMarkerObject(this);
m_ConfigEntry.InitClientSettings(this, m_MarkerWidgetComp, true);
return m_wRoot.FindAnyWidget("MarkerText") != null;
}
string ERR_ProbeMapText()
{
if (!m_wRoot) return "";
TextWidget text = TextWidget.Cast(m_wRoot.FindAnyWidget("MarkerText"));
if (!text) return "";
return text.GetText();
}
void ERR_ProbeDetachMapWidget()
{
// Remove only this probe object's native subscriptions/widgets.
SCR_MapEntity.GetOnMapClose().Remove(OnMapClosed);
SCR_MapEntity.GetOnLayerChanged().Remove(OnMapLayerChanged);
if (m_wRoot) m_wRoot.RemoveFromHierarchy();
m_wRoot = null;
m_MarkerWidgetComp = null;
}
}
class ERR_NativeObjectiveMapMarker : ERR_DiscoveryMarker
{
bool ERR_ProbeAttachMapWidget()
{
ERR_ProbeDetachMapWidget();
SCR_MapMarkerManagerComponent manager = SCR_MapMarkerManagerComponent.GetInstance();
if (!manager || !manager.GetMarkerConfig()) return false;
m_ConfigEntry = manager.GetMarkerConfig().GetMarkerEntryConfigByType(GetType());
if (!m_ConfigEntry) return false;
m_wRoot = GetGame().GetWorkspace().CreateWidgets(m_ConfigEntry.GetMarkerLayout());
if (!m_wRoot) return false;
m_wRoot.SetVisible(false);
m_MarkerWidgetComp = SCR_MapMarkerWidgetComponent.Cast(m_wRoot.FindHandler(SCR_MapMarkerWidgetComponent));
if (!m_MarkerWidgetComp) return false;
m_MarkerWidgetComp.SetMarkerObject(this);
m_ConfigEntry.InitClientSettings(this, m_MarkerWidgetComp, true);
return m_wRoot.FindAnyWidget("MarkerText") != null;
}
string ERR_ProbeMapText()
{
if (!m_wRoot) return "";
TextWidget text = TextWidget.Cast(m_wRoot.FindAnyWidget("MarkerText"));
if (!text) return "";
return text.GetText();
}
void ERR_ProbeDetachMapWidget()
{
// Remove only this probe object's native subscriptions/widgets.
SCR_MapEntity.GetOnMapClose().Remove(OnMapClosed);
SCR_MapEntity.GetOnLayerChanged().Remove(OnMapLayerChanged);
if (m_wRoot) m_wRoot.RemoveFromHierarchy();
m_wRoot = null;
m_MarkerWidgetComp = null;
}
}
class ERR_NativeDiscoveryMapProbe
{
protected static ref ERR_NativePlainMapMarker s_Plain;
protected static ref ERR_NativeObjectiveMapMarker s_Objective;
static void Stop()
{
SCR_MapMarkerManagerComponent manager = SCR_MapMarkerManagerComponent.GetInstance();
if (s_Plain)
{
s_Plain.ERR_ProbeDetachMapWidget();
if (manager) manager.RemoveStaticMarker(s_Plain);
}
if (s_Objective)
{
s_Objective.ERR_ProbeDetachMapWidget();
if (manager) manager.RemoveStaticMarker(s_Objective);
}
s_Plain = null;
s_Objective = null;
}
protected static bool Check(bool success, string detail)
{
if (success) { Print("ERR SMOKE PASS: discovery map " + detail); return true; }
Print("ERR SMOKE FAIL: discovery map " + detail, LogLevel.ERROR);
Stop();
return false;
}
// Invoke once after ordinary fixture admission and native map manager init.
static bool Run()
{
Stop();
SCR_MapMarkerManagerComponent manager = SCR_MapMarkerManagerComponent.GetInstance();
if (!Check(manager && manager.GetMarkerConfig() && GetGame().GetPlayerController(), "native local controller and marker manager available")) return false;
int originalCount = manager.GetStaticMarkers().Count();
s_Plain = new ERR_NativePlainMapMarker();
s_Plain.SetType(SCR_EMapMarkerType.PLACED_CUSTOM);
s_Plain.SetMarkerConfigID(0);
s_Plain.SetCustomText("Unknown location");
manager.InsertStaticMarker(s_Plain, true);
if (!Check(s_Plain.ERR_ProbeAttachMapWidget() && s_Plain.ERR_ProbeMapText() == "Unknown location", "native baseline label rendered in actual hidden marker layout")) return false;
s_Plain.SetCustomText("Radio station");
if (!Check(s_Plain.GetCustomText() == "Radio station" && s_Plain.ERR_ProbeMapText() == "Unknown location", "native data-only setter reproduces stale already-open label")) return false;
if (!Check(s_Plain.CanBeRemovedByOwner() && SCR_MapMarkersUI.IsOwnedMarker(s_Plain), "plain local marker exposes native delete/edit/drag eligibility")) return false;
s_Objective = new ERR_NativeObjectiveMapMarker();
s_Objective.SetType(SCR_EMapMarkerType.PLACED_CUSTOM);
s_Objective.SetMarkerConfigID(0);
s_Objective.SetCustomText("Unknown location");
manager.InsertStaticMarker(s_Objective, true);
if (!Check(s_Objective.GetMarkerOwnerID() == -1 && !SCR_MapMarkersUI.IsOwnedMarker(s_Objective) && !s_Objective.CanBeRemovedByOwner(), "local insertion retains objective identity and disables native edit/drag/delete eligibility")) return false;
if (!Check(s_Objective.ERR_ProbeAttachMapWidget() && s_Objective.ERR_ProbeMapText() == "Unknown location", "objective first label rendered through actual native layout")) return false;
Widget originalWidget = s_Objective.GetRootWidget();
s_Objective.SetCustomText("Radio station");
if (!Check(s_Objective.GetRootWidget() == originalWidget && s_Objective.ERR_ProbeMapText() == "Radio station", "identification refreshes existing native widget without recreating marker")) return false;
if (!Check(manager.GetStaticMarkers().Count() == originalCount + 2, "label change creates no additional marker")) return false;
s_Objective.ERR_ProbeDetachMapWidget();
s_Objective.SetCustomText("Initial helicopter pickup site");
if (!Check(!s_Objective.GetRootWidget() && s_Objective.GetCustomText() == "Initial helicopter pickup site", "closed widget accepts data-only update safely")) return false;
if (!Check(s_Objective.ERR_ProbeAttachMapWidget() && s_Objective.ERR_ProbeMapText() == "Initial helicopter pickup site", "new native widget renders latest saved label")) return false;
Stop();
return Check(manager.GetStaticMarkers().Count() == originalCount, "owned probe widgets and markers removed; mission markers preserved");
}
}
#endif
@@ -0,0 +1,129 @@
#ifdef WORKBENCH
[BaseContainerProps(category: "Respawn")]
modded class ERR_SpawnLogic
{
ERR_LocalAdmission ERR_LocalProbeHelper()
{
if (!ERR_NativeLocalAdmissionProbe.Enabled()) return null;
return m_LocalAdmission;
}
bool ERR_LocalProbeIdle(int connection)
{
return ERR_NativeLocalAdmissionProbe.Enabled() && !m_Attempts.Contains(connection) && !m_Retries.Contains(connection) && !m_Pending.Contains(connection) && !m_AuthorizedPositions.Contains(connection) && m_SpawnTickets.IsEmpty();
}
bool ERR_LocalProbeClosed()
{
return ERR_NativeLocalAdmissionProbe.Enabled() && m_AdmissionsStopped && m_LocalAdmissionFailed;
}
bool ERR_LocalProbeRefuseController(int connection)
{
if (!ERR_NativeLocalAdmissionProbe.Enabled() || !m_LocalAdmission || !m_LocalAdmission.OfflineMode() || !m_LocalAdmission.Selected() || !m_Identities.Contains(connection)) return false;
// Deliberately invalid METHOD ARGUMENT, not a fabricated native disconnect or
// replacement controller. The real controller and backend remain untouched.
UUID rejected = m_LocalAdmission.Resolve(ERR_GameMode.Get(), connection, null, m_Identities.Get(connection));
if (!rejected.IsNull()) return false;
return !CheckLocalAdmissionContext();
}
}
class ERR_NativeLocalAdmissionProbe
{
protected ERR_GameMode m_Mode;
protected SCR_PlayerController m_Controller;
protected IEntity m_Actor;
protected ERR_SpawnLogic m_Logic;
protected ERR_LocalAdmission m_Helper;
protected ERR_PlayerLife m_Life;
protected UUID m_Key;
protected int m_Connection;
protected int m_Participants;
protected int m_Changes;
protected int m_Step;
protected float m_Deadline;
protected float m_Wait;
protected bool m_Listening;
protected bool m_Stopped;
static bool Enabled()
{
string enabled, outcome;
return Replication.IsServer() && System.GetCLIParam("errBaseSmoke", enabled) && enabled == "1" && System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "local-admission";
}
void ERR_NativeLocalAdmissionProbe(ERR_GameMode mode)
{
m_Mode = mode; m_Deadline = GetGame().GetWorld().GetWorldTime() + 15000;
}
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: local admission " + detail); return true; }
Print("ERR SMOKE FAIL: local admission " + detail, LogLevel.ERROR); Stop(); return false;
}
protected void Changed(int connection) { m_Changes++; }
protected int Participants()
{
array<ref ERR_PlayerLife> lives = {}; m_Mode.Players().Participants(lives); return lives.Count();
}
protected bool Unchanged()
{
return m_Controller && m_Actor && m_Life && m_Mode.Players().Get(m_Connection) == m_Life && m_Mode.Players().CurrentActor(m_Actor) == m_Life && m_Life.AccountId == m_Key && m_Life.Connected && m_Life.UsedLife && m_Life.Actor == m_Actor && m_Controller.GetControlledEntity() == m_Actor && GetGame().GetPlayerManager().GetPlayerController(m_Connection) == m_Controller && Participants() == m_Participants && !m_Mode.Players().CanEnter(m_Connection, ERR_Phase.PRISON) && m_Logic.ERR_LocalProbeIdle(m_Connection);
}
protected bool DisposableHelpers()
{
ERR_LocalAdmission disconnected = new ERR_LocalAdmission(m_Mode);
if (!Check(disconnected.Resolve(m_Mode, m_Connection, m_Controller, m_Key) == m_Key && disconnected.Validate() && !disconnected.LocalKey(), "disposable helper accepts actual sole native controller using existing round key")) return false;
// Opaque alternative input only: no backend read/write, roster bind or login.
UUID alternative = "00e77500-0000-0000-0000-000000000001";
if (!Check(disconnected.Resolve(m_Mode, m_Connection, m_Controller, alternative) == m_Key && disconnected.Resolve(m_Mode, m_Connection, m_Controller, UUID.NULL_UUID) == m_Key, "late opaque identity input and null input retain the selected key")) return false;
if (!Check(!disconnected.Disconnected(m_Connection + 1) && disconnected.Validate(), "unrelated disconnect argument does not close helper")) return false;
if (!Check(disconnected.Disconnected(m_Connection) && !disconnected.Validate() && disconnected.Resolve(m_Mode, m_Connection, m_Controller, m_Key).IsNull(), "matching disconnect argument latches disposable helper closed")) return false;
disconnected.Stop();
if (!Check(!disconnected.Validate() && disconnected.Resolve(m_Mode, m_Connection, m_Controller, m_Key).IsNull(), "stopped disposable helper cannot reopen with the genuine controller")) return false;
ERR_LocalAdmission invalid = new ERR_LocalAdmission(m_Mode);
if (!Check(invalid.Resolve(m_Mode, m_Connection, null, m_Key).IsNull() && invalid.Resolve(m_Mode, m_Connection, m_Controller, m_Key).IsNull() && !invalid.Selected(), "invalid controller argument before selection fails closed without selecting another life")) return false;
invalid.Stop();
Print("ERR LOCAL ADMISSION LIMIT: disposable method-argument fault injection; no native disconnect or replacement occurred; Listen/Dedicated pass-through remains source-policy coverage, not multiplayer evidence");
return true;
}
void Stop()
{
if (m_Stopped) return;
m_Stopped = true;
if (m_Listening && m_Mode) m_Mode.Players().OnChanged().Remove(Changed);
m_Listening = false;
}
int Tick(float now)
{
if (m_Stopped) return -1;
if (!Enabled() || now > m_Deadline) { Check(false, "explicit enabled fixture completes within fifteen seconds"); return -1; }
if (now < m_Wait) return 0;
if (m_Step == 0)
{
m_Controller = SCR_PlayerController.Cast(GetGame().GetPlayerController());
SCR_RespawnSystemComponent respawn = SCR_RespawnSystemComponent.GetInstance();
if (respawn) m_Logic = ERR_SpawnLogic.Cast(respawn.GetSpawnLogic());
if (!Check(RplSession.Mode() == RplMode.None && m_Controller && m_Logic && GetGame().GetPlayerManager().GetPlayerCount() == 1, "actual standalone session has one native local controller and successor spawn logic")) return -1;
m_Connection = m_Controller.GetPlayerId(); m_Actor = m_Controller.GetControlledEntity();
m_Life = m_Mode.Players().Get(m_Connection); m_Helper = m_Logic.ERR_LocalProbeHelper();
if (!Check(m_Actor && m_Life && m_Helper && m_Helper.OfflineMode() && m_Helper.Selected() && m_Helper.Validate(), "real native admission already created the local controlled prisoner and selected helper")) return -1;
m_Key = m_Life.AccountId; m_Participants = Participants();
if (!Check(!m_Key.IsNull() && Unchanged(), "existing prisoner has one spent life and no pending spawn metadata")) return -1;
if (!DisposableHelpers()) return -1;
m_Mode.Players().OnChanged().Insert(Changed); m_Listening = true;
Print("ERR LOCAL ADMISSION REPLAY: invoking existing callback twice for already admitted real controller; this does not represent new native authentication/audits");
m_Logic.OnPlayerAuditSuccess_S(m_Connection);
m_Logic.OnPlayerAuditSuccess_S(m_Connection);
if (!Check(Unchanged() && m_Changes == 0, "replayed callbacks preserve exact roster object, used life, key, actor and no roster-change event")) return -1;
m_Step = 1; m_Wait = now + 1500; return 0;
}
if (m_Step == 1)
{
if (!Check(Unchanged() && m_Changes == 0 && m_Helper.Validate(), "native update interval after replay creates no replacement actor, life or pending request")) return -1;
if (!Check(m_Logic.ERR_LocalProbeRefuseController(m_Connection), "live helper rejects invalid controller argument through production context guard")) return -1;
// The guard has synchronously entered the real terminal transition and
// stopped services. No second callback, login, UUID or actor is introduced.
if (!Check(m_Mode.Phase() == ERR_Phase.DEFEAT && m_Mode.EndReason().Contains("offline player context changed") && m_Logic.ERR_LocalProbeClosed() && !m_Helper.Validate() && Unchanged(), "production failure closes admission and ends round while preserving original used life and controlled actor")) return -1;
Stop(); m_Step = 2; return 1;
}
return 0;
}
}
#endif
@@ -0,0 +1,321 @@
#ifdef WORKBENCH
// New native-input feasibility experiment. No original Escapists flight code.
// Only the exact fixture-owned AI pilot and helicopter receive these hooks.
modded class SCR_CharacterControllerComponent
{
override void OnPrepareControls(IEntity owner, ActionManager am, float dt, bool player)
{
super.OnPrepareControls(owner, am, dt, player);
ERR_NativePoweredFlightProbe probe = ERR_NativePoweredFlightProbe.Current();
if (probe && !player) probe.PreparePilot(this, owner, dt);
}
}
modded class SCR_HelicopterControllerComponent
{
protected override event void OnPrepareControls()
{
super.OnPrepareControls();
ERR_NativePoweredFlightProbe probe = ERR_NativePoweredFlightProbe.Current();
if (probe) probe.PrepareHelicopter(this);
}
}
class ERR_PoweredFlightRules
{
static bool Authority(bool server, bool enabled, bool stopped, bool ownedPilot, bool ownedSeat, bool playerPresent, bool alive, bool fueled)
{
return server && enabled && !stopped && ownedPilot && ownedSeat && !playerPresent && alive && fueled;
}
static float ClampInput(float value, float limit = 1)
{
return Math.Clamp(value, -limit, limit);
}
static float DesiredVertical(float height, bool landing)
{
float target = 8;
if (landing) target = 0;
return Math.Clamp((target - height) * 0.6, -0.8, 1.5);
}
static bool Hover(float height, float speed, float vertical, float upright, float drift)
{
return height >= 7 && height <= 9 && speed < 1.5 && Math.AbsFloat(vertical) < 0.5 && upright > 0.98 && drift < 8;
}
static bool Landed(bool contact, float height, float speed, float angular, float upright)
{
return contact && height < 1 && height > -1 && speed < 0.5 && angular < 0.1 && upright > 0.98;
}
}
class ERR_NativePoweredFlightProbe
{
protected static ERR_NativePoweredFlightProbe s_Current;
protected ERR_GameMode m_Mode;
protected Vehicle m_Aircraft;
protected SCR_AIGroup m_Group;
protected IEntity m_Pilot;
protected HelicopterControllerComponent m_Controller;
protected CharacterControllerComponent m_Character;
protected CharacterInputContext m_Input;
protected VehicleHelicopterSimulation m_Simulation;
protected FuelManagerComponent m_Fuel;
protected PilotCompartmentSlot m_Seat;
protected CollectiveMode m_OriginalMode;
protected vector m_Home;
protected float m_Heading;
protected float m_Deadline;
protected float m_StageDeadline;
protected float m_Stable = -1;
protected float m_PreviousTick = -1;
protected float m_NextLog;
protected float m_InitialFuel;
protected float m_Collective = -1;
protected float m_LastControl;
protected int m_Stage;
protected int m_Wakes;
protected int m_StartRequests;
protected int m_ControlUpdates;
protected int m_HelicopterUpdates;
protected bool m_Bound;
protected bool m_ModeSaved;
protected bool m_Stopped;
protected bool m_Complete;
protected bool m_ReadbackOK = true;
static const ResourceName AIRCRAFT = "{DF5CCB7C0FF049F4}Prefabs/Vehicles/Helicopters/Mi8MT/Mi8MT_unarmed_transport.et";
static const ResourceName GROUP = "{8DE0C0830FE0C33D}Prefabs/Groups/OPFOR/Group_USSR_Base.et";
static const ResourceName PILOT = "{86E565387A634103}Prefabs/Characters/Campaign/Final/OPFOR/USSR_Army/Regular/Campaign_USSR_Player_MortarCrew.et";
static bool Enabled()
{
string enabled, outcome;
return Replication.IsServer() && System.GetCLIParam("errBaseSmoke", enabled) && enabled == "1" && System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "powered-flight";
}
static ERR_NativePoweredFlightProbe Current() { return s_Current; }
void ERR_NativePoweredFlightProbe(ERR_GameMode mode)
{
m_Mode = mode; m_Deadline = GetGame().GetWorld().GetWorldTime() + 240000;
if (Enabled() && !s_Current) s_Current = this;
else m_Stopped = true;
}
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: powered-flight " + detail); return true; }
Print("ERR SMOKE FAIL: powered-flight " + detail, LogLevel.ERROR); Stop(); return false;
}
protected bool PlayerPresent()
{
if (!m_Aircraft || !m_Group || m_Group.GetPlayerCount(true) > 0) return true;
array<int> ids = {}; GetGame().GetPlayerManager().GetPlayers(ids);
foreach (int id : ids)
{
IEntity actor = GetGame().GetPlayerManager().GetPlayerControlledEntity(id);
if (actor && (actor == m_Pilot || CompartmentAccessComponent.GetVehicleIn(actor) == m_Aircraft)) return true;
}
return false;
}
protected bool OwnPilot()
{
return m_Pilot && m_Group && ERR_Actors.Group(m_Pilot) == m_Group && !ERR_Actors.Player(m_Pilot);
}
protected bool PoweredAuthority()
{
bool occupied = m_Seat && m_Seat.GetOccupant() == m_Pilot && m_Controller && m_Controller.GetPilotCompartmentSlot() == m_Seat && CompartmentAccessComponent.GetVehicleIn(m_Pilot) == m_Aircraft;
bool alive = ERR_Actors.Alive(m_Aircraft) && ERR_Actors.CanFight(m_Pilot);
SCR_VehicleDamageManagerComponent damage;
if (m_Aircraft) damage = SCR_VehicleDamageManagerComponent.Cast(m_Aircraft.FindComponent(SCR_VehicleDamageManagerComponent));
alive = alive && damage && damage.GetEngineFunctional() && damage.GetMovementDamage() < 1 && !damage.IsOnFire();
if (m_Simulation)
for (int rotor = 0; rotor < m_Simulation.RotorCount(); rotor++) if (m_Simulation.RotorGetState(rotor) == RotorState.DESTROYED) alive = false;
return ERR_PoweredFlightRules.Authority(Replication.IsServer(), Enabled(), m_Stopped, OwnPilot(), occupied, PlayerPresent(), alive, m_Fuel && m_Fuel.HasFuel());
}
void PrepareHelicopter(HelicopterControllerComponent controller)
{
if (!m_Bound || controller != m_Controller || !PoweredAuthority()) return;
// Native SCR controller normally reapplies device preferences every update.
// Override only this owned fixture controller, never global/user settings.
controller.SetCollectiveMode(CollectiveMode.Direct); m_HelicopterUpdates++;
}
void PreparePilot(CharacterControllerComponent controller, IEntity owner, float dt)
{
if (!m_Bound || controller != m_Character || owner != m_Pilot || !PoweredAuthority() || !m_Input || !m_Simulation || dt <= 0 || dt > 0.2) return;
vector transform[4]; m_Simulation.GetWorldTransform(transform);
vector velocity = m_Simulation.GetVelocity();
vector angular = m_Simulation.GetAngularVelocityLocal();
float height = transform[3][1] - m_Home[1];
float forward, aside, torque;
float collective = -1;
if (m_Stage >= 4 && m_Stage <= 6 && m_Controller.IsEngineOn())
{
float desiredVertical = ERR_PoweredFlightRules.DesiredVertical(height, m_Stage == 6);
float error = desiredVertical - velocity[1];
m_Collective = Math.Clamp(m_Collective + error * dt * 0.12, -0.95, 0.8);
collective = Math.Clamp(m_Collective + error * 0.12, -1, 0.9);
vector offset = m_Home - transform[3]; offset[1] = 0;
vector acceleration = offset * 0.08 - velocity * 0.3; acceleration[1] = 0;
// Small local cyclic corrections counter drift/tilt through native inputs.
// Sign/gain and native Direct-mode response remain experimental evidence.
forward = ERR_PoweredFlightRules.ClampInput((vector.Dot(acceleration, transform[2]) / 9.81 + transform[2][1]) * 0.8 - angular[0] * 0.3, 0.15);
aside = ERR_PoweredFlightRules.ClampInput((vector.Dot(acceleration, transform[0]) / 9.81 + transform[0][1]) * 0.8 + angular[2] * 0.3, 0.15);
float headingError = m_Heading - m_Aircraft.GetAngles()[0];
while (headingError > 180) headingError -= 360;
while (headingError < -180) headingError += 360;
torque = ERR_PoweredFlightRules.ClampInput(headingError * 0.01 - angular[1] * 0.3, 0.3);
if (m_Stage == 6 && ERR_PoweredFlightRules.Landed(m_Simulation.HasAnyGroundContact(), height, velocity.Length(), angular.Length(), transform[1][1])) collective = -1;
}
m_Input.SetVehicleThrottle(1);
m_Input.SetVehicleCollective(collective);
m_Input.SetVehicleCyclicForward(forward);
m_Input.SetVehicleCyclicAside(aside);
m_Input.SetVehicleAntiTorque(torque);
m_ReadbackOK = m_ReadbackOK && Math.AbsFloat(m_Input.GetVehicleCollective() - collective) < 0.001 && Math.AbsFloat(m_Input.GetVehicleCyclicForward() - forward) < 0.001 && Math.AbsFloat(m_Input.GetVehicleCyclicAside() - aside) < 0.001 && Math.AbsFloat(m_Input.GetVehicleAntiTorque() - torque) < 0.001;
m_ControlUpdates++; m_LastControl = GetGame().GetWorld().GetWorldTime();
}
protected bool FindPad(out vector result)
{
vector anchor = m_Mode.ExtractionVehicle().GetOrigin();
for (int index = 0; index < 12; index++)
{
float angle = index * Math.PI / 6;
vector candidate = ERR_Actors.Ground(anchor + Vector(Math.Cos(angle) * 120, 0, Math.Sin(angle) * 120));
if (candidate[1] < 2) continue;
bool flat = true;
for (int edge = 0; edge < 4; edge++)
{
float side = edge * Math.PI / 2;
vector point = candidate + Vector(Math.Cos(side) * 14, 0, Math.Sin(side) * 14);
if (Math.AbsFloat(SCR_TerrainHelper.GetTerrainY(point) - candidate[1]) > 0.8) flat = false;
}
if (!flat || !SCR_WorldTools.TraceCilinderUtil(candidate + "0 2.5 0", 15, 4, TraceFlags.ENTS)) continue;
TraceBox clearance = new TraceBox(); clearance.Start = candidate + "0 3 0"; clearance.End = candidate + "0 35 0";
clearance.Mins = "-15 -1 -15"; clearance.Maxs = "15 1 15"; clearance.Flags = TraceFlags.ENTS | TraceFlags.WORLD;
if (GetGame().GetWorld().TraceMove(clearance, null) < 1) continue;
result = candidate; return true;
}
return false;
}
protected void Diagnostic(float now)
{
if (!m_Simulation || !m_Aircraft || now < m_NextLog) return;
m_NextLog = now + 2000;
vector transform[4]; m_Simulation.GetWorldTransform(transform);
PrintFormat("ERR POWERED FLIGHT: stage=%1 height=%2 drift=%3 speed=%4 vertical=%5 up=%6 groundContact=%7 fuel=%8 engine=%9", m_Stage, transform[3][1] - m_Home[1], vector.DistanceXZ(transform[3], m_Home), m_Simulation.GetVelocity().Length(), m_Simulation.GetVelocity()[1], transform[1][1], m_Simulation.HasAnyGroundContact(), m_Fuel.GetTotalFuel(), m_Controller.IsEngineOn());
PrintFormat("ERR POWERED INPUT: pilotHooks=%1 vehicleHooks=%2 readback=%3 collective=%4 cyclicF=%5 cyclicR=%6 antiTorque=%7 rotorRPM=%8 targetRPM=%9", m_ControlUpdates, m_HelicopterUpdates, m_ReadbackOK, m_Input.GetVehicleCollective(), m_Input.GetVehicleCyclicForward(), m_Input.GetVehicleCyclicAside(), m_Input.GetVehicleAntiTorque(), m_Simulation.RotorGetRPM(0), m_Simulation.RotorGetRPMTarget(0));
}
void Stop()
{
if (m_Stopped) return;
// Input cleanup is confined to the same unclaimed test pilot/aircraft.
bool ours = m_Bound && OwnPilot() && !PlayerPresent();
m_Stopped = true; m_Bound = false;
if (s_Current == this) s_Current = null;
if (ours && m_Input)
{
m_Input.SetVehicleCollective(0); m_Input.SetVehicleThrottle(0);
m_Input.SetVehicleCyclicForward(0); m_Input.SetVehicleCyclicAside(0); m_Input.SetVehicleAntiTorque(0);
if (m_Controller && m_ModeSaved) m_Controller.SetCollectiveMode(m_OriginalMode);
}
if (m_Group && m_Group.GetPlayerCount(true) == 0) m_Group.SetPermanentLOD(-1);
// Keep real airborne/failed test actors for diagnosis and mode-owned cleanup.
// Stop never repairs, stabilizes, teleports, refuels or replaces them.
}
int Tick(float now)
{
if (m_Complete) return 1;
if (m_Stopped) return -1;
if (!Enabled() || !m_Mode || now >= m_Deadline || ERR_MissionRules.Terminal(m_Mode.Phase())) { Check(false, "server fixture active within 240-second bound"); return -1; }
if (m_PreviousTick >= 0 && (now < m_PreviousTick || now - m_PreviousTick > 1500)) m_Stable = -1;
m_PreviousTick = now;
if (m_Stage == 0)
{
if (!m_Mode.ExtractionVehicle()) return 0;
vector pad;
if (!Check(FindPad(pad), "separate clear flat test pad found; extraction vehicle untouched")) return -1;
m_Aircraft = Vehicle.Cast(ERR_Actors.Spawn(AIRCRAFT, pad));
m_Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(GROUP, pad + "20 0 0"));
m_Mode.Own(m_Aircraft); m_Mode.Own(m_Group);
if (!Check(m_Aircraft && m_Group, "one stock unarmed airframe and own empty group created once")) return -1;
m_Group.SetPermanentLOD(0); m_Group.ActivateAI();
m_Controller = HelicopterControllerComponent.Cast(m_Aircraft.FindComponent(HelicopterControllerComponent));
if (m_Controller) { m_Simulation = m_Controller.GetSimulation(); m_Seat = m_Controller.GetPilotCompartmentSlot(); }
m_Fuel = FuelManagerComponent.Cast(m_Aircraft.FindComponent(FuelManagerComponent));
if (!Check(m_Simulation && m_Seat && !m_Seat.GetOccupant() && m_Fuel && m_Fuel.HasFuel(), "native flight simulation, actual active pilot seat and finite fuel present")) return -1;
AIGroup nativeGroup = m_Group;
m_Pilot = m_Seat.SpawnCharacterInCompartment(PILOT, nativeGroup, GROUP);
if (!Check(m_Pilot && nativeGroup == m_Group, "one existing vanilla character spawned through native pilot-seat mechanism")) return -1;
m_Mode.Own(m_Pilot);
m_Stage = 1; m_StageDeadline = now + 30000; return 0;
}
if (m_Stage == 1)
{
Physics physics = m_Aircraft.GetPhysics();
if (physics && physics.IsDynamic() && m_Simulation.IsValid() && m_Wakes == 0) { m_Wakes = 1; physics.SetActive(ActiveState.ACTIVE); m_Stable = -1; return 0; }
if (now >= m_StageDeadline) { Check(false, "initial native seating and ground settling complete in 30 seconds"); return -1; }
if (!m_Wakes || !OwnPilot() || m_Seat.GetOccupant() != m_Pilot || !ERR_CasualtyLoadingRules.AircraftReady(m_Aircraft)) { m_Stable = -1; return 0; }
if (m_Stable < 0) m_Stable = now;
if (now - m_Stable < 1000) return 0;
m_Character = CharacterControllerComponent.Cast(m_Pilot.FindComponent(CharacterControllerComponent));
if (m_Character) m_Input = m_Character.GetInputContext();
if (!Check(m_Input && m_Group.GetAgentsCount() == 1 && PoweredAuthority(), "one living native AI pilot physically occupies its own fueled helicopter")) return -1;
m_Home = m_Aircraft.GetOrigin(); m_Heading = m_Aircraft.GetAngles()[0];
m_OriginalMode = m_Controller.GetCollectiveMode(); m_ModeSaved = true;
m_InitialFuel = m_Fuel.GetTotalFuel(); m_Bound = true;
m_Stage = 2; m_StageDeadline = now + 5000; return 0;
}
Diagnostic(now);
if (!PoweredAuthority() || !m_ReadbackOK) { Check(false, "current owned conscious pilot, fuel, rotors and exact input readback remain valid"); return -1; }
if (m_Stage == 2)
{
if (now >= m_StageDeadline) { Check(false, "native AI input and helicopter preparation hooks execute before engine start"); return -1; }
if (m_ControlUpdates < 3 || m_HelicopterUpdates < 3) return 0;
if (!Check(m_Controller.GetCollectiveMode() == CollectiveMode.Direct && m_Input.GetVehicleCollective() == -1, "owned native input hooks apply minimum collective in Direct mode")) return -1;
m_StartRequests = 1;
bool started = m_Controller.StartEngine();
PrintFormat("ERR POWERED ENGINE REQUEST: StartEngine returned=%1; normal procedure, no force-start or simulation EngineStart", started);
m_Stage = 3; m_StageDeadline = now + 65000; return 0;
}
if (now - m_LastControl > 1000) { Check(false, "native control callback remains live during test flight"); return -1; }
vector transform[4]; m_Simulation.GetWorldTransform(transform);
float height = transform[3][1] - m_Home[1];
float speed = m_Simulation.GetVelocity().Length();
float drift = vector.DistanceXZ(transform[3], m_Home);
if (height > 18 || height < -3 || drift > 22 || transform[1][1] < 0.85) { Check(false, "physical test stays inside height, drift and tilt safety envelope"); return -1; }
if (m_Stage == 3)
{
if (now >= m_StageDeadline) { Check(false, "normal engine startup and native rotor RPM complete within 65 seconds"); return -1; }
float targetRPM = m_Simulation.RotorGetRPMTarget(0);
if (!m_Controller.IsEngineOn() || targetRPM <= 0 || m_Simulation.RotorGetRPM(0) < targetRPM * 0.9 || m_Fuel.GetTotalFuel() >= m_InitialFuel) return 0;
if (!Check(m_StartRequests == 1 && m_Fuel.GetTotalFuel() < m_InitialFuel, "single normal engine start spins physical rotor and consumes native fuel")) return -1;
m_Collective = -0.8; m_Stage = 4; m_StageDeadline = now + 65000; m_Stable = -1; return 0;
}
if (m_Stage >= 4 && m_Stage <= 6 && !m_Controller.IsEngineOn()) { Check(false, "powered flight retains real running engine"); return -1; }
if (m_Stage == 4 || m_Stage == 5)
{
bool hover = !m_Simulation.HasAnyGroundContact() && ERR_PoweredFlightRules.Hover(height, speed, m_Simulation.GetVelocity()[1], transform[1][1], drift);
if (!hover) m_Stable = -1;
if (hover && m_Stable < 0) m_Stable = now;
if (now >= m_StageDeadline) { Check(false, "actual powered takeoff and five-second stable hover reached in 65 seconds"); return -1; }
if (m_Stable >= 0 && now - m_Stable >= 5000)
{
if (!Check(m_ControlUpdates > 100 && m_Fuel.GetTotalFuel() < m_InitialFuel - 0.05, "actual 7–9 metre powered hover stable five seconds with native fuel consumption")) return -1;
m_Stage = 6; m_StageDeadline = now + 65000; m_Stable = -1;
}
return 0;
}
if (m_Stage == 6)
{
bool landed = ERR_PoweredFlightRules.Landed(m_Simulation.HasAnyGroundContact(), height, speed, m_Simulation.GetAngularVelocity().Length(), transform[1][1]);
if (!landed) m_Stable = -1;
if (landed && m_Stable < 0) m_Stable = now;
if (now >= m_StageDeadline) { Check(false, "actual native contact landing reached within 65 seconds"); return -1; }
if (m_Stable < 0 || now - m_Stable < 2000) return 0;
if (!Check(drift < 8 && m_Fuel.GetTotalFuel() < m_InitialFuel - 0.05 && m_Wakes == 1 && m_StartRequests == 1, "same physical aircraft and living AI pilot land with stable ground contact after powered hover")) return -1;
m_Controller.StopEngine(false);
m_Stage = 7; m_StageDeadline = now + 15000; return 0;
}
if (m_Stage == 7)
{
if (now >= m_StageDeadline) { Check(false, "normal engine shutdown completed on actual ground"); return -1; }
if (m_Controller.IsEngineOn()) return 0;
if (!Check(ERR_PoweredFlightRules.Landed(m_Simulation.HasAnyGroundContact(), height, speed, m_Simulation.GetAngularVelocity().Length(), transform[1][1]) && PoweredAuthority(), "native engine off after contact landing; no transform/velocity/health/fuel correction")) return -1;
Stop(); m_Complete = true; return 1;
}
return 0;
}
}
#endif
@@ -67,7 +67,7 @@ modded class SCR_BaseItemSupportStationComponent
ownerName = owner.GetName();
if (owner.GetPrefabData()) prefab = owner.GetPrefabData().GetPrefabName();
}
SCR_EntityCatalogManagerComponent catalog = SCR_EntityCatalogManagerComponent.GetInstance();
bool globalCatalogPresent = SCR_EntityCatalogManagerComponent.GetInstance() != null;
SCR_BaseGameMode baseMode;
if (GetGame()) baseMode = SCR_BaseGameMode.Cast(GetGame().GetGameMode());
int gameState = -1;
@@ -80,7 +80,7 @@ modded class SCR_BaseItemSupportStationComponent
ERR_GameMode mode = ERR_GameMode.Cast(baseMode);
if (mode) phase = mode.Phase();
}
string detail = string.Format("ERR STATION TEARDOWN DIAG: n=%1 owner=%2 prefab=%3 cachedCatalog=%4 globalCatalog=%5", s_ERRStationTeardownLines, ownerName, prefab, m_EntityCatalogManager != null, catalog != null);
string detail = string.Format("ERR STATION TEARDOWN DIAG: n=%1 owner=%2 prefab=%3 cachedCatalog=%4 globalCatalog=%5", s_ERRStationTeardownLines, ownerName, prefab, m_EntityCatalogManager != null, globalCatalogPresent);
detail += string.Format(" modePresent=%1 gameState=%2 errPhase=%3 endData=%4 call=OnDelete/InitValidSetup", baseMode != null, gameState, phase, hasEndData);
detail += string.Format(" initSeen=%1 initMode=%2 initMainWorld=%3 initSiteOwned=%4 initInInventory=%5 initParentPrefab=%6", m_ERRStationInitSeen, m_ERRStationInitModePresent, m_ERRStationInitMainWorld, m_ERRStationInitSiteOwned, m_ERRStationInitInInventory, m_ERRStationInitParentPrefab);
bool deleteMainWorld;
@@ -111,4 +111,20 @@ modded class ArmaReforgerScripted
super.OnBeforeWorldCleanup();
}
}
// Observe normal finalized admission; this never creates a body or identity.
modded class ERR_GameMode
{
override void PlayerSpawned(int id, IEntity actor)
{
super.PlayerSpawned(id, actor);
string project;
if (!System.GetCLIParam("errOrdinaryScenario", project) || (project != "core" && project != "chat")) return;
PlayerController local = GetGame().GetPlayerController();
if (!local || local.GetPlayerId() != id || local.GetControlledEntity() != actor || !ERR_Actors.Alive(actor)) return;
if (!Players().CurrentActor(actor) || Phase() != ERR_Phase.PRISON) return;
PrintFormat("ERR ORDINARY: local participant verified project=%1 phase=prison", project);
}
}
#endif
+55 -4
View File
@@ -10,6 +10,8 @@ class ERR_SpawnLogic : SCR_SpawnLogic
protected ref array<ref ERR_SpawnTicket> m_SpawnTickets = {};
protected ERR_GameMode m_LifecycleMode;
protected bool m_AdmissionsStopped;
protected ref ERR_LocalAdmission m_LocalAdmission;
protected bool m_LocalAdmissionFailed;
protected void BindLifecycle(ERR_GameMode mode)
{
@@ -18,11 +20,17 @@ class ERR_SpawnLogic : SCR_SpawnLogic
{
m_LifecycleMode.OnPhaseChanged().Remove(AdmissionPhaseChanged);
m_LifecycleMode.OnStop().Remove(StopAdmissions);
m_LifecycleMode.OnTick().Remove(CheckLocalAdmissionTick);
}
if (m_LocalAdmission && m_LocalAdmission.OfflineMode()) { StopAdmissions(); m_Identities.Clear(); }
if (m_LocalAdmission) m_LocalAdmission.Stop();
m_LocalAdmission = new ERR_LocalAdmission(mode);
m_LocalAdmissionFailed = false;
m_AdmissionsStopped = false;
m_LifecycleMode = mode;
mode.OnPhaseChanged().Insert(AdmissionPhaseChanged);
mode.OnStop().Insert(StopAdmissions);
mode.OnTick().Insert(CheckLocalAdmissionTick);
}
override void OnPlayerAuditSuccess_S(int playerId)
@@ -40,20 +48,47 @@ class ERR_SpawnLogic : SCR_SpawnLogic
ERR_GameMode mode = ERR_GameMode.Get();
BindLifecycle(mode);
SCR_PlayerController resultController = SCR_PlayerController.Cast(GetGame().GetPlayerManager().GetPlayerController(playerId));
if (resultController) resultController.ERR_ResetEscapeResult();
if (resultController && (!m_LocalAdmission || !m_LocalAdmission.OfflineMode())) resultController.ERR_ResetEscapeResult();
#ifndef WORKBENCH
string diagnosticProject;
if (System.GetCLIParam("errOrdinaryScenario", diagnosticProject))
if (System.GetCLIParam("errOrdinaryScenario", diagnosticProject) && (diagnosticProject == "core" || diagnosticProject == "chat"))
{
PrintFormat("ERR ORDINARY ADMISSION: server=%1 sessionMode=%2 modePresent=%3 controllerPresent=%4 identityPresent=%5 nameLength=%6", Replication.IsServer(), RplSession.Mode(), mode != null, resultController != null, !identity.IsNull(), GetGame().GetPlayerManager().GetPlayerName(playerId).Length());
// Read cached native identity metadata only. Never print identity/name bytes,
// override cached backend identity or change its native utility.
string rawIdentity;
int rawLength = -1;
BackendApi backend = GetGame().GetBackendApi();
if (backend)
{
rawIdentity = backend.GetPlayerIdentityId(playerId);
rawLength = rawIdentity.Length();
}
UUID rawUUID = rawIdentity;
PrintFormat("ERR ORDINARY IDENTITY: rawLength=%1 rawEmpty=%2 rawNull=%3 rawFormatValid=%4 rawNullSentinel=%5 helperLength=%6 helperFormatValid=%7 dedicated=%8", rawLength, rawIdentity.IsEmpty(), rawUUID.IsNull(), UUID.IsUUID(rawIdentity), rawIdentity == UUID.NULL_UUID, identity.Length(), UUID.IsUUID(identity), RplSession.Mode() == RplMode.Dedicated);
}
#endif
if (m_LocalAdmission) identity = m_LocalAdmission.Resolve(mode, playerId, resultController, identity);
if (!CheckLocalAdmissionContext()) return;
// Repeated real callbacks retain the existing attempt/body/life and result.
if (m_LocalAdmission && m_LocalAdmission.Selected() && m_Identities.Contains(playerId))
{
if (m_Identities.Get(playerId) != identity) { m_LocalAdmission.Disconnected(playerId); CheckLocalAdmissionContext(); return; }
if (resultController) resultController.ERR_PublishEscapeResult(mode.Players(), mode.Extraction());
return;
}
if (!mode || !mode.Players().Bind(playerId, identity))
{
Print("Rereintroduced: player identity could not be bound; spawn withheld", LogLevel.ERROR);
return;
}
if (resultController) resultController.ERR_PublishEscapeResult(mode.Players(), mode.Extraction());
if (resultController)
{
if (m_LocalAdmission && m_LocalAdmission.OfflineMode()) resultController.ERR_ResetEscapeResult();
resultController.ERR_PublishEscapeResult(mode.Players(), mode.Extraction());
}
m_Identities.Set(playerId, identity);
if (ResolveReconnection(playerId))
if ((!m_LocalAdmission || !m_LocalAdmission.LocalKey()) && ResolveReconnection(playerId))
{
mode.PlayerSpawned(playerId, GetGame().GetPlayerManager().GetPlayerControlledEntity(playerId));
SCR_PlayerController.Cast(GetGame().GetPlayerManager().GetPlayerController(playerId)).ERR_SetObserver(ERR_ObserverReason.NONE);
@@ -68,6 +103,7 @@ class ERR_SpawnLogic : SCR_SpawnLogic
ERR_GameMode mode = ERR_GameMode.Get();
SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerManager().GetPlayerController(playerId));
if (!controller || !mode || m_AdmissionsStopped || !m_Identities.Contains(playerId) || !m_Attempts.Contains(playerId) || m_Pending.Contains(playerId)) return;
if (!CheckLocalAdmissionContext()) return;
if (controller.GetControlledEntity()) return;
if (ERR_MissionRules.Terminal(mode.Phase())) return;
if (!mode.PreparationError().IsEmpty()) { Observe(playerId, ERR_ObserverReason.SPAWN_UNAVAILABLE); return; }
@@ -150,6 +186,7 @@ class ERR_SpawnLogic : SCR_SpawnLogic
{
ERR_GameMode mode = ERR_GameMode.Get();
if (!Replication.IsServer() || m_AdmissionsStopped || !mode || !ticket || ticket.Mode != mode || !ticket.Actor || !ticket.Request || !ticket.Controller) return false;
if (!CheckLocalAdmissionContext()) return false;
ERR_PlayerLife life = mode.Players().Get(ticket.ConnectionId);
if (!life || !m_Identities.Contains(ticket.ConnectionId)) return false;
PlayerController controller = GetGame().GetPlayerManager().GetPlayerController(ticket.ConnectionId);
@@ -197,10 +234,21 @@ class ERR_SpawnLogic : SCR_SpawnLogic
CancelAdmission(playerId, reason);
}
}
protected bool CheckLocalAdmissionContext()
{
if (!m_LocalAdmission || m_LocalAdmission.Validate()) return true;
if (m_LocalAdmissionFailed || m_AdmissionsStopped) return false;
m_LocalAdmissionFailed = true;
StopAdmissions();
if (m_LifecycleMode) m_LifecycleMode.ERR_FailLocalAdmission();
return false;
}
protected void CheckLocalAdmissionTick(float now) { CheckLocalAdmissionContext(); }
protected void StopAdmissions()
{
if (!Replication.IsServer()) return;
m_AdmissionsStopped = true;
if (m_LocalAdmission) m_LocalAdmission.Stop();
array<int> pending = {};
foreach (int id, vector position : m_AuthorizedPositions) pending.Insert(id);
foreach (ERR_SpawnTicket ticket : m_SpawnTickets)
@@ -277,6 +325,7 @@ class ERR_SpawnLogic : SCR_SpawnLogic
}
override void OnPlayerDisconnected_S(int playerId, KickCauseCode cause, int timeout)
{
if (m_LocalAdmission && m_LocalAdmission.Disconnected(playerId)) CheckLocalAdmissionContext();
CancelAdmission(playerId, ERR_ObserverReason.NONE);
m_Identities.Remove(playerId);
ERR_GameMode mode = ERR_GameMode.Get();
@@ -287,6 +336,7 @@ class ERR_SpawnLogic : SCR_SpawnLogic
{
ERR_GameMode mode = ERR_GameMode.Get();
if (!Replication.IsServer() || m_AdmissionsStopped || !mode || !data || !m_Identities.Contains(playerId) || !mode.Players().CanEnter(playerId, mode.Phase())) return false;
if (!CheckLocalAdmissionContext()) return false;
ERR_PlayerLife life = mode.Players().Get(playerId);
if (!life || life.AccountId != m_Identities.Get(playerId)) return false;
if (!m_Pending.Contains(playerId) || !m_AuthorizedPositions.Contains(playerId) || data.GetPrefab() != mode.Factions().PrisonerPrefab) return false;
@@ -300,6 +350,7 @@ class ERR_SpawnLogic : SCR_SpawnLogic
{
m_LifecycleMode.OnPhaseChanged().Remove(AdmissionPhaseChanged);
m_LifecycleMode.OnStop().Remove(StopAdmissions);
m_LifecycleMode.OnTick().Remove(CheckLocalAdmissionTick);
}
GetGame().GetCallqueue().Remove(RetrySpawn);
GetGame().GetCallqueue().Remove(ObserveAfterLoss);