Integrate startup optics and supply vehicle lock picking

This commit is contained in:
2026-09-20 05:34:30 +02:00
parent 9205e2129a
commit 963cab50c0
104 changed files with 16171 additions and 25 deletions
@@ -89,6 +89,7 @@ modded class ERR_GameMode
{
ERR_NativeDiscoveryMapProbe.Stop();
ERR_NativeProgressionIdentityProbe.Stop();
if (m_ERRTransportProbe) m_ERRTransportProbe.Stop();
if (m_ERRPatrolEast) m_ERRPatrolEast.Stop();
if (m_ERRPatrolWest) m_ERRPatrolWest.Stop();
if (m_ERRBaseEnvironmentRestartProbe) m_ERRBaseEnvironmentRestartProbe.Stop();
@@ -627,7 +628,7 @@ modded class ERR_GameMode
}
if (m_ERRExtractionCase == "transport-loss")
{
m_ERRTransportProbe = new ERR_NativeTransportProbe(this);
m_ERRTransportProbe = new ERR_NativeTransportProbe(this, m_ERRBasePlayer);
m_ERRBaseStep = 300;
return;
}
@@ -64,7 +64,7 @@ class ERR_NativeFactionCatalogProbe
if (!Check(catalogManager && catalogManager.GetFactionEntityCatalogOfType(EEntityCatalogType.VEHICLE, "US") && catalogManager.GetFactionEntityCatalogOfType(EEntityCatalogType.ITEM, "USSR"), "global catalog manager retains faction vehicle and inventory access")) return false;
ERR_SiteRuntime depot = mode.Sites().Find("everon-supply-2");
Vehicle helicopter = mode.ExtractionVehicle();
if (!Check(depot && depot.Transport && ERR_Actors.Alive(depot.Transport) && depot.Transport.GetPrefabData().GetPrefabName() == UAZ(), "physical finite depot vehicle is supported UAZ")) return false;
if (!Check(depot && depot.Transport && ERR_Actors.Alive(depot.Transport) && depot.Transport.GetPrefabData().GetPrefabName() == "{FA65287C19DE304B}Prefabs/ERR/Vehicles/SupplyUAZ.et", "physical finite depot vehicle is successor supply UAZ")) return false;
if (!Check(helicopter && ERR_Actors.Alive(helicopter) && helicopter.GetPrefabData().GetPrefabName() == Mi8() && helicopter.FindComponent(HelicopterControllerComponent), "physical extraction aircraft is supported Mi8")) return false;
array<BaseCompartmentSlot> seats = {};
if (!Check(mode.Extraction().Seats(seats) >= 12, "physical aircraft still has required capacity")) return false;
@@ -4,6 +4,9 @@
class ERR_NativeTransportProbe
{
protected ERR_GameMode m_Mode;
protected IEntity m_Player;
protected ref ERR_NativeVehicleSecurityProbe m_SecurityProbe;
protected bool m_SecurityChecked;
protected ERR_SiteRuntime m_Site;
protected Vehicle m_Vehicle;
protected CarControllerComponent m_Controller;
@@ -13,11 +16,13 @@ class ERR_NativeTransportProbe
protected BaseCompartmentSlot m_Passenger;
protected int m_Step;
protected float m_Wait;
void ERR_NativeTransportProbe(ERR_GameMode mode) { m_Mode = mode; }
void ERR_NativeTransportProbe(ERR_GameMode mode, IEntity player) { m_Mode = mode; m_Player = player; }
void Stop() { if (m_SecurityProbe) m_SecurityProbe.Stop(); }
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: transport " + detail); return true; }
Print("ERR SMOKE FAIL: transport " + detail, LogLevel.ERROR);
Stop();
return false;
}
protected bool DefinitionChecks()
@@ -61,6 +66,18 @@ class ERR_NativeTransportProbe
}
int Tick(float now)
{
if (!m_SecurityChecked)
{
if (!m_SecurityProbe)
{
ERR_SiteRuntime securitySite = m_Mode.Sites().Find("everon-supply-2");
if (!Check(securitySite && securitySite.Transport, "security prelude uses existing supply transport")) return -1;
m_SecurityProbe = new ERR_NativeVehicleSecurityProbe(m_Mode, m_Player, securitySite.Transport);
}
int result = m_SecurityProbe.Tick(now);
if (result <= 0) return result;
m_SecurityChecked = true;
}
if (now < m_Wait) return 0;
if (m_Step == 0)
{
@@ -0,0 +1,241 @@
#ifdef WORKBENCH
// Explicit transport-loss prelude. Diagnostic direct callbacks, not held input,
// client action RPC, manual boarding, multiplayer or late-join replication proof.
class ERR_NativeVehicleSecurityProbe
{
protected ERR_GameMode m_Mode;
protected IEntity m_Player;
protected Vehicle m_Vehicle;
protected ERR_VehicleSecurityComponent m_Security;
protected ERR_ForceVehicleLockAction m_Breach;
protected ERR_SecureGetInAction m_DriverAction;
protected DamageManagerComponent m_Damage;
protected bool m_PreviousDamageEnabled;
protected bool m_DamageChanged;
protected bool m_Moved;
protected bool m_Stopped;
protected vector m_Original[4];
protected vector m_Destination;
protected float m_Deadline;
protected float m_Wait;
protected int m_Step;
void ERR_NativeVehicleSecurityProbe(ERR_GameMode mode, IEntity player, Vehicle vehicle)
{
m_Mode = mode; m_Player = player; m_Vehicle = vehicle;
}
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: vehicle-security " + detail); return true; }
Print("ERR SMOKE FAIL: vehicle-security " + detail, LogLevel.ERROR);
Stop();
return false;
}
void Stop()
{
if (m_Stopped) return;
m_Stopped = true;
// Restore only the original body whose state this fixture changed. Never
// touch a replacement body/controller after loss of canonical ownership.
if (m_Damage && m_DamageChanged) m_Damage.EnableDamageHandling(m_PreviousDamageEnabled);
m_DamageChanged = false;
m_Damage = null;
if (!m_Moved || !m_Player || !m_Mode || !m_Mode.Players()) return;
if (!m_Mode.Players().CurrentActor(m_Player)) return;
if (!ERR_Actors.CanFight(m_Player) || CompartmentAccessComponent.GetVehicleIn(m_Player)) return;
ChimeraCharacter character = ChimeraCharacter.Cast(m_Player);
if (character) character.Teleport(m_Original);
m_Moved = false;
}
protected bool DriverAvailable()
{
bool available = m_DriverAction.CanBePerformedScript(m_Player);
if (available) return true;
m_DriverAction.ERR_ProbeRejectedEntry(m_Player);
// A direct diagnostic call has no selected UI context. Bind only the unique
// actual driver door if the stock resolver confirms the context is missing.
if (m_DriverAction.ERR_ProbeResolvedDoor(m_Player) != -1) return false;
return m_DriverAction.ERR_ProbeDeclaredDriverDoor(m_Player);
}
protected bool Inspect()
{
m_Security = ERR_VehicleSecurityComponent.Find(m_Vehicle);
if (!Check(m_Security && m_Security.GetOwner() == m_Vehicle && m_Security.Locked(), "authored supply vehicle initially has its locked successor component")) return false;
BaseCompartmentManagerComponent manager = BaseCompartmentManagerComponent.Cast(m_Vehicle.FindComponent(BaseCompartmentManagerComponent));
array<BaseCompartmentSlot> seats = {};
if (manager) manager.GetCompartments(seats);
if (!Check(seats.Count() == 5, "native root manager aggregates all five original seats")) return false;
int front, rear;
foreach (BaseCompartmentSlot slot : seats)
{
if (!Check(slot && !slot.GetOccupant(), "original seat is present and initially empty")) return false;
ERR_SecureGetInAction action = ERR_SecureGetInAction.Cast(slot.GetGetInAction());
if (!Check(action && action.HasLocalEffectOnlyScript(), "native seat contains scoped successor local entry action")) return false;
if (!Check(ERR_VehicleSecurityComponent.Find(action.GetOwner()) == m_Security, "seat action resolves root security through physical hierarchy")) return false;
if (!Check(!action.CanBePerformedScript(m_Player), "locked entry availability refuses the canonical prisoner")) return false;
action.PerformAction(action.GetOwner(), m_Player);
if (action.GetOwner() == m_Vehicle) front++; else rear++;
if (SCR_CompartmentAccessComponent.GetCompartmentType(slot) == ECompartmentType.PILOT) m_DriverAction = action;
}
if (!Check(front == 2 && rear == 3 && m_DriverAction, "two root and three attached rear entry substitutions are all present")) return false;
BaseActionsManagerComponent actionsManager = BaseActionsManagerComponent.Cast(m_Vehicle.FindComponent(BaseActionsManagerComponent));
array<BaseUserAction> actions = {};
if (actionsManager) actionsManager.GetActionsList(actions);
array<ERR_SecureOpenVehicleDoorAction> doors = {};
foreach (BaseUserAction candidate : actions)
{
if (!candidate || candidate.GetOwner() != m_Vehicle) continue;
ERR_ForceVehicleLockAction breach = ERR_ForceVehicleLockAction.Cast(candidate);
if (breach) m_Breach = breach;
ERR_SecureOpenVehicleDoorAction door = ERR_SecureOpenVehicleDoorAction.Cast(candidate);
if (door && !doors.Contains(door)) doors.Insert(door);
}
if (!Check(doors.Count() == 4 && m_Breach, "four scoped open-door actions and held breach action exist in native prefab")) return false;
foreach (ERR_SecureOpenVehicleDoorAction door : doors)
{
if (!Check(door.HasLocalEffectOnlyScript() && !door.CanBePerformedScript(m_Player), "native local door action refuses locked use")) return false;
door.PerformAction(door.GetOwner(), m_Player);
}
if (!Check(!m_Breach.HasLocalEffectOnlyScript() && m_Breach.CheckOnServerFirstScript() && !m_Breach.CanBroadcastScript(), "breach action advertises nonlocal server-first single execution")) return false;
if (!Check(!m_Security.TryBreach(null) && m_Security.Locked(), "missing current player cannot directly breach native component")) return false;
string reason;
if (!Check(!m_Security.CanBreach(m_Player, reason), "canonical prisoner at original distant position cannot breach")) return false;
Print("ERR VEHICLE SECURITY DISTANT REASON: " + reason);
return true;
}
int Tick(float now)
{
if (m_Stopped) return -1;
if (m_Deadline == 0) m_Deadline = now + 30000;
if (!m_Mode || !m_Mode.Active() || !m_Mode.Players() || !m_Mode.Players().CurrentActor(m_Player))
{
Check(false, "canonical admitted prisoner and active mission retained"); return -1;
}
if (!ERR_Actors.CanFight(m_Player) || !ERR_Actors.Alive(m_Vehicle) || now > m_Deadline)
{
Check(false, "living actors and bounded probe deadline retained"); return -1;
}
if (now < m_Wait) return 0;
if (m_Step == 0)
{
if (!Check(!CompartmentAccessComponent.GetVehicleIn(m_Player), "canonical prisoner is on foot before diagnostic relocation")) return -1;
if (!Inspect()) return -1;
m_Damage = DamageManagerComponent.Cast(m_Player.FindComponent(DamageManagerComponent));
if (!Check(m_Damage && ChimeraCharacter.Cast(m_Player), "canonical prisoner supports protected diagnostic repositioning")) return -1;
m_PreviousDamageEnabled = m_Damage.IsDamageHandlingEnabled();
m_Damage.EnableDamageHandling(false);
m_DamageChanged = true;
m_Player.GetWorldTransform(m_Original);
vector transform[4]; m_Player.GetWorldTransform(transform);
m_Destination = ERR_Actors.Ground(m_Vehicle.CoordToParent("-2 0 0.3"));
transform[3] = m_Destination;
m_Moved = true;
ChimeraCharacter.Cast(m_Player).Teleport(transform);
m_Step = 1; m_Wait = now + 1500;
return 0;
}
if (m_Step == 1)
{
if (vector.Distance(m_Player.GetOrigin(), m_Destination) > 0.75) return 0;
if (!Check(!CompartmentAccessComponent.GetVehicleIn(m_Player), "locked direct entry callbacks left the prisoner outside")) return -1;
BaseCompartmentManagerComponent manager = BaseCompartmentManagerComponent.Cast(m_Vehicle.FindComponent(BaseCompartmentManagerComponent));
for (int index = 0; index < 4; index++)
if (!Check(manager && !manager.IsDoorOpen(index), "locked direct door callbacks left actual native door closed")) return -1;
string reason;
bool canBreach = m_Security.CanBreach(m_Player, reason);
PrintFormat("ERR VEHICLE SECURITY REACH: player=%1 vehicle=%2 available=%3 reason=%4", m_Player.GetOrigin(), m_Vehicle.GetOrigin(), canBreach, reason);
if (!Check(canBreach && m_Breach.CanBePerformedScript(m_Player), "configured native stationary vehicle passes actual driver-lock distance and LOS")) return -1;
m_Breach.PerformAction(m_Player, m_Player);
if (!Check(m_Security.Locked(), "wrong native action owner cannot unlock")) return -1;
if (!Check(m_Security.TryBreach(m_Player) && !m_Security.Locked(), "direct canonical server breach unlocks the existing vehicle")) return -1;
if (!Check(!m_Security.TryBreach(m_Player) && !m_Security.Locked(), "duplicate completion does not relock or repeat breach")) return -1;
if (!Check(!m_Breach.CanBeShownScript(m_Player), "completed held action disappears after unlock")) return -1;
// This invokes ordinary native eligibility, not forced scripted boarding.
if (!Check(DriverAvailable(), "unlocked driver seat passes ordinary native boarding availability")) return -1;
Stop();
return 1;
}
return 0;
}
}
// Failure logger reads the same protected stock-action caches and computed door.
// The separate explicit-context fallback temporarily binds a declared native
// driver door and restores it; faction, reservations and admission are untouched.
modded class ERR_SecureGetInAction
{
int ERR_ProbeResolvedDoor(IEntity user) { return GetRelevantDoorIndex(user); }
bool ERR_ProbeDeclaredDriverDoor(IEntity user)
{
if (GetRelevantDoorIndex(user) != -1) return false;
BaseCompartmentSlot seat = GetCompartmentSlot();
if (!seat || !m_CompartmentManager) return false;
if (SCR_CompartmentAccessComponent.GetCompartmentType(seat) != ECompartmentType.PILOT) return false;
array<int> doors = {};
seat.GetAvailableDoorIndices(doors);
if (doors.Count() != 1 || !m_CompartmentManager.GetDoorInfo(doors[0])) return false;
int previous = GetCompartmentDoorInfoIndex();
SetCompartmentDoorInfoIndex(doors[0]);
// Run the unchanged successor + vanilla eligibility chain with that exact
// native seat's declared door. No GetInVehicle call or eligibility bypass.
bool available = CanBePerformedScript(user);
PrintFormat("ERR VEHICLE ENTRY EXPLICIT CONTEXT: declaredDriverDoor=%1 previous=%2 available=%3", doors[0], previous, available);
if (!available) ERR_ProbeRejectedEntry(user);
SetCompartmentDoorInfoIndex(previous);
return available;
}
void ERR_ProbeRejectedEntry(IEntity user)
{
Print("ERR VEHICLE ENTRY REJECT: reason=" + GetCannotPerformReason());
BaseCompartmentSlot seat = GetCompartmentSlot();
SCR_ChimeraCharacter character = SCR_ChimeraCharacter.Cast(user);
PrintFormat("ERR VEHICLE ENTRY OBJECTS: owner=%1 seat=%2 character=%3 manager=%4 damage=%5", GetOwner(), seat, character, m_CompartmentManager, m_DamageManager);
if (m_DamageManager) PrintFormat("ERR VEHICLE ENTRY DAMAGE: state=%1", m_DamageManager.GetState());
if (!seat || !character) return;
SCR_CharacterControllerComponent controller = SCR_CharacterControllerComponent.Cast(character.GetCharacterController());
CompartmentAccessComponent access = character.GetCompartmentAccessComponent();
if (controller) PrintFormat("ERR VEHICLE ENTRY ACTOR: loitering=%1 access=%2", controller.IsLoitering(), access);
if (access) PrintFormat("ERR VEHICLE ENTRY TRANSITION: in=%1 gettingIn=%2 gettingOut=%3", access.IsInCompartment(), access.IsGettingIn(), access.IsGettingOut());
Faction playerFaction = character.GetFaction();
Vehicle vehicle = Vehicle.Cast(SCR_EntityHelper.GetMainParent(seat.GetOwner(), true));
Faction vehicleFaction;
if (vehicle) vehicleFaction = vehicle.GetFaction();
string playerKey, vehicleKey;
bool hostile;
if (playerFaction) playerKey = playerFaction.GetFactionKey();
if (vehicleFaction) vehicleKey = vehicleFaction.GetFactionKey();
if (playerFaction && vehicleFaction) hostile = playerFaction.IsFactionEnemy(vehicleFaction);
PrintFormat("ERR VEHICLE ENTRY FACTIONS: player=%1 vehicle=%2 hostile=%3", playerKey, vehicleKey, hostile);
SCR_VehicleFactionAffiliationComponent affiliation;
if (vehicle) affiliation = SCR_VehicleFactionAffiliationComponent.Cast(vehicle.FindComponent(SCR_VehicleFactionAffiliationComponent));
if (affiliation) PrintFormat("ERR VEHICLE ENTRY AFFILIATION: current=%1 default=%2 occupied=%3", affiliation.GetAffiliatedFactionKey(), affiliation.GetDefaultFactionKey(), affiliation.IsVehicleOccupied());
PrintFormat("ERR VEHICLE ENTRY SEAT: occupant=%1 reserved=%2 reservedByCaller=%3 accessible=%4", seat.GetOccupant(), seat.IsReserved(), seat.IsReservedBy(user), seat.IsCompartmentAccessible());
if (m_pLockComp) PrintFormat("ERR VEHICLE ENTRY BASELOCK: locked=%1 reason=%2", m_pLockComp.IsLocked(user, seat), m_pLockComp.GetCannotPerformReason(user));
else Print("ERR VEHICLE ENTRY BASELOCK: absent");
float supplies;
bool suppliesKnown;
if (m_ResourceComp) suppliesKnown = SCR_ResourceSystemHelper.GetStoredResources(m_ResourceComp, supplies);
bool blocksSupplies;
if (m_CompartmentManager) blocksSupplies = m_CompartmentManager.BlockSuppliesIfOccupied();
PrintFormat("ERR VEHICLE ENTRY SUPPLIES: cachedResource=%1 blocks=%2 known=%3 amount=%4", m_ResourceComp, blocksSupplies, suppliesKnown, supplies);
int door = GetRelevantDoorIndex(user);
PrintFormat("ERR VEHICLE ENTRY DOOR: stored=%1 resolved=%2", GetCompartmentDoorInfoIndex(), door);
if (!m_CompartmentManager || !access) return;
if (door >= 0 && m_CompartmentManager.GetDoorInfo(door))
PrintFormat("ERR VEHICLE ENTRY RESOLVED DOOR: blockedByUser=%1 doorUser=%2 canGetIn=%3", m_CompartmentManager.IsGetInAndOutBlockedByDoorUser(door), m_CompartmentManager.GetDoorUser(door), access.CanGetInVehicleViaDoor(seat.GetOwner(), m_CompartmentManager, door));
else Print("ERR VEHICLE ENTRY RESOLVED DOOR: missing or invalid context; no indexed native query issued");
array<int> doors = {};
seat.GetAvailableDoorIndices(doors);
if (doors.Count() > 8) { Print("ERR VEHICLE ENTRY DOORS: unexpected count above diagnostic bound"); return; }
foreach (int index : doors)
{
if (index < 0 || !m_CompartmentManager.GetDoorInfo(index))
{
PrintFormat("ERR VEHICLE ENTRY CANDIDATE DOOR: invalid declared index=%1; no indexed native query issued", index);
continue;
}
PrintFormat("ERR VEHICLE ENTRY CANDIDATE DOOR: index=%1 blockedByUser=%2 canGetIn=%3", index, m_CompartmentManager.IsGetInAndOutBlockedByDoorUser(index), access.CanGetInVehicleViaDoor(seat.GetOwner(), m_CompartmentManager, index));
}
}
}
#endif
@@ -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. 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);
return string.Format("The map marks the initial helicopter pickup site. A prisoner must pilot the helicopter. Gather everyone you intend to rescue before flying beyond %1 kilometres from the prison: crossing that boundary ends the round for everyone. Only living prisoners aboard escape, including unconscious passengers; anyone left behind does not escape. To load an unconscious prisoner, place them on the ground beside the grounded, stationary helicopter and use 'Load casualty into extraction helicopter' on them.", radius / 1000.0);
}
static string RadioFeedbackMessage(ERR_RadioFeedback feedback)
{
@@ -8,6 +8,13 @@ modded class SCR_PlayerController
override void OnControlledEntityChanged(IEntity from, IEntity to)
{
super.OnControlledEntityChanged(from, to);
// A new/reclaimed character needs the current objective again even if the
// phase has not changed. The existing monitor still owns delivery/retries.
if (to && from != to)
{
m_ERRGuidePhase = -1;
m_ERRGuideMessage = "";
}
ERR_StartGuide();
}
protected override void ERR_OnObserverMonitorStarted()
@@ -166,6 +166,8 @@ class ERR_SiteService
CarControllerComponent controller = CarControllerComponent.Cast(site.Transport.FindComponent(CarControllerComponent));
if (!controller) return Fail("transport must be wheeled: " + site.Definition.Id);
controller.SetPersistentHandBrake(true);
ERR_VehicleSecurityComponent security = ERR_VehicleSecurityComponent.Find(site.Transport);
if (security && !security.Configure()) return Fail("transport security configuration: " + site.Definition.Id);
return true;
}
protected bool TransportReady(ERR_SiteRuntime site)
@@ -0,0 +1,71 @@
// Native held progress/cancellation is authored by Duration in the prefab.
// Server-first execution revalidates the current admitted body and physical
// conditions. It does not independently attest four seconds of server hold time.
class ERR_ForceVehicleLockAction : ScriptedUserAction
{
override bool HasLocalEffectOnlyScript() { return false; }
override bool CheckOnServerFirstScript() { return true; }
override bool CanBroadcastScript() { return false; }
override bool CanBeShownScript(IEntity user)
{
ERR_VehicleSecurityComponent security = ERR_VehicleSecurityComponent.Find(GetOwner());
return security && security.Locked();
}
override bool CanBePerformedScript(IEntity user)
{
SetCannotPerformReason(string.Empty);
ERR_VehicleSecurityComponent security = ERR_VehicleSecurityComponent.Find(GetOwner());
if (!security) { SetCannotPerformReason("Vehicle lock is unavailable"); return false; }
string reason;
bool allowed = security.CanBreach(user, reason);
SetCannotPerformReason(reason);
return allowed;
}
override void PerformAction(IEntity pOwnerEntity, IEntity pUserEntity)
{
if (!Replication.IsServer() || pOwnerEntity != GetOwner()) return;
ERR_VehicleSecurityComponent security = ERR_VehicleSecurityComponent.Find(pOwnerEntity);
if (security) security.TryBreach(pUserEntity);
}
}
// Both native parent classes are LOCAL-effect actions. Keep native boarding and
// door animation behavior; these scoped guards run on the executing client.
class ERR_SecureGetInAction : SCR_GetInUserAction
{
override bool CanBePerformedScript(IEntity user)
{
SetCannotPerformReason(string.Empty);
ERR_VehicleSecurityComponent security = ERR_VehicleSecurityComponent.Find(GetOwner());
if (security && security.Locked())
{
SetCannotPerformReason("Vehicle locked: hold Force vehicle lock at the driver door");
return false;
}
return super.CanBePerformedScript(user);
}
override void PerformAction(IEntity pOwnerEntity, IEntity pUserEntity)
{
if (pOwnerEntity != GetOwner() || !CanBePerformedScript(pUserEntity)) return;
super.PerformAction(pOwnerEntity, pUserEntity);
}
}
class ERR_SecureOpenVehicleDoorAction : SCR_OpenVehicleDoorUserAction
{
override bool CanBePerformedScript(IEntity user)
{
SetCannotPerformReason(string.Empty);
ERR_VehicleSecurityComponent security = ERR_VehicleSecurityComponent.Find(GetOwner());
if (security && security.Locked())
{
SetCannotPerformReason("Vehicle locked: hold Force vehicle lock at the driver door");
return false;
}
return super.CanBePerformedScript(user);
}
override void PerformAction(IEntity pOwnerEntity, IEntity pUserEntity)
{
if (pOwnerEntity != GetOwner() || !CanBePerformedScript(pUserEntity)) return;
super.PerformAction(pOwnerEntity, pUserEntity);
}
}
@@ -0,0 +1,104 @@
class ERR_VehicleSecurityComponentClass : ScriptComponentClass {}
// Only attached to the successor supply UAZ. This is an interaction lock, not
// an anti-cheat boundary around the engine's local vehicle-entry API.
class ERR_VehicleSecurityComponent : ScriptComponent
{
[RplProp()] protected bool m_Configured;
[RplProp()] protected bool m_Breached;
// Same authored local point as the dedicated exterior action context.
[Attribute("-0.95 1.05 0.3")] protected vector m_LockAnchor;
static ERR_VehicleSecurityComponent Find(IEntity entity)
{
while (entity)
{
ERR_VehicleSecurityComponent security = ERR_VehicleSecurityComponent.Cast(entity.FindComponent(ERR_VehicleSecurityComponent));
if (security) return security;
entity = entity.GetParent();
}
return null;
}
bool Locked() { return !m_Breached; }
bool Configure()
{
if (!Replication.IsServer()) return false;
ERR_GameMode mode = ERR_GameMode.Get();
if (!mode || mode.Phase() != ERR_Phase.PREPARING) return false;
if (!Vehicle.Cast(GetOwner()) || !ERR_Actors.Alive(GetOwner())) return false;
// Duplicate registration never resets a breached or configured lock.
if (m_Configured) return true;
m_Configured = true;
Replication.BumpMe();
return true;
}
static bool Motion(float speed, float angularSpeed)
{
return speed >= 0 && speed <= 0.25 && angularSpeed >= 0 && angularSpeed <= 0.1;
}
static bool Reach(float distance)
{
return distance >= 0 && distance <= 2;
}
protected bool EmptyVehicle()
{
BaseCompartmentManagerComponent manager = BaseCompartmentManagerComponent.Cast(GetOwner().FindComponent(BaseCompartmentManagerComponent));
if (!manager) return false;
array<BaseCompartmentSlot> seats = {};
manager.GetCompartments(seats);
if (seats.IsEmpty() || seats.Count() > 8) return false;
foreach (BaseCompartmentSlot seat : seats)
{
if (!seat || seat.GetOccupant()) return false;
}
return true;
}
protected bool ClearReach(IEntity user, vector anchor)
{
ChimeraCharacter character = ChimeraCharacter.Cast(user);
if (!character || !GetGame()) return false;
TraceParam trace = new TraceParam();
trace.Start = character.AimingPosition();
trace.End = anchor;
trace.Flags = TraceFlags.WORLD | TraceFlags.ENTS;
trace.LayerMask = TRACE_LAYER_CAMERA;
trace.Exclude = user;
if (GetGame().GetWorld().TraceMove(trace, null) >= 1) return true;
IEntity hit = trace.TraceEnt;
while (hit)
{
if (hit == GetOwner()) return true;
hit = hit.GetParent();
}
return false;
}
bool CanBreach(IEntity user, out string reason)
{
reason = string.Empty;
if (!m_Configured) { reason = "Vehicle lock is not ready"; return false; }
if (!Locked()) { reason = "Vehicle lock has already been forced"; return false; }
ERR_GameMode mode = ERR_GameMode.Get();
if (!mode || !mode.Active()) { reason = "Mission is not active"; return false; }
if (!ERR_Actors.Player(user) || !ERR_Actors.CanFight(user)) { reason = "You must be alive and conscious"; return false; }
if (CompartmentAccessComponent.GetVehicleIn(user)) { reason = "Leave the vehicle first"; return false; }
if (!ERR_Actors.Alive(GetOwner())) { reason = "Vehicle is destroyed"; return false; }
Physics physics = GetOwner().GetPhysics();
if (!physics || !Motion(physics.GetVelocity().Length(), physics.GetAngularVelocity().Length())) { reason = "Vehicle must be stationary"; return false; }
if (!EmptyVehicle()) { reason = "Vehicle must be unoccupied"; return false; }
vector anchor = GetOwner().CoordToParent(m_LockAnchor);
if (!Reach(vector.Distance(user.GetOrigin(), anchor))) { reason = "Move closer to the driver door lock"; return false; }
if (!ClearReach(user, anchor)) { reason = "The driver door lock is obstructed"; return false; }
return true;
}
bool TryBreach(IEntity user)
{
if (!Replication.IsServer()) return false;
ERR_GameMode mode = ERR_GameMode.Get();
if (!mode || !mode.Players() || !mode.Players().CurrentActor(user)) return false;
string reason;
if (!CanBreach(user, reason)) return false;
m_Breached = true;
Replication.BumpMe();
return true;
}
}