Validate independent packed addons and complete physical mortar salvos

This commit is contained in:
2026-09-19 11:42:13 +02:00
parent 7c53b4bb81
commit dfd7cc49e2
532 changed files with 63902 additions and 96 deletions
@@ -519,6 +519,9 @@ modded class ERR_GameMode
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRPoweredFlightProbe = new ERR_NativePoweredFlightProbe(this);
string autohoverMode;
if (System.GetCLIParam("errFlightAutohover", autohoverMode) && autohoverMode == "1")
if (!ERR_BaseCheck(m_ERRPoweredFlightProbe.SetAutohoverMode(true), "owned native autohover experiment selected before allocation")) return;
m_ERRBaseDeadline = now + 250000;
m_ERRBaseStep = 1800;
return;
@@ -30,6 +30,22 @@ modded class ERR_NativePoweredFlightProbe
return PoweredAuthority();
}
override void PreparePilotActions(CharacterControllerComponent controller, IEntity owner, ActionManager am, float dt)
{
int before = m_ControlUpdates;
super.PreparePilotActions(controller, owner, am, dt);
if (m_ControlUpdates == before || controller != m_Character || owner != m_Pilot || !ERR_InputDiagActive()) return;
m_ERRWrittenSequence = m_ControlUpdates;
m_ERRWrittenAt = GetGame().GetWorld().GetWorldTime();
// These are actual owned virtual-action values, not native consumption.
m_ERRWrittenCollective = am.GetActionValue("HelicopterCollectiveIncrease") - am.GetActionValue("HelicopterCollectiveDecrease");
float forward = am.GetActionValue("HelicopterCyclicForward") - am.GetActionValue("HelicopterCyclicBack");
float aside = am.GetActionValue("HelicopterCyclicRight") - am.GetActionValue("HelicopterCyclicLeft");
float torque = am.GetActionValue("HelicopterAntiTorqueRight") - am.GetActionValue("HelicopterAntiTorqueLeft");
m_ERRWrittenCyclic = Vector(forward, aside, torque);
m_ERRWrittenThrottle = m_Input.GetVehicleThrottle(); // observation only in action mode
}
override void ApplyPilot(CharacterControllerComponent controller, IEntity owner, float dt)
{
// Snapshot native context before this callback's owned final writes.
@@ -5,8 +5,9 @@ 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.PreparePilotActions(this, owner, am, dt);
super.OnPrepareControls(owner, am, dt, player);
if (probe && !player) probe.ObservePilotPreparation(this, owner);
}
protected override void OnApplyControls(IEntity owner, float timeSlice)
@@ -78,11 +79,24 @@ class ERR_PoweredFlightRules
}
static float NativeCollective(float signedDemand)
{
// The native apply stage clamps negative collective to zero (100422).
// Keep the feedback controller's signed state; encode its final demand in
// the nonnegative native collective domain, unlike signed cyclic/torque.
// Preserve the legacy Apply experiment unchanged. Later observations showed
// overwritten positive inputs too, so this is not a proven native domain.
return (ClampInput(signedDemand) + 1) * 0.5;
}
static float CollectiveCommand(float signedDemand, bool actions, int stage)
{
// Preserve pre-engine/startup zero and the old Apply comparison branch.
if (!actions) return NativeCollective(signedDemand);
if (stage == 9) return 0.05;
if (stage == 10) return -0.05;
if (stage == 7) return -1; // keep minimum pitch while the stopped engine's rotors coast
if (stage >= 4 && stage <= 6) return ClampInput(signedDemand);
return 0;
}
static bool NegativeCollectiveObserved(float command, float signal)
{
return command >= -0.06 && command <= -0.04 && Math.AbsFloat(signal - command) <= 0.01;
}
static bool CollectiveObserved(float command, float signal)
{
return command >= 0.04 && command <= 0.06 && Math.AbsFloat(signal - command) <= 0.01;
@@ -134,6 +148,9 @@ class ERR_NativePoweredFlightProbe
protected int m_PilotPreparations;
protected int m_PositiveGateUpdates;
protected float m_PositiveSignalSince = -1;
protected int m_NegativeGateUpdates;
protected float m_NegativeSignalSince = -1;
protected bool m_SignedCollectiveProven;
protected int m_HelicopterUpdates;
protected bool m_PilotSpawnRequested;
protected AIAgent m_PilotAgent;
@@ -147,6 +164,14 @@ class ERR_NativePoweredFlightProbe
protected bool m_Stopped;
protected bool m_Complete;
protected bool m_ReadbackOK = true;
protected bool m_ActionInputs;
protected bool m_Started;
protected bool m_AutohoverMode;
protected bool m_AutohoverSaved;
protected bool m_OriginalAutohover;
protected HelicopterControllerComponent m_AutohoverController;
protected ActionManager m_OwnedActions;
protected int m_ActionContextSamples;
static const ResourceName AIRCRAFT = "{DF5CCB7C0FF049F4}Prefabs/Vehicles/Helicopters/Mi8MT/Mi8MT_unarmed_transport.et";
static const ResourceName GROUP = "{000CD338713F2B5A}Prefabs/AI/Groups/Group_Base.et";
static const ResourceName PILOT = "{86E565387A634103}Prefabs/Characters/Campaign/Final/OPFOR/USSR_Army/Regular/Campaign_USSR_Player_MortarCrew.et";
@@ -158,10 +183,48 @@ class ERR_NativePoweredFlightProbe
static ERR_NativePoweredFlightProbe Current() { return s_Current; }
void ERR_NativePoweredFlightProbe(ERR_GameMode mode)
{
string actionInputs;
m_ActionInputs = System.GetCLIParam("errFlightActionInputs", actionInputs) && actionInputs == "1";
m_Mode = mode; m_Deadline = GetGame().GetWorld().GetWorldTime() + 240000;
if (Enabled() && !s_Current) s_Current = this;
else m_Stopped = true;
}
// Fixture configuration only: caller must select this before the first Tick.
bool SetAutohoverMode(bool enabled)
{
if (m_Started || m_Stopped || m_Complete || m_Bound || m_Aircraft) return false;
m_AutohoverMode = enabled;
return true;
}
protected bool BeginAutohover()
{
if (!m_AutohoverMode) return true;
if (m_AutohoverSaved || !m_Bound || !PoweredAuthority() || !m_Controller || m_Controller.GetOwner() != m_Aircraft) return false;
m_AutohoverController = m_Controller;
m_OriginalAutohover = m_Controller.GetAutohoverEnabled();
m_AutohoverSaved = true;
m_Controller.SetAutohoverEnabled(true);
return m_Controller.GetAutohoverEnabled();
}
protected bool AutohoverReady()
{
if (!m_AutohoverMode) return true;
// Native autohover also rewrites collective. Prove direct input consumption
// first, then enable its controller only for the bounded flight phase.
if (!m_AutohoverSaved && (m_Stage == 2 || m_Stage == 3 || m_Stage == 9 || m_Stage == 10)) return true;
return m_AutohoverSaved && m_AutohoverController && m_AutohoverController == m_Controller && m_Controller.GetOwner() == m_Aircraft && m_Controller.GetAutohoverEnabled();
}
protected void RestoreAutohover(bool ownedSeat)
{
if (ownedSeat && m_AutohoverSaved && m_AutohoverController && m_AutohoverController == m_Controller && m_Controller.GetOwner() == m_Aircraft)
{
m_Controller.SetAutohoverEnabled(m_OriginalAutohover);
PrintFormat("ERR POWERED AUTOHOVER RESTORE: original=%1 observed=%2", m_OriginalAutohover, m_Controller.GetAutohoverEnabled());
if (m_Controller.GetAutohoverEnabled() != m_OriginalAutohover) Print("ERR SMOKE FAIL: powered-flight original owned autohover state restored", LogLevel.ERROR);
}
m_AutohoverSaved = false;
m_AutohoverController = null;
}
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: powered-flight " + detail); return true; }
@@ -200,22 +263,24 @@ class ERR_NativePoweredFlightProbe
// Override only this owned fixture controller, never global/user settings.
controller.SetCollectiveMode(CollectiveMode.Direct); m_HelicopterUpdates++;
}
bool UsesActionInputs() { return m_ActionInputs; }
void ObservePilotPreparation(CharacterControllerComponent controller, IEntity owner)
{
if (!m_Bound || controller != m_Character || owner != m_Pilot || !PoweredAuthority()) return;
m_PilotPreparations++;
}
// Native prepare inputs are overwritten before application (101342).
// Integrate and write once here, after vanilla OnApplyControls, never in both.
void ApplyPilot(CharacterControllerComponent controller, IEntity owner, float dt)
// Both selectable input paths share one computation. Only the selected
// path integrates per callback; neither path invokes the other writer.
protected bool ComputePilotDemand(CharacterControllerComponent controller, IEntity owner, float dt, out float collective, out vector axes)
{
if (!m_Bound || controller != m_Character || owner != m_Pilot || !PoweredAuthority() || !m_Input || !m_Simulation || dt <= 0 || dt > 0.2) return;
if (!m_Bound || controller != m_Character || owner != m_Pilot || !PoweredAuthority() || !AutohoverReady() || !m_Input || !m_Simulation || dt <= 0 || dt > 0.2) return false;
if (m_ActionInputs && m_Stage >= 4 && m_Stage <= 6 && !m_SignedCollectiveProven) return false;
vector transform[4]; m_Aircraft.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;
collective = -1;
if (m_Stage == 9) collective = -0.9; // native0.05, bounded signal-consumption proof
if (m_Stage >= 4 && m_Stage <= 6 && m_Controller.IsEngineOn())
{
@@ -224,18 +289,73 @@ class ERR_NativePoweredFlightProbe
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;
// Native105226 already brakes before8m refusal; increase only velocity damping.
vector acceleration = offset * 0.08 - velocity * 0.6; 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];
forward = ERR_PoweredFlightRules.ClampInput((vector.Dot(acceleration, transform[2]) / 9.81 + transform[2][1]) * 0.8 - angular[0] * 1.2, 0.15);
aside = ERR_PoweredFlightRules.ClampInput((vector.Dot(acceleration, transform[0]) / 9.81 + transform[0][1]) * 0.8 + angular[2] * 1.2, 0.15);
// Native autohover also adjusts collective; observe its physical response.
// Give its lateral controller zero custom cyclic in this experiment.
// Preserve collective/yaw and bounds; never combine two cyclic controllers.
if (m_AutohoverMode) { forward = 0; aside = 0; }
float headingError = m_Heading - m_Aircraft.GetYawPitchRoll()[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;
}
collective = ERR_PoweredFlightRules.NativeCollective(collective);
collective = ERR_PoweredFlightRules.CollectiveCommand(collective, m_ActionInputs, m_Stage);
axes = Vector(forward, aside, torque);
return true;
}
protected bool WriteActionPair(ActionManager am, string positive, string negative, float demand)
{
float a = Math.Clamp(demand, 0, 1);
float b = Math.Clamp(-demand, 0, 1);
am.SetActionValue(positive, a); am.SetActionValue(negative, b);
return Math.AbsFloat(am.GetActionValue(positive) - a) < 0.001 && Math.AbsFloat(am.GetActionValue(negative) - b) < 0.001;
}
protected bool OwnActionManager(ActionManager am)
{
// Read the global manager only to reject shared ownership. Never write it.
return am && m_PilotAgent && am == m_PilotAgent.GetActionManager() && am != GetGame().GetInputManager();
}
void PreparePilotActions(CharacterControllerComponent controller, IEntity owner, ActionManager am, float dt)
{
if (!m_ActionInputs || !m_Bound || controller != m_Character || owner != m_Pilot || !PoweredAuthority()) return;
if (!OwnActionManager(am)) { m_ReadbackOK = false; return; }
m_OwnedActions = am;
// Same short activation pattern used by vanilla game.c; this manager belongs
// solely to the fixture AI, never to global hardware or the human player.
// Native104256 reports false from ActivateContext while already active.
// Treat observed active state as authority, not an undocumented return meaning.
bool alreadyActive = am.IsContextActive("HelicopterContext");
bool activated;
if (!alreadyActive) activated = am.ActivateContext("HelicopterContext", 1);
bool contextActive = am.IsContextActive("HelicopterContext");
if (m_ActionContextSamples < 4)
{
m_ActionContextSamples++;
PrintFormat("ERR POWERED ACTION CONTEXT: sample=%1 activated=%2 active=%3 dt=%4 input=%5 simulation=%6", m_ActionContextSamples, activated, contextActive, dt, m_Input != null, m_Simulation != null);
}
if (!contextActive) { m_ReadbackOK = false; return; }
float collective; vector axes;
if (!ComputePilotDemand(controller, owner, dt, collective, axes)) return;
bool a = WriteActionPair(am, "HelicopterCollectiveIncrease", "HelicopterCollectiveDecrease", collective);
bool b = WriteActionPair(am, "HelicopterCyclicForward", "HelicopterCyclicBack", axes[0]);
bool c = WriteActionPair(am, "HelicopterCyclicRight", "HelicopterCyclicLeft", axes[1]);
bool d = WriteActionPair(am, "HelicopterAntiTorqueRight", "HelicopterAntiTorqueLeft", axes[2]);
m_ReadbackOK = m_ReadbackOK && a && b && c && d;
m_ControlUpdates++; m_LastControl = GetGame().GetWorld().GetWorldTime();
}
void ApplyPilot(CharacterControllerComponent controller, IEntity owner, float dt)
{
// The experimental actor-action path and old context path never both write.
if (m_ActionInputs) return;
float collective; vector axes;
if (!ComputePilotDemand(controller, owner, dt, collective, axes)) return;
float forward = axes[0]; float aside = axes[1]; float torque = axes[2];
m_Input.SetVehicleThrottle(1);
m_Input.SetVehicleCollective(collective);
m_Input.SetVehicleCyclicForward(forward);
@@ -294,8 +414,20 @@ class ERR_NativePoweredFlightProbe
{
if (!m_Simulation || !m_Aircraft || now < m_NextLog) return;
m_NextLog = now + 2000;
if (m_Stage == 4 && now < m_StageDeadline - 57000) m_NextLog = now + 250;
if (m_ActionInputs)
{
bool ownedActions = OwnActionManager(m_OwnedActions);
bool contextActive = m_OwnedActions && m_OwnedActions.IsContextActive("HelicopterContext");
PrintFormat("ERR POWERED ACTION MANAGER: selected=1 exactOwnedNonGlobal=%1 contextActive=%2 virtualWrites=%3", ownedActions, contextActive, m_ControlUpdates);
}
vector transform[4]; m_Aircraft.GetWorldTransform(transform);
PrintFormat("ERR POWERED AUTOHOVER: selected=%1 saved=%2 enabled=%3 exactController=%4", m_AutohoverMode, m_AutohoverSaved, m_Controller.GetAutohoverEnabled(), m_AutohoverController == m_Controller);
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());
vector rotorAxis; m_Simulation.RotorGetAxis(0, rotorAxis);
PrintFormat("ERR POWERED HEADING: desiredYaw=%1 actualYawPitchRoll=%2", m_Heading, m_Aircraft.GetYawPitchRoll());
PrintFormat("ERR POWERED ROTOR AXIS: local=%1 acceleration=%2", rotorAxis, m_Simulation.GetAcceleration());
PrintFormat("ERR POWERED MOTION: offset=%1 velocity=%2 angularLocal=%3 forward=%4 right=%5", m_Home - transform[3], m_Simulation.GetVelocity(), m_Simulation.GetAngularVelocityLocal(), transform[2], transform[0]);
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));
}
protected bool PollNativeFlightComponents(float now)
@@ -378,14 +510,24 @@ class ERR_NativePoweredFlightProbe
if (m_Stopped) return;
// Input cleanup is confined to the same unclaimed test pilot/aircraft.
bool ours = m_Bound && OwnPilot() && !PlayerPresent();
bool ownSeat = ours && m_Seat && m_Seat.GetOccupant() == m_Pilot && m_Controller && m_Controller.GetPilotCompartmentSlot() == m_Seat && CompartmentAccessComponent.GetVehicleIn(m_Pilot) == m_Aircraft;
RestoreAutohover(ownSeat);
m_Stopped = true; m_Bound = false;
if (s_Current == this) s_Current = null;
if (ours && m_Input)
if (ours && m_ActionInputs && OwnActionManager(m_OwnedActions))
{
WriteActionPair(m_OwnedActions, "HelicopterCollectiveIncrease", "HelicopterCollectiveDecrease", 0);
WriteActionPair(m_OwnedActions, "HelicopterCyclicForward", "HelicopterCyclicBack", 0);
WriteActionPair(m_OwnedActions, "HelicopterCyclicRight", "HelicopterCyclicLeft", 0);
WriteActionPair(m_OwnedActions, "HelicopterAntiTorqueRight", "HelicopterAntiTorqueLeft", 0);
}
if (ours && !m_ActionInputs && 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 (ours && m_Controller && m_ModeSaved) m_Controller.SetCollectiveMode(m_OriginalMode);
m_OwnedActions = null;
if (m_PilotLODSaved && m_PilotAgent && OwnPilot() && !PlayerPresent()) m_PilotAgent.SetPermanentLOD(m_OriginalPilotLOD);
if (m_Group && m_Group.GetPlayerCount(true) == 0) m_Group.SetPermanentLOD(-1);
// Keep real airborne/failed test actors for diagnosis and mode-owned cleanup.
@@ -395,8 +537,9 @@ class ERR_NativePoweredFlightProbe
{
if (m_Complete) return 1;
if (m_Stopped) return -1;
m_Started = true;
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_PositiveSignalSince = -1; }
if (m_PreviousTick >= 0 && (now < m_PreviousTick || now - m_PreviousTick > 1500)) { m_Stable = -1; m_PositiveSignalSince = -1; m_NegativeSignalSince = -1; }
m_PreviousTick = now;
if (m_Stage == 0)
{
@@ -448,13 +591,13 @@ class ERR_NativePoweredFlightProbe
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_Home = m_Aircraft.GetOrigin(); m_Heading = m_Aircraft.GetYawPitchRoll()[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 (!PoweredAuthority() || !m_ReadbackOK || !AutohoverReady()) { Check(false, "current owned conscious pilot, fuel, rotors, selected autohover 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; }
@@ -487,7 +630,7 @@ class ERR_NativePoweredFlightProbe
m_PositiveGateUpdates = m_ControlUpdates; m_PositiveSignalSince = -1;
m_Stage = 9; m_StageDeadline = now + 3000; return 0;
}
if (((m_Stage >= 4 && m_Stage <= 6) || m_Stage == 9) && !m_Controller.IsEngineOn()) { Check(false, "powered flight retains real running engine"); return -1; }
if (((m_Stage >= 4 && m_Stage <= 6) || m_Stage == 9 || m_Stage == 10) && !m_Controller.IsEngineOn()) { Check(false, "powered flight retains real running engine"); return -1; }
if (m_Stage == 9)
{
if (now >= m_StageDeadline) { Check(false, "positive native collective reaches actual rotor signal before climb within three seconds"); return -1; }
@@ -502,6 +645,31 @@ class ERR_NativePoweredFlightProbe
if (m_PositiveSignalSince < 0) m_PositiveSignalSince = now;
if (now - m_PositiveSignalSince < 500) return 0;
if (!Check(true, "positive collective persists in existing native rotor signal across actual ticks before climb")) return -1;
if (m_ActionInputs)
{
m_NegativeGateUpdates = m_ControlUpdates; m_NegativeSignalSince = -1;
m_Stage = 10; m_StageDeadline = now + 3000; return 0;
}
if (!Check(BeginAutohover(), "selected native autohover enabled after direct collective consumption proof; exact owned controller readback")) return -1;
m_Collective = -0.8; m_Stage = 4; m_StageDeadline = now + 65000; m_Stable = -1; return 0;
}
if (m_Stage == 10)
{
if (now >= m_StageDeadline) { Check(false, "negative actor collective reaches actual rotor signal before climb within three seconds"); return -1; }
if (!Check(m_ActionInputs && m_Controller.GetCollectiveMode() == CollectiveMode.Direct && !m_Controller.GetAutohoverEnabled(), "negative consumption proof uses owned actor actions in Direct mode with autohover off")) return -1;
SignalsManagerComponent negativeSignals = m_Controller.GetSignalsManager();
int negativeIndex = -1;
if (negativeSignals) negativeIndex = negativeSignals.FindSignal("MainRotorCollective");
if (!Check(negativeIndex >= 0, "existing native main rotor collective signal available for negative proof")) return -1;
float negativeObserved = negativeSignals.GetSignalValue(negativeIndex);
bool negativeMatched = m_ControlUpdates >= m_NegativeGateUpdates + 3 && ERR_PoweredFlightRules.NegativeCollectiveObserved(m_Input.GetVehicleCollective(), negativeObserved);
PrintFormat("ERR POWERED NEGATIVE COLLECTIVE GATE: command=%1 rotorSignal=%2 actionUpdates=%3 matched=%4", m_Input.GetVehicleCollective(), negativeObserved, m_ControlUpdates - m_NegativeGateUpdates, negativeMatched);
if (!negativeMatched) { m_NegativeSignalSince = -1; return 0; }
if (m_NegativeSignalSince < 0) m_NegativeSignalSince = now;
if (now - m_NegativeSignalSince < 500) return 0;
if (!Check(true, "negative actor collective persists in existing native rotor signal across actual ticks before signed flight demand")) return -1;
m_SignedCollectiveProven = true;
if (!Check(BeginAutohover(), "selected native autohover enabled only after both direct collective consumption proofs")) return -1;
m_Collective = -0.8; m_Stage = 4; m_StageDeadline = now + 65000; m_Stable = -1; return 0;
}
if (m_Stage == 4 || m_Stage == 5)
@@ -532,6 +700,7 @@ class ERR_NativePoweredFlightProbe
{
if (now >= m_StageDeadline) { Check(false, "normal engine shutdown completed on actual ground"); return -1; }
if (m_Controller.IsEngineOn()) return 0;
PrintFormat("ERR POWERED SHUTDOWN: height=%1 speed=%2 angular=%3 up=%4 contact=%5 authority=%6 collective=%7 rotorRPM=%8", height, speed, m_Simulation.GetAngularVelocity().Length(), transform[1][1], m_Simulation.HasAnyGroundContact(), PoweredAuthority(), m_Input.GetVehicleCollective(), m_Simulation.RotorGetRPM(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;
}
@@ -11,7 +11,7 @@ class ERR_ObjectiveText
}
static string Extraction(float radius)
{
return string.Format("The map marks the initial helicopter pickup site. Board the helicopter and fly beyond %1 kilometres from the prison. Living prisoners aboard escape, including unconscious passengers. Dead or absent prisoners do not.", radius / 1000.0);
return string.Format("The map marks the initial helicopter pickup site. Board the helicopter and fly beyond %1 kilometres from the prison. To load an unconscious prisoner, place them on the ground beside the grounded, stationary helicopter and use 'Load casualty into extraction helicopter' on them. Living prisoners aboard escape; dead or absent prisoners do not.", radius / 1000.0);
}
static string RadioFeedbackMessage(ERR_RadioFeedback feedback)
{
@@ -46,8 +46,8 @@ class ERR_ObjectiveText
mode.Locations(locations);
foreach (ERR_LocationComponent location : locations)
if (location.Kind() == ERR_LocationKind.RADIO && location.Knowledge() == 2)
return "The radio station is identified on your map. Reach the station and tune its radio to start the transmission.";
return "Choose either marked supply cache. Each has equipment and enough radio documents to identify the station. If one cache is lost, use the other. If every document is lost, a close approach with a clear view can identify the station itself. Nearby medical and guard sites can be discovered by approaching with a clear view.";
return "The radio station is identified on your map. Reach its radio and complete the 'Contact extraction HQ' action to start the transmission.";
return "Choose either marked supply cache. Take its intelligence folders into your own inventory to reveal the station on your map. If only a search area appears, take another folder. Each cache has enough folders; use the other if one is lost. If every folder is lost, a close approach with a clear view can identify the station itself. Nearby medical and guard sites can also be discovered by approaching with a clear view.";
}
if (phase == ERR_Phase.RADIO_DEFENCE)
{
+33 -2
View File
@@ -6,6 +6,29 @@ enum ERR_ObserverReason
SPAWN_UNAVAILABLE
}
// Presentation only. Existing server admission and observer selection own state.
class ERR_ObserverGuidance
{
static string Message(int reason, bool survivorAvailable)
{
if (reason == ERR_ObserverReason.NONE) return "";
string message = "You are spectating until the next round.";
if (reason == ERR_ObserverReason.SPENT_LIFE)
message = "Your life for this round has been used. You can play again next round.";
else if (reason == ERR_ObserverReason.LATE_EXTRACTION)
message = "Extraction has started. New prisoners can join next round.";
else if (reason == ERR_ObserverReason.SPAWN_UNAVAILABLE)
message = "Your prisoner could not be spawned. You are spectating until the next round.";
if (!survivorAvailable)
return message + " No survivor is currently available to follow. The view will follow a survivor when one becomes available.";
return message + " Use /esc spectate next or /esc spectate previous to change who you follow.";
}
static bool Refresh(bool shown, int previousReason, int reason, bool previousAvailable, bool survivorAvailable)
{
return reason != ERR_ObserverReason.NONE && (!shown || previousReason != reason || previousAvailable != survivorAvailable);
}
}
// A local follow camera. It never possesses its target or offers free flight.
[BaseContainerProps(), SCR_BaseManualCameraComponentTitle()]
class ERR_FollowSurvivorCameraComponent : SCR_BaseManualCameraComponent
@@ -66,6 +89,8 @@ modded class SCR_PlayerController
protected SCR_ManualCamera m_ERRObserverCamera;
protected bool m_ERRObserverMonitor;
protected bool m_ERRObserverReady;
protected ERR_ObserverReason m_ERRShownObserverReason;
protected bool m_ERRShownObserverAvailable;
protected float m_ERRLastCycle = -1000;
ERR_ObserverReason ERR_ObserverState() { return m_ERRObserverReason; }
@@ -197,11 +222,17 @@ modded class SCR_PlayerController
if (!manager || manager.CurrentCamera() != m_ERRObserverCamera) return;
SCR_RespawnSystemComponent respawn = SCR_RespawnSystemComponent.GetInstance();
if (respawn) respawn.DestroyLoadingPlaceholder();
if (!m_ERRObserverReady)
// The server-selected replicated ID distinguishes no target from a target
// whose local entity is still streaming. Never imply no survivors from delay.
bool survivorAvailable = m_ERRObservedId != RplId.Invalid();
if (ERR_ObserverGuidance.Refresh(m_ERRObserverReady, m_ERRShownObserverReason, m_ERRObserverReason, m_ERRShownObserverAvailable, survivorAvailable))
{
SCR_HintManagerComponent hints = SCR_HintManagerComponent.GetInstance();
if (hints) hints.ShowCustom("You are spectating the surviving prisoners until the next round.", "Spectating", 12);
if (!hints) return; // retry on the existing monitor; do not lose the first hint
hints.ShowCustom(ERR_ObserverGuidance.Message(m_ERRObserverReason, survivorAvailable), "Spectating", 12);
m_ERRObserverReady = true;
m_ERRShownObserverReason = m_ERRObserverReason;
m_ERRShownObserverAvailable = survivorAvailable;
}
}
protected void ERR_CloseObserverCamera()