Add bounded aircraft control and verify current packaged runtime closure

This commit is contained in:
2026-09-19 13:31:51 +02:00
parent 4125018d04
commit a880e0485c
695 changed files with 79317 additions and 44 deletions
+10
View File
@@ -86,5 +86,15 @@ GenericEntity {
coords 0 -0.1 -6
angles 0 0 0
}
DecalEntity : "{4AD6EB80A8B1FA3A}Prefabs/Decals/Dirt/Decal_Dirt_Patch_03.et" {
ID "FCFBA935CC13E016"
components {
Hierarchy "{EF4B704D646D1759}" {}
}
coords 0 0 0
angles -90 0 0
scale 10
FarPlane 1.5
}
}
}
+29 -3
View File
@@ -3,7 +3,11 @@ class ERR_UnlockPrisonAction : ScriptedUserAction
override bool CanBePerformedScript(IEntity user)
{
ERR_GameMode mode = ERR_GameMode.Get();
return mode && mode.Phase() == ERR_Phase.PRISON && ERR_Actors.CanFight(user);
SetCannotPerformReason(string.Empty);
if (!mode) { SetCannotPerformReason("Mission is not ready"); return false; }
if (mode.Phase() != ERR_Phase.PRISON) { SetCannotPerformReason("Prison lock is not available in this mission phase"); return false; }
if (!ERR_Actors.CanFight(user)) { SetCannotPerformReason("You must be alive and conscious"); return false; }
return true;
}
override void PerformAction(IEntity pOwnerEntity, IEntity pUserEntity)
{
@@ -28,7 +32,15 @@ class ERR_PrisonDoorAction : SCR_DoorUserAction
override bool CanBePerformedScript(IEntity user)
{
ERR_GameMode mode = ERR_GameMode.Get();
return mode && mode.Phase() > ERR_Phase.PRISON && super.CanBePerformedScript(user);
SetCannotPerformReason(string.Empty);
if (!mode) { SetCannotPerformReason("Mission is not ready"); return false; }
if (mode.Phase() <= ERR_Phase.PRISON)
{
SetCannotPerformReason("Pick the prison lock before opening the gate");
return false;
}
// Vanilla owns any obstruction/door-specific reason after unlocking.
return super.CanBePerformedScript(user);
}
}
@@ -38,7 +50,21 @@ class ERR_TuneRadioAction : ScriptedUserAction
{
ERR_GameMode mode = ERR_GameMode.Get();
ERR_LocationComponent location = ERR_LocationComponent.Cast(GetOwner().FindComponent(ERR_LocationComponent));
return mode && mode.Phase() == ERR_Phase.RADIO_SEARCH && location && location.Knowledge() == 2 && ERR_Actors.CanFight(user);
SetCannotPerformReason(string.Empty);
if (!mode) { SetCannotPerformReason("Mission is not ready"); return false; }
if (mode.Phase() != ERR_Phase.RADIO_SEARCH)
{
string reason = "Radio is not available in this mission phase";
if (mode.Phase() == ERR_Phase.PRISON) reason = "Escape the prison first";
if (mode.Phase() == ERR_Phase.RADIO_DEFENCE) reason = "Transmission is already in progress; defend the radio";
if (mode.Phase() == ERR_Phase.EXTRACTION) reason = "Transmission is complete; reach the extraction helicopter";
SetCannotPerformReason(reason);
return false;
}
if (!location) { SetCannotPerformReason("Radio location is unavailable"); return false; }
if (location.Knowledge() != 2) { SetCannotPerformReason("Identify this radio through recovered intel or close reconnaissance first"); return false; }
if (!ERR_Actors.CanFight(user)) { SetCannotPerformReason("You must be alive and conscious"); return false; }
return true;
}
override void PerformAction(IEntity pOwnerEntity, IEntity pUserEntity)
{
@@ -55,6 +55,147 @@ class ERR_OrdinaryScenarioLaunchRequest : Managed
}
// One optional capture request per world, with no camera or gameplay mutation.
// Managed callback ownership matches the ordinary launch request above.
class ERR_OrdinaryScreenshotRequest : Managed
{
protected static ref ERR_OrdinaryScreenshotRequest s_Request;
protected static bool s_Attempted;
protected ERR_GameMode m_Mode;
protected PlayerController m_Controller;
protected IEntity m_Actor;
protected ref ERR_PlayerLife m_Life;
protected UUID m_Account;
protected int m_PlayerId;
protected bool m_Finished;
protected static bool Enabled()
{
string project, screenshot;
if (!Replication.IsServer() || System.IsConsoleApp()) return false;
if (!System.GetCLIParam("errOrdinaryScenario", project) || (project != "core" && project != "chat")) return false;
return System.GetCLIParam("errOrdinaryScreenshot", screenshot) && screenshot == "1";
}
static void Queue(ERR_GameMode mode, PlayerController controller, IEntity actor)
{
if (s_Attempted || !Enabled() || !mode || !controller || !actor || !mode.Players()) return;
ERR_PlayerLife life = mode.Players().CurrentActor(actor);
if (!life) return;
ERR_OrdinaryScreenshotRequest request = new ERR_OrdinaryScreenshotRequest();
request.m_Mode = mode; request.m_Controller = controller; request.m_Actor = actor;
request.m_Life = life; request.m_Account = life.AccountId; request.m_PlayerId = controller.GetPlayerId();
if (!request.Valid()) return;
s_Attempted = true; s_Request = request;
mode.OnStop().Insert(request.Cancel);
GetGame().GetCallqueue().CallLater(request.Capture, 3000, false);
Print("ERR ORDINARY: optional prisoner screenshot queued after finalized admission");
}
protected bool Valid()
{
if (!Enabled() || !GetGame() || !m_Mode || m_Mode != ERR_GameMode.Get()) return false;
if (!GetGame().GetWorld() || m_Mode.GetWorld() != GetGame().GetWorld() || !m_Mode.Active() || m_Mode.Phase() != ERR_Phase.PRISON) return false;
if (!m_Controller || m_Controller != GetGame().GetPlayerController() || m_Controller.GetPlayerId() != m_PlayerId || m_PlayerId <= 0) return false;
PlayerManager manager = GetGame().GetPlayerManager();
if (!manager || manager.GetPlayerController(m_PlayerId) != m_Controller || manager.GetPlayerControlledEntity(m_PlayerId) != m_Actor || m_Controller.GetControlledEntity() != m_Actor) return false;
if (!ERR_Actors.Alive(m_Actor) || !m_Mode.Players()) return false;
ERR_PlayerLife current = m_Mode.Players().CurrentActor(m_Actor);
return current && current == m_Life && current.AccountId == m_Account;
}
void Cancel()
{
if (m_Finished) return;
m_Finished = true;
if (GetGame() && GetGame().GetCallqueue()) GetGame().GetCallqueue().Remove(Capture);
if (m_Mode) m_Mode.OnStop().Remove(Cancel);
m_Mode = null; m_Controller = null; m_Actor = null; m_Life = null;
}
// Read-only projection evidence, only inside the already validated capture.
// World AABB corners transformed to prison space are an envelope, not a
// promise that the material's irregular clutter mask fills that envelope.
protected void YardDiagnostic()
{
array<ERR_LocationComponent> locations = {};
m_Mode.Locations(locations);
if (locations.Count() > 64) return;
IEntity prison;
foreach (ERR_LocationComponent location : locations)
{
if (!location || location.Kind() != ERR_LocationKind.PRISON || location.Id() != "everon-prison-1") continue;
IEntity owner = location.GetOwner();
if (!owner || owner.GetWorld() != m_Mode.GetWorld() || !owner.GetPrefabData()) continue;
if (owner.GetPrefabData().GetPrefabName() != "{BE6B8D56349038A6}Prefabs/ERR/Prison.et") continue;
if (prison) { Print("ERR YARD: ambiguous registered prison; no measurement"); return; }
prison = owner;
}
if (!prison) { Print("ERR YARD: exact registered prison missing; no measurement"); return; }
vector prisonTransform[4]; prison.GetWorldTransform(prisonTransform);
PrintFormat("ERR YARD: prison world origin=%1 basisX=%2 basisY=%3 basisZ=%4", prisonTransform[3], prisonTransform[0], prisonTransform[1], prisonTransform[2]);
array<IEntity> entities = {prison};
array<int> depths = {0};
int decals;
for (int index = 0; index < entities.Count(); index++)
{
IEntity entity = entities[index];
if (!entity) { Print("ERR YARD: hierarchy changed; partial measurement"); return; }
DecalEntity decal = DecalEntity.Cast(entity);
if (decal)
{
decals++;
vector mins, maxs; decal.GetWorldBounds(mins, maxs);
vector worldTransform[4]; decal.GetWorldTransform(worldTransform);
string nativeType = decal.Type().ToString();
ResourceName prefab;
if (decal.GetPrefabData()) prefab = decal.GetPrefabData().GetPrefabName();
PrintFormat("ERR YARD: decal=%1 type=%2 prefab=%3 worldMin=%4 worldMax=%5", decals, nativeType, prefab, mins, maxs);
PrintFormat("ERR YARD: decal=%1 world origin=%2 basisX=%3 basisY=%4 basisZ=%5", decals, worldTransform[3], worldTransform[0], worldTransform[1], worldTransform[2]);
for (int x = 0; x < 2; x++)
for (int y = 0; y < 2; y++)
for (int z = 0; z < 2; z++)
{
vector corner = mins;
if (x == 1) corner[0] = maxs[0];
if (y == 1) corner[1] = maxs[1];
if (z == 1) corner[2] = maxs[2];
vector localCorner = prison.CoordToLocal(corner);
PrintFormat("ERR YARD: decal=%1 corner=%2/%3/%4 prisonLocal=%5", decals, x, y, z, localCorner);
}
}
IEntity child = entity.GetChildren();
while (child)
{
if (depths[index] >= 8 || entities.Count() >= 64 || entities.Contains(child))
{
Print("ERR YARD: hierarchy bound or duplicate reached; partial measurement");
return;
}
entities.Insert(child); depths.Insert(depths[index] + 1);
child = child.GetSibling();
}
}
PrintFormat("ERR YARD: measurement complete nodes=%1 decals=%2; bounds do not certify clutter-mask coverage", entities.Count(), decals);
}
protected void Capture()
{
if (m_Finished) return;
bool valid = Valid();
if (valid) YardDiagnostic();
Cancel();
if (!valid)
{
Print("ERR ORDINARY: optional prisoner screenshot cancelled; original admission no longer current");
return;
}
System.MakeScreenshot("$logs:err-ordinary-prison");
Print("ERR ORDINARY: optional prisoner screenshot requested without camera or input changes");
}
static void ClearWorld()
{
if (s_Request) s_Request.Cancel();
s_Request = null;
s_Attempted = false;
}
}
modded class ArmaReforgerScripted
{
protected ref ERR_OrdinaryScenarioLaunchRequest m_ERRDiagnosticLaunchRequest;
@@ -106,6 +247,7 @@ modded class ArmaReforgerScripted
override protected void OnBeforeWorldCleanup()
{
ERR_OrdinaryScreenshotRequest.ClearWorld();
if (m_ERRDiagnosticLaunchRequest) m_ERRDiagnosticLaunchRequest.Cancel();
m_ERRDiagnosticLaunchRequest = null;
super.OnBeforeWorldCleanup();
@@ -124,6 +266,7 @@ modded class ERR_GameMode
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);
ERR_OrdinaryScreenshotRequest.Queue(this, local, actor);
}
}
@@ -120,6 +120,10 @@ class ERR_SpawnLogic : SCR_SpawnLogic
Observe(playerId, reason);
return;
}
// Every active, eligible admission consumes one attempt, even when native
// prerequisites or all safe positions are unavailable. Preparing and an
// already-pending request return above without spending this budget.
m_Attempts.Set(playerId, attempts + 1);
SCR_PlayerFactionAffiliationComponent affiliation = GetPlayerFactionComponent_S(playerId);
SCR_RespawnComponent respawn = GetPlayerRespawnComponent_S(playerId);
Faction faction = mode.PlayerFaction();
@@ -127,7 +131,6 @@ class ERR_SpawnLogic : SCR_SpawnLogic
affiliation.RequestFaction(faction);
vector position;
if (!SpawnPosition(mode, position)) { RetryLater(playerId); return; }
m_Attempts.Set(playerId, attempts + 1);
SCR_FreeSpawnData request = new SCR_FreeSpawnData(mode.Factions().PrisonerPrefab, position);
if (!respawn.CanSpawn(request)) { RetryLater(playerId); return; }
m_AuthorizedPositions.Set(playerId, position);