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
@@ -0,0 +1,136 @@
ERR_ScenarioDefinition {
MinimumRadioSources 2
Sites {
ERR_SiteDefinition {
Id "everon-prison-1"
Label "Prison"
Kind 0
Prefab "{BE6B8D56349038A6}Prefabs/ERR/Prison.et"
Position 6090 0 7160
Guarded 1
GuardOffset 35 0 0
Container "{40986810D0B1F4AE}Prefabs/ERR/SupplyCache.et"
ContainerOffset -4 0 0
Stock {
ERR_SiteStock { Prefab "{C0F7DD85A86B2900}Prefabs/Weapons/Handguns/PM/Handgun_PM.et" Count 5 }
ERR_SiteStock { Prefab "{8B853CDD11BA916E}Prefabs/Weapons/Magazines/Magazine_9x18_PM_8rnd_Ball.et" Count 8 }
ERR_SiteStock { Prefab "{13772C903CB5E4F7}Prefabs/Items/Equipment/Maps/Map_Paper_01/PaperMap_01_folded.et" Count 1 }
}
}
ERR_SiteDefinition {
Id "everon-supply-1"
Label "Southern supply cache"
Kind 4
Prefab "{7C3D16C17C24D351}Prefabs/ERR/SupplySite.et"
Position 6150 0 7050
Guarded 1
GuardOffset 18 0 0
Container "{40986810D0B1F4AE}Prefabs/ERR/SupplyCache.et"
ContainerOffset 0 0 0
Stock {
ERR_SiteStock { Prefab "{FA5C25BF66A53DCF}Prefabs/Weapons/Rifles/AK74/Rifle_AK74.et" Count 8 }
ERR_SiteStock { Prefab "{BBB50A815A2F916B}Prefabs/Weapons/Magazines/Magazine_545x39_AK_30rnd_Ball.et" Count 16 }
ERR_SiteStock { Prefab "{C3F1FA1E2EC2B345}Prefabs/Items/Medicine/FieldDressing_01/FieldDressing_USSR_01.et" Count 12 }
ERR_SiteStock { Prefab "{554159A6740C3F3E}Prefabs/ERR/ObjectiveDocument.et" Count 2 TargetId "everon-radio-1" }
}
RevealOnEscape 1
}
ERR_SiteDefinition {
Id "everon-radio-1"
Label "Radio station"
DiscoverRadius 8
Kind 1
Prefab "{F7E7E3D6ACF0C860}Prefabs/ERR/Radio.et"
Position 6500 0 7200
Guarded 1
GuardOffset 30 0 0
}
ERR_SiteDefinition {
Id "everon-extraction-1"
Label "Initial helicopter pickup site"
Kind 2
Prefab "{033503110AC34EAC}Prefabs/ERR/Extraction.et"
Position 7000 0 7500
Guarded 1
GuardOffset 30 0 0
}
ERR_SiteDefinition {
Id "everon-supply-2"
Label "Western supply cache"
Kind 4
Prefab "{7C3D16C17C24D351}Prefabs/ERR/SupplySite.et"
Position 5960 0 7220
Guarded 1
GuardOffset 16 0 0
Patrol {
ERR_SitePatrolPoint { Offset 18 0 0 }
ERR_SitePatrolPoint { Offset 18 0 24 }
ERR_SitePatrolPoint { Offset -6 0 24 }
ERR_SitePatrolPoint { Offset -6 0 0 }
}
Container "{40986810D0B1F4AE}Prefabs/ERR/SupplyCache.et"
DiscoverRadius 25
RevealOnEscape 1
Transport ERR_SiteTransport {
Prefab "{259EE7B78C51B624}Prefabs/Vehicles/Wheeled/UAZ469/UAZ469.et"
Offset 0 0 -8
Heading 180
ClearanceRadius 4
MaximumTerrainRange 1
}
Stock {
ERR_SiteStock { Prefab "{FA5C25BF66A53DCF}Prefabs/Weapons/Rifles/AK74/Rifle_AK74.et" Count 4 }
ERR_SiteStock { Prefab "{BBB50A815A2F916B}Prefabs/Weapons/Magazines/Magazine_545x39_AK_30rnd_Ball.et" Count 8 }
ERR_SiteStock { Prefab "{C3F1FA1E2EC2B345}Prefabs/Items/Medicine/FieldDressing_01/FieldDressing_USSR_01.et" Count 6 }
ERR_SiteStock { Prefab "{554159A6740C3F3E}Prefabs/ERR/ObjectiveDocument.et" Count 2 TargetId "everon-radio-1" }
}
}
ERR_SiteDefinition {
Id "everon-medical-1"
Label "Medical stash"
Kind 4
Prefab "{7C3D16C17C24D351}Prefabs/ERR/SupplySite.et"
Position 6340 0 7400
Guarded 0
GuardOffset 16 0 0
Container "{40986810D0B1F4AE}Prefabs/ERR/SupplyCache.et"
DiscoverRadius 25
RevealOnEscape 0
Stock {
ERR_SiteStock { Prefab "{C3F1FA1E2EC2B345}Prefabs/Items/Medicine/FieldDressing_01/FieldDressing_USSR_01.et" Count 12 }
ERR_SiteStock { Prefab "{527D7C5D2E476BDC}Prefabs/Items/Medicine/SalineBag_01/SalineBag_USSR_01.et" Count 6 }
ERR_SiteStock { Prefab "{0D9A5DCF89AE7AA9}Prefabs/Items/Medicine/MorphineInjection_01/MorphineInjection_01.et" Count 6 }
ERR_SiteStock { Prefab "{13772C903CB5E4F7}Prefabs/Items/Equipment/Maps/Map_Paper_01/PaperMap_01_folded.et" Count 3 }
}
}
ERR_SiteDefinition {
Id "everon-outpost-1"
Label "Eastern guard outpost"
Kind 3
Prefab "{7C3D16C17C24D351}Prefabs/ERR/SupplySite.et"
Position 6740 0 7300
Guarded 1
GuardOffset 16 0 0
Patrol {
ERR_SitePatrolPoint { Offset 18 0 0 }
ERR_SitePatrolPoint { Offset 18 0 24 }
ERR_SitePatrolPoint { Offset -6 0 24 }
ERR_SitePatrolPoint { Offset -6 0 0 }
}
Container "{40986810D0B1F4AE}Prefabs/ERR/SupplyCache.et"
DiscoverRadius 35
RevealOnEscape 0
Stock {
ERR_SiteStock { Prefab "{FA5C25BF66A53DCF}Prefabs/Weapons/Rifles/AK74/Rifle_AK74.et" Count 2 }
ERR_SiteStock { Prefab "{BBB50A815A2F916B}Prefabs/Weapons/Magazines/Magazine_545x39_AK_30rnd_Ball.et" Count 10 }
ERR_SiteStock { Prefab "{243948B23D90BECB}Prefabs/Items/Equipment/Binoculars/Binoculars_B8/Binoculars_B8.et" Count 2 }
ERR_SiteStock { Prefab "{C3F1FA1E2EC2B345}Prefabs/Items/Medicine/FieldDressing_01/FieldDressing_USSR_01.et" Count 4 }
}
Props {
ERR_SiteProp { Prefab "{4B6AC1D8701C35A1}Prefabs/ERR/FieldCover.et" Offset 0 0 -4 }
ERR_SiteProp { Prefab "{4B6AC1D8701C35A1}Prefabs/ERR/FieldCover.et" Offset -4 0 0 Angles 90 0 0 }
ERR_SiteProp { Prefab "{4B6AC1D8701C35A1}Prefabs/ERR/FieldCover.et" Offset 4 0 0 Angles 90 0 0 }
}
}
}
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{BC310DC0CD27F523}Configs/ERR/EveronSites.conf"
Configurations {
ResourceClass PC {
}
ResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,24 @@
SCR_EntityCatalogMultiList {
m_eEntityCatalogType VEHICLE
m_aMultiLists {
SCR_EntityCatalogMultiListEntry {
m_sIdentifier "Mission transport"
m_aEntities {
SCR_EntityCatalogEntry {
m_sEntityPrefab "{259EE7B78C51B624}Prefabs/Vehicles/Wheeled/UAZ469/UAZ469.et"
m_aEntityDataList {
SCR_EntityCatalogEditorData { m_eValidEditorModes 33 }
SCR_EntityCatalogSpawnerData { m_eSlotTypes 24 }
}
}
SCR_EntityCatalogEntry {
m_sEntityPrefab "{DF5CCB7C0FF049F4}Prefabs/Vehicles/Helicopters/Mi8MT/Mi8MT_unarmed_transport.et"
m_aEntityDataList {
SCR_EntityCatalogEditorData { m_eValidEditorModes 33 }
SCR_EntityCatalogSpawnerData { m_eSlotTypes VEHICLE_LARGE }
}
}
}
}
}
}
@@ -0,0 +1,7 @@
MetaFileClass {
Name "{E4785D3AF1D043C2}Configs/ERR/Factions/MissionVehicles.conf"
Configurations {
ResourceClass PC {}
ResourceClass HEADLESS : PC {}
}
}
@@ -0,0 +1,277 @@
SCR_Faction : "{E7B7EA4823DD81F0}Configs/Factions/BLUFOR.conf" {
FactionKey "US"
UIInfo SCR_FactionUIInfo "{528C961236B7DCD3}" {
Name "#AR-Faction_US"
Icon "{EB7F65DBC9392557}UI/Textures/Editor/EditableEntities/Factions/EditableEntity_Faction_USA.edds"
m_sNameUpper "#AR-Faction_US-UC"
}
"Identity of soldiers" FactionIdentity "{570D128F4022EF5A}" : "{B600946E9ACC17C2}Configs/Identities/FactionIdentity_US.conf" {
}
"Radio encryption key" "chickenNuggets"
m_sFactionFlag "{46FB2FB2C2125A3C}UI/Textures/Flags/US-flag-base-3it.edds"
m_FactionFlagMaterial "{610803CC3F3D6649}Assets/Props/Fabric/Flags/Data/Flag_1_2_USA.emat"
m_FactionLabel FACTION_US
m_CallsignInfo SCR_FactionCallsignInfo "{5DA0F2A6677ADA9E}" : "{CB7824D572DD0D5C}Configs/Callsigns/Callsigns_US.conf" {
}
m_iFactionRadioFrequency 48000
m_aEntityCatalogs {
SCR_EntityCatalogMultiList "{5C68A0EE815FA292}" : "{B56E8B4D4B195559}Configs/EntityCatalog/US/Characters_EntityCatalog_US.conf" {
}
SCR_EntityCatalogMultiList "{5C9E82F29B327997}" : "{E4785D3AF1D043C2}Configs/ERR/Factions/MissionVehicles.conf" {
}
SCR_EntityCatalogMultiList "{5C68A0EE8B97CA50}" : "{4E70E7C4C2C17D4A}Configs/EntityCatalog/US/Groups_EntityCatalog_US.conf" {
}
SCR_EntityCatalog "{5C68A0EEB7DB3E84}" : "{0E4B62F186F97C43}Configs/EntityCatalog/US/WeaponTripod_EntityCatalog_US.conf" {
}
SCR_EntityCatalogMultiList "{5C68A0EEB3F124F9}" : "{5F7EC52FC40A03E2}Configs/EntityCatalog/US/InventoryItems_EntityCatalog_US.conf" {
}
SCR_EntityCatalog "{5C9EDDD37394AD40}" : "{0BF36EFF9FFAF92B}Configs/EntityCatalog/US/SupplyContainerItems_EntityCatalog_US.conf" {
}
}
m_aFlagNames +{
}
m_aBaseCallsigns {
SCR_MilitaryBaseCallsign "{5C90D92F493E764B}" {
m_sCallsign "#AR-Campaign_PointAlabama"
m_sCallsignShort "#AR-Comm_Variable_Base_Alabama_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Alabama_US_UC"
m_iSignalIndex 0
}
SCR_MilitaryBaseCallsign "{5C90D92F49F79967}" {
m_sCallsign "#AR-Campaign_PointAlaska"
m_sCallsignShort "#AR-Comm_Variable_Base_Alaska_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Alaska_US_UC"
m_iSignalIndex 1
}
SCR_MilitaryBaseCallsign "{5C90D92F482DA59B}" {
m_sCallsign "#AR-Campaign_PointArizona"
m_sCallsignShort "#AR-Comm_Variable_Base_Arizona_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Arizona_US_UC"
m_iSignalIndex 2
}
SCR_MilitaryBaseCallsign "{5C90D92F483330D2}" {
m_sCallsign "#AR-Campaign_PointArkansas"
m_sCallsignShort "#AR-Comm_Variable_Base_Arkansas_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Arkansas_US_UC"
m_iSignalIndex 3
}
SCR_MilitaryBaseCallsign "{5C90D92F48170DA8}" {
m_sCallsign "#AR-Campaign_PointCalifornia"
m_sCallsignShort "#AR-Comm_Variable_Base_California_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_California_US_UC"
m_iSignalIndex 4
}
SCR_MilitaryBaseCallsign "{5C90D92F487834E8}" {
m_sCallsign "#AR-Campaign_PointColorado"
m_sCallsignShort "#AR-Comm_Variable_Base_Colorado_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Colorado_US_UC"
m_iSignalIndex 5
}
SCR_MilitaryBaseCallsign "{5C90D92F48410A86}" {
m_sCallsign "#AR-Campaign_PointConnecticut"
m_sCallsignShort "#AR-Comm_Variable_Base_Connecticut_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Connecticut_US_UC"
m_iSignalIndex 6
}
SCR_MilitaryBaseCallsign "{5C90D92F48A4E31F}" {
m_sCallsign "#AR-Campaign_PointDelaware"
m_sCallsignShort "#AR-Comm_Variable_Base_Delaware_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Delaware_US_UC"
m_iSignalIndex 7
}
SCR_MilitaryBaseCallsign "{5C90D92F488C6B57}" {
m_sCallsign "#AR-Campaign_PointFlorida"
m_sCallsignShort "#AR-Comm_Variable_Base_Florida_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Florida_US_UC"
m_iSignalIndex 8
}
SCR_MilitaryBaseCallsign "{5C90D92F4896F764}" {
m_sCallsign "#AR-Campaign_PointGeorgia"
m_sCallsignShort "#AR-Comm_Variable_Base_Georgia_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Georgia_US_UC"
m_iSignalIndex 9
}
SCR_MilitaryBaseCallsign "{5C90D92F48FBD90E}" {
m_sCallsign "#AR-Campaign_PointHawaii"
m_sCallsignShort "#AR-Comm_Variable_Base_Hawaii_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Hawaii_US_UC"
m_iSignalIndex 10
}
SCR_MilitaryBaseCallsign "{5C90D92F48DFF562}" {
m_sCallsign "#AR-Campaign_PointIdaho"
m_sCallsignShort "#AR-Comm_Variable_Base_Idaho_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Idaho_US_UC"
m_iSignalIndex 11
}
SCR_MilitaryBaseCallsign "{5C90D92F7724C851}" {
m_sCallsign "#AR-Campaign_PointIllinois"
m_sCallsignShort "#AR-Comm_Variable_Base_Illinois_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Illinois_US_UC"
m_iSignalIndex 12
}
SCR_MilitaryBaseCallsign "{5C90D92F77097F3F}" {
m_sCallsign "#AR-Campaign_PointIndiana"
m_sCallsignShort "#AR-Comm_Variable_Base_Indiana_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Indiana_US_UC"
m_iSignalIndex 13
}
SCR_MilitaryBaseCallsign "{5C90D92F77102B55}" {
m_sCallsign "#AR-Campaign_PointIowa"
m_sCallsignShort "#AR-Comm_Variable_Base_Iowa_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Iowa_US_UC"
m_iSignalIndex 14
}
SCR_MilitaryBaseCallsign "{5C90D92F777ABD04}" {
m_sCallsign "#AR-Campaign_PointKansas"
m_sCallsignShort "#AR-Comm_Variable_Base_Kansas_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Kansas_US_UC"
m_iSignalIndex 15
}
SCR_MilitaryBaseCallsign "{5C90D92F775EBBC1}" {
m_sCallsign "#AR-Campaign_PointKentucky"
m_sCallsignShort "#AR-Comm_Variable_Base_Kentucky_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Kentucky_US_UC"
m_iSignalIndex 16
}
SCR_MilitaryBaseCallsign "{5C90D92F77A28EB6}" {
m_sCallsign "#AR-Campaign_PointLouisiana"
m_sCallsignShort "#AR-Comm_Variable_Base_Louisiana_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Louisiana_US_UC"
m_iSignalIndex 17
}
SCR_MilitaryBaseCallsign "{5C90D92F77871C23}" {
m_sCallsign "#AR-Campaign_PointMaine"
m_sCallsignShort "#AR-Comm_Variable_Base_Maine_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Maine_US_UC"
m_iSignalIndex 18
}
SCR_MilitaryBaseCallsign "{5C90D92F77EB96A0}" {
m_sCallsign "#AR-Campaign_PointMaryland"
m_sCallsignShort "#AR-Comm_Variable_Base_Maryland_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Maryland_US_UC"
m_iSignalIndex 19
}
SCR_MilitaryBaseCallsign "{5C90D92F77CC2518}" {
m_sCallsign "#AR-Campaign_PointMassachusetts"
m_sCallsignShort "#AR-Comm_Variable_Base_Massachusetts_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Massachusetts_US_UC"
m_iSignalIndex 20
}
SCR_MilitaryBaseCallsign "{5C90D92F77D3715B}" {
m_sCallsign "#AR-Campaign_PointMichigan"
m_sCallsignShort "#AR-Comm_Variable_Base_Michigan_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Michigan_US_UC"
m_iSignalIndex 21
}
SCR_MilitaryBaseCallsign "{5C90D92F7635D418}" {
m_sCallsign "#AR-Campaign_PointMinnesota"
m_sCallsignShort "#AR-Comm_Variable_Base_Minnesota_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Minnesota_US_UC"
m_iSignalIndex 22
}
SCR_MilitaryBaseCallsign "{5C90D92F761C3FF2}" {
m_sCallsign "#AR-Campaign_PointMississippi"
m_sCallsignShort "#AR-Comm_Variable_Base_Mississippi_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Mississippi_US_UC"
m_iSignalIndex 23
}
SCR_MilitaryBaseCallsign "{5C90D92F7660CFAA}" {
m_sCallsign "#AR-Campaign_PointMissouri"
m_sCallsignShort "#AR-Comm_Variable_Base_Missouri_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Missouri_US_UC"
m_iSignalIndex 24
}
SCR_MilitaryBaseCallsign "{5C90D92F764A6E6B}" {
m_sCallsign "#AR-Campaign_PointMontana"
m_sCallsignShort "#AR-Comm_Variable_Base_Montana_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Montana_US_UC"
m_iSignalIndex 25
}
SCR_MilitaryBaseCallsign "{5C90D92F76AD4D35}" {
m_sCallsign "#AR-Campaign_PointNebraska"
m_sCallsignShort "#AR-Comm_Variable_Base_Nebraska_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Nebraska_US_UC"
m_iSignalIndex 26
}
SCR_MilitaryBaseCallsign "{5C90D92F76B1EF3A}" {
m_sCallsign "#AR-Campaign_PointNevada"
m_sCallsignShort "#AR-Comm_Variable_Base_Nevada_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Nevada_US_UC"
m_iSignalIndex 27
}
SCR_MilitaryBaseCallsign "{5C90D92F7695FF2F}" {
m_sCallsign "#AR-Campaign_PointNewYork"
m_sCallsignShort "#AR-Comm_Variable_Base_NewYork_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_NewYork_US_UC"
m_iSignalIndex 28
}
SCR_MilitaryBaseCallsign "{5C90D92F76FF94B1}" {
m_sCallsign "#AR-Campaign_PointOhio"
m_sCallsignShort "#AR-Comm_Variable_Base_Ohio_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Ohio_US_UC"
m_iSignalIndex 29
}
SCR_MilitaryBaseCallsign "{5C90D92F76C68EED}" {
m_sCallsign "#AR-Campaign_PointOklahoma"
m_sCallsignShort "#AR-Comm_Variable_Base_Oklahoma_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Oklahoma_US_UC"
m_iSignalIndex 30
}
SCR_MilitaryBaseCallsign "{5C90D92F752A9F29}" {
m_sCallsign "#AR-Campaign_PointOregon"
m_sCallsignShort "#AR-Comm_Variable_Base_Oregon_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Oregon_US_UC"
m_iSignalIndex 31
}
SCR_MilitaryBaseCallsign "{5C90D92F750E6FB9}" {
m_sCallsign "#AR-Campaign_PointPennsylvania"
m_sCallsignShort "#AR-Comm_Variable_Base_Pennsylvania_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Pennsylvania_US_UC"
m_iSignalIndex 32
}
SCR_MilitaryBaseCallsign "{5C90D92F7517240F}" {
m_sCallsign "#AR-Campaign_PointTexas"
m_sCallsignShort "#AR-Comm_Variable_Base_Texas_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Texas_US_UC"
m_iSignalIndex 33
}
SCR_MilitaryBaseCallsign "{5C90D92F7574D867}" {
m_sCallsign "#AR-Campaign_PointUtah"
m_sCallsignShort "#AR-Comm_Variable_Base_Utah_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Utah_US_UC"
m_iSignalIndex 34
}
SCR_MilitaryBaseCallsign "{5C90D92F755C4871}" {
m_sCallsign "#AR-Campaign_PointVermont"
m_sCallsignShort "#AR-Comm_Variable_Base_Vermont_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Vermont_US_UC"
m_iSignalIndex 35
}
SCR_MilitaryBaseCallsign "{5C90D92F75A0502B}" {
m_sCallsign "#AR-Campaign_PointVirginia"
m_sCallsignShort "#AR-Comm_Variable_Base_Virginia_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Virginia_US_UC"
m_iSignalIndex 36
}
SCR_MilitaryBaseCallsign "{5C90D92F758ABFD3}" {
m_sCallsign "#AR-Campaign_PointWashington"
m_sCallsignShort "#AR-Comm_Variable_Base_Washington_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Washington_US_UC"
m_iSignalIndex 37
}
SCR_MilitaryBaseCallsign "{5C90D92F75EF8263}" {
m_sCallsign "#AR-Campaign_PointWisconsin"
m_sCallsignShort "#AR-Comm_Variable_Base_Wisconsin_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Wisconsin_US_UC"
m_iSignalIndex 38
}
SCR_MilitaryBaseCallsign "{5C90D92F75F0F224}" {
m_sCallsign "#AR-Campaign_PointWyoming"
m_sCallsignShort "#AR-Comm_Variable_Base_Wyoming_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Wyoming_US_UC"
m_iSignalIndex 39
}
}
m_FactionHomeTerritoryConfig SCR_FactionHomeTerritoryConfig "{5AEE94D54FB58C33}" : "{B548A5B264939A98}Configs/Identities/Territories/FactionHomeTerritory_US.conf" {
}
}
@@ -0,0 +1,7 @@
MetaFileClass {
Name "{5C67A1DDE0044806}Configs/ERR/Factions/US.conf"
Configurations {
ResourceClass PC {}
ResourceClass HEADLESS : PC {}
}
}
@@ -0,0 +1,276 @@
SCR_Faction : "{B122E3B10BB3404B}Configs/Factions/OPFOR.conf" {
FactionKey "USSR"
UIInfo SCR_FactionUIInfo "{528C96127F8B6B08}" {
Name "#AR-Faction_USSR"
Icon "{40B12B0DF911B856}UI/Textures/Editor/EditableEntities/Factions/EditableEntity_Faction_USSR.edds"
m_sNameUpper "#AR-Faction_USSR-UC"
}
"Identity of soldiers" FactionIdentity "{570CFDC6685B6B25}" : "{FBBCD73926989580}Configs/Identities/FactionIdentity_USSR.conf" {
}
"Radio encryption key" "coldBorscht"
m_sFactionFlag "{50824EB9F2C215AB}UI/Textures/Flags/USSR-flag-base-3it.edds"
m_FactionFlagMaterial "{B47B52D9DA68905D}Assets/Props/Fabric/Flags/Data/Flag_1_2_SovietArmy.emat"
m_FactionLabel FACTION_USSR
m_CallsignInfo SCR_FactionCallsignInfo "{5DA0F2A67DFB8809}" : "{958043CE1648EC43}Configs/Callsigns/Callsigns_USSR.conf" {
}
m_iFactionRadioFrequency 42000
m_aEntityCatalogs {
SCR_EntityCatalogMultiList "{5C68A0E9BD5E5DD9}" : "{3B679F1799D55F6F}Configs/EntityCatalog/USSR/Characters_EntityCatalog_USSR.conf" {
}
SCR_EntityCatalogMultiList "{5C9E82F285C09E24}" : "{E4785D3AF1D043C2}Configs/ERR/Factions/MissionVehicles.conf" {
}
SCR_EntityCatalogMultiList "{5C68A0E9B9AA3C2D}" : "{37D5B75AAC3E72EA}Configs/EntityCatalog/USSR/Groups_EntityCatalog_USSR.conf" {
}
SCR_EntityCatalog "{5C68A0E9A51460AB}" : "{DDF17C8C140BD1F2}Configs/EntityCatalog/USSR/WeaponTripod_EntityCatalog_USSR.conf" {
}
SCR_EntityCatalogMultiList "{5C68A0E9AFE1E631}" : "{C53421647C3D0D2E}Configs/EntityCatalog/USSR/InventoryItems_EntityCatalog_USSR.conf" {
}
SCR_EntityCatalog "{5C9EDDD378BA3298}" : "{B4CA6761AC935808}Configs/EntityCatalog/USSR/SupplyContainerItems_EntityCatalog_USSR.conf" {
}
}
m_iIdentityVoiceSignal 100
m_aBaseCallsigns {
SCR_MilitaryBaseCallsign "{5C9108F95F3FB520}" {
m_sCallsign "#AR-Campaign_PointArena"
m_sCallsignShort "#AR-Comm_Variable_Base_Arena_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Arena_RU_UC"
m_iSignalIndex 0
}
SCR_MilitaryBaseCallsign "{5C9108F95F0034DB}" {
m_sCallsign "#AR-Campaign_PointAvrora"
m_sCallsignShort "#AR-Comm_Variable_Base_Avrora_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Avrora_RU_UC"
m_iSignalIndex 1
}
SCR_MilitaryBaseCallsign "{5C9108F95F6514E7}" {
m_sCallsign "#AR-Campaign_PointBekas"
m_sCallsignShort "#AR-Comm_Variable_Base_Bekas_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Bekas_RU_UC"
m_iSignalIndex 2
}
SCR_MilitaryBaseCallsign "{5C9108F95F4EE8BF}" {
m_sCallsign "#AR-Campaign_PointBizon"
m_sCallsignShort "#AR-Comm_Variable_Base_Bizon_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Bizon_RU_UC"
m_iSignalIndex 3
}
SCR_MilitaryBaseCallsign "{5C9108F95F5021F9}" {
m_sCallsign "#AR-Campaign_PointBolero"
m_sCallsignShort "#AR-Comm_Variable_Base_Bolero_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Bolero_RU_UC"
m_iSignalIndex 4
}
SCR_MilitaryBaseCallsign "{5C9108F95FBA4C89}" {
m_sCallsign "#AR-Campaign_PointCafftan"
m_sCallsignShort "#AR-Comm_Variable_Base_Cafftan_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Cafftan_RU_UC"
m_iSignalIndex 5
}
SCR_MilitaryBaseCallsign "{5C9108F95F9C00A9}" {
m_sCallsign "#AR-Campaign_PointKaban"
m_sCallsignShort "#AR-Comm_Variable_Base_Kaban_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Kaban_RU_UC"
m_iSignalIndex 6
}
SCR_MilitaryBaseCallsign "{5C9108F95FE17E10}" {
m_sCallsign "#AR-Campaign_PointConsul"
m_sCallsignShort "#AR-Comm_Variable_Base_Consul_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Consul_RU_UC"
m_iSignalIndex 7
}
SCR_MilitaryBaseCallsign "{5C9108F95FCBA475}" {
m_sCallsign "#AR-Campaign_PointDimok"
m_sCallsignShort "#AR-Comm_Variable_Base_Dimok_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Dimok_RU_UC"
m_iSignalIndex 8
}
SCR_MilitaryBaseCallsign "{5C9108F95E2B2963}" {
m_sCallsign "#AR-Campaign_PointEffir"
m_sCallsignShort "#AR-Comm_Variable_Base_Effir_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Effir_RU_UC"
m_iSignalIndex 9
}
SCR_MilitaryBaseCallsign "{5C9108F95E3040F5}" {
m_sCallsign "#AR-Campaign_PointFakir"
m_sCallsignShort "#AR-Comm_Variable_Base_Fakir_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Fakir_RU_UC"
m_iSignalIndex 10
}
SCR_MilitaryBaseCallsign "{5C9108F95E17C67D}" {
m_sCallsign "#AR-Campaign_PointGips"
m_sCallsignShort "#AR-Comm_Variable_Base_Gips_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Gips_RU_UC"
m_iSignalIndex 11
}
SCR_MilitaryBaseCallsign "{5C9108F95E797F8A}" {
m_sCallsign "#AR-Campaign_PointGussar"
m_sCallsignShort "#AR-Comm_Variable_Base_Gussar_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Gussar_RU_UC"
m_iSignalIndex 12
}
SCR_MilitaryBaseCallsign "{5C9108F95E431F83}" {
m_sCallsign "#AR-Campaign_PointIgla"
m_sCallsignShort "#AR-Comm_Variable_Base_Igla_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Igla_RU_UC"
m_iSignalIndex 13
}
SCR_MilitaryBaseCallsign "{5C9108F95EA7F87E}" {
m_sCallsign "#AR-Campaign_PointKhek"
m_sCallsignShort "#AR-Comm_Variable_Base_Khek_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Khek_RU_UC"
m_iSignalIndex 14
}
SCR_MilitaryBaseCallsign "{5C9108F95E88C6B9}" {
m_sCallsign "#AR-Campaign_PointLednik"
m_sCallsignShort "#AR-Comm_Variable_Base_Lednik_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Lednik_RU_UC"
m_iSignalIndex 15
}
SCR_MilitaryBaseCallsign "{5C9108F95EEDE322}" {
m_sCallsign "#AR-Campaign_PointMatros"
m_sCallsignShort "#AR-Comm_Variable_Base_Matros_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Matros_RU_UC"
m_iSignalIndex 16
}
SCR_MilitaryBaseCallsign "{5C9108F95EF6244A}" {
m_sCallsign "#AR-Campaign_PointMuskat"
m_sCallsignShort "#AR-Comm_Variable_Base_Muskat_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Muskat_RU_UC"
m_iSignalIndex 17
}
SCR_MilitaryBaseCallsign "{5C9108F95EDAF04A}" {
m_sCallsign "#AR-Campaign_PointNagan"
m_sCallsignShort "#AR-Comm_Variable_Base_Nagan_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Nagan_RU_UC"
m_iSignalIndex 18
}
SCR_MilitaryBaseCallsign "{5C9108F95D3CCF27}" {
m_sCallsign "#AR-Campaign_PointNitka"
m_sCallsignShort "#AR-Comm_Variable_Base_Nitka_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Nitka_RU_UC"
m_iSignalIndex 19
}
SCR_MilitaryBaseCallsign "{5C9108F95D076483}" {
m_sCallsign "#AR-Campaign_PointOmega"
m_sCallsignShort "#AR-Comm_Variable_Base_Omega_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Omega_RU_UC"
m_iSignalIndex 20
}
SCR_MilitaryBaseCallsign "{5C9108F95D68D0F3}" {
m_sCallsign "#AR-Campaign_PointPlazma"
m_sCallsignShort "#AR-Comm_Variable_Base_Plazma_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Plazma_RU_UC"
m_iSignalIndex 21
}
SCR_MilitaryBaseCallsign "{5C9108F95D4C0F8E}" {
m_sCallsign "#AR-Campaign_PointPolyn"
m_sCallsignShort "#AR-Comm_Variable_Base_Polyn_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Polyn_RU_UC"
m_iSignalIndex 22
}
SCR_MilitaryBaseCallsign "{5C9108F95D56EEEE}" {
m_sCallsign "#AR-Campaign_PointProlog"
m_sCallsignShort "#AR-Comm_Variable_Base_Prolog_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Prolog_RU_UC"
m_iSignalIndex 23
}
SCR_MilitaryBaseCallsign "{5C9108F95DBA92EE}" {
m_sCallsign "#AR-Campaign_PointRabkor"
m_sCallsignShort "#AR-Comm_Variable_Base_Rabkor_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Rabkor_RU_UC"
m_iSignalIndex 24
}
SCR_MilitaryBaseCallsign "{5C9108F95C69680D}" {
m_sCallsign "#AR-Campaign_PointRadon"
m_sCallsignShort "#AR-Comm_Variable_Base_Radon_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Radon_RU_UC"
m_iSignalIndex 25
}
SCR_MilitaryBaseCallsign "{5C9108F95C4FD930}" {
m_sCallsign "#AR-Campaign_PointSadko"
m_sCallsignShort "#AR-Comm_Variable_Base_Sadko_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Sadko_RU_UC"
m_iSignalIndex 26
}
SCR_MilitaryBaseCallsign "{5C9108F95C5255FD}" {
m_sCallsign "#AR-Campaign_PointSekret"
m_sCallsignShort "#AR-Comm_Variable_Base_Sekret_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Sekret_RU_UC"
m_iSignalIndex 27
}
SCR_MilitaryBaseCallsign "{5C9108F95CB621AE}" {
m_sCallsign "#AR-Campaign_PointShtanga"
m_sCallsignShort "#AR-Comm_Variable_Base_Shtanga_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Shtanga_RU_UC"
m_iSignalIndex 28
}
SCR_MilitaryBaseCallsign "{5C9108F95C9545BC}" {
m_sCallsign "#AR-Campaign_PointShuka"
m_sCallsignShort "#AR-Comm_Variable_Base_Shuka_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Shuka_RU_UC"
m_iSignalIndex 29
}
SCR_MilitaryBaseCallsign "{5C9108F95CFE26C3}" {
m_sCallsign "#AR-Campaign_PointTabor"
m_sCallsignShort "#AR-Comm_Variable_Base_Tabor_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Tabor_RU_UC"
m_iSignalIndex 30
}
SCR_MilitaryBaseCallsign "{5C9108F95CDD263C}" {
m_sCallsign "#AR-Campaign_PointTenor"
m_sCallsignShort "#AR-Comm_Variable_Base_Tenor_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Tenor_RU_UC"
m_iSignalIndex 31
}
SCR_MilitaryBaseCallsign "{5C9108F95B27128C}" {
m_sCallsign "#AR-Campaign_PointTsikson"
m_sCallsignShort "#AR-Comm_Variable_Base_Tsikson_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Tsikson_RU_UC"
m_iSignalIndex 32
}
SCR_MilitaryBaseCallsign "{5C9108F95B08D83D}" {
m_sCallsign "#AR-Campaign_PointUlan"
m_sCallsignShort "#AR-Comm_Variable_Base_Ulan_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Ulan_RU_UC"
m_iSignalIndex 33
}
SCR_MilitaryBaseCallsign "{5C9108F95B6D3FC2}" {
m_sCallsign "#AR-Campaign_PointVermout"
m_sCallsignShort "#AR-Comm_Variable_Base_Vermout_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Vermout_RU_UC"
m_iSignalIndex 34
}
SCR_MilitaryBaseCallsign "{5C9108F95B712949}" {
m_sCallsign "#AR-Campaign_PointViympel"
m_sCallsignShort "#AR-Comm_Variable_Base_Viympel_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Viympel_RU_UC"
m_iSignalIndex 35
}
SCR_MilitaryBaseCallsign "{5C9108F95B57D185}" {
m_sCallsign "#AR-Campaign_PointYantar"
m_sCallsignShort "#AR-Comm_Variable_Base_Yantar_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Yantar_RU_UC"
m_iSignalIndex 36
}
SCR_MilitaryBaseCallsign "{5C9108F95BB17114}" {
m_sCallsign "#AR-Campaign_PointYolka"
m_sCallsignShort "#AR-Comm_Variable_Base_Yolka_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Yolka_RU_UC"
m_iSignalIndex 37
}
SCR_MilitaryBaseCallsign "{5C9108F95B97C054}" {
m_sCallsign "#AR-Campaign_PointZheton"
m_sCallsignShort "#AR-Comm_Variable_Base_Zheton_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Zheton_RU_UC"
m_iSignalIndex 38
}
SCR_MilitaryBaseCallsign "{5C9108F95BFC33E5}" {
m_sCallsign "#AR-Campaign_PointZurna"
m_sCallsignShort "#AR-Comm_Variable_Base_Zurna_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Zurna_RU_UC"
m_iSignalIndex 39
}
}
m_FactionHomeTerritoryConfig SCR_FactionHomeTerritoryConfig "{5AEE94D5517C30BD}" : "{31FC78AF00523A99}Configs/Identities/Territories/FactionHomeTerritory_USSR.conf" {
}
}
@@ -0,0 +1,7 @@
MetaFileClass {
Name "{D7A5CFA62A36480D}Configs/ERR/Factions/USSR.conf"
Configurations {
ResourceClass PC {}
ResourceClass HEADLESS : PC {}
}
}
@@ -0,0 +1,21 @@
SCR_GameOverScreenConfig : "{651583FFFB7FC765}Configs/GameOverScreen/BaseGameOverScreensConfig.conf" {
m_aGameOverScreens {
ERR_EscapeGameOverScreenInfo "{6B193DC8DDC63507}" {
m_GameOverScreenId VICTORY
m_GameOverContentLayout "{C4507D07F3C54831}UI/layouts/HUD/GameOver/EndScreen/EndScreenContent_NoImage.layout"
m_OptionalParams SCR_BaseGameOverScreenInfoOptional "{4984A5C1BFEFC04E}" {
m_sTitle "Escape complete"
m_sAudioOneShot "SOUND_ONVICTORY"
}
}
ERR_FailureGameOverScreenInfo "{E66F29A7A8AD7438}" {
m_GameOverScreenId LOOSE
m_GameOverContentLayout "{C4507D07F3C54831}UI/layouts/HUD/GameOver/EndScreen/EndScreenContent_NoImage.layout"
m_OptionalParams SCR_BaseGameOverScreenInfoOptional "{3DBF0BDE715945FA}" {
m_sTitle "Escape failed"
m_sDebriefing "No surviving prisoners can complete the escape, or the extraction helicopter has been lost."
m_sAudioOneShot "SOUND_ONDEFEAT"
}
}
}
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{F6633922A25F9441}Configs/ERR/GameOverScreens.conf"
Configurations {
ResourceClass PC {
}
ResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,7 @@
ERR_FactionCatalog {
PlayerFactionKey "US"
EnemyFactionKey "USSR"
PrisonerPrefab "{3FF693715C9F8928}Prefabs/ERR/Prisoner.et"
GuardGroupPrefab "{D5A4D0D3AE1A3E26}Prefabs/ERR/Patrol.et"
ExtractionPrefab "{DF5CCB7C0FF049F4}Prefabs/Vehicles/Helicopters/Mi8MT/Mi8MT_unarmed_transport.et"
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{BE50B97B8E2B36E6}Configs/ERR/VanillaFactions.conf"
Configurations {
ResourceClass PC {
}
ResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,11 @@
SCR_MissionHeader {
World "{B6C516F86302C679}Worlds/ERR_Everon.ent"
SystemsConfig "{C50579A2EC48E8EF}Configs/Systems/MissionSystems.conf"
m_sName "Rereintroduced: Everon Base (development)"
m_sAuthor "Rereintroduced contributors"
m_sDescription "Independent base escape mission using vanilla Reforger. Companion addons optional. Development scenario."
m_sGameMode "Escape"
m_iPlayerCount 12
m_eSaveTypes 0
m_bShowInScenarioMenu 1
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{AD62322D5213C440}Missions/ERR_Everon_Base.conf"
Configurations {
ResourceClass PC {
}
ResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,6 @@
GenericEntity {
components {
ERR_LocationComponent "{C17CA14398A1CD7E}" { m_Kind 2 }
RplComponent "{0BF5BAAA5FBFEECE}" { Streamable Disabled }
}
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{033503110AC34EAC}Prefabs/ERR/Extraction.et"
Configurations {
EntityTemplateResourceClass PC {
}
EntityTemplateResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,10 @@
SCR_FactionManager : "{A242612865F2A48E}Prefabs/MP/Managers/Factions/FactionManager_Base.et" {
Factions {
SCR_Faction : "{5C67A1DDE0044806}Configs/ERR/Factions/US.conf" {
m_sFactionBackground "{F8AC305446D5D480}UI/Textures/DeployMenu/Loadout-BG/Loadout_US_BG.imageset"
}
SCR_Faction : "{D7A5CFA62A36480D}Configs/ERR/Factions/USSR.conf" {
m_sFactionBackground "{21533F2089005056}UI/Textures/DeployMenu/Loadout-BG/Loadout_BG_USSR.imageset"
}
}
}
@@ -0,0 +1,7 @@
MetaFileClass {
Name "{5414041A15DC4B54}Prefabs/ERR/FactionManager.et"
Configurations {
EntityTemplateResourceClass PC {}
EntityTemplateResourceClass HEADLESS : PC {}
}
}
@@ -0,0 +1,13 @@
GenericEntity {
components {
MeshObject "{A557F6DC012E5101}" {
Object "{268DBDABB3C94AD7}Assets/Props/Military/Sandbags/Sandbag_01_short.xob"
Materials {
MaterialAssignClass "{A557F6DC012E5102}" { SourceMaterial "Sandbags_01_Short" AssignedMaterial "{31529E064AA7C917}Assets/Props/Military/Sandbags/Data/Sandbags_01_short_Burlap.emat" }
MaterialAssignClass "{A557F6DC012E5103}" { SourceMaterial "Sandbags_01_MLOD" AssignedMaterial "{2169E8EB73E1C127}Assets/Props/Military/Sandbags/Data/Sandbags_01_Burlap_MLOD.emat" }
}
}
RigidBody "{A557F6DC012E5104}" { ModelGeometry 1 Static 1 }
RplComponent "{A557F6DC012E5105}" {}
}
}
@@ -0,0 +1,7 @@
MetaFileClass {
Name "{4B6AC1D8701C35A1}Prefabs/ERR/FieldCover.et"
Configurations {
ResourceClass PC {}
ResourceClass HEADLESS : PC {}
}
}
@@ -0,0 +1,14 @@
ERR_GameMode : "{0F307326459A1395}Prefabs/MP/Modes/GameMode_Base.et" {
components {
SCR_GameOverScreenManagerComponent "{56DEC646298AD219}" {
m_GameOverScreenConfig SCR_GameOverScreenConfig "{56DEC6462FFDD82D}" : "{F6633922A25F9441}Configs/ERR/GameOverScreens.conf" {}
}
ERR_ScenarioComponent "{B78E910E88DE30D2}" {
m_Definition "{BC310DC0CD27F523}Configs/ERR/EveronSites.conf"
}
SCR_RespawnSystemComponent "{56B2B4793051E7C9}" {
m_SpawnLogic ERR_SpawnLogic "{79AD722B2EFD595F}" {}
}
}
m_bUseSpawnPreload 0
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{978E045E53EF36C8}Prefabs/ERR/GameMode.et"
Configurations {
EntityTemplateResourceClass PC {
}
EntityTemplateResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,5 @@
GenericEntity : "{6D56FED1E55A8F84}Prefabs/Items/Misc/IntelligenceFolder_E_01/IntelligenceFolder_E_01.et" {
components {
ERR_DocumentComponent "{E4598D0BE2C251A3}" {}
}
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{554159A6740C3F3E}Prefabs/ERR/ObjectiveDocument.et"
Configurations {
ResourceClass PC {
}
ResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,10 @@
SCR_AIGroup : "{E552DABF3636C2AD}Prefabs/Groups/OPFOR/Group_USSR_RifleSquad.et" {
m_aUnitPrefabSlots {
"{5436629450D8387A}Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_SL.et"
"{612F43A4D5AE765F}Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_RTO.et"
"{23ADBBC31B6A3DC6}Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_AR.et"
"{1C78331E156A3D65}Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_AT.et"
"{333DA6244C7DA34C}Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_SR.et"
"{BF643BE4ADBDFDD3}Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_LAT.et"
}
}
@@ -0,0 +1,7 @@
MetaFileClass {
Name "{D5A4D0D3AE1A3E26}Prefabs/ERR/Patrol.et"
Configurations {
EntityTemplateResourceClass PC {}
EntityTemplateResourceClass HEADLESS {}
}
}
@@ -0,0 +1,90 @@
GenericEntity {
components {
ERR_LocationComponent "{3B60CBEB3C04333C}" { m_Kind 0 }
Hierarchy "{30ADADE77D1EC3B8}" {}
RplComponent "{7FA5168C6FBD0C22}" {}
}
{
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "2879356FFFBE36E0"
coords -6 0 6
angles 0 0 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "C99EF9AFCEBA863D"
coords -3 0 6
angles 0 0 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "91048277F04ACA61"
coords 0 0 6
angles 0 0 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "8F2200A922FB0A6E"
coords 3 0 6
angles 0 0 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "3C442429BBCF5D2F"
coords -6 0 -6
angles 0 -90 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "9E855CC802C592CA"
coords 6 0 -6
angles 0 -90 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "778DAC42C9208F67"
coords -6 0 -3
angles 0 -90 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "7E350EB9053673C4"
coords 6 0 -3
angles 0 -90 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "AD17884D5EA37D37"
coords -6 0 0
angles 0 -90 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "1A157E23E1A995D8"
coords 6 0 0
angles 0 -90 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "3A78A6F3ACB7ACB1"
coords -6 0 3
angles 0 -90 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "275B472B565FFF6C"
coords 6 0 3
angles 0 -90 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "1B20E11B3FC7B59A"
coords -6 0 -6
angles 0 0 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "2735B52246DD6213"
coords -3 0 -6
angles 0 0 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "28101A0E33470B38"
coords 3 0 -6
angles 0 0 0
}
GenericEntity : "{C3FDC4DBCD58C2F5}Prefabs/ERR/PrisonDoor.et" {
ID "54D8F96ECC743335"
scale 1.5
coords 0 -0.1 -6
angles 0 0 0
}
}
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{BE6B8D56349038A6}Prefabs/ERR/Prison.et"
Configurations {
EntityTemplateResourceClass PC {
}
EntityTemplateResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,19 @@
GenericEntity : "{34B08027DBDE322A}Prefabs/Structures/BuildingParts/Doors/Door_MetalGate_01_Short_LEFT_EXT_ST_B.et" {
components {
DoorComponent "{221C667A9F7D4B69}" {
DoorAction ERR_PrisonDoorAction "{50888D3646A61640}" {
ParentContextList { "SCR_DoorUserAction" }
}
}
ActionsManagerComponent "{F32B667C3C823800}" {
ActionContexts { UserActionContext "{50888D37F529F720}" { ContextName "SCR_DoorUserAction" Radius 2 } }
additionalActions {
ERR_UnlockPrisonAction "{CA7918FEA177BFB8}" {
ParentContextList { "SCR_DoorUserAction" }
UIInfo SCR_ActionUIInfo "{F734C81E11AA6BEF}" { Name "Pick prison lock" }
Duration 4
}
}
}
}
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{C3FDC4DBCD58C2F5}Prefabs/ERR/PrisonDoor.et"
Configurations {
EntityTemplateResourceClass PC {
}
EntityTemplateResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,8 @@
GenericEntity {
components {
Hierarchy "{4F20A90EDBD625A4}" {}
MeshObject "{4F20A90EDBD625A1}" { Object "{0B96D139FEBCA006}Assets/Structures/Walls/Concrete/ConcreteWall_01_3m.xob" }
RigidBody "{4F20A90EDBD625A2}" { ModelGeometry 1 Static 1 }
RplComponent "{4F20A90EDBD625A3}" {}
}
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et"
Configurations {
EntityTemplateResourceClass PC {
}
EntityTemplateResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,55 @@
SCR_ChimeraCharacter : "{836E7E39AAC5888B}Prefabs/Characters/Factions/BLUFOR/US_Army/Character_US_Base.et" {
components {
ActionsManagerComponent "{520EA1D2F659CE02}" {
additionalActions {
ERR_LoadCasualtyAction "{E7016B8C499EC8A3}" {
ParentContextList { "Chest" "LowerTorso" }
UIInfo SCR_ActionUIInfo "{E7016B8C499EC8A4}" { Name "Load casualty into extraction helicopter" }
Duration 5
}
}
}
SCR_EditableCharacterComponent "{520EA1D2F659C85C}" {
m_UIInfo SCR_EditableEntityUIInfo "{5298E609432D192D}" {
Name "Prisoner"
}
}
SCR_CharacterInventoryStorageComponent "{520EA1D2DB118DAC}" {
components {
SCR_EquipmentStorageComponent "{56B49DA9722B635D}" {
InitialStorageSlots {
SCR_EquipmentStorageSlot BinocularSlot { Prefab "" }
SCR_EquipmentStorageSlot WristwatchSlot {
Prefab "{78ED4FEF62BBA728}Prefabs/Items/Equipment/Watches/Watch_SandY184A.et"
}
}
}
}
}
BaseLoadoutManagerComponent "{520EA1D2DB11821E}" {
Slots {
LoadoutSlotInfo Hat { Prefab "{F545C1BE7C99954A}Prefabs/Characters/HeadGear/Hat_Patrol_US_01/Hat_Patrol_US_01.et" }
LoadoutSlotInfo Jacket { Prefab "{3CCA7A9BB4FD3197}Prefabs/Characters/Uniforms/Jacket_US_BDU_rolledup.et" }
LoadoutSlotInfo Pants { Prefab "{604BB72BE8E023C2}Prefabs/Characters/Uniforms/Pants_US_BDU.et" }
SCR_FeetLoadoutSlotInfo Boots { Prefab "{DAAFD15478BDE1C3}Prefabs/Characters/Footwear/CombatBoots_US_01.et" }
LoadoutSlotInfo ArmoredVest { Prefab "" }
LoadoutSlotInfo Back { Prefab "" }
LoadoutSlotInfo Vest { Prefab "" }
LoadoutSlotInfo HandWear { Prefab "" }
}
}
SCR_InventoryStorageManagerComponent "{520EA1D2DB118DE5}" {
InitialInventoryItems {
ItemsInitConfigurationItem "{EA7AC3C2078B68C1}" {
TargetStorage "Prefabs/Characters/Uniforms/Pants_US_BDU.et"
PrefabsToSpawn {
"{A81F501D3EF6F38E}Prefabs/Items/Medicine/FieldDressing_01/FieldDressing_US_01.et"
"{A81F501D3EF6F38E}Prefabs/Items/Medicine/FieldDressing_01/FieldDressing_US_01.et"
"{D70216B1B2889129}Prefabs/Items/Medicine/Tourniquet_01/Tourniquet_US_01.et"
"{0D9A5DCF89AE7AA9}Prefabs/Items/Medicine/MorphineInjection_01/MorphineInjection_01.et"
}
}
}
}
}
}
@@ -0,0 +1,7 @@
MetaFileClass {
Name "{3FF693715C9F8928}Prefabs/ERR/Prisoner.et"
Configurations {
EntityTemplateResourceClass PC {}
EntityTemplateResourceClass HEADLESS : PC {}
}
}
@@ -0,0 +1,16 @@
GenericEntity {
components {
MeshObject "{111C06CB740ED195}" { Object "{77C9FCF3FB1246E9}Assets/Items/Equipment/Radios/Radio_R107M/Radio_R107M_01_item.xob" }
RigidBody "{4C174A44D07B91BA}" { ModelGeometry 1 Static 1 }
ERR_LocationComponent "{C19CA6CC83470879}" { m_Kind 1 }
RplComponent "{54ED7463161A5622}" { Streamable Disabled }
ActionsManagerComponent "{B081EC0768636EAB}" {
ActionContexts { UserActionContext "{C1A75A5D8B7EF1E4}" { ContextName "ERR_Radio" Radius 2 } }
additionalActions { ERR_TuneRadioAction "{7ABBB6B5D73DDD95}" {
ParentContextList { "ERR_Radio" }
UIInfo SCR_ActionUIInfo "{1B42BB7CEDECB994}" { Name "Contact extraction HQ" }
Duration 8
} }
}
}
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{F7E7E3D6ACF0C860}Prefabs/ERR/Radio.et"
Configurations {
EntityTemplateResourceClass PC {
}
EntityTemplateResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,7 @@
SCR_ManualCamera {
m_bInputEnabled 0
m_bStartAtEntity 1
m_aComponents {
ERR_FollowSurvivorCameraComponent "{965C2E8B2339B926}" {}
}
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{EB1210AAE1BB8F54}Prefabs/ERR/SpectatorCamera.et"
Configurations {
EntityTemplateResourceClass PC {
}
EntityTemplateResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,28 @@
GenericEntity : "{EBBC859CCD94D96D}Prefabs/Props/EmptyStorages/USSR_AmmoBox_empty.et" {
components {
MeshObject "{6530845A09309E37}" {
Object "{819ED3266EE5113B}Assets/Props/Military/AmmoBox/EquipmentBoxWooden_01/EquipmentBoxWooden_Weapon_01.xob"
Materials {
MaterialAssignClass "{6530845A093091BB}" {
SourceMaterial "EquipmentBoxWooden_Weapon_01"
AssignedMaterial "{7D2E831AFA5CF50D}Assets/Props/Military/AmmoBox/EquipmentBoxWooden_01/Data/EquipmentBoxWooden_Weapon_01_USSR.emat"
}
MaterialAssignClass "{6530845A093091B2}" {
SourceMaterial "EquipmentBoxWooden_Weapon_01_Decal"
}
}
}
SCR_UniversalInventoryStorageComponent "{6530845A09302DE0}" {
MaxCumulativeVolume 120000
MaxItemSize 120 40 40
m_fMaxWeight 80
Attributes SCR_ItemAttributeCollection "{6530845A09302DFF}" {
ItemDisplayName SCR_InventoryUIInfo "{6530845A09302D03}" {
Name "Supply cache"
Description "Limited equipment shared between prisoners."
}
}
}
InventoryStorageManagerComponent "{18A1D42A468F584F}" {}
}
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{40986810D0B1F4AE}Prefabs/ERR/SupplyCache.et"
Configurations {
ResourceClass PC {
}
ResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,6 @@
GenericEntity {
components {
ERR_LocationComponent "{8E7CFEA92C1811F0}" { m_Kind 4 }
RplComponent "{98A4A31435A9CA04}" { Streamable Disabled }
}
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{7C3D16C17C24D351}Prefabs/ERR/SupplySite.et"
Configurations {
ResourceClass PC {
}
ResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,39 @@
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);
}
override void PerformAction(IEntity pOwnerEntity, IEntity pUserEntity)
{
ERR_GameMode mode = ERR_GameMode.Get();
if (!mode || !mode.UnlockPrison(pUserEntity)) return;
BaseDoorComponent door = BaseDoorComponent.Cast(pOwnerEntity.FindComponent(BaseDoorComponent));
if (door) door.UseDoorAction(pUserEntity);
}
}
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);
}
}
class ERR_TuneRadioAction : ScriptedUserAction
{
override bool CanBePerformedScript(IEntity user)
{
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);
}
override void PerformAction(IEntity pOwnerEntity, IEntity pUserEntity)
{
ERR_GameMode mode = ERR_GameMode.Get();
if (mode) mode.TuneRadio(pUserEntity, ERR_LocationComponent.Cast(pOwnerEntity.FindComponent(ERR_LocationComponent)));
}
}
@@ -0,0 +1,81 @@
class ERR_Actors
{
static bool Alive(IEntity actor)
{
if (!actor) return false;
DamageManagerComponent damage = DamageManagerComponent.Cast(actor.FindComponent(DamageManagerComponent));
return damage && !damage.IsDestroyed();
}
static bool CanFight(IEntity actor)
{
if (!Alive(actor)) return false;
CharacterControllerComponent controller = CharacterControllerComponent.Cast(actor.FindComponent(CharacterControllerComponent));
return controller && !controller.IsUnconscious();
}
static SCR_AIGroup Group(IEntity actor)
{
if (!actor) return null;
AIControlComponent control = AIControlComponent.Cast(actor.FindComponent(AIControlComponent));
if (!control || !control.GetAIAgent()) return null;
return SCR_AIGroup.Cast(control.GetAIAgent().GetParentGroup());
}
static bool Player(IEntity actor)
{
return actor && GetGame().GetPlayerManager().GetPlayerIdFromControlledEntity(actor) > 0;
}
static IEntity Spawn(ResourceName prefab, vector position, vector angles = "0 0 0")
{
if (!Replication.IsServer() || prefab.IsEmpty()) return null;
Resource resource = Resource.Load(prefab);
if (!resource || !resource.IsValid()) return null;
EntitySpawnParams params = new EntitySpawnParams();
params.TransformMode = ETransformMode.WORLD;
Math3D.AnglesToMatrix(angles, params.Transform);
params.Transform[3] = position;
return GetGame().SpawnEntityPrefab(resource, GetGame().GetWorld(), params);
}
static void DeleteSpawned(IEntity entity)
{
if (!Replication.IsServer() || !entity || Player(entity)) return;
SCR_AIGroup group = SCR_AIGroup.Cast(entity);
if (group)
{
// Never remove a group that has acquired a player since mission creation.
if (group.GetPlayerCount(true) > 0) return;
group.DespawnMembers();
}
SCR_EntityHelper.DeleteEntityAndChildren(entity);
}
static vector Ground(vector position)
{
position[1] = GetGame().GetWorld().GetSurfaceY(position[0], position[2]) + 0.1;
return position;
}
static void Players(out array<IEntity> players, bool conscious = false)
{
players.Clear();
array<int> ids = {};
GetGame().GetPlayerManager().GetPlayers(ids);
foreach (int id : ids)
{
IEntity actor = GetGame().GetPlayerManager().GetPlayerControlledEntity(id);
if (Alive(actor) && (!conscious || CanFight(actor))) players.Insert(actor);
}
}
}
// One server-owned implementation is installed by the AI addon.
class ERR_CommandService
{
void Tick(float now) {}
void Stop() {}
void RegisterGroup(SCR_AIGroup group) {}
void UnregisterGroup(SCR_AIGroup group) {}
}
@@ -0,0 +1,111 @@
// Transport-independent administration. Only the owning controller supplies
// authorization, derived on the server from its authenticated player ID.
class ERR_CommandArguments
{
static string CoreSetting(string section, string option)
{
if (section == "mission" && option == "radio") return "radioDefenceSeconds";
if (section == "intel" && option == "reveal") return "intelRevealChancePercent";
return "";
}
static bool Integer(string text, out int value)
{
if (text.IsEmpty() || text.Length() > 6) return false;
for (int i = 0; i < text.Length(); i++)
if ("0123456789".IndexOf(text.Substring(i, 1)) < 0) return false;
value = text.ToInt();
return true;
}
}
class ERR_AdminService
{
protected string Setting(string section, string option)
{
return ERR_CommandArguments.CoreSetting(section, option);
}
protected bool Extension(ERR_GameMode mode, string arguments, array<string> replies) { return false; }
protected bool TimeCommand(ERR_GameMode mode, array<string> parts, array<string> replies)
{
if (parts.Count() < 2 || parts[0] != "mission" || (parts[1] != "time" && parts[1] != "speed")) return false;
string key = "startTimeMinutes";
if (parts[1] == "speed") key = "timeMultiplier";
if (parts.Count() == 3 && parts[2] == "show")
{
if (mode.Time()) replies.Insert(mode.Time().Status());
replies.Insert("Next round: " + ERR_TimeService.Clock(mode.Settings().Get("startTimeMinutes")) + ", " + mode.Settings().Get("timeMultiplier").ToString() + "x progression.");
return true;
}
int value;
bool valid;
if (parts.Count() == 4 && parts[2] == "set")
{
if (key == "startTimeMinutes") valid = ERR_TimeService.ParseClock(parts[3], value);
else valid = ERR_CommandArguments.Integer(parts[3], value);
}
if (valid && mode.Settings().Set(key, value))
replies.Insert("Saved for next round: " + ERR_TimeService.Clock(mode.Settings().Get("startTimeMinutes")) + ", " + mode.Settings().Get("timeMultiplier").ToString() + "x progression. Current round clock unchanged.");
else replies.Insert("Invalid time or settings could not be saved. Use mission time set HH:MM (00:00-23:59) or mission speed set N (1-24). Previous setting retained.");
return true;
}
protected string SettingHelp(string key)
{
if (key == "intelRevealChancePercent")
return "Use /esc intel reveal set N (0-100). This is the chance that a first document identifies its location exactly; otherwise it gives a rough location. A second document for that location always identifies it. Changes affect newly recovered documents immediately; known locations and spent documents remain unchanged.";
return "";
}
protected void SettingCommand(ERR_Settings settings, string key, array<string> parts, array<string> replies)
{
if (parts.Count() == 3 && parts[2] == "show") { replies.Insert(key + "=" + settings.Get(key).ToString()); return; }
string help = SettingHelp(key);
if (parts.Count() == 3 && parts[2] == "help" && !help.IsEmpty()) { replies.Insert(help); return; }
int value;
if (parts.Count() == 4 && parts[2] == "set" && ERR_CommandArguments.Integer(parts[3], value) && settings.Set(key, value))
replies.Insert(key + "=" + value.ToString() + " saved");
else
{
string failure = "Invalid value or settings could not be saved; previous setting retained.";
if (!help.IsEmpty()) failure += " " + help;
replies.Insert(failure);
}
}
protected string Help()
{
return "/esc status | settings | mission abandon | mission radio show|set N | mission date show|set YYYY-MM-DD|authored | mission weather show|set authored|clear|overcast|rain | mission time show|set HH:MM | mission speed show|set N | intel reveal show|set N|help | spectate next|previous";
}
void Execute(ERR_GameMode mode, bool authorized, string arguments, array<string> replies)
{
replies.Clear();
if (!Replication.IsServer() || !mode) return;
if (!authorized) { replies.Insert("Administrator access required."); return; }
if (arguments.Length() > 160) { replies.Insert("Command is too long."); return; }
arguments = arguments.Trim(); arguments.ToLower();
if (arguments == "status") { replies.Insert(mode.Status()); Extension(mode, arguments, replies); return; }
if (arguments == "mission abandon")
{
if (mode.AbandonRound()) replies.Insert("Round abandoned. The normal defeat and restart sequence has begun.");
else replies.Insert("Only an active round can be abandoned.");
return;
}
if (!mode.Settings()) { replies.Insert("Mission settings are not ready."); return; }
if (arguments == "settings")
{
foreach (string key : ERR_Settings.Keys()) replies.Insert(key + "=" + mode.Settings().Get(key).ToString());
return;
}
if (Extension(mode, arguments, replies)) return;
array<string> parts = {};
arguments.Split(" ", parts, true);
if (ERR_EnvironmentCommands.Execute(mode.Settings(), mode.Environment(), parts, replies)) return;
if (TimeCommand(mode, parts, replies)) return;
if (parts.Count() == 3 || parts.Count() == 4)
{
string key = Setting(parts[0], parts[1]);
if (!key.IsEmpty())
{
SettingCommand(mode.Settings(), key, parts, replies);
return;
}
}
replies.Insert(Help());
}
}
@@ -0,0 +1,82 @@
// Conservative fallback for a sleeping, engine-off Mi8 whose native simulation
// has not yet populated contact flags. Reads physical support; never wakes it.
class ERR_CasualtyGroundSupport
{
static bool SupportHit(float fraction, float normalY, bool selfHit, bool dynamicHit)
{
return fraction >= 0 && fraction < 1 && normalY >= 0.85 && !selfHit && !dynamicHit;
}
static bool BelongsTo(IEntity entity, IEntity root)
{
while (entity)
{
if (entity == root) return true;
entity = entity.GetParent();
}
return false;
}
static bool WheelCenter(Vehicle aircraft, string bone, out vector center, bool diagnostics = false)
{
if (!aircraft) return false;
if (aircraft.GetBoneIndex(bone) == -1)
{
if (diagnostics) Print("ERR GEAR PIVOT MISSING: " + bone);
return false;
}
vector identity[4]; Math3D.MatrixIdentity4(identity);
PointInfo pivot = new PointInfo();
pivot.Set(aircraft, bone, identity);
vector world[4]; pivot.GetWorldTransform(world);
center = world[3];
if (diagnostics) PrintFormat("ERR GEAR PIVOT: bone=%1 node=%2 center=%3", bone, pivot.GetNodeId(), center);
return pivot.GetNodeId() != -1;
}
static bool WheelSupported(Vehicle aircraft, vector center, float radius, bool diagnostics = false)
{
vector bottom = center - Vector(0, radius, 0);
TraceParam trace = new TraceParam();
trace.Start = bottom + "0 0.10 0";
trace.End = bottom - "0 0.12 0";
trace.Flags = TraceFlags.WORLD | TraceFlags.ENTS;
trace.LayerMask = TRACE_LAYER_CAMERA;
trace.Exclude = aircraft;
float fraction = aircraft.GetWorld().TraceMove(trace, null);
bool selfHit = trace.TraceEnt && BelongsTo(trace.TraceEnt, aircraft);
bool dynamicHit;
if (trace.TraceEnt)
{
Physics surface = trace.TraceEnt.GetPhysics();
// Unknown/entity-only support and moving actors/vehicles are not parking.
dynamicHit = !surface || surface.IsDynamic();
}
bool supported = SupportHit(fraction, trace.TraceNorm[1], selfHit, dynamicHit);
if (diagnostics)
{
vector contact = vector.Lerp(trace.Start, trace.End, fraction);
float terrainY = aircraft.GetWorld().GetSurfaceY(bottom[0], bottom[2]);
PrintFormat("ERR GEAR TERRAIN: bottom=%1 terrainY=%2 tireTerrainGap=%3", bottom, terrainY, bottom[1] - terrainY);
PrintFormat("ERR GEAR SUPPORT: center=%1 radius=%2 bottom=%3 fraction=%4 normal=%5 self=%6 dynamic=%7 supported=%8 gap=%9", center, radius, bottom, fraction, trace.TraceNorm, selfHit, dynamicHit, supported, bottom[1] - contact[1]);
}
return supported;
}
static bool Parked(Vehicle aircraft, bool diagnostics = false)
{
if (!aircraft || !aircraft.GetPrefabData() || !aircraft.GetPrefabData().GetPrefabName().Contains("/Mi8MT/")) return false;
Physics physics = aircraft.GetPhysics();
HelicopterControllerComponent controller = HelicopterControllerComponent.Cast(aircraft.FindComponent(HelicopterControllerComponent));
if (!physics || !controller || !controller.GetSimulation()) return false;
if (diagnostics) PrintFormat("ERR PARKED SUPPORT: physicsAwake=%1 engineOn=%2 nativeContact=%3", physics.IsActive(), controller.GetSimulation().EngineIsOn(), controller.GetSimulation().HasAnyGroundContact());
if (physics.IsActive() || controller.GetSimulation().EngineIsOn()) return false;
vector front, left, right;
if (!WheelCenter(aircraft, "v_wheel_F", front, diagnostics) || !WheelCenter(aircraft, "v_wheel_bL", left, diagnostics) || !WheelCenter(aircraft, "v_wheel_bR", right, diagnostics)) return false;
// Fail closed if native pivot resolution collapses to an owner origin or
// otherwise cannot represent the authored three-point landing footprint.
if (vector.DistanceXZ(front, left) < 1 || vector.DistanceXZ(front, right) < 1 || vector.DistanceXZ(left, right) < 1) return false;
// Radii come from installed Mi8_base.et's authored landing-gear configuration.
// Every wheel needs a short, upward-facing static collision directly below.
bool a = WheelSupported(aircraft, front, 0.30, diagnostics);
bool b = WheelSupported(aircraft, left, 0.45, diagnostics);
bool c = WheelSupported(aircraft, right, 0.45, diagnostics);
return a && b && c;
}
}
@@ -0,0 +1,48 @@
// Round-owned server service; only the aircraft reference is replicated for action visibility.
modded class ERR_GameMode
{
protected ref ERR_CasualtyLoadingService m_ERRCasualtyLoading;
[RplProp()]
protected RplId m_ERRCasualtyAircraftId = RplId.Invalid();
ERR_CasualtyLoadingService CasualtyLoading() { return m_ERRCasualtyLoading; }
Vehicle CasualtyLoadingAircraft()
{
if (Replication.IsServer()) return ExtractionVehicle();
RplComponent component = RplComponent.Cast(Replication.FindItem(m_ERRCasualtyAircraftId));
if (!component) return null;
return Vehicle.Cast(component.GetEntity());
}
protected void ERR_UpdateCasualtyAircraft(float now)
{
Vehicle aircraft = ExtractionVehicle();
if (!aircraft)
{
if (m_ERRCasualtyAircraftId != RplId.Invalid()) { m_ERRCasualtyAircraftId = RplId.Invalid(); Replication.BumpMe(); }
return;
}
RplComponent component = RplComponent.Cast(aircraft.FindComponent(RplComponent));
if (!component || m_ERRCasualtyAircraftId == component.Id()) return;
m_ERRCasualtyAircraftId = component.Id();
Replication.BumpMe();
}
protected void ERR_StopCasualtyLoading()
{
if (m_ERRCasualtyLoading)
{
Players().OnChanged().Remove(m_ERRCasualtyLoading.RosterChanged);
OnTick().Remove(m_ERRCasualtyLoading.Tick);
m_ERRCasualtyLoading.Stop();
}
OnTick().Remove(ERR_UpdateCasualtyAircraft);
}
protected override void EOnInit(IEntity owner)
{
super.EOnInit(owner);
if (!Replication.IsServer() || SCR_Global.IsEditMode()) return;
m_ERRCasualtyLoading = new ERR_CasualtyLoadingService(this);
OnTick().Insert(ERR_UpdateCasualtyAircraft);
OnTick().Insert(m_ERRCasualtyLoading.Tick);
Players().OnChanged().Insert(m_ERRCasualtyLoading.RosterChanged);
OnStop().Insert(ERR_StopCasualtyLoading);
}
}
@@ -0,0 +1,75 @@
// Only successor service methods initiate this controller-owned handshake.
// No caller-supplied entity/seat RPC can ask the server to load another player.
modded class SCR_PlayerController
{
protected RplId m_ERRPreparedLoadMode = RplId.Invalid();
protected int m_ERRPreparedLoadNonce;
protected int m_ERRPreparedLoadClock;
protected int m_ERRLastLoadNonce;
void ERR_PrepareCasualtyLoad(RplId modeId, int nonce)
{
if (!Replication.IsServer()) return;
if (this == GetGame().GetPlayerController()) ERR_PrepareCasualtyLoadOwner(modeId, nonce);
else Rpc(ERR_PrepareCasualtyLoadOwner, modeId, nonce);
}
[RplRpc(RplChannel.Reliable, RplRcver.Owner)]
protected void ERR_PrepareCasualtyLoadOwner(RplId modeId, int nonce)
{
ERR_GameMode mode = ERR_GameMode.Get();
if (this != GetGame().GetPlayerController() || !mode || !mode.Active() || mode.Phase() != ERR_Phase.EXTRACTION || ERR_CasualtyLoadingRules.EntityId(mode) != modeId || !ERR_CasualtyLoadingRules.GroundSettled(GetControlledEntity())) return;
if (m_ERRPreparedLoadMode == modeId && nonce <= m_ERRLastLoadNonce) return;
if (nonce <= 0) return;
m_ERRPreparedLoadMode = modeId;
m_ERRPreparedLoadNonce = nonce;
m_ERRLastLoadNonce = nonce;
m_ERRPreparedLoadClock = System.GetTickCount();
if (Replication.IsServer()) ERR_CasualtyLoadReady(modeId, nonce);
else Rpc(ERR_CasualtyLoadReady, modeId, nonce);
}
[RplRpc(RplChannel.Reliable, RplRcver.Server)]
protected void ERR_CasualtyLoadReady(RplId modeId, int nonce)
{
ERR_GameMode mode = ERR_GameMode.Get();
if (!Replication.IsServer() || !mode || ERR_CasualtyLoadingRules.EntityId(mode) != modeId || !mode.CasualtyLoading()) return;
mode.CasualtyLoading().OwnerReady(this, nonce);
}
void ERR_CommitCasualtyLoad(RplId modeId, int nonce, RplId callerId, RplId actorId, RplId vehicleId, RplId ownerId, int slotId)
{
if (!Replication.IsServer()) return;
if (this == GetGame().GetPlayerController()) ERR_CommitCasualtyLoadOwner(modeId, nonce, callerId, actorId, vehicleId, ownerId, slotId);
else Rpc(ERR_CommitCasualtyLoadOwner, modeId, nonce, callerId, actorId, vehicleId, ownerId, slotId);
}
[RplRpc(RplChannel.Reliable, RplRcver.Owner)]
protected void ERR_CommitCasualtyLoadOwner(RplId modeId, int nonce, RplId callerId, RplId actorId, RplId vehicleId, RplId ownerId, int slotId)
{
bool lease = this == GetGame().GetPlayerController() && m_ERRPreparedLoadMode == modeId && m_ERRPreparedLoadNonce == nonce && ERR_CasualtyLoadingRules.Within(m_ERRPreparedLoadClock, System.GetTickCount(), 3000);
if (m_ERRPreparedLoadMode == modeId && m_ERRPreparedLoadNonce == nonce) m_ERRPreparedLoadNonce = 0;
// Consume the local lease before the native call so duplicates cannot act.
bool accepted;
ERR_GameMode mode = ERR_GameMode.Get();
IEntity actor = GetControlledEntity();
IEntity caller = ERR_CasualtyLoadingRules.Entity(callerId);
Vehicle vehicle = Vehicle.Cast(ERR_CasualtyLoadingRules.Entity(vehicleId));
IEntity owner = ERR_CasualtyLoadingRules.Entity(ownerId);
if (lease && mode && ERR_CasualtyLoadingRules.EntityId(mode) == modeId && ERR_CasualtyLoadingRules.EntityId(actor) == actorId && vehicle == mode.CasualtyLoadingAircraft() && ERR_CasualtyLoadingRules.Nearby(mode, caller, actor) && owner)
{
BaseCompartmentManagerComponent manager = BaseCompartmentManagerComponent.Cast(owner.FindComponent(BaseCompartmentManagerComponent));
SCR_CompartmentAccessComponent access = SCR_CompartmentAccessComponent.Cast(actor.FindComponent(SCR_CompartmentAccessComponent));
BaseCompartmentSlot seat;
if (manager) seat = manager.FindCompartment(slotId);
if (access && ERR_CasualtyLoadingRules.TransportBench(seat, vehicle) && seat.GetOwner() == owner && !seat.GetOccupant() && seat.IsCompartmentAccessible() && !seat.IsGetInLockedFor(actor) && (!seat.IsReserved() || seat.IsReservedBy(actor)))
accepted = access.GetInVehicle(owner, seat, true, -1, ECloseDoorAfterActions.INVALID, false);
}
// Missing replicated entities or rejected native entry fail once; never retry.
if (Replication.IsServer()) ERR_CasualtyLoadResult(modeId, nonce, accepted);
else Rpc(ERR_CasualtyLoadResult, modeId, nonce, accepted);
}
[RplRpc(RplChannel.Reliable, RplRcver.Server)]
protected void ERR_CasualtyLoadResult(RplId modeId, int nonce, bool accepted)
{
ERR_GameMode mode = ERR_GameMode.Get();
if (!Replication.IsServer() || !mode || ERR_CasualtyLoadingRules.EntityId(mode) != modeId || !mode.CasualtyLoading()) return;
mode.CasualtyLoading().OwnerResult(this, nonce, accepted);
}
}
@@ -0,0 +1,305 @@
// First release supports only the registered Mi8 extraction aircraft's physical
// child transport benches. Nothing changes health, ownership, or seat classes.
class ERR_CasualtyLoadingRules
{
static int RoundAction(int phase)
{
if (phase == ERR_Phase.PREPARING) return 0;
if (ERR_MissionRules.Terminal(phase)) return 2;
return 1;
}
static bool ObserveTicket(bool committed, bool physical, bool authorized)
{
return physical && (committed || authorized);
}
static bool Within(int started, int now, int limit) { return now >= started && now - started < limit; }
static bool SettledFor(int started, int observed, int now)
{
return started >= 0 && now >= started && now - started >= 5000 && Within(observed, now, 2000);
}
static bool Ranges(float callerToCasualty, float callerToAircraft, float casualtyToAircraft)
{
return callerToCasualty >= 0 && callerToCasualty <= 3 && callerToAircraft >= 0 && callerToAircraft <= 12 && casualtyToAircraft >= 0 && casualtyToAircraft <= 12;
}
static bool AircraftMotion(bool groundContact, float up, float speed, float angularSpeed)
{
return groundContact && up >= 0.85 && speed >= 0 && speed <= 0.5 && angularSpeed >= 0 && angularSpeed <= 0.1;
}
static RplId EntityId(IEntity entity)
{
if (!entity) return RplId.Invalid();
RplComponent component = RplComponent.Cast(entity.FindComponent(RplComponent));
if (!component) return RplId.Invalid();
return component.Id();
}
static IEntity Entity(RplId id)
{
if (id == RplId.Invalid()) return null;
RplComponent component = RplComponent.Cast(Replication.FindItem(id));
if (!component) return null;
return component.GetEntity();
}
static bool Down(IEntity actor)
{
if (!ERR_Actors.Alive(actor)) return false;
CharacterControllerComponent controller = CharacterControllerComponent.Cast(actor.FindComponent(CharacterControllerComponent));
return controller && controller.GetLifeState() == ECharacterLifeState.INCAPACITATED;
}
static bool GroundSettled(IEntity actor)
{
if (!Down(actor) || CompartmentAccessComponent.GetVehicleIn(actor)) return false;
CharacterAnimationComponent animation = CharacterAnimationComponent.Cast(actor.FindComponent(CharacterAnimationComponent));
SCR_CompartmentAccessComponent access = SCR_CompartmentAccessComponent.Cast(actor.FindComponent(SCR_CompartmentAccessComponent));
Physics physics = actor.GetPhysics();
return animation && !animation.PhysicsIsFalling() && physics && physics.GetVelocity().Length() <= 0.5 && access && !access.IsGettingIn() && !access.IsGettingOut();
}
static bool AircraftReady(Vehicle aircraft)
{
if (!ERR_Actors.Alive(aircraft)) return false;
HelicopterControllerComponent controller = HelicopterControllerComponent.Cast(aircraft.FindComponent(HelicopterControllerComponent));
Physics physics = aircraft.GetPhysics();
if (!controller || !controller.GetSimulation() || !physics) return false;
vector transform[4]; aircraft.GetWorldTransform(transform);
if (!AircraftMotion(true, transform[1][1], physics.GetVelocity().Length(), physics.GetAngularVelocity().Length())) return false;
if (controller.GetSimulation().HasAnyGroundContact()) return true;
return ERR_CasualtyGroundSupport.Parked(aircraft);
}
static bool LineOfSight(IEntity caller, IEntity casualty)
{
ChimeraCharacter rescuer = ChimeraCharacter.Cast(caller);
ChimeraCharacter patient = ChimeraCharacter.Cast(casualty);
if (!rescuer || !patient || !GetGame()) return false;
TraceParam trace = new TraceParam();
trace.Start = rescuer.AimingPosition();
trace.End = patient.AimingPosition();
trace.Flags = TraceFlags.WORLD | TraceFlags.ENTS;
trace.LayerMask = TRACE_LAYER_CAMERA;
trace.Exclude = caller;
if (GetGame().GetWorld().TraceMove(trace, null) >= 1) return true;
// Hitting the intended body's clothing/attachment is still sight of that body.
IEntity hit = trace.TraceEnt;
while (hit)
{
if (hit == casualty) return true;
hit = hit.GetParent();
}
return false;
}
static bool Nearby(ERR_GameMode mode, IEntity caller, IEntity casualty)
{
if (!mode || !mode.Active() || mode.Phase() != ERR_Phase.EXTRACTION || !caller || caller == casualty) return false;
CharacterControllerComponent rescuer = CharacterControllerComponent.Cast(caller.FindComponent(CharacterControllerComponent));
if (!ERR_Actors.CanFight(caller) || !rescuer || rescuer.GetLifeState() != ECharacterLifeState.ALIVE || !GroundSettled(casualty) || CompartmentAccessComponent.GetVehicleIn(caller)) return false;
Vehicle aircraft = mode.CasualtyLoadingAircraft();
if (!aircraft || !Ranges(vector.Distance(caller.GetOrigin(), casualty.GetOrigin()), vector.Distance(caller.GetOrigin(), aircraft.GetOrigin()), vector.Distance(casualty.GetOrigin(), aircraft.GetOrigin())) || !AircraftReady(aircraft)) return false;
Faction faction = mode.PlayerFaction();
return faction && SCR_FactionManager.SGetFaction(caller) == faction && SCR_FactionManager.SGetFaction(casualty) == faction && LineOfSight(caller, casualty);
}
static bool TransportBench(BaseCompartmentSlot seat, Vehicle aircraft)
{
if (!seat || !aircraft || seat.GetType() != ECompartmentType.CARGO || SCR_PatientCompartmentSlot.Cast(seat)) return false;
IEntity owner = seat.GetOwner();
if (!owner || owner == aircraft || !owner.GetPrefabData() || !owner.GetPrefabData().GetPrefabName().Contains("VehPart_MI8_seats")) return false;
while (owner)
{
if (owner == aircraft) return true;
owner = owner.GetParent();
}
return false;
}
}
class ERR_CasualtySettling
{
IEntity Actor;
UUID Account;
int Connection;
int Since;
int Observed;
}
class ERR_CasualtyLoadTicket
{
IEntity Caller;
IEntity Casualty;
Vehicle Aircraft;
BaseCompartmentSlot Seat;
UUID CallerAccount;
UUID CasualtyAccount;
int CallerConnection;
int CasualtyConnection;
int Nonce;
int Started;
int CommittedAt;
bool Committed;
bool OwnerReported;
}
class ERR_CasualtyLoadingService
{
protected ERR_GameMode m_Mode;
protected bool m_Stopped;
protected int m_NextNonce;
protected ref array<ref ERR_CasualtySettling> m_Settling = {};
protected ref array<ref ERR_CasualtyLoadTicket> m_Pending = {};
protected ref ScriptInvoker m_OnResolved = new ScriptInvoker();
void ERR_CasualtyLoadingService(ERR_GameMode mode) { m_Mode = mode; }
ScriptInvoker OnResolved() { return m_OnResolved; } // (IEntity casualty, bool physicallyLoaded)
int PendingCount() { return m_Pending.Count(); }
protected ERR_PlayerLife CurrentLife(IEntity actor)
{
if (!m_Mode || !m_Mode.Players()) return null;
return m_Mode.Players().CurrentActor(actor);
}
protected bool SameLife(IEntity actor, UUID account, int connection)
{
ERR_PlayerLife life = CurrentLife(actor);
return life && life.AccountId == account && life.ConnectionId == connection;
}
protected bool Settled(IEntity actor, int now)
{
foreach (ERR_CasualtySettling sample : m_Settling)
if (sample.Actor == actor && SameLife(actor, sample.Account, sample.Connection)) return ERR_CasualtyLoadingRules.GroundSettled(actor) && ERR_CasualtyLoadingRules.SettledFor(sample.Since, sample.Observed, now);
return false;
}
protected void Observe(int now)
{
for (int index = m_Settling.Count() - 1; index >= 0; index--)
{
ERR_CasualtySettling sample = m_Settling[index];
if (!SameLife(sample.Actor, sample.Account, sample.Connection) || !ERR_CasualtyLoadingRules.GroundSettled(sample.Actor) || !ERR_CasualtyLoadingRules.Within(sample.Observed, now, 2000)) m_Settling.Remove(index);
else sample.Observed = now;
}
array<ref ERR_PlayerLife> lives = {}; m_Mode.Players().Participants(lives);
foreach (ERR_PlayerLife life : lives)
{
if (!CurrentLife(life.Actor) || !ERR_CasualtyLoadingRules.GroundSettled(life.Actor)) continue;
bool found;
foreach (ERR_CasualtySettling tracked : m_Settling) if (tracked.Actor == life.Actor) { found = true; break; }
if (found) continue;
ERR_CasualtySettling added = new ERR_CasualtySettling();
added.Actor = life.Actor; added.Account = life.AccountId; added.Connection = life.ConnectionId; added.Since = now; added.Observed = now;
m_Settling.Insert(added);
}
}
protected bool PendingActor(IEntity caller, IEntity casualty)
{
foreach (ERR_CasualtyLoadTicket pending : m_Pending)
if (pending.Caller == caller || pending.Casualty == casualty) return true;
return false;
}
protected BaseCompartmentSlot AvailableSeat(IEntity casualty, Vehicle aircraft)
{
array<BaseCompartmentSlot> seats = {}; m_Mode.Extraction().Seats(seats);
foreach (BaseCompartmentSlot seat : seats)
{
if (!ERR_CasualtyLoadingRules.TransportBench(seat, aircraft) || seat.GetOccupant() || seat.IsReserved() || !seat.IsCompartmentAccessible() || seat.IsGetInLockedFor(casualty)) continue;
bool pendingSeat;
foreach (ERR_CasualtyLoadTicket pending : m_Pending) if (pending.Seat == seat) { pendingSeat = true; break; }
if (!pendingSeat) return seat;
}
return null;
}
bool CanLoad(IEntity caller, IEntity casualty)
{
if (!Replication.IsServer() || m_Stopped || !ERR_CasualtyLoadingRules.Nearby(m_Mode, caller, casualty)) return false;
if (!CurrentLife(caller) || !CurrentLife(casualty) || !Settled(casualty, System.GetTickCount()) || PendingActor(caller, casualty)) return false;
return AvailableSeat(casualty, m_Mode.ExtractionVehicle()) != null;
}
bool Begin(IEntity caller, IEntity casualty)
{
if (!CanLoad(caller, casualty) || m_NextNonce >= 2147483646) return false;
ERR_PlayerLife callerLife = CurrentLife(caller);
ERR_PlayerLife casualtyLife = CurrentLife(casualty);
SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerManager().GetPlayerController(casualtyLife.ConnectionId));
if (!controller || ERR_CasualtyLoadingRules.EntityId(m_Mode) == RplId.Invalid() || ERR_CasualtyLoadingRules.EntityId(caller) == RplId.Invalid() || ERR_CasualtyLoadingRules.EntityId(casualty) == RplId.Invalid() || ERR_CasualtyLoadingRules.EntityId(m_Mode.ExtractionVehicle()) == RplId.Invalid()) return false;
ERR_CasualtyLoadTicket ticket = new ERR_CasualtyLoadTicket();
ticket.Caller = caller; ticket.Casualty = casualty; ticket.Aircraft = m_Mode.ExtractionVehicle();
ticket.Seat = AvailableSeat(casualty, ticket.Aircraft);
if (!ticket.Seat || ERR_CasualtyLoadingRules.EntityId(ticket.Seat.GetOwner()) == RplId.Invalid()) return false;
ticket.CallerAccount = callerLife.AccountId; ticket.CasualtyAccount = casualtyLife.AccountId;
ticket.CallerConnection = callerLife.ConnectionId; ticket.CasualtyConnection = casualtyLife.ConnectionId;
ticket.Started = System.GetTickCount(); ticket.Nonce = ++m_NextNonce;
m_Pending.Insert(ticket);
ticket.Seat.SetReserved(casualty);
if (!ticket.Seat.IsReservedBy(casualty)) { Resolve(ticket, false); return false; }
// Preparation never moves the actor. Its owner starts a local real-time lease.
controller.ERR_PrepareCasualtyLoad(ERR_CasualtyLoadingRules.EntityId(m_Mode), ticket.Nonce);
return true;
}
protected ERR_CasualtyLoadTicket Ticket(SCR_PlayerController controller, int nonce)
{
if (!controller || !GetGame() || !GetGame().GetPlayerManager() || GetGame().GetPlayerManager().GetPlayerController(controller.GetPlayerId()) != controller) return null;
foreach (ERR_CasualtyLoadTicket ticket : m_Pending)
if (ticket.Nonce == nonce && ticket.CasualtyConnection == controller.GetPlayerId() && ticket.Casualty == controller.GetControlledEntity()) return ticket;
return null;
}
void OwnerReady(SCR_PlayerController controller, int nonce)
{
if (!Replication.IsServer() || m_Stopped) return;
ERR_CasualtyLoadTicket ticket = Ticket(controller, nonce);
if (!ticket || ticket.Committed) return;
int now = System.GetTickCount();
if (!ValidTicket(ticket) || !ERR_CasualtyLoadingRules.Within(ticket.Started, now, 5000) || !Settled(ticket.Casualty, now) || !ERR_CasualtyLoadingRules.Nearby(m_Mode, ticket.Caller, ticket.Casualty) || ticket.Seat.GetOccupant() || !ticket.Seat.IsCompartmentAccessible() || ticket.Seat.IsGetInLockedFor(ticket.Casualty) || !ticket.Seat.IsReservedBy(ticket.Casualty)) { Resolve(ticket, false); return; }
ticket.Committed = true; ticket.CommittedAt = now;
// Final authority/physical validation is the commit boundary. No retries.
controller.ERR_CommitCasualtyLoad(ERR_CasualtyLoadingRules.EntityId(m_Mode), nonce, ERR_CasualtyLoadingRules.EntityId(ticket.Caller), ERR_CasualtyLoadingRules.EntityId(ticket.Casualty), ERR_CasualtyLoadingRules.EntityId(ticket.Aircraft), ERR_CasualtyLoadingRules.EntityId(ticket.Seat.GetOwner()), ticket.Seat.GetCompartmentSlotID());
}
void OwnerResult(SCR_PlayerController controller, int nonce, bool accepted)
{
if (!Replication.IsServer() || m_Stopped) return;
ERR_CasualtyLoadTicket ticket = Ticket(controller, nonce);
if (!ticket || !ticket.Committed || ticket.OwnerReported) return;
ticket.OwnerReported = true;
if (!accepted) Print("Rereintroduced [casualty]: owner declined loading; observing physical outcome until expiry.");
// Neither positive nor negative client acknowledgements release a reserved
// seat while an authorized owner command could still be in flight.
}
protected bool PhysicalTicket(ERR_CasualtyLoadTicket ticket)
{
// Death cannot revoke an in-flight commit. Keep the vacant seat reserved
// while the captured body exists; its owner may still see the prior state.
return m_Mode && m_Mode.Active() && m_Mode.Phase() == ERR_Phase.EXTRACTION && ticket.Aircraft == m_Mode.ExtractionVehicle() && ERR_Actors.Alive(ticket.Aircraft) && ticket.Casualty != null && ERR_CasualtyLoadingRules.TransportBench(ticket.Seat, ticket.Aircraft);
}
protected bool ValidTicket(ERR_CasualtyLoadTicket ticket)
{
return m_Mode && m_Mode.Active() && m_Mode.Phase() == ERR_Phase.EXTRACTION && ticket.Aircraft == m_Mode.ExtractionVehicle() && ERR_Actors.Alive(ticket.Aircraft) && ERR_Actors.Alive(ticket.Casualty) && SameLife(ticket.Caller, ticket.CallerAccount, ticket.CallerConnection) && SameLife(ticket.Casualty, ticket.CasualtyAccount, ticket.CasualtyConnection) && ERR_CasualtyLoadingRules.TransportBench(ticket.Seat, ticket.Aircraft);
}
protected void Resolve(ERR_CasualtyLoadTicket ticket, bool success)
{
int index = m_Pending.Find(ticket); if (index < 0) return;
m_Pending.Remove(index);
if (ticket.Seat && ticket.Casualty && ticket.Seat.IsReservedBy(ticket.Casualty)) ticket.Seat.SetReserved(null);
m_OnResolved.Invoke(ticket.Casualty, success);
}
void Tick(float worldNow)
{
if (!Replication.IsServer() || m_Stopped || !m_Mode) return;
int roundAction = ERR_CasualtyLoadingRules.RoundAction(m_Mode.Phase());
if (roundAction == 0) return;
if (roundAction == 2) { Stop(); return; }
int now = System.GetTickCount();
Observe(now);
array<ref ERR_CasualtyLoadTicket> snapshot = {}; foreach (ERR_CasualtyLoadTicket item : m_Pending) snapshot.Insert(item);
foreach (ERR_CasualtyLoadTicket ticket : snapshot)
{
if (m_Stopped) return;
// Commit authorizes one move of the captured body; a later caller/target
// disconnect cannot revoke that command or release its seat prematurely.
if (!ERR_CasualtyLoadingRules.ObserveTicket(ticket.Committed, PhysicalTicket(ticket), ValidTicket(ticket))) { Resolve(ticket, false); continue; }
bool aboard = CompartmentAccessComponent.GetVehicleIn(ticket.Casualty) == ticket.Aircraft && ticket.Seat.GetOccupant() == ticket.Casualty;
if (aboard && ticket.Committed) { Resolve(ticket, ERR_Actors.Alive(ticket.Casualty)); continue; }
if (ticket.Committed)
{
// Owner's prepare-to-commit lease is only 3s; this 8s observation budget
// leaves room for replication. Timeouts do not issue compensating teleports.
if (!ERR_CasualtyLoadingRules.Within(ticket.CommittedAt, now, 8000)) Resolve(ticket, false);
}
else if (!ERR_CasualtyLoadingRules.Within(ticket.Started, now, 5000) || !ERR_CasualtyLoadingRules.Nearby(m_Mode, ticket.Caller, ticket.Casualty)) Resolve(ticket, false);
}
}
void RosterChanged(int connection) { if (GetGame()) Tick(GetGame().GetWorld().GetWorldTime()); }
void Stop()
{
if (!Replication.IsServer() || m_Stopped) return;
m_Stopped = true; m_Settling.Clear();
while (!m_Pending.IsEmpty()) Resolve(m_Pending[m_Pending.Count() - 1], false);
}
}
@@ -0,0 +1,51 @@
// Read replicated discovery locally. A server-wide marker list never leaks
// locations which have not been recovered through gameplay.
modded class ERR_LocationComponent
{
protected ref SCR_MapMarkerBase m_ERRMarker;
protected int m_ERRShown;
protected string m_ERRShownLabel;
protected override void EOnInit(IEntity owner)
{
super.EOnInit(owner);
if (RplSession.Mode() != RplMode.Dedicated && !SCR_Global.IsEditMode())
GetGame().GetCallqueue().CallLater(ERR_UpdateMarker, 1500, true);
}
protected void ERR_UpdateMarker()
{
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)
{
if (!Label().IsEmpty()) label = Label();
else if (Kind() == ERR_LocationKind.RADIO) label = "Radio station";
else if (Kind() == ERR_LocationKind.EXTRACTION) label = "Initial helicopter pickup site";
else if (Kind() == ERR_LocationKind.DEPOT) label = "Supply cache";
else if (Kind() == ERR_LocationKind.PRISON) label = "Prison";
else label = Id();
}
m_ERRMarker.SetCustomText(label);
m_ERRShown = Knowledge();
m_ERRShownLabel = Label();
}
protected override void OnDelete(IEntity owner)
{
if (GetGame()) GetGame().GetCallqueue().Remove(ERR_UpdateMarker);
SCR_MapMarkerManagerComponent manager = SCR_MapMarkerManagerComponent.GetInstance();
if (manager && m_ERRMarker) manager.RemoveStaticMarker(m_ERRMarker);
m_ERRMarker = null;
super.OnDelete(owner);
}
}
@@ -0,0 +1,62 @@
// Base mission discovery: recovering physical documents works without companions.
// The Intel addon optionally assigns extra documents to enemy inventories.
class ERR_DiscoveryService
{
protected ERR_GameMode m_Mode;
protected ref set<IEntity> m_Spent = new set<IEntity>();
void ERR_DiscoveryService(ERR_GameMode mode) { m_Mode = mode; }
static void Folders(InventoryStorageManagerComponent inventory, out array<IEntity> folders)
{
folders.Clear();
SCR_PrefabNamePredicate predicate = new SCR_PrefabNamePredicate();
predicate.prefabName = ERR_Resources.IntelFolder();
array<IEntity> intel = {};
inventory.FindItems(intel, predicate, EStoragePurpose.PURPOSE_ANY);
foreach (IEntity folder : intel)
if (folder && !folders.Contains(folder)) folders.Insert(folder);
array<IEntity> objectives = {};
predicate.prefabName = ERR_Resources.ObjectiveDocument();
inventory.FindItems(objectives, predicate, EStoragePurpose.PURPOSE_ANY);
foreach (IEntity objective : objectives)
if (objective && !folders.Contains(objective)) folders.Insert(objective);
}
void Tick(float now)
{
if (!Replication.IsServer()) return;
array<IEntity> players = {};
ERR_Actors.Players(players);
foreach (IEntity player : players) Recover(player);
}
protected void Recover(IEntity player)
{
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(player.FindComponent(InventoryStorageManagerComponent));
if (!inventory) return;
array<IEntity> folders = {};
Folders(inventory, folders);
foreach (IEntity folder : folders)
{
if (!folder || m_Spent.Contains(folder) || m_Spent.Count() >= 4096) continue;
array<ERR_LocationComponent> locations = {};
m_Mode.Locations(locations);
array<ERR_LocationComponent> eligible = {};
ERR_DocumentComponent document = ERR_DocumentComponent.Cast(folder.FindComponent(ERR_DocumentComponent));
foreach (ERR_LocationComponent location : locations)
{
if (!location || location.Knowledge() >= 2) continue;
if (document)
{
if (location.Id() == document.TargetId()) eligible.Insert(location);
}
else if (location.Kind() != ERR_LocationKind.PRISON && location.Kind() != ERR_LocationKind.EXTRACTION) eligible.Insert(location);
}
if (eligible.IsEmpty()) continue;
ERR_LocationComponent chosen = eligible.GetRandomElement();
if (!chosen.Discover(Math.RandomInt(0, 100), m_Mode.Settings().Get("intelRevealChancePercent"))) continue;
m_Spent.Insert(folder);
PrintFormat("Rereintroduced [intel]: player %1 recovered folder; location=%2 knowledge=%3", GetGame().GetPlayerManager().GetPlayerIdFromControlledEntity(player), chosen.Id(), chosen.Knowledge());
}
}
void Stop() { m_Spent.Clear(); }
}
@@ -0,0 +1,8 @@
[ComponentEditorProps(category: "Rereintroduced", description: "Physical document identifying one authored objective")]
class ERR_DocumentComponentClass : ScriptComponentClass {}
class ERR_DocumentComponent : ScriptComponent
{
protected string m_TargetId;
void SetTarget(string id) { if (Replication.IsServer()) m_TargetId = id; }
string TargetId() { return m_TargetId; }
}
@@ -0,0 +1,164 @@
// The server applies optional date/weather choices once at round preparation.
// Zero values preserve the world's authored environment and existing defaults.
class ERR_EnvironmentService
{
protected TimeAndWeatherManagerEntity m_Manager;
protected bool m_Started;
protected bool m_Stopped;
protected int m_DateCode;
protected int m_Profile;
static string Pad2(int value)
{
if (value < 10) return "0" + value.ToString();
return value.ToString();
}
static string Date(int code)
{
if (code == 0) return "authored";
if (!ERR_Settings.Valid("startDateCode", code)) return "unavailable";
int year = 1900 + code / 10000;
int month = (code % 10000) / 100;
int day = code % 100;
return year.ToString() + "-" + Pad2(month) + "-" + Pad2(day);
}
static bool ParseDate(string text, out int code)
{
if (text == "authored") { code = 0; return true; }
if (text.Length() != 10 || text.Substring(4, 1) != "-" || text.Substring(7, 1) != "-") return false;
int year, month, day;
if (!ERR_CommandArguments.Integer(text.Substring(0, 4), year) || !ERR_CommandArguments.Integer(text.Substring(5, 2), month) || !ERR_CommandArguments.Integer(text.Substring(8, 2), day)) return false;
if (year < 1900 || year > 2050 || month < 1 || month > 12 || day < 1 || day > 31) return false;
int parsed = (year - 1900) * 10000 + month * 100 + day;
if (!ERR_Settings.Valid("startDateCode", parsed)) return false;
code = parsed;
return true;
}
static string ProfileName(int profile)
{
if (profile == 0) return "authored";
if (profile == 1) return "clear";
if (profile == 2) return "overcast";
if (profile == 3) return "rain";
return "unavailable";
}
static string WeatherStateName(int profile)
{
if (profile == 1) return "Clear";
if (profile == 2) return "Overcast";
if (profile == 3) return "Rainy";
return "";
}
static bool ParseProfile(string text, out int profile)
{
if (text == "authored") { profile = 0; return true; }
if (text == "clear") { profile = 1; return true; }
if (text == "overcast") { profile = 2; return true; }
if (text == "rain" || text == "rainy") { profile = 3; return true; }
return false;
}
static bool HasWeatherState(TimeAndWeatherManagerEntity manager, string name)
{
if (!manager || name.IsEmpty()) return false;
array<ref WeatherState> states = {};
manager.GetWeatherStatesList(states);
foreach (WeatherState state : states)
if (state && state.GetStateName() == name) return true;
return false;
}
bool Start(ERR_GameMode mode)
{
if (!Replication.IsServer() || !mode || !mode.Settings()) return false;
ChimeraWorld world = ChimeraWorld.CastFrom(mode.GetWorld());
if (!world) return false;
return StartOn(world.GetTimeAndWeatherManager(), mode.Settings().Get("startDateCode"), mode.Settings().Get("weatherProfile"));
}
protected bool StartOn(TimeAndWeatherManagerEntity manager, int dateCode, int profile)
{
if (!Replication.IsServer() || m_Stopped) return false;
if (m_Started) return true;
if (!manager || !ERR_Settings.Valid("startDateCode", dateCode) || !ERR_Settings.Valid("weatherProfile", profile)) return false;
int year = 1900 + dateCode / 10000;
int month = (dateCode % 10000) / 100;
int day = dateCode % 100;
if (dateCode != 0 && !manager.CheckValidDate(year, month, day)) return false;
string weather = WeatherStateName(profile);
BaseWeatherStateTransitionManager transitions = manager.GetTransitionManager();
if (profile != 0 && (!transitions || !manager.GetCurrentWeatherState() || !HasWeatherState(manager, weather))) return false;
int previousYear, previousMonth, previousDay;
manager.GetDate(previousYear, previousMonth, previousDay);
if (dateCode != 0 && !manager.SetDate(year, month, day, true)) return false;
if (profile != 0)
{
string previousWeather = manager.GetCurrentWeatherState().GetStateName();
bool previousLooping = manager.IsWeatherLooping();
// Use the native replicated weather route, never local editor previews.
// Loop the selected family; native variants/wind still follow vanilla rules.
manager.ForceWeatherTo(true, weather, 0, 24);
WeatherState current = manager.GetCurrentWeatherState();
WeatherState next = transitions.GetNextState();
if (!manager.IsWeatherLooping() || ((!current || current.GetStateName() != weather) && (!next || next.GetStateName() != weather)))
{
manager.ForceWeatherTo(previousLooping, previousWeather, 0, 24);
if (dateCode != 0) manager.SetDate(previousYear, previousMonth, previousDay, true);
return false;
}
}
m_Manager = manager;
m_DateCode = dateCode;
m_Profile = profile;
m_Started = true;
PrintFormat("Rereintroduced [environment]: date=%1 weather=%2", Date(dateCode), ProfileName(profile));
return true;
}
bool Started() { return m_Started && !m_Stopped; }
int DateCode() { return m_DateCode; }
int Profile() { return m_Profile; }
int CurrentDateCode()
{
if (!Started() || !m_Manager) return -1;
return (m_Manager.GetYear() - 1900) * 10000 + m_Manager.GetMonth() * 100 + m_Manager.GetDay();
}
string CurrentWeather()
{
if (!Started() || !m_Manager) return "unavailable";
WeatherState weather = m_Manager.GetCurrentWeatherState();
if (!weather) return "unavailable";
return weather.GetStateName();
}
string Status()
{
if (!Started()) return "Environment not started.";
return "Current date " + Date(CurrentDateCode()) + ", weather " + CurrentWeather() + " (round profile " + ProfileName(m_Profile) + ").";
}
void Stop() { m_Stopped = true; m_Manager = null; }
}
// Called after the server-owned admin authorization gate. Saving cannot change
// the active service; accepted changes are consumed by the next mission instance.
class ERR_EnvironmentCommands
{
static bool Execute(ERR_Settings settings, ERR_EnvironmentService environment, array<string> parts, array<string> replies)
{
if (!Replication.IsServer() || !settings || parts.Count() < 2 || parts[0] != "mission" || (parts[1] != "date" && parts[1] != "weather")) return false;
if (parts.Count() == 3 && parts[2] == "show")
{
if (environment) replies.Insert(environment.Status());
replies.Insert("Next round: date " + ERR_EnvironmentService.Date(settings.Get("startDateCode")) + ", weather " + ERR_EnvironmentService.ProfileName(settings.Get("weatherProfile")) + ".");
return true;
}
int value;
bool valid;
string key = "startDateCode";
if (parts[1] == "weather") key = "weatherProfile";
if (parts.Count() == 4 && parts[2] == "set")
{
if (key == "startDateCode") valid = ERR_EnvironmentService.ParseDate(parts[3], value);
else valid = ERR_EnvironmentService.ParseProfile(parts[3], value);
}
if (valid && settings.Set(key, value))
replies.Insert("Saved for next round: date " + ERR_EnvironmentService.Date(settings.Get("startDateCode")) + ", weather " + ERR_EnvironmentService.ProfileName(settings.Get("weatherProfile")) + ". Current round unchanged.");
else replies.Insert("Invalid environment or settings could not be saved. Use mission date set YYYY-MM-DD (1900-2050) or authored; mission weather set authored|clear|overcast|rain. Previous setting retained.");
return true;
}
}
@@ -0,0 +1,72 @@
[BaseContainerProps()]
class ERR_EscapeGameOverScreenInfo : SCR_BaseGameOverScreenInfo
{
protected ERR_PersonalEscapeResult PersonalResult(int playerId)
{
SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerController());
if (!controller || controller.GetPlayerId() != playerId) return ERR_PersonalEscapeResult.UNKNOWN;
return controller.ERR_EscapeResult();
}
override string GetTitle(int playerId, array<int> otherPlayerIds)
{
string title = "The helicopter escaped";
if (PersonalResult(playerId) == ERR_PersonalEscapeResult.ESCAPED) title = "You escaped Everon";
#ifdef WORKBENCH
PrintFormat("ERR RESULT UI: player=%1 title=%2", playerId, title);
#endif
return title;
}
override string GetDebriefing(int playerId, array<int> otherPlayerIds)
{
ERR_PersonalEscapeResult result = PersonalResult(playerId);
if (result == ERR_PersonalEscapeResult.ESCAPED)
return "You crossed the extraction boundary aboard the helicopter.";
if (result == ERR_PersonalEscapeResult.MISSED) return "You did not escape with the helicopter.";
return "Your personal escape result is not available.";
}
}
// End data and the controller replicate independently. Refresh an already-open
// native result screen when the identity-derived controller result arrives.
modded class SCR_GameOverScreenContentUIComponent
{
protected bool m_ERREscapeContent;
override void InitContent(SCR_GameOverScreenUIContentData endScreenUIContent)
{
SCR_PlayerController.ERR_OnEscapeResultChanged().Remove(ERR_RefreshEscapeContent);
super.InitContent(endScreenUIContent);
m_ERREscapeContent = ERR_EscapeGameOverScreenInfo.Cast(endScreenUIContent.m_GameOverInfo) != null;
if (!m_ERREscapeContent) return;
SCR_PlayerController.ERR_OnEscapeResultChanged().Insert(ERR_RefreshEscapeContent);
ERR_RefreshEscapeContent();
}
protected void ERR_RefreshEscapeContent()
{
if (!m_ERREscapeContent || !m_wRoot) return;
SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerController());
int playerId = -1;
if (controller) playerId = controller.GetPlayerId();
ERR_EscapeGameOverScreenInfo info = new ERR_EscapeGameOverScreenInfo();
TextWidget title = TextWidget.Cast(m_wRoot.FindAnyWidget(m_sTileName));
TextWidget debrief = TextWidget.Cast(m_wRoot.FindAnyWidget(m_sDebriefingName));
if (title) title.SetText(info.GetTitle(playerId, null));
if (debrief) debrief.SetText(info.GetDebriefing(playerId, null));
}
override void HandlerDeattached(Widget w)
{
SCR_PlayerController.ERR_OnEscapeResultChanged().Remove(ERR_RefreshEscapeContent);
m_ERREscapeContent = false;
super.HandlerDeattached(w);
}
}
[BaseContainerProps()]
class ERR_FailureGameOverScreenInfo : SCR_BaseGameOverScreenInfo
{
override string GetDebriefing(int playerId, array<int> otherPlayerIds)
{
ERR_GameMode mode = ERR_GameMode.Get();
if (mode && !mode.EndReason().IsEmpty()) return mode.EndReason();
return "No surviving prisoners can complete the escape, or the extraction helicopter has been lost.";
}
}
@@ -0,0 +1,112 @@
// Initial preparation only. A newly spawned vehicle may start asleep above its
// landing gear contact plane. Wake once, then observe native gravity/contact;
// never correct its transform, apply an impulse, or replenish its supplies.
class ERR_ExtractionPlacement
{
protected ERR_GameMode m_Mode;
protected Vehicle m_Aircraft;
protected bool m_Stopped;
protected int m_WakeCount;
protected float m_Deadline;
protected float m_StableSince = -1;
protected float m_LastObservation = -1;
protected float m_NextDiagnostic;
protected ERR_PrepareResult m_Result = ERR_PrepareResult.PENDING;
protected string m_Failure;
void ERR_ExtractionPlacement(ERR_GameMode mode, Vehicle aircraft)
{
m_Mode = mode; m_Aircraft = aircraft;
m_Deadline = GetGame().GetWorld().GetWorldTime() + 30000;
}
static bool CanWake(bool initialized, int count, float now, float deadline)
{
return initialized && count == 0 && now < deadline;
}
static bool Fresh(float previous, float now)
{
return previous >= 0 && now >= previous && now - previous <= 1500;
}
static bool QualificationAllowed(bool previouslyReady, float now, float deadline)
{
return previouslyReady || now < deadline;
}
static bool Stable(float since, float previous, float now)
{
return since >= 0 && previous >= since && now >= previous && now - previous <= 1500 && now - since >= 1000;
}
int WakeCount() { return m_WakeCount; }
bool Ready() { return !m_Stopped && m_Result == ERR_PrepareResult.READY; }
string FailureReason() { return m_Failure; }
void Stop() { m_Stopped = true; }
protected ERR_PrepareResult Fail(string reason)
{
m_Failure = reason; m_Result = ERR_PrepareResult.FAILED;
Print("Rereintroduced [extraction placement]: " + reason, LogLevel.ERROR);
return m_Result;
}
protected void Diagnostic(bool ready)
{
if (!m_Aircraft) return;
Physics physics = m_Aircraft.GetPhysics();
HelicopterControllerComponent controller = HelicopterControllerComponent.Cast(m_Aircraft.FindComponent(HelicopterControllerComponent));
vector entity[4]; m_Aircraft.GetWorldTransform(entity);
PrintFormat("ERR EXTRACTION PLACEMENT: wakes=%1 alive=%2 ready=%3 origin=%4 up=%5", m_WakeCount, ERR_Actors.Alive(m_Aircraft), ready, entity[3], entity[1][1]);
if (physics) PrintFormat("ERR EXTRACTION PHYSICS: dynamic=%1 awake=%2 speed=%3 angularSpeed=%4", physics.IsDynamic(), physics.IsActive(), physics.GetVelocity().Length(), physics.GetAngularVelocity().Length());
if (controller && controller.GetSimulation())
{
vector nativeTransform[4]; controller.GetSimulation().GetWorldTransform(nativeTransform);
PrintFormat("ERR EXTRACTION NATIVE: valid=%1 contact=%2 engine=%3 origin=%4 up=%5 entityDifference=%6", controller.GetSimulation().IsValid(), controller.GetSimulation().HasAnyGroundContact(), controller.GetSimulation().EngineIsOn(), nativeTransform[3], nativeTransform[1][1], vector.Distance(nativeTransform[3], entity[3]));
}
// Diagnostics read every wheel even while physics is awake. The strict parked
// fallback itself still requires sleep and never alters the vehicle.
vector front, left, right;
if (ERR_CasualtyGroundSupport.WheelCenter(m_Aircraft, "v_wheel_F", front, true)) ERR_CasualtyGroundSupport.WheelSupported(m_Aircraft, front, 0.30, true);
if (ERR_CasualtyGroundSupport.WheelCenter(m_Aircraft, "v_wheel_bL", left, true)) ERR_CasualtyGroundSupport.WheelSupported(m_Aircraft, left, 0.45, true);
if (ERR_CasualtyGroundSupport.WheelCenter(m_Aircraft, "v_wheel_bR", right, true)) ERR_CasualtyGroundSupport.WheelSupported(m_Aircraft, right, 0.45, true);
}
ERR_PrepareResult Poll(float now)
{
if (m_Stopped || m_Result == ERR_PrepareResult.FAILED) return ERR_PrepareResult.FAILED;
if (!Replication.IsServer() || !m_Mode || m_Mode.Phase() != ERR_Phase.PREPARING || m_Mode.ExtractionVehicle() != m_Aircraft) return Fail("initial aircraft ownership/phase changed");
if (!ERR_Actors.Alive(m_Aircraft) || !m_Aircraft.GetPrefabData() || !m_Aircraft.GetPrefabData().GetPrefabName().Contains("/Mi8MT/")) return Fail("registered initial Mi8 is missing, destroyed or unsupported");
Physics physics = m_Aircraft.GetPhysics();
HelicopterControllerComponent controller = HelicopterControllerComponent.Cast(m_Aircraft.FindComponent(HelicopterControllerComponent));
bool initialized = physics && physics.IsDynamic() && controller && controller.GetSimulation() && controller.GetSimulation().IsValid();
if (CanWake(initialized, m_WakeCount, now, m_Deadline))
{
// Production preparation owns the empty registered aircraft. The count is
// latched BEFORE the native call, preventing repeated wakes on any callback.
m_WakeCount = 1;
physics.SetActive(ActiveState.ACTIVE);
m_LastObservation = now;
Diagnostic(false);
m_NextDiagnostic = now + 1000;
return ERR_PrepareResult.PENDING; // never accept in the mutation frame
}
FuelManagerComponent fuel = FuelManagerComponent.Cast(m_Aircraft.FindComponent(FuelManagerComponent));
bool ready = initialized && m_WakeCount == 1 && fuel && fuel.HasFuel() && ERR_CasualtyLoadingRules.AircraftReady(m_Aircraft);
if (now >= m_NextDiagnostic)
{
Diagnostic(ready);
PrintFormat("ERR EXTRACTION FUEL: manager=%1 hasFuel=%2", fuel != null, fuel && fuel.HasFuel());
m_NextDiagnostic = now + 1000;
}
if (!ready || !Fresh(m_LastObservation, now)) m_StableSince = -1;
if (ready)
{
if (m_StableSince < 0) m_StableSince = now;
if (Stable(m_StableSince, m_LastObservation, now))
{
if (!QualificationAllowed(m_Result == ERR_PrepareResult.READY, now, m_Deadline)) return Fail("initial aircraft stable qualification missed the 30-second preparation deadline");
if (m_Result != ERR_PrepareResult.READY) Print("Rereintroduced [extraction placement]: native supported aircraft stable for one second, fuel present, exactly one initial wake");
m_Result = ERR_PrepareResult.READY;
m_LastObservation = now;
return m_Result;
}
}
m_Result = ERR_PrepareResult.PENDING;
m_LastObservation = now;
if (now >= m_Deadline) return Fail("initial aircraft did not become alive, fueled and physically supported/stable within 30 seconds; no replacement or repositioning requested");
return m_Result;
}
}
@@ -0,0 +1,101 @@
enum ERR_ExtractionState { WAITING, ESCAPED, LOST }
// Mission progression consumes the result; this service never spawns a vehicle.
class ERR_ExtractionService
{
static const int REQUIRED_SEATS = 12;
protected ref ERR_PlayerRoster m_Roster;
protected Vehicle m_Vehicle;
protected bool m_Assigned;
protected bool m_Begun;
protected bool m_Stopped;
protected vector m_Center;
protected float m_Radius;
protected ERR_ExtractionState m_State;
protected ref set<UUID> m_Escapees = new set<UUID>();
protected int m_ParticipantCount;
protected ref ScriptInvoker m_OnResolved = new ScriptInvoker();
void ERR_ExtractionService(ERR_PlayerRoster roster) { m_Roster = roster; }
Vehicle VehicleEntity() { return m_Vehicle; }
ERR_ExtractionState State() { return m_State; }
ScriptInvoker OnResolved() { return m_OnResolved; }
int EscapedCount() { return m_Escapees.Count(); }
int LeftBehindCount() { return m_ParticipantCount - m_Escapees.Count(); }
bool DidEscape(UUID identity) { return m_Escapees.Contains(identity); }
bool Assign(Vehicle vehicle)
{
if (!Replication.IsServer() || m_Assigned || m_Stopped || !ERR_Actors.Alive(vehicle)) return false;
m_Vehicle = vehicle;
m_Assigned = true;
return true;
}
int Seats(out array<BaseCompartmentSlot> seats)
{
seats.Clear();
if (!m_Vehicle) return 0;
BaseCompartmentManagerComponent manager = BaseCompartmentManagerComponent.Cast(m_Vehicle.FindComponent(BaseCompartmentManagerComponent));
if (!manager) return 0;
array<BaseCompartmentSlot> all = {};
manager.GetCompartments(all);
foreach (BaseCompartmentSlot slot : all)
{
if (!slot) continue;
ECompartmentType type = SCR_CompartmentAccessComponent.GetCompartmentType(slot);
if (type == ECompartmentType.PILOT || type == ECompartmentType.CARGO) seats.Insert(slot);
}
return seats.Count();
}
bool Ready()
{
if (!m_Assigned || !ERR_Actors.Alive(m_Vehicle) || !m_Vehicle.FindComponent(HelicopterControllerComponent)) return false;
array<BaseCompartmentSlot> seats = {};
if (Seats(seats) < REQUIRED_SEATS) return false;
foreach (BaseCompartmentSlot slot : seats)
if (SCR_CompartmentAccessComponent.GetCompartmentType(slot) == ECompartmentType.PILOT) return true;
return false;
}
bool Begin(vector center, float radius)
{
if (!Replication.IsServer() || m_Begun || m_Stopped || !m_Roster || radius <= 0 || !Ready()) return false;
m_Center = center;
m_Radius = radius;
m_Begun = true;
return true;
}
void Tick(ERR_Phase phase)
{
if (!Replication.IsServer() || !m_Begun || m_Stopped || m_State != ERR_ExtractionState.WAITING) return;
if (phase < ERR_Phase.PRISON || ERR_MissionRules.Terminal(phase)) return;
if (!ERR_Actors.Alive(m_Vehicle)) { Resolve(ERR_ExtractionState.LOST); return; }
if (phase != ERR_Phase.EXTRACTION || vector.DistanceXZ(m_Vehicle.GetOrigin(), m_Center) <= m_Radius) return;
array<ref ERR_PlayerLife> lives = {};
m_Roster.Participants(lives);
foreach (ERR_PlayerLife life : lives)
{
if (!ERR_Actors.Alive(life.Actor) || CompartmentAccessComponent.GetVehicleIn(life.Actor) != m_Vehicle) continue;
// Reserved disconnected characters and unconscious passengers remain alive.
// A recycled connection ID is never an identity for the escape snapshot.
m_Escapees.Insert(life.AccountId);
}
if (m_Escapees.IsEmpty()) return;
m_ParticipantCount = lives.Count();
Resolve(ERR_ExtractionState.ESCAPED);
}
protected void Resolve(ERR_ExtractionState result)
{
m_State = result;
m_OnResolved.Invoke(result);
}
void ConnectedWinners(out array<int> winners)
{
winners.Clear();
if (m_State != ERR_ExtractionState.ESCAPED || !m_Roster) return;
array<ref ERR_PlayerLife> lives = {};
m_Roster.Participants(lives);
foreach (ERR_PlayerLife life : lives)
if (life.Connected && m_Roster.Get(life.ConnectionId) == life && m_Escapees.Contains(life.AccountId)) winners.Insert(life.ConnectionId);
}
void Stop() { m_Stopped = true; }
}
@@ -0,0 +1,42 @@
// One authored faction/asset contract, supplied by the game mode resource.
[BaseContainerProps()]
class ERR_FactionCatalog
{
[Attribute()] string PlayerFactionKey;
[Attribute()] string EnemyFactionKey;
[Attribute("", UIWidgets.ResourceNamePicker, "Unarmed prisoner character", "et")]
ResourceName PrisonerPrefab;
[Attribute("", UIWidgets.ResourceNamePicker, "Initial physical guard squad", "et")]
ResourceName GuardGroupPrefab;
[Attribute("", UIWidgets.ResourceNamePicker, "Extraction helicopter", "et")]
ResourceName ExtractionPrefab;
bool Valid()
{
return !PlayerFactionKey.IsEmpty() && !EnemyFactionKey.IsEmpty() && PlayerFactionKey != EnemyFactionKey && !PrisonerPrefab.IsEmpty() && !GuardGroupPrefab.IsEmpty() && !ExtractionPrefab.IsEmpty();
}
Faction PlayerFaction()
{
if (!GetGame().GetFactionManager()) return null;
return GetGame().GetFactionManager().GetFactionByKey(PlayerFactionKey);
}
Faction EnemyFaction()
{
if (!GetGame().GetFactionManager()) return null;
return GetGame().GetFactionManager().GetFactionByKey(EnemyFactionKey);
}
bool WorldReady()
{
if (!Valid()) return false;
Faction player = PlayerFaction();
Faction enemy = EnemyFaction();
if (!player || !enemy || !player.IsFactionEnemy(enemy) || !enemy.IsFactionEnemy(player)) return false;
array<ResourceName> required = {PrisonerPrefab, GuardGroupPrefab, ExtractionPrefab};
foreach (ResourceName prefab : required)
{
Resource resource = Resource.Load(prefab);
if (!resource || !resource.IsValid()) return false;
}
return true;
}
}
@@ -0,0 +1,419 @@
class ERR_GameModeClass : SCR_BaseGameModeClass {}
class ERR_GameMode : SCR_BaseGameMode
{
[RplProp(), Attribute("6090 0 7160", UIWidgets.Coords, "Prison position; configured from authored scenario")]
protected vector m_PrisonPosition;
[Attribute("6090 0 7160", UIWidgets.Coords, "Centre of extraction boundary")]
protected vector m_EscapeCenter;
[RplProp(), Attribute("9000", UIWidgets.EditBox, "Helicopter must cross this radius to escape")]
protected float m_EscapeRadius;
[RplProp()]
protected ERR_Phase m_Phase;
[Attribute("{BE50B97B8E2B36E6}Configs/ERR/VanillaFactions.conf", UIWidgets.ResourceNamePicker, "Faction and asset catalog", "conf")]
protected ResourceName m_FactionDefinition;
[RplProp()]
protected string m_PreparationError;
protected ref ERR_FactionCatalog m_Factions;
protected ref ERR_Settings m_Settings;
protected ref ERR_TimeService m_Time = new ERR_TimeService();
protected ref ERR_EnvironmentService m_Environment = new ERR_EnvironmentService();
protected ref ERR_DiscoveryService m_Discovery;
protected ref ERR_Objectives m_Objectives;
protected ref ERR_PlayerRoster m_Roster = new ERR_PlayerRoster();
protected ref ERR_AdminService m_Admin = new ERR_AdminService();
protected ref ERR_CommandService m_Command;
protected ref array<ERR_LocationComponent> m_Locations = {};
protected ref array<IEntity> m_OwnedEntities = {};
protected ref ERR_ExtractionService m_Extraction;
[RplProp()]
protected string m_EndReason;
protected ref ERR_RadioDefenceService m_RadioDefence;
[RplProp()]
protected ERR_RadioFeedback m_RadioFeedback;
protected ref ERR_RecoveryService m_Recovery;
[RplProp()]
protected string m_RecoveryStatus;
protected bool m_StartedPlayers;
protected bool m_Stopped;
protected float m_NoSurvivorsSince = -1;
protected ref ScriptInvoker m_OnPhaseChanged = new ScriptInvoker();
protected ref ScriptInvoker m_OnTick = new ScriptInvoker();
protected ref ScriptInvoker m_OnStop = new ScriptInvoker();
protected ref ScriptInvoker m_OnLocationDiscovered = new ScriptInvoker();
protected ref ScriptInvoker m_OnGroup = new ScriptInvoker();
static ERR_GameMode Get()
{
if (!GetGame()) return null;
return ERR_GameMode.Cast(GetGame().GetGameMode());
}
ERR_Phase Phase() { return m_Phase; }
ERR_PlayerRoster Players() { return m_Roster; }
ERR_AdminService Admin() { return m_Admin; }
ERR_Settings Settings() { return m_Settings; }
ERR_TimeService Time() { return m_Time; }
ERR_EnvironmentService Environment() { return m_Environment; }
ERR_RadioDefenceService RadioDefence() { return m_RadioDefence; }
ERR_RecoveryService Recovery() { return m_Recovery; }
string RecoveryStatus() { return m_RecoveryStatus; }
ERR_CommandService Command() { return m_Command; }
ERR_Objectives Objectives() { return m_Objectives; }
ERR_ExtractionService Extraction() { return m_Extraction; }
string EndReason() { return m_EndReason; }
Vehicle ExtractionVehicle()
{
if (!m_Extraction) return null;
return m_Extraction.VehicleEntity();
}
float EscapeRadius() { return m_EscapeRadius; }
ERR_RadioFeedback RadioFeedback() { return m_RadioFeedback; }
// Called only from existing authority transition/tick paths; no extra timer.
protected bool RefreshRadioFeedback()
{
if (!Replication.IsServer()) return false;
bool defending = m_Phase == ERR_Phase.RADIO_DEFENCE && m_RadioDefence != null && m_RadioDefence.State() == ERR_RadioDefenceState.DEFENDING;
bool occupied = defending && m_RadioDefence.Occupied();
ERR_RadioFeedback next = ERR_RadioFeedbackRules.Next(m_RadioFeedback, defending, occupied);
if (next == m_RadioFeedback) return false;
m_RadioFeedback = next;
return true;
}
bool DefenceOccupied() { return m_RadioDefence && m_RadioDefence.Occupied(); }
int DefenceSecondsRemaining()
{
if (!m_RadioDefence) return 0;
return m_RadioDefence.SecondsRemaining(GetWorld().GetWorldTime());
}
ScriptInvoker OnPhaseChanged() { return m_OnPhaseChanged; }
ScriptInvoker OnTick() { return m_OnTick; }
ScriptInvoker OnStop() { return m_OnStop; }
ScriptInvoker OnGroup() { return m_OnGroup; }
ScriptInvoker OnLocationDiscovered() { return m_OnLocationDiscovered; }
void LocationDiscovered(ERR_LocationComponent location, int previous, int current)
{
if (!Replication.IsServer() || !m_Locations.Contains(location) || current <= previous) return;
m_OnLocationDiscovered.Invoke(location, previous, current);
}
ERR_FactionCatalog Factions() { return m_Factions; }
Faction PlayerFaction()
{
if (!m_Factions) return null;
return m_Factions.PlayerFaction();
}
Faction EnemyFaction()
{
if (!m_Factions) return null;
return m_Factions.EnemyFaction();
}
string PreparationError() { return m_PreparationError; }
string Status()
{
if (!m_PreparationError.IsEmpty()) return "Mission startup failed: " + m_PreparationError;
if (!m_EndReason.IsEmpty()) return m_EndReason;
if (m_Phase == ERR_Phase.PREPARING) return "Preparing mission sites and physical supplies.";
string phase;
if (m_Phase == ERR_Phase.PRISON) phase = "Escape the prison";
else if (m_Phase == ERR_Phase.RADIO_SEARCH) phase = "Locate the radio station";
else if (m_Phase == ERR_Phase.RADIO_DEFENCE) phase = "Defend the radio transmission";
else if (m_Phase == ERR_Phase.EXTRACTION) phase = "Helicopter extraction";
else if (m_Phase == ERR_Phase.VICTORY) phase = "Escape complete";
else phase = "Mission lost";
if (!m_RecoveryStatus.IsEmpty()) phase += " " + m_RecoveryStatus;
return phase;
}
protected void RecoveryChanged(string message)
{
if (!Replication.IsServer() || m_RecoveryStatus == message) return;
m_RecoveryStatus = message;
Replication.BumpMe();
}
bool AbandonRound()
{
if (!Replication.IsServer() || !Active() || m_Stopped || !m_PreparationError.IsEmpty()) return false;
if (!ERR_MissionRules.CanTransition(m_Phase, ERR_Phase.DEFEAT)) return false;
m_EndReason = "An administrator abandoned this round.";
return Transition(ERR_Phase.DEFEAT);
}
protected void FailPreparation(string detail)
{
if (!Replication.IsServer() || m_Phase != ERR_Phase.PREPARING || !m_PreparationError.IsEmpty()) return;
m_PreparationError = detail;
Replication.BumpMe();
Print("Rereintroduced: scenario preparation failed; spawning remains disabled: " + detail, LogLevel.ERROR);
StopServices();
ERR_SiteService sites = Sites();
if (sites) sites.Rollback();
for (int i = m_OwnedEntities.Count() - 1; i >= 0; i--) ERR_Actors.DeleteSpawned(m_OwnedEntities[i]);
m_OwnedEntities.Clear();
}
vector PrisonPosition() { return ERR_Actors.Ground(m_PrisonPosition); }
bool Active() { return m_Phase >= ERR_Phase.PRISON && !ERR_MissionRules.Terminal(m_Phase); }
protected ERR_CommandService CreateCommandService() { return null; }
void ConfigureStart(vector position)
{
if (!Replication.IsServer() || m_Phase != ERR_Phase.PREPARING) return;
m_PrisonPosition = position;
m_EscapeCenter = position;
Replication.BumpMe();
}
ERR_SiteService Sites()
{
ERR_ScenarioComponent scenario = ERR_ScenarioComponent.Cast(FindComponent(ERR_ScenarioComponent));
if (!scenario) return null;
return scenario.Sites();
}
void RegisterLocation(ERR_LocationComponent location)
{
if (!location || location.Id().IsEmpty() || m_Locations.Contains(location)) return;
foreach (ERR_LocationComponent existing : m_Locations)
{
if (existing.Id() == location.Id())
{
Print("Rereintroduced: duplicate location ID " + location.Id(), LogLevel.ERROR);
return;
}
}
m_Locations.Insert(location);
}
bool HasLocation(ERR_LocationComponent location) { return location && m_Locations.Contains(location); }
void UnregisterLocation(ERR_LocationComponent location) { m_Locations.RemoveItem(location); }
void Locations(out array<ERR_LocationComponent> locations) { locations.Copy(m_Locations); }
void Own(IEntity entity) { if (entity && !m_OwnedEntities.Contains(entity)) m_OwnedEntities.Insert(entity); }
bool SetExtractionVehicle(Vehicle vehicle)
{
return Replication.IsServer() && m_Phase == ERR_Phase.PREPARING && m_Extraction && m_Extraction.Assign(vehicle);
}
void PublishEscapeResults(bool reset = false)
{
if (!Replication.IsServer()) return;
array<int> connected = {};
GetGame().GetPlayerManager().GetPlayers(connected);
foreach (int id : connected)
{
SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerManager().GetPlayerController(id));
if (!controller) continue;
if (reset) controller.ERR_ResetEscapeResult();
else controller.ERR_PublishEscapeResult(m_Roster, m_Extraction);
}
}
void RegisterGroup(SCR_AIGroup group)
{
if (!Replication.IsServer() || !group) return;
Own(group);
if (m_Command) m_Command.RegisterGroup(group);
m_OnGroup.Invoke(group);
}
bool Transition(ERR_Phase phase)
{
if (!Replication.IsServer() || !m_PreparationError.IsEmpty() || !ERR_MissionRules.CanTransition(m_Phase, phase)) return false;
ERR_Phase previous = m_Phase;
m_Phase = phase;
RefreshRadioFeedback();
if (ERR_MissionRules.Terminal(phase)) PublishEscapeResults();
Replication.BumpMe();
PrintFormat("Rereintroduced [mission]: phase %1 -> %2", previous, phase);
m_OnPhaseChanged.Invoke(previous, phase);
if (ERR_MissionRules.Terminal(phase))
{
StopServices();
array<int> winners = {};
if (phase == ERR_Phase.VICTORY && m_Extraction) m_Extraction.ConnectedWinners(winners);
EGameOverTypes reason = EGameOverTypes.LOOSE;
if (phase == ERR_Phase.VICTORY) reason = EGameOverTypes.VICTORY;
EndGameMode(SCR_GameModeEndData.Create(reason, winners));
}
return true;
}
bool UnlockPrison(IEntity player)
{
if (!Replication.IsServer() || !ERR_Actors.Player(player) || !ERR_Actors.CanFight(player)) return false;
if (vector.DistanceXZ(player.GetOrigin(), PrisonPosition()) > 15) return false;
return m_Phase == ERR_Phase.PRISON && Transition(ERR_Phase.RADIO_SEARCH);
}
bool TuneRadio(IEntity player, ERR_LocationComponent location)
{
return m_RadioDefence && m_RadioDefence.Begin(player, location);
}
protected void RadioStarted(ERR_LocationComponent location)
{
if (!Replication.IsServer() || !m_RadioDefence || location != m_RadioDefence.Location()) return;
if (!Transition(ERR_Phase.RADIO_DEFENCE)) m_RadioDefence.Stop();
}
protected void RadioCompleted(ERR_LocationComponent location)
{
if (!Replication.IsServer() || m_Phase != ERR_Phase.RADIO_DEFENCE || !m_RadioDefence || m_RadioDefence.State() != ERR_RadioDefenceState.COMPLETED || location != m_RadioDefence.Location()) return;
foreach (ERR_LocationComponent candidate : m_Locations)
if (candidate.Kind() == ERR_LocationKind.EXTRACTION) candidate.Discover(0, 100);
Transition(ERR_Phase.EXTRACTION);
}
void PlayerSpawned(int id, IEntity actor)
{
if (!Replication.IsServer() || !actor || ERR_MissionRules.Terminal(m_Phase)) return;
m_Roster.Spawned(id, actor);
m_StartedPlayers = true;
m_NoSurvivorsSince = -1;
}
protected string SettingsPath() { return ERR_Settings.PATH; }
protected override void EOnInit(IEntity owner)
{
super.EOnInit(owner);
if (SCR_Global.IsEditMode()) return;
m_Factions = ERR_FactionCatalog.Cast(SCR_BaseContainerTools.CreateInstanceFromPrefab(m_FactionDefinition));
if (!System.IsConsoleApp()) GetGame().GetCallqueue().CallLater(BindLocalObserver, 250, true);
if (!Replication.IsServer()) return;
m_Settings = new ERR_Settings(SettingsPath());
OnStop().Insert(m_Environment.Stop);
m_Recovery = new ERR_RecoveryService(this);
m_Recovery.OnChanged().Insert(RecoveryChanged);
OnStop().Insert(m_Recovery.Stop);
m_RadioDefence = new ERR_RadioDefenceService(this);
m_RadioDefence.OnStarted().Insert(RadioStarted);
m_RadioDefence.OnCompleted().Insert(RadioCompleted);
OnPhaseChanged().Insert(m_RadioDefence.PhaseChanged);
OnStop().Insert(m_RadioDefence.Stop);
m_Extraction = new ERR_ExtractionService(m_Roster);
PublishEscapeResults(true);
m_Extraction.OnResolved().Insert(ExtractionResolved);
OnStop().Insert(m_Extraction.Stop);
m_Discovery = new ERR_DiscoveryService(this);
OnTick().Insert(m_Discovery.Tick);
OnStop().Insert(m_Discovery.Stop);
m_Objectives = new ERR_Objectives(this);
OnPhaseChanged().Insert(m_Objectives.PhaseChanged);
OnTick().Insert(m_Objectives.Tick);
OnStop().Insert(m_Objectives.Stop);
m_Command = CreateCommandService();
GetGame().GetCallqueue().CallLater(ServerTick, 1000, true);
GetGame().GetCallqueue().CallLater(StartScenario, 1500, false);
}
protected void BindLocalObserver()
{
SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerController());
if (!controller) return;
controller.ERR_StartObserverMonitor();
GetGame().GetCallqueue().Remove(BindLocalObserver);
}
protected void StartScenario()
{
if (m_Phase != ERR_Phase.PREPARING || !m_PreparationError.IsEmpty()) return;
if (!m_Factions || !m_Factions.WorldReady()) { FailPreparation("The configured player and enemy factions or required assets are missing, invalid or not hostile."); return; }
// Scenario content is created through a separate authoring component.
ERR_ScenarioComponent scenario = ERR_ScenarioComponent.Cast(FindComponent(ERR_ScenarioComponent));
ERR_PrepareResult prepared = ERR_PrepareResult.FAILED;
if (scenario) prepared = scenario.Prepare();
if (prepared == ERR_PrepareResult.PENDING)
{
GetGame().GetCallqueue().CallLater(StartScenario, 250, false);
return;
}
if (prepared == ERR_PrepareResult.FAILED)
{
string detail = "Scenario component is missing.";
if (scenario) detail = scenario.FailureReason();
FailPreparation(detail);
return;
}
if (!m_Extraction || !m_Extraction.Begin(m_EscapeCenter, m_EscapeRadius))
{
FailPreparation("The extraction vehicle must be a helicopter with a pilot position and at least twelve pilot/passenger seats, with a valid escape boundary.");
return;
}
if (!m_Environment.Start(this)) { FailPreparation("The selected date or vanilla weather profile could not be initialized."); return; }
if (!m_Time.Start(this)) { FailPreparation("The world clock could not be initialized."); return; }
Transition(ERR_Phase.PRISON);
}
protected void ExtractionResolved(ERR_ExtractionState result)
{
if (!Replication.IsServer() || !Active()) return;
if (result == ERR_ExtractionState.LOST)
{
m_EndReason = "The extraction helicopter was lost. No replacement is available.";
Transition(ERR_Phase.DEFEAT);
}
else if (result == ERR_ExtractionState.ESCAPED)
{
m_EndReason = string.Format("Escape complete: %1 escaped; %2 did not escape.", m_Extraction.EscapedCount(), m_Extraction.LeftBehindCount());
Transition(ERR_Phase.VICTORY);
}
}
protected void ServerTick()
{
if (!Replication.IsServer() || !Active()) return;
float now = GetWorld().GetWorldTime();
m_Extraction.Tick(m_Phase);
if (!Active()) return;
m_RadioDefence.Tick(now);
if (RefreshRadioFeedback()) Replication.BumpMe();
m_Recovery.Tick();
if (!Active()) return;
if (GetGame().GetPlayerManager().GetPlayerCount() == 0)
{
m_NoSurvivorsSince = -1;
return;
}
if (m_StartedPlayers && !m_Roster.HasSurvivor())
{
if (m_NoSurvivorsSince < 0) m_NoSurvivorsSince = now;
if (now - m_NoSurvivorsSince >= 30000)
{
m_EndReason = "No surviving prisoners remain.";
Transition(ERR_Phase.DEFEAT);
return;
}
}
else m_NoSurvivorsSince = -1;
array<int> connected = {};
GetGame().GetPlayerManager().GetPlayers(connected);
foreach (int id : connected)
{
SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerManager().GetPlayerController(id));
if (controller) controller.ERR_UpdateObserverServer();
}
if (m_Command) m_Command.Tick(now);
m_OnTick.Invoke(now);
}
protected void StopServices()
{
if (m_Stopped) return;
m_Stopped = true;
GetGame().GetCallqueue().Remove(ServerTick);
if (m_Command) m_Command.Stop();
m_OnStop.Invoke();
if (Replication.IsServer())
{
array<int> connected = {};
GetGame().GetPlayerManager().GetPlayers(connected);
foreach (int id : connected)
{
SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerManager().GetPlayerController(id));
if (controller) controller.ERR_SetObserver(ERR_ObserverReason.NONE);
}
foreach (IEntity entity : m_OwnedEntities)
if (SCR_AIGroup.Cast(entity)) ERR_Actors.DeleteSpawned(entity);
}
}
void ~ERR_GameMode()
{
if (GetGame())
{
GetGame().GetCallqueue().Remove(StartScenario);
GetGame().GetCallqueue().Remove(BindLocalObserver);
StopServices();
}
m_Locations.Clear();
m_OwnedEntities.Clear();
}
}
@@ -0,0 +1,12 @@
// Initial preparation only. A READY site service never consults this rule again.
class ERR_InitialGuardRules
{
static float TimeoutMilliseconds() { return 90000; }
static ERR_PrepareResult State(int requested, int physical, int conscious, bool queueComplete, bool expired)
{
if (requested <= 0 || physical > requested || conscious > physical || conscious < 0 || physical < 0) return ERR_PrepareResult.FAILED;
if (expired) return ERR_PrepareResult.FAILED;
if (physical == requested && conscious == requested && queueComplete) return ERR_PrepareResult.READY;
return ERR_PrepareResult.PENDING;
}
}
@@ -0,0 +1,19 @@
// GetItems may enumerate the same physical entity through multiple storages.
// Identity is the entity reference, never its prefab: distinct rounds still count.
class ERR_Inventory
{
static int AppendUnique(InventoryStorageManagerComponent inventory, notnull array<IEntity> items)
{
if (!inventory) return items.Count();
array<IEntity> raw = {};
inventory.GetItems(raw);
foreach (IEntity item : raw)
if (item && !items.Contains(item)) items.Insert(item);
return items.Count();
}
static int Items(InventoryStorageManagerComponent inventory, out array<IEntity> items)
{
items.Clear();
return AppendUnique(inventory, items);
}
}
@@ -0,0 +1,23 @@
// Attach only to a successor-owned prisoner prefab, with a native action duration.
class ERR_LoadCasualtyAction : ScriptedUserAction
{
override bool CanBeShownScript(IEntity user)
{
return ERR_CasualtyLoadingRules.Nearby(ERR_GameMode.Get(), user, GetOwner());
}
override bool CanBePerformedScript(IEntity user)
{
ERR_GameMode mode = ERR_GameMode.Get();
if (!ERR_CasualtyLoadingRules.Nearby(mode, user, GetOwner())) return false;
// Clients do not own the authoritative roster. Server always checks again.
if (!Replication.IsServer()) return true;
return mode.CasualtyLoading() && mode.CasualtyLoading().CanLoad(user, GetOwner());
}
override bool HasLocalEffectOnlyScript() { return false; }
override void PerformAction(IEntity pOwnerEntity, IEntity pUserEntity)
{
if (!Replication.IsServer() || pOwnerEntity != GetOwner()) return;
ERR_GameMode mode = ERR_GameMode.Get();
if (mode && mode.CasualtyLoading()) mode.CasualtyLoading().Begin(pUserEntity, pOwnerEntity);
}
}
@@ -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);
}
}
@@ -0,0 +1,69 @@
enum ERR_LocationKind { PRISON, RADIO, EXTRACTION, OUTPOST, DEPOT }
[ComponentEditorProps(category: "Rereintroduced", description: "Server-owned location with replicated discovery state")]
class ERR_LocationComponentClass : ScriptComponentClass {}
class ERR_LocationComponent : ScriptComponent
{
[RplProp(onRplName: "IdentityChanged"), Attribute("", UIWidgets.EditBox, "Stable scenario location ID")]
protected string m_Id;
[RplProp()]
protected string m_Label;
[RplProp(), Attribute("0", UIWidgets.ComboBox, "Location kind", enums: ParamEnumArray.FromEnum(ERR_LocationKind))]
protected ERR_LocationKind m_Kind;
[RplProp()]
protected int m_Knowledge;
string Id() { return m_Id; }
string Label() { return m_Label; }
ERR_LocationKind Kind() { return m_Kind; }
int Knowledge() { return m_Knowledge; }
vector Position() { return GetOwner().GetOrigin(); }
bool Configure(string id, ERR_LocationKind kind, string label = "")
{
if (!Replication.IsServer() || id.IsEmpty()) return false;
ERR_GameMode mode = ERR_GameMode.Get();
if (!mode || mode.Phase() != ERR_Phase.PREPARING) return false;
mode.UnregisterLocation(this);
m_Id = id;
m_Label = label;
m_Kind = kind;
mode.RegisterLocation(this);
Replication.BumpMe();
return true;
}
protected void IdentityChanged()
{
ERR_GameMode mode = ERR_GameMode.Get();
if (!mode) return;
mode.UnregisterLocation(this);
mode.RegisterLocation(this);
}
bool Discover(int roll, int chance)
{
if (!Replication.IsServer() || m_Knowledge >= 2) return false;
int previous = m_Knowledge;
m_Knowledge = ERR_MissionRules.Discovery(m_Knowledge, roll, chance);
Replication.BumpMe();
ERR_GameMode mode = ERR_GameMode.Get();
if (mode) mode.LocationDiscovered(this, previous, m_Knowledge);
return true;
}
override void OnPostInit(IEntity owner)
{
super.OnPostInit(owner);
if (!SCR_Global.IsEditMode()) SetEventMask(owner, EntityEvent.INIT);
}
protected override void EOnInit(IEntity owner)
{
ERR_GameMode mode = ERR_GameMode.Get();
if (mode) mode.RegisterLocation(this);
}
protected override void OnDelete(IEntity owner)
{
ERR_GameMode mode = ERR_GameMode.Get();
if (mode) mode.UnregisterLocation(this);
super.OnDelete(owner);
}
}
@@ -0,0 +1,46 @@
enum ERR_Phase
{
PREPARING,
PRISON,
RADIO_SEARCH,
RADIO_DEFENCE,
EXTRACTION,
VICTORY,
DEFEAT
}
// Pure mission policy. Entity queries and authority checks belong to the owner.
class ERR_MissionRules
{
static bool Terminal(int phase)
{
return phase == ERR_Phase.VICTORY || phase == ERR_Phase.DEFEAT;
}
static bool CanTransition(int from, int to)
{
if (Terminal(from) || from == to) return false;
if (to == ERR_Phase.DEFEAT) return from >= ERR_Phase.PRISON && from <= ERR_Phase.EXTRACTION;
if (from == ERR_Phase.PREPARING) return to == ERR_Phase.PRISON;
if (from == ERR_Phase.PRISON) return to == ERR_Phase.RADIO_SEARCH;
if (from == ERR_Phase.RADIO_SEARCH) return to == ERR_Phase.RADIO_DEFENCE;
if (from == ERR_Phase.RADIO_DEFENCE) return to == ERR_Phase.EXTRACTION;
return from == ERR_Phase.EXTRACTION && to == ERR_Phase.VICTORY;
}
static bool DefenceComplete(float now, float since, float duration, bool occupied)
{
return occupied && since >= 0 && duration > 0 && now >= since && now - since >= duration;
}
static bool ContactFresh(float observed, float now)
{
return observed >= 0 && now >= observed && now - observed <= 120000;
}
static int Discovery(int knowledge, int roll, int identifyPercent)
{
if (knowledge >= 1 || roll < identifyPercent) return 2;
return 1;
}
}
@@ -0,0 +1,76 @@
#ifdef WORKBENCH
// Test adapter exercises the production setting handler with a disposable file.
// Actual mode.Admin below still tests its server authorization and alias dispatch.
class ERR_NativeAdminSettingsAdapter : ERR_AdminService
{
void Probe(ERR_Settings settings, string arguments, array<string> replies)
{
replies.Clear();
arguments = arguments.Trim(); arguments.ToLower();
array<string> parts = {};
arguments.Split(" ", parts, true);
if (parts.Count() != 3 && parts.Count() != 4) return;
string key = Setting(parts[0], parts[1]);
if (!key.IsEmpty()) SettingCommand(settings, key, parts, replies);
}
}
class ERR_NativeAdminSettingsProbe
{
protected static bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: admin settings " + detail); return true; }
Print("ERR SMOKE FAIL: admin settings " + detail, LogLevel.ERROR);
return false;
}
protected static bool Storage(string path)
{
ERR_Settings settings = new ERR_Settings(path);
ERR_NativeAdminSettingsAdapter adapter = new ERR_NativeAdminSettingsAdapter();
array<string> replies = {};
map<string, int> initial = new map<string, int>();
foreach (string key : ERR_Settings.Keys()) initial.Set(key, settings.Get(key));
array<int> values = {0, 100, 15};
foreach (int value : values)
{
adapter.Probe(settings, " INTEL REVEAL SET " + value.ToString() + " ", replies);
if (!Check(replies.Count() == 1 && replies[0] == "intelRevealChancePercent=" + value.ToString() + " saved" && settings.Get("intelRevealChancePercent") == value, "authorized setting handler accepts normalized value " + value.ToString())) return false;
ERR_Settings reloaded = new ERR_Settings(path);
if (!Check(reloaded.Get("intelRevealChancePercent") == value, "discovery probability survives native JSON reload")) return false;
foreach (string key, int original : initial)
if (key != "intelRevealChancePercent" && !Check(reloaded.Get(key) == original, "unrelated setting preserved: " + key)) return false;
}
array<string> invalid = {"-1", "101", "1.5", "100junk", "999999"};
foreach (string argument : invalid)
{
adapter.Probe(settings, "intel reveal set " + argument, replies);
ERR_Settings reloaded = new ERR_Settings(path);
if (!Check(replies.Count() == 1 && replies[0].Contains("Invalid") && replies[0].Contains("0-100") && settings.Get("intelRevealChancePercent") == 15 && reloaded.Get("intelRevealChancePercent") == 15, "invalid input preserves live and stored value: " + argument)) return false;
}
adapter.Probe(settings, "intel reveal help", replies);
if (!Check(replies.Count() == 1 && replies[0].Contains("second document") && replies[0].Contains("newly recovered") && replies[0].Contains("remain unchanged"), "help explains exact versus rough discovery and apply timing")) return false;
adapter.Probe(settings, "mission radio show", replies);
return Check(replies.Count() == 1 && replies[0] == "radioDefenceSeconds=" + initial.Get("radioDefenceSeconds").ToString(), "existing radio alias and response remain intact");
}
static bool Run(ERR_GameMode mode)
{
if (!Check(Replication.IsServer() && mode && mode.Admin() && mode.Settings(), "native Core command service and settings ready")) return false;
int original = mode.Settings().Get("intelRevealChancePercent");
array<string> replies = {};
mode.Admin().Execute(mode, false, "intel reveal set 100", replies);
if (!Check(replies.Count() == 1 && replies[0] == "Administrator access required." && mode.Settings().Get("intelRevealChancePercent") == original, "actual unauthorized command cannot mutate discovery")) return false;
mode.Admin().Execute(mode, true, " INTEL REVEAL SHOW ", replies);
if (!Check(replies.Count() == 1 && replies[0] == "intelRevealChancePercent=" + original.ToString(), "actual Core alias dispatches without Chat")) return false;
mode.Admin().Execute(mode, true, "intel reveal set 101", replies);
if (!Check(replies.Count() == 1 && replies[0].Contains("0-100") && mode.Settings().Get("intelRevealChancePercent") == original, "actual dispatch rejects out-of-range value with guidance")) return false;
mode.Admin().Execute(mode, true, "intel reveal help", replies);
if (!Check(replies.Count() == 1 && replies[0].Contains("second document"), "actual Core help describes physical document progression")) return false;
string path = "$profile:erri-native-admin-settings-probe.json";
if (!Check(!FileIO.FileExists(path) && !FileIO.FileExists(path + ".bak") && !FileIO.FileExists(path + ".tmp"), "disposable fixture filenames are unused")) return false;
bool result = Storage(path);
FileIO.DeleteFile(path); FileIO.DeleteFile(path + ".bak"); FileIO.DeleteFile(path + ".tmp");
if (!result) return false;
return Check(mode.Settings().Get("intelRevealChancePercent") == original && !FileIO.FileExists(path) && !FileIO.FileExists(path + ".bak") && !FileIO.FileExists(path + ".tmp"), "fixture removed its files and never changed actual mission settings");
}
}
#endif
@@ -0,0 +1,738 @@
// Explicit local engine fixture, excluded from game/server builds.
#ifdef WORKBENCH
modded class ERR_GameMode
{
protected ref ERR_NativeEnvironmentRestartProbe m_ERRBaseEnvironmentRestartProbe;
protected override string SettingsPath()
{
string enabled, outcome;
if (System.GetCLIParam("errBaseSmoke", enabled) && enabled == "1" && System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "environment-restart")
{
m_ERRBaseEnvironmentRestartProbe = new ERR_NativeEnvironmentRestartProbe(this);
return ERR_NativeEnvironmentRestartProbe.PATH;
}
return super.SettingsPath();
}
protected ref ERR_NativeCasualtyServiceProbe m_ERRCasualtyServiceProbe;
protected ref ERR_NativePrisonerProbe m_ERRPrisonerProbe;
protected ref ERR_NativeGuardCancelProbe m_ERRGuardCancelProbe;
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 ref ERR_NativePatrolProbe m_ERRPatrolEast;
protected ref ERR_NativePatrolProbe m_ERRPatrolWest;
protected int m_ERRBaseStep;
protected bool m_ERRObjectiveCloseChecked;
protected bool m_ERRStartupFailure;
protected bool m_ERRCacheLoss;
protected string m_ERRExtractionCase;
protected ref ERR_NativeExtractionProbe m_ERRExtractionProbe;
protected ref ERR_NativeTransportProbe m_ERRTransportProbe;
protected ref ERR_NativeLifecycleProbe m_ERRLifecycleProbe;
protected ref ERR_NativeRecoveryProbe m_ERRRecoveryProbe;
protected ref ERR_NativeRadioDefenceProbe m_ERRBaseRadioProbe;
protected string m_ERRActiveCacheId = "everon-supply-1";
protected int m_ERRMedicalDiscoveries;
protected IEntity m_ERRSightBlocker;
protected float m_ERRBaseDeadline;
protected float m_ERRBaseWait;
protected IEntity m_ERRBasePlayer;
protected ERR_LocationComponent m_ERRBaseRadio;
protected ref array<IEntity> m_ERRBaseDocuments = {};
protected override void EOnInit(IEntity owner)
{
super.EOnInit(owner);
string enabled;
if (SCR_Global.IsEditMode() || !Replication.IsServer() || !System.GetCLIParam("errBaseSmoke", enabled) || enabled != "1") return;
ERR_NativeObjectiveProbe.BeginRound();
string outcome;
m_ERRStartupFailure = System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "startup-failure";
m_ERRExtractionCase = outcome;
if (outcome == "extraction-startup-failure" && m_Factions) m_Factions.ExtractionPrefab = "{259EE7B78C51B624}Prefabs/Vehicles/Wheeled/UAZ469/UAZ469.et";
if (outcome == "extraction-boundary-failure") m_EscapeRadius = 0;
m_ERRCacheLoss = outcome == "cache-loss";
if (m_ERRCacheLoss) m_ERRActiveCacheId = "everon-supply-2";
OnLocationDiscovered().Insert(ERR_BaseDiscovered);
OnStop().Insert(ERR_NativeObjectiveProbe.Stop);
OnStop().Insert(ERR_StopRadioProbe);
OnStop().Insert(ERR_StopHealthProbes);
if (m_ERRStartupFailure && m_Factions) m_Factions.EnemyFactionKey = m_Factions.PlayerFactionKey;
m_ERRBaseDeadline = GetWorld().GetWorldTime() + 210000;
if (outcome == "patrols") m_ERRBaseDeadline = GetWorld().GetWorldTime() + 330000;
OnPhaseChanged().Insert(ERR_BaseResult);
GetGame().GetCallqueue().CallLater(ERR_BaseTick, 500, true);
Print("ERR BASE SMOKE: core-only scripted escape; not manual or multiplayer acceptance");
}
protected bool ERR_BaseCheck(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: base " + detail); return true; }
ERR_NativeObjectiveProbe.Stop();
ERR_StopRadioProbe();
ERR_StopHealthProbes();
Print("ERR SMOKE FAIL: base " + detail, LogLevel.ERROR);
GetGame().GetCallqueue().Remove(ERR_BaseTick);
return false;
}
protected void ERR_StopHealthProbes()
{
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_ERRCasualtyServiceProbe) m_ERRCasualtyServiceProbe.Stop();
if (m_ERRObjectiveLossProbe) m_ERRObjectiveLossProbe.Stop();
if (m_ERRBaseMedicalProbe) m_ERRBaseMedicalProbe.Stop();
if (m_ERRCasualtyLoadProbe) m_ERRCasualtyLoadProbe.Cleanup();
if (m_ERRRecoveryProbe) m_ERRRecoveryProbe.Stop();
}
protected void ERR_StopRadioProbe() { if (m_ERRBaseRadioProbe) m_ERRBaseRadioProbe.Stop(); }
protected void ERR_BaseMove(vector position)
{
vector transform[4];
m_ERRBasePlayer.GetWorldTransform(transform);
transform[3] = ERR_Actors.Ground(position);
ChimeraCharacter.Cast(m_ERRBasePlayer).Teleport(transform);
}
protected ERR_UnlockPrisonAction ERR_BaseLock(IEntity entity)
{
BaseActionsManagerComponent manager = BaseActionsManagerComponent.Cast(entity.FindComponent(BaseActionsManagerComponent));
if (manager)
{
array<BaseUserAction> actions = {};
manager.GetActionsList(actions);
foreach (BaseUserAction action : actions)
{
ERR_UnlockPrisonAction lock = ERR_UnlockPrisonAction.Cast(action);
if (lock) return lock;
}
}
IEntity child = entity.GetChildren();
while (child)
{
ERR_UnlockPrisonAction lock = ERR_BaseLock(child);
if (lock) return lock;
child = child.GetSibling();
}
return null;
}
protected bool ERR_BaseCover()
{
int count;
foreach (IEntity entity : m_OwnedEntities)
{
if (!entity || !entity.GetPrefabData() || entity.GetPrefabData().GetPrefabName() != "{4B6AC1D8701C35A1}Prefabs/ERR/FieldCover.et") continue;
vector transform[4];
entity.GetWorldTransform(transform);
vector up = transform[1];
if (!ERR_BaseCheck(up[1] > 0.99, "outpost barrier is upright")) return false;
vector low, high;
entity.GetWorldBounds(low, high);
vector center = (low + high) * 0.5;
vector across = "0 0 2";
if (high[0] - low[0] < high[2] - low[2]) across = "2 0 0";
TraceParam trace = new TraceParam();
trace.Start = center + across;
trace.End = center - across;
trace.Flags = TraceFlags.ENTS;
trace.LayerMask = TRACE_LAYER_CAMERA;
float fraction = GetWorld().TraceMove(trace, null);
PrintFormat("ERR COVER GEOMETRY: low=%1 high=%2 trace=%3", low, high, fraction);
if (!ERR_BaseCheck(fraction < 1 && trace.TraceEnt == entity, "outpost barrier has physical collision")) return false;
count++;
}
return ERR_BaseCheck(count == 3, "exactly three authored outpost barriers exist");
}
protected void ERR_BaseDiscovered(ERR_LocationComponent location, int previous, int current)
{
if (location.Id() == "everon-medical-1" && previous == 0 && current == 2) m_ERRMedicalDiscoveries++;
}
protected void ERR_BaseResult(ERR_Phase previous, ERR_Phase current)
{
if (!ERR_MissionRules.Terminal(current)) return;
GetGame().GetCallqueue().Remove(ERR_BaseTick);
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;
ERR_FailureGameOverScreenInfo result = new ERR_FailureGameOverScreenInfo();
if (!ERR_BaseCheck(EndReason().Contains("helicopter was lost") && result.GetDebriefing(0, null) == EndReason(), "early loss has a specific replicated and debriefing reason")) return;
Print("ERR SMOKE COMPLETE: early extraction vehicle loss");
return;
}
if (m_ERRExtractionCase == "casualty-service")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1402, "casualty service fixture finished before explicit test reset")) return;
Print("ERR SMOKE COMPLETE: native casualty service with helper-only identity adapter; reset requested by fixture");
return;
}
if (m_ERRExtractionCase == "radio-document-loss" || m_ERRExtractionCase == "radio-document-loss-partial")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1302, "radio document-loss fixture finished before explicit test reset")) return;
Print("ERR SMOKE COMPLETE: native " + m_ERRExtractionCase + "; reset requested by fixture");
return;
}
if (m_ERRExtractionCase == "guard-cancel")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1502, "guard cancellation fixture finished before explicit test reset")) return;
Print("ERR SMOKE COMPLETE: native guard-cancel; reset requested by fixture");
return;
}
if (m_ERRExtractionCase == "prisoner-kit")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 702, "prisoner-kit fixture finished before explicit test reset")) return;
Print("ERR SMOKE COMPLETE: native prisoner-kit; reset requested by fixture");
return;
}
if (m_ERRExtractionCase == "medical")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 602, "medical fixture finished before explicit test reset")) return;
Print("ERR SMOKE COMPLETE: native medical; reset requested by fixture");
return;
}
if ((m_ERRExtractionCase == "casualty-load" || m_ERRExtractionCase == "casualty-direct" || m_ERRExtractionCase == "casualty-bench" || m_ERRExtractionCase == "casualty-remove"))
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 802, "casualty-load fixture finished before explicit test reset")) return;
Print("ERR SMOKE COMPLETE: native " + m_ERRExtractionCase + "; reset requested by fixture");
return;
}
if (m_ERRExtractionCase == "environment-restart")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1202, "environment restart round finished before explicit test reset")) return;
PrintFormat("ERR SMOKE COMPLETE: environment restart round %1; reset requested by fixture", m_ERRBaseEnvironmentRestartProbe.Round());
return;
}
if (m_ERRExtractionCase == "environment")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1002, "environment fixture finished before explicit test reset")) return;
Print("ERR SMOKE COMPLETE: native environment; reset requested by fixture");
return;
}
if (m_ERRExtractionCase == "patrols")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1102, "both patrol routes finished before explicit test reset")) return;
Print("ERR SMOKE COMPLETE: native finite patrol routes and cleanup; reset requested by fixture");
return;
}
if (m_ERRExtractionCase == "recovery")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 502 && EndReason().Contains("administrator abandoned"), "explicit authorized abandonment uses normal defeat result")) return;
if (!ERR_BaseCheck(!RecoveryStatus().IsEmpty() && !AbandonRound(), "terminal event observes active warning and rejects repeated abandonment")) return;
Print("ERR SMOKE COMPLETE: recovery status and explicit administrator abandonment");
return;
}
if (m_ERRExtractionCase == "objective-ui")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 902, "objective UI fixture finished before explicit test reset")) return;
Print("ERR SMOKE COMPLETE: native objective UI close, reopen and deleted-task cleanup; reset requested by fixture");
return;
}
if (m_ERRExtractionCase == "lifecycle")
{
if (!ERR_BaseCheck(m_ERRBaseStep == 402, "lifecycle fixture finished before explicit test reset")) return;
Print("ERR SMOKE COMPLETE: native lifecycle admission guards; reset requested by fixture");
return;
}
if (m_ERRExtractionCase == "transport-loss")
{
if (!ERR_BaseCheck(m_ERRBaseStep == 302, "transport fixture finished before explicit test reset")) return;
Print("ERR SMOKE COMPLETE: finite native ground transport; reset requested by fixture");
return;
}
if (m_ERRExtractionCase == "extraction-capacity")
{
if (!ERR_BaseCheck(m_ERRBaseStep == 202, "capacity fixture finished before its explicit test reset")) return;
Print("ERR SMOKE COMPLETE: physical capacity and isolated extraction results; reset requested by fixture");
return;
}
if (!ERR_BaseCheck(current == ERR_Phase.VICTORY && m_ERRBaseStep == 5, "escape and restart reached without companion services")) return;
Print("ERR SMOKE COMPLETE: core-only escape");
}
protected void ERR_BaseTick()
{
float now = GetWorld().GetWorldTime();
if (now > m_ERRBaseDeadline) { ERR_BaseCheck(false, "timeout at " + m_ERRBaseStep.ToString()); return; }
if (m_ERRExtractionCase == "extraction-startup-failure" || m_ERRExtractionCase == "extraction-boundary-failure")
{
if (PreparationError().IsEmpty()) return;
if (!ERR_BaseCheck(Phase() == ERR_Phase.PREPARING && !Active() && m_Stopped, "invalid extraction setup never starts gameplay")) return;
array<IEntity> players = {};
ERR_Actors.Players(players);
if (!ERR_BaseCheck(players.IsEmpty() && Sites() && Sites().Count() == 0 && m_OwnedEntities.IsEmpty() && !ExtractionVehicle(), "late startup failure rolls back all authored stock, scenery, groups and vehicle")) return;
if (!ERR_BaseCheck(PreparationError().Contains("twelve") && !Transition(ERR_Phase.PRISON), "invalid extraction retains actionable diagnosis and blocks progression")) return;
GetGame().GetCallqueue().Remove(ERR_BaseTick);
Print("ERR SMOKE COMPLETE: expected late extraction startup failure");
return;
}
if (m_ERRStartupFailure)
{
if (PreparationError().IsEmpty()) return;
if (!ERR_BaseCheck(Phase() == ERR_Phase.PREPARING && !Active() && m_Stopped, "invalid faction setup stops services without starting the mission")) return;
array<IEntity> players = {};
ERR_Actors.Players(players);
if (!ERR_BaseCheck(players.IsEmpty() && !Sites() && m_OwnedEntities.IsEmpty(), "failed setup spawns no players, sites or supplies")) return;
array<string> replies = {};
Admin().Execute(this, true, "status", replies);
if (!ERR_BaseCheck(replies.Count() == 1 && replies[0].Contains("configured player and enemy factions"), "administrator receives retained startup failure cause")) return;
if (!ERR_BaseCheck(!Transition(ERR_Phase.PRISON) && !Transition(ERR_Phase.EXTRACTION), "failed preparation cannot be advanced into gameplay")) return;
GetGame().GetCallqueue().Remove(ERR_BaseTick);
Print("ERR SMOKE COMPLETE: expected startup-failure");
return;
}
if (m_ERRBaseStep == 1400)
{
int result = m_ERRCasualtyServiceProbe.Tick(now);
if (result < 0) { ERR_BaseCheck(false, "casualty service fixture failed"); return; }
if (result == 1) { m_ERRBaseStep = 1402; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 1300)
{
int result = m_ERRObjectiveLossProbe.Tick(now);
if (result < 0) { ERR_StopHealthProbes(); GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1) { m_ERRBaseStep = 1302; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 1500)
{
int result = m_ERRGuardCancelProbe.Tick(now);
if (result < 0) { ERR_StopHealthProbes(); GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1) { m_ERRBaseStep = 1502; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 700)
{
int result = m_ERRPrisonerProbe.Tick(now);
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1) { m_ERRBaseStep = 702; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 600)
{
int result = m_ERRBaseMedicalProbe.Tick(now);
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1) { m_ERRBaseStep = 602; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 800)
{
int result = m_ERRCasualtyLoadProbe.Tick(now);
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1) { m_ERRBaseStep = 802; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 1200)
{
int result = m_ERRBaseEnvironmentRestartProbe.Tick(now);
if (result < 0) { ERR_BaseCheck(false, "environment restart fixture failed"); return; }
if (result == 1) { m_ERRBaseStep = 1202; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 1000)
{
int result = m_ERRBaseEnvironmentProbe.Tick(now);
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1) { m_ERRBaseStep = 1002; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 1100)
{
int east = m_ERRPatrolEast.Tick(now);
int west = m_ERRPatrolWest.Tick(now);
if (east < 0 || west < 0) { ERR_StopHealthProbes(); GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (east == 1 && west == 1)
{
if (!ERR_NativePatrolProbe.StopChecks(this)) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
m_ERRBaseStep = 1102; Transition(ERR_Phase.DEFEAT);
}
return;
}
if (m_ERRBaseStep == 500)
{
int result = m_ERRRecoveryProbe.Tick(now);
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1)
{
m_ERRBaseStep = 502;
array<string> replies = {};
Admin().Execute(this, true, "mission abandon", replies);
ERR_BaseCheck(Phase() == ERR_Phase.DEFEAT && replies.Count() == 1 && replies[0].Contains("Round abandoned") && RecoveryStatus().IsEmpty() && Recovery().Conscious() == 0 && Recovery().Incapacitated() == 0, "administrator command returns accepted outcome after recovery cleanup");
}
return;
}
if (m_ERRBaseStep == 400)
{
int result = m_ERRLifecycleProbe.Tick(now);
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1) { m_ERRBaseStep = 402; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 300)
{
int result = m_ERRTransportProbe.Tick(now);
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1) { m_ERRBaseStep = 302; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 200)
{
int result = m_ERRExtractionProbe.Tick(now);
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1) { m_ERRBaseStep = 202; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 901)
{
int result = ERR_NativeObjectiveProbe.TickCloseReopen(this, now);
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1)
{
if (m_ERRExtractionCase == "objective-ui")
{
if (!ERR_NativeObjectiveProbe.BeginDeletedTaskClose(this, now)) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
m_ERRBaseStep = 903;
}
else { m_ERRObjectiveCloseChecked = true; m_ERRBaseStep = 0; }
}
return;
}
if (m_ERRBaseStep == 903)
{
int result = ERR_NativeObjectiveProbe.TickDeletedTaskClose(this, now);
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1) { m_ERRBaseStep = 902; Transition(ERR_Phase.DEFEAT); }
return;
}
if (now < m_ERRBaseWait) return;
if (m_ERRBaseStep == 0)
{
array<IEntity> players = {};
ERR_Actors.Players(players, true);
if (players.Count() != 1 || Phase() != ERR_Phase.PRISON) return;
m_ERRBasePlayer = players[0];
if (!m_ERRCatalogChecked)
{
if (!ERR_NativeFactionCatalogProbe.Run(this)) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
m_ERRCatalogChecked = true;
}
if (m_ERRExtractionCase == "casualty-service")
{
m_ERRCasualtyServiceProbe = new ERR_NativeCasualtyServiceProbe(this);
m_ERRBaseStep = 1400;
return;
}
if (m_ERRExtractionCase == "radio-document-loss" || m_ERRExtractionCase == "radio-document-loss-partial")
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRObjectiveLossProbe = new ERR_NativeObjectiveLossProbe(this, m_ERRBasePlayer, m_ERRExtractionCase == "radio-document-loss-partial");
m_ERRBaseStep = 1300;
return;
}
if (m_ERRExtractionCase == "guard-cancel")
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRGuardCancelProbe = new ERR_NativeGuardCancelProbe(this);
m_ERRBaseStep = 1500;
return;
}
if (m_ERRExtractionCase == "prisoner-kit")
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRPrisonerProbe = new ERR_NativePrisonerProbe(this, m_ERRBasePlayer);
m_ERRBaseStep = 700;
return;
}
if (m_ERRExtractionCase == "medical")
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRBaseMedicalProbe = new ERR_NativeMedicalProbe(this);
m_ERRBaseStep = 600;
return;
}
if ((m_ERRExtractionCase == "casualty-load" || m_ERRExtractionCase == "casualty-direct" || m_ERRExtractionCase == "casualty-bench" || m_ERRExtractionCase == "casualty-remove"))
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
if (m_ERRExtractionCase == "casualty-remove") m_ERRCasualtyLoadProbe = new ERR_NativeCasualtyRemoveProbe(this);
else m_ERRCasualtyLoadProbe = new ERR_NativeCasualtyLoadProbe(this, m_ERRExtractionCase != "casualty-load", m_ERRExtractionCase == "casualty-bench");
m_ERRBaseStep = 800;
return;
}
if (m_ERRExtractionCase == "environment-restart")
{
if (!ERR_BaseCheck(m_ERRBaseEnvironmentRestartProbe != null, "environment restart selected isolated settings before mode initialization")) return;
m_ERRBaseStep = 1200;
return;
}
if (m_ERRExtractionCase == "environment")
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRBaseEnvironmentProbe = new ERR_NativeEnvironmentProbe(this);
m_ERRBaseStep = 1000;
return;
}
if (m_ERRExtractionCase == "patrols")
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRPatrolEast = new ERR_NativePatrolProbe(this, "everon-outpost-1");
m_ERRPatrolWest = new ERR_NativePatrolProbe(this, "everon-supply-2");
m_ERRBaseStep = 1100;
return;
}
if (m_ERRExtractionCase == "recovery")
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRRecoveryProbe = new ERR_NativeRecoveryProbe(this, m_ERRBasePlayer);
m_ERRBaseStep = 500;
return;
}
if (m_ERRExtractionCase == "lifecycle")
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRLifecycleProbe = new ERR_NativeLifecycleProbe(this, m_ERRBasePlayer);
m_ERRBaseStep = 400;
return;
}
if (m_ERRExtractionCase == "transport-survey")
{
ERR_NativeTransportSurvey.Run(this);
GetGame().GetCallqueue().Remove(ERR_BaseTick);
return;
}
if (m_ERRExtractionCase == "transport-loss")
{
m_ERRTransportProbe = new ERR_NativeTransportProbe(this);
m_ERRBaseStep = 300;
return;
}
if (m_ERRExtractionCase == "early-helicopter-loss")
{
if (!ERR_BaseCheck(Extraction().Ready() && Players().HasSurvivor(), "ready helicopter and living prisoner before deliberate loss")) return;
m_ERRBaseStep = 201;
ERR_Actors.DeleteSpawned(ExtractionVehicle());
return;
}
if (m_ERRExtractionCase == "extraction-capacity")
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRExtractionProbe = new ERR_NativeExtractionProbe(this);
m_ERRBaseStep = 200;
return;
}
string survey;
if (System.GetCLIParam("errSmokeOutcome", survey) && survey == "site-survey")
{
ERR_NativeSiteSurvey probe = new ERR_NativeSiteSurvey();
probe.Run();
GetGame().GetCallqueue().Remove(ERR_BaseTick);
return;
}
if (!m_ERRObjectiveCloseChecked)
{
if (!ERR_NativeObjectiveProbe.BeginCloseReopen(this, now)) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
m_ERRBaseStep = 901;
return;
}
if (!ERR_NativeAdminSettingsProbe.Run(this) || !ERR_NativeDefinitionProbe.Run() || !ERR_NativeContractsProbe.Run(this, m_ERRBasePlayer) || !ERR_BaseCover() || !ERR_NativeTimeProbe.Run(this)) return;
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
if (!ERR_BaseCheck(Objectives() && Objectives().Current() && Objectives().Current().GetTaskName() == "Escape the prison", "native prison objective exists")) return;
if (!ERR_NativeObjectiveProbe.Observe(this)) return;
ERR_SiteRuntime cache = Sites().Find(m_ERRActiveCacheId);
if (!ERR_BaseCheck(Sites().Count() == 7 && cache && cache.Container && vector.DistanceXZ(cache.Container.GetOrigin(), PrisonPosition()) > 100, "authored supply cache is a separate scavenging location")) return;
array<IEntity> stock = {};
ERR_Inventory.Items(InventoryStorageManagerComponent.Cast(cache.Container.FindComponent(InventoryStorageManagerComponent)), stock);
PrintFormat("ERR BASE STOCK: cache items=%1", stock.Count());
int expectedStock;
foreach (ERR_SiteStock entry : cache.Definition.Stock) expectedStock += entry.Count;
if (!ERR_BaseCheck(stock.Count() >= expectedStock, "cache holds finite physical equipment and objective documents")) return;
if (!ERR_BaseCheck(Command() == null, "no companion HQ or support controller loaded")) return;
if (!ERR_BaseCheck(vector.DistanceXZ(m_ERRBasePlayer.GetOrigin(), PrisonPosition()) < 5, "prisoner spawned in authored prison")) return;
foreach (ERR_LocationComponent location : m_Locations)
if (location.Kind() == ERR_LocationKind.RADIO) m_ERRBaseRadio = location;
ERR_UnlockPrisonAction lock;
foreach (IEntity entity : m_OwnedEntities)
{
if (!entity) continue;
if (!lock) lock = ERR_BaseLock(entity);
}
foreach (IEntity item : stock)
if (item && item.GetPrefabData() && item.GetPrefabData().GetPrefabName() == ERR_Resources.ObjectiveDocument()) m_ERRBaseDocuments.Insert(item);
if (m_ERRCacheLoss)
{
ERR_Actors.DeleteSpawned(Sites().Find("everon-supply-1").Container);
if (!ERR_BaseCheck(!Sites().Find("everon-supply-1").Container, "primary cache lost before any clues are recovered")) return;
}
if (!ERR_BaseCheck(lock && m_ERRBaseRadio && m_ERRBaseDocuments.Count() == 2 && ERR_Actors.Alive(ExtractionVehicle()), "gate, clues, radio and helicopter created by core")) return;
ERR_BaseMove(lock.GetOwner().GetOrigin() + "1 0 1");
lock.PerformAction(lock.GetOwner(), m_ERRBasePlayer);
if (!ERR_BaseCheck(Phase() == ERR_Phase.RADIO_SEARCH, "gate action begins escape")) return;
if (!ERR_BaseCheck(m_Settings.Set("radioDefenceSeconds", 30), "test defence duration accepted")) return;
SCR_Task prisonTask = SCR_TaskSystem.GetTaskFromTaskID(ERR_Objectives.Id(ERR_Phase.PRISON), false);
if (!ERR_BaseCheck(prisonTask && prisonTask.GetTaskState() == SCR_ETaskState.COMPLETED, "previous native objective completes on phase change")) return;
if (!ERR_BaseCheck(Objectives().Current().GetTaskUIVisibility() == SCR_ETaskUIVisibility.LIST_ONLY, "search objective does not expose a hidden site")) return;
if (!ERR_NativeObjectiveProbe.Observe(this)) return;
if (!ERR_BaseCheck(Sites().Find("everon-supply-1").Location.Knowledge() == 2 && Sites().Find("everon-supply-2").Location.Knowledge() == 2, "both named supply routes revealed after escape")) return;
ERR_SiteRuntime medical = Sites().Find("everon-medical-1");
if (!ERR_BaseCheck(medical.Location.Knowledge() == 0 && Sites().Find("everon-outpost-1").Location.Knowledge() == 0, "optional sites remain hidden until approached")) return;
ERR_BaseMove(medical.Location.Position() + "0 0 8");
CharacterControllerComponent.Cast(m_ERRBasePlayer.FindComponent(CharacterControllerComponent)).SetUnconscious(true);
m_ERRBaseStep = 10;
m_ERRBaseWait = now + 6000;
}
else if (m_ERRBaseStep == 10)
{
ERR_SiteRuntime medical = Sites().Find("everon-medical-1");
if (!ERR_BaseCheck(medical.Location.Knowledge() == 0 && !Sites().CanObserve(m_ERRBasePlayer, medical), "unconscious player cannot discover optional sites")) return;
CharacterControllerComponent.Cast(m_ERRBasePlayer.FindComponent(CharacterControllerComponent)).SetUnconscious(false);
ERR_BaseMove(medical.Location.Position() + "0 0 65");
m_ERRBaseStep = 11;
m_ERRBaseWait = now + 6000;
}
else if (m_ERRBaseStep == 11)
{
ERR_SiteRuntime medical = Sites().Find("everon-medical-1");
if (!ERR_BaseCheck(medical.Location.Knowledge() == 0 && !Sites().CanObserve(m_ERRBasePlayer, medical), "distant player cannot discover optional sites")) return;
ERR_BaseMove(medical.Location.Position() + "0 0 8");
m_ERRSightBlocker = ERR_Actors.Spawn("{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et", ERR_Actors.Ground(medical.Location.Position() + "-1.5 0 4"));
if (!ERR_BaseCheck(m_ERRSightBlocker != null, "isolated visibility obstacle created")) return;
Own(m_ERRSightBlocker);
vector low, high;
m_ERRSightBlocker.GetWorldBounds(low, high);
PrintFormat("ERR VISIBILITY OBSTACLE: low=%1 high=%2 observer=%3 target=%4", low, high, m_ERRBasePlayer.GetOrigin(), medical.Location.Position());
m_ERRBaseStep = 115;
m_ERRBaseWait = now + 6000;
}
else if (m_ERRBaseStep == 115)
{
ERR_SiteRuntime medical = Sites().Find("everon-medical-1");
if (!ERR_BaseCheck(medical.Location.Knowledge() == 0 && !Sites().CanObserve(m_ERRBasePlayer, medical), "physical wall prevents nearby site discovery")) return;
ERR_Actors.DeleteSpawned(m_ERRSightBlocker);
m_ERRBaseStep = 12;
m_ERRBaseWait = now + 6000;
}
else if (m_ERRBaseStep == 12)
{
ERR_SiteRuntime medical = Sites().Find("everon-medical-1");
if (!ERR_BaseCheck(medical.Location.Knowledge() == 2 && medical.Location.Label() == "Medical stash" && m_ERRMedicalDiscoveries == 1, "conscious nearby player visually discovers named medical stash once")) return;
if (!ERR_BaseCheck(!Sites().CanObserve(m_ERRBasePlayer, Sites().Find("everon-radio-1")), "distant medical-site discovery does not reveal the radio")) return;
m_ERRBaseStep = 1;
}
else if (m_ERRBaseStep == 1 || m_ERRBaseStep == 2)
{
ERR_BaseMove(Sites().Find(m_ERRActiveCacheId).Container.GetOrigin() + "0 0 2");
SCR_InventoryStorageManagerComponent inventory = SCR_InventoryStorageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(SCR_InventoryStorageManagerComponent));
InventoryStorageManagerComponent source = InventoryStorageManagerComponent.Cast(Sites().Find(m_ERRActiveCacheId).Container.FindComponent(InventoryStorageManagerComponent));
BaseInventoryStorageComponent destination = inventory.FindStorageForItem(m_ERRBaseDocuments[m_ERRBaseStep - 1]);
if (!ERR_BaseCheck(destination && source.TryMoveItemToStorage(m_ERRBaseDocuments[m_ERRBaseStep - 1], destination), "physical clue recovered")) return;
m_ERRBaseStep++;
m_ERRBaseWait = now + 2500;
}
else if (m_ERRBaseStep == 3)
{
if (!ERR_BaseCheck(Objectives().Current().GetTaskDescription().Contains("The radio station is identified on your map. Reach"), "objective guidance reacts to discovered radio")) return;
if (!ERR_NativeObjectiveProbe.Observe(this)) return;
ERR_SiteRuntime cache = Sites().Find(m_ERRActiveCacheId);
array<IEntity> stock = {};
ERR_Inventory.Items(InventoryStorageManagerComponent.Cast(cache.Container.FindComponent(InventoryStorageManagerComponent)), stock);
foreach (IEntity item : stock) if (item) SCR_EntityHelper.DeleteEntityAndChildren(item);
if (!ERR_BaseCheck(m_ERRMedicalDiscoveries == 1, "discovery ticks do not repeat the location event")) return;
if (m_ERRCacheLoss && !ERR_BaseCheck(!Sites().Find("everon-supply-1").Container, "lost primary cache stays absent while alternate documents identify the radio")) return;
if (!ERR_BaseCheck(m_ERRBaseRadio.Knowledge() == 2, "documents identify radio without Intel addon")) return;
ERR_BaseMove(m_ERRBaseRadio.Position() + "2 0 0");
m_ERRBaseStep = 30;
m_ERRBaseWait = now + 1000;
}
else if (m_ERRBaseStep == 30)
{
m_ERRBaseRadioProbe = new ERR_NativeRadioDefenceProbe(this, m_ERRBasePlayer, m_ERRBaseRadio);
m_ERRBaseStep = 301;
}
else if (m_ERRBaseStep == 301)
{
int result = m_ERRBaseRadioProbe.Tick(now);
if (result < 0) { ERR_StopRadioProbe(); GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 0) return;
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.INACTIVE, "radio feedback remains inactive before tuning")) return;
if (!ERR_BaseCheck(TuneRadio(m_ERRBasePlayer, m_ERRBaseRadio), "identified radio accepts transmission")) return;
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.WAITING && ERR_ObjectiveText.Description(this, false).Contains("Waiting for"), "radio feedback publishes waiting before first occupancy sample")) return;
m_ERRBaseStep = 31;
m_ERRBaseWait = now + 5000;
}
else if (m_ERRBaseStep == 31)
{
ERR_SiteRuntime cache = Sites().Find(m_ERRActiveCacheId);
array<IEntity> stock = {};
ERR_Inventory.Items(InventoryStorageManagerComponent.Cast(cache.Container.FindComponent(InventoryStorageManagerComponent)), stock);
if (!ERR_BaseCheck(stock.IsEmpty() && Sites().Prepare() == ERR_PrepareResult.READY, "depleted stock stays empty when preparation is queried again")) return;
ERR_Actors.DeleteSpawned(cache.Container);
if (!ERR_BaseCheck(DefenceOccupied() && DefenceSecondsRemaining() > 0 && DefenceSecondsRemaining() < 30 && Objectives().Current().GetTaskDescription().Contains("seconds remaining"), "native objective reports advancing radio progress")) return;
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.TRANSMITTING && ERR_ObjectiveText.Description(this, false).Contains("Transmission in progress"), "radio guide reports transmitting without querying a countdown")) return;
if (!ERR_NativeObjectiveProbe.Observe(this)) return;
CharacterControllerComponent.Cast(m_ERRBasePlayer.FindComponent(CharacterControllerComponent)).SetUnconscious(true);
m_ERRBaseStep = 310;
m_ERRBaseWait = now + 35000;
}
else if (m_ERRBaseStep == 310)
{
CharacterControllerComponent controller = CharacterControllerComponent.Cast(m_ERRBasePlayer.FindComponent(CharacterControllerComponent));
if (!ERR_BaseCheck(controller.IsUnconscious() && ERR_Actors.Alive(m_ERRBasePlayer) && Players().HasSurvivor() && Phase() == ERR_Phase.RADIO_DEFENCE, "unconscious survivor does not trigger defeat after the full grace period")) return;
if (!ERR_BaseCheck(!DefenceOccupied() && DefenceSecondsRemaining() == 30, "unconscious radio defender cannot advance transmission")) return;
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.INTERRUPTED && ERR_ObjectiveText.Description(this, false).Contains("Progress has reset"), "radio guide reports interruption after unconsciousness")) return;
SCR_PlayerController playerController = SCR_PlayerController.Cast(GetGame().GetPlayerController());
if (!ERR_BaseCheck(playerController.GetControlledEntity() == m_ERRBasePlayer && playerController.ERR_ObserverState() == ERR_ObserverReason.NONE && !playerController.ERR_HasObserverCamera(), "unconscious player retains their body and camera")) return;
controller.SetUnconscious(false);
m_ERRBaseStep = 311;
m_ERRBaseWait = now + 6000;
}
else if (m_ERRBaseStep == 311)
{
if (!ERR_BaseCheck(ERR_Actors.CanFight(m_ERRBasePlayer) && DefenceOccupied() && DefenceSecondsRemaining() < 30, "regaining consciousness resumes transmission from zero")) return;
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.TRANSMITTING && ERR_ObjectiveText.Description(this, false).Contains("Transmission in progress"), "radio guide returns to transmitting after recovery")) return;
int playerId = GetGame().GetPlayerManager().GetPlayerIdFromControlledEntity(m_ERRBasePlayer);
if (!ERR_BaseCheck(!Players().CanEnter(playerId, Phase()), "regaining consciousness does not grant another life")) return;
ERR_BaseMove(m_ERRBaseRadio.Position() + "90 0 0");
m_ERRBaseStep = 32;
m_ERRBaseWait = now + 2500;
}
else if (m_ERRBaseStep == 32)
{
if (!ERR_BaseCheck(!Sites().Find(m_ERRActiveCacheId).Container, "lost supply cache is not replaced")) return;
if (!ERR_BaseCheck(!DefenceOccupied() && DefenceSecondsRemaining() == 30 && Objectives().Current().GetTaskDescription().Contains("interrupted"), "objective reports interruption and reset")) return;
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.INTERRUPTED && ERR_ObjectiveText.Description(this, false).Contains("Return a conscious survivor"), "radio guide reports interruption after leaving the area")) return;
if (!ERR_NativeObjectiveProbe.Observe(this)) return;
ERR_BaseMove(m_ERRBaseRadio.Position() + "2 0 0");
m_ERRBaseStep = 4;
m_ERRBaseWait = now + 33000;
}
else if (m_ERRBaseStep == 4)
{
if (!ERR_BaseCheck(Phase() == ERR_Phase.EXTRACTION, "radio defence reveals extraction") || !ERR_NativeTimeProbe.VerifyProgression(this)) return;
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.INACTIVE, "radio feedback clears after completion")) return;
if (!ERR_NativeObjectiveProbe.Observe(this)) return;
CompartmentAccessComponent access = CompartmentAccessComponent.Cast(m_ERRBasePlayer.FindComponent(CompartmentAccessComponent));
BaseCompartmentSlot slot = access.FindFreeCompartment(ExtractionVehicle(), ECompartmentType.PILOT);
if (!ERR_BaseCheck(slot && access.GetInVehicle(ExtractionVehicle(), slot, true, -1, ECloseDoorAfterActions.INVALID, false), "player physically boards extraction helicopter")) return;
m_ERRBaseStep = 5;
m_ERRBaseWait = now + 3000;
}
else if (m_ERRBaseStep == 5)
{
if (!ERR_BaseCheck(CompartmentAccessComponent.GetVehicleIn(m_ERRBasePlayer) == ExtractionVehicle(), "extraction requires real occupancy")) return;
vector transform[4];
ExtractionVehicle().GetWorldTransform(transform);
transform[3] = m_EscapeCenter + Vector(m_EscapeRadius + 100, 150, 0);
ExtractionVehicle().SetWorldTransform(transform);
m_ERRBaseWait = now + 4000;
}
}
}
#endif
@@ -0,0 +1,80 @@
#ifdef WORKBENCH
// Positive check observes production initial placement; the fixture never wakes
// the mission helicopter. Negative transforms
// and velocity are applied only to an independent, disposable empty aircraft.
class ERR_NativeCasualtyGroundProbe
{
protected ERR_GameMode m_Mode;
protected Vehicle m_Negative;
protected float m_Wait;
protected float m_Deadline;
protected int m_Step;
protected bool m_Stopped;
void ERR_NativeCasualtyGroundProbe(ERR_GameMode mode) { m_Mode = mode; }
void Stop()
{
if (m_Stopped) return;
m_Stopped = true;
ERR_Actors.DeleteSpawned(m_Negative); m_Negative = null;
}
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: casualty ground " + detail); return true; }
Print("ERR SMOKE FAIL: casualty ground " + detail, LogLevel.ERROR); Stop(); return false;
}
int Tick(float now)
{
if (m_Stopped) return -1;
if (m_Deadline > 0 && now > m_Deadline) { Check(false, "bounded ground-support probe deadline"); return -1; }
if (now < m_Wait) return 0;
if (m_Step == 0)
{
Vehicle parked = m_Mode.ExtractionVehicle();
ERR_SiteService sites = m_Mode.Sites();
ERR_ExtractionPlacement placement;
if (sites) placement = sites.ExtractionPlacement();
if (!Check(placement && placement.Ready() && placement.WakeCount() == 1, "production initial placement completed with exactly one wake before player spawn")) return -1;
bool support = ERR_CasualtyGroundSupport.Parked(parked, true);
HelicopterControllerComponent controller = HelicopterControllerComponent.Cast(parked.FindComponent(HelicopterControllerComponent));
bool nativeContact = controller && controller.GetSimulation() && controller.GetSimulation().HasAnyGroundContact();
if (!Check((nativeContact || support) && ERR_CasualtyLoadingRules.AircraftReady(parked), "naturally settled mission aircraft has native landing contact or three strict wheel supports")) return -1;
vector raised = parked.GetOrigin() + "0 20 0";
m_Negative = Vehicle.Cast(ERR_Actors.Spawn(m_Mode.Factions().ExtractionPrefab, raised));
if (!Check(m_Negative != null, "separate empty airborne aircraft created for rejection checks")) return -1;
m_Mode.Own(m_Negative);
DamageManagerComponent damage = DamageManagerComponent.Cast(m_Negative.FindComponent(DamageManagerComponent));
if (!Check(damage != null, "negative aircraft damage manager available")) return -1;
damage.EnableDamageHandling(false);
m_Deadline = now + 10000; m_Wait = now + 500; m_Step = 1; return 0;
}
if (m_Step == 1)
{
vector position = m_Negative.GetOrigin();
float height = position[1] - GetGame().GetWorld().GetSurfaceY(position[0], position[2]);
bool support = ERR_CasualtyGroundSupport.Parked(m_Negative, true);
PrintFormat("ERR GROUND NEGATIVE: raisedPosition=%1 terrainClearance=%2", position, height);
if (!Check(ERR_Actors.Alive(m_Negative) && height > 5 && !support && !ERR_CasualtyLoadingRules.AircraftReady(m_Negative), "living aircraft above terrain is rejected even if stationary or sleeping")) return -1;
vector tilted[4]; Math3D.AnglesToMatrix("0 0 90", tilted); tilted[3] = position;
m_Negative.SetWorldTransform(tilted);
m_Wait = now + 500; m_Step = 2; return 0;
}
if (m_Step == 2)
{
vector actual[4]; m_Negative.GetWorldTransform(actual);
Physics physics = m_Negative.GetPhysics();
if (!Check(physics != null, "negative aircraft has actual native physics")) return -1;
PrintFormat("ERR GROUND NEGATIVE: tiltedUp=%1 position=%2", actual[1][1], actual[3]);
if (!Check(ERR_Actors.Alive(m_Negative) && actual[1][1] < 0.85 && !ERR_CasualtyLoadingRules.AircraftMotion(true, actual[1][1], 0, 0) && !ERR_CasualtyLoadingRules.AircraftReady(m_Negative), "actual tilted aircraft is rejected independently by upright policy")) return -1;
// Read back an actual native velocity on the disposable clone. No mission
// aircraft activation, simulation state, engine or velocity is changed.
physics.SetVelocity("2 0 0");
float speed = physics.GetVelocity().Length();
PrintFormat("ERR GROUND NEGATIVE: nativeSpeed=%1", speed);
if (!Check(speed > 0.5 && !ERR_CasualtyLoadingRules.AircraftMotion(true, 1, speed, 0) && !ERR_CasualtyLoadingRules.AircraftReady(m_Negative), "actual native velocity is rejected independently by movement policy")) return -1;
if (!Check(ERR_Actors.Alive(m_Mode.ExtractionVehicle()) && ERR_CasualtyLoadingRules.AircraftReady(m_Mode.ExtractionVehicle()), "mission aircraft remains untouched and eligible after negative checks")) return -1;
Stop(); return 1;
}
return 0;
}
}
#endif
@@ -0,0 +1,255 @@
#ifdef WORKBENCH
// Native mechanism experiment, NOT proof of an available player load/drag action.
// Caller must explicitly opt in and tick this independently of medical fixtures.
class ERR_NativeCasualtyLoadProbe
{
protected ERR_GameMode m_Mode;
protected IEntity m_Caller;
protected IEntity m_Casualty;
protected Vehicle m_Vehicle;
protected SCR_CompartmentAccessComponent m_Access;
protected SCR_CharacterDamageManagerComponent m_Damage;
protected BaseCompartmentSlot m_Seat;
protected vector m_CallerTransform[4];
protected vector m_GroundPosition;
protected bool m_CallerMoved;
protected bool m_Direct;
protected bool m_Bench;
protected float m_DownSettledAt;
protected float m_NextDiagnostic;
protected int m_Entered;
protected int m_Left;
protected int m_Step;
protected float m_Deadline;
protected float m_Wait;
void ERR_NativeCasualtyLoadProbe(ERR_GameMode mode, bool direct = false, bool bench = false) { m_Mode = mode; m_Direct = direct; m_Bench = bench; }
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: casualty-load " + detail); return true; }
Print("ERR SMOKE FAIL: casualty-load " + detail, LogLevel.ERROR);
Cleanup();
return false;
}
void Cleanup()
{
if (m_Access)
{
if (m_Access.GetOnCompartmentEntered(false)) m_Access.GetOnCompartmentEntered(false).Remove(Entered);
if (m_Access.GetOnCompartmentLeft(false)) m_Access.GetOnCompartmentLeft(false).Remove(Left);
}
// Restore only the same living local body, never a replacement/reconnected actor.
PlayerController controller = GetGame().GetPlayerController();
if (m_CallerMoved && m_Caller && controller && controller.GetControlledEntity() == m_Caller && ERR_Actors.Alive(m_Caller) && !CompartmentAccessComponent.GetVehicleIn(m_Caller))
ChimeraCharacter.Cast(m_Caller).Teleport(m_CallerTransform);
m_CallerMoved = false;
ERR_Actors.DeleteSpawned(m_Casualty);
m_Casualty = null;
}
protected void Entered(IEntity target, BaseCompartmentManagerComponent manager, int managerId, int slotId, bool move)
{
m_Entered++;
PrintFormat("ERR CASUALTY ENTER EVENT: manager=%1 slot=%2 move=%3 targetRoot=%4 count=%5", managerId, slotId, move, target == m_Vehicle, m_Entered);
}
protected void Left(IEntity target, BaseCompartmentManagerComponent manager, int managerId, int slotId, bool move)
{
m_Left++;
PrintFormat("ERR CASUALTY EXIT EVENT: manager=%1 slot=%2 move=%3 targetRoot=%4 count=%5", managerId, slotId, move, target == m_Vehicle, m_Left);
}
protected bool BelongsToAircraft(IEntity entity)
{
while (entity)
{
if (entity == m_Vehicle) return true;
entity = entity.GetParent();
}
return false;
}
protected BaseCompartmentSlot FindTransportBench()
{
array<BaseCompartmentSlot> seats = {};
m_Mode.Extraction().Seats(seats);
foreach (BaseCompartmentSlot seat : seats)
{
if (!seat || seat.GetType() != ECompartmentType.CARGO || SCR_PatientCompartmentSlot.Cast(seat)) continue;
IEntity owner = seat.GetOwner();
if (!owner || owner == m_Vehicle || !BelongsToAircraft(owner) || !owner.GetPrefabData()) continue;
ResourceName prefab = owner.GetPrefabData().GetPrefabName();
// These installed Mi8 seat-part prefabs own the real transport benches.
// No compartment class, animation, accessibility or health setting is changed.
if (!prefab.Contains("VehPart_MI8_seats")) continue;
if (!seat.IsCompartmentAccessible() || seat.IsGetInLockedFor(m_Casualty) || seat.GetOccupant()) continue;
return seat;
}
return null;
}
protected void Trace(string stage)
{
if (!m_Casualty || !m_Access) return;
CharacterControllerComponent controller = CharacterControllerComponent.Cast(m_Casualty.FindComponent(CharacterControllerComponent));
CharacterAnimationComponent animation = CharacterAnimationComponent.Cast(m_Casualty.FindComponent(CharacterAnimationComponent));
if (controller && animation)
PrintFormat("ERR CASUALTY STATE: stage=%1 life=%2 ragdoll=%3 falling=%4 physicsLinked=%5 gettingIn=%6 gettingOut=%7", stage, controller.GetLifeState(), animation.IsRagdollActive(), animation.PhysicsIsFalling(), animation.PhysicsIsLinked(), m_Access.IsGettingIn(), m_Access.IsGettingOut());
if (m_Seat)
{
IEntity owner = m_Seat.GetOwner();
string prefab;
if (owner && owner.GetPrefabData()) prefab = owner.GetPrefabData().GetPrefabName();
PrintFormat("ERR CASUALTY SEAT: stage=%1 name=%2 ownerPrefab=%3 rootOwner=%4 hierarchyValid=%5 slotId=%6 managerId=%7", stage, m_Seat.GetCompartmentUniqueName(), prefab, owner == m_Vehicle, BelongsToAircraft(owner), m_Seat.GetCompartmentSlotID(), m_Seat.GetCompartmentMgrID());
PrintFormat("ERR CASUALTY OCCUPANCY: stage=%1 inCompartment=%2 actualSlot=%3 targetOccupant=%4 vehicleRoot=%5 enterEvents=%6 exitEvents=%7", stage, m_Access.IsInCompartment(), m_Access.GetCompartment() == m_Seat, m_Seat.GetOccupant() == m_Casualty, CompartmentAccessComponent.GetVehicleIn(m_Casualty) == m_Vehicle, m_Entered, m_Left);
}
}
protected bool DownOnGround()
{
CharacterControllerComponent controller = CharacterControllerComponent.Cast(m_Casualty.FindComponent(CharacterControllerComponent));
return ERR_Actors.Alive(m_Casualty) && controller && controller.GetLifeState() == ECharacterLifeState.INCAPACITATED && !CompartmentAccessComponent.GetVehicleIn(m_Casualty);
}
protected bool IncapacitateGroundCasualty()
{
if (!Check(m_Damage && ERR_Actors.Alive(m_Casualty), "living fixture casualty has native damage manager before incapacitation")) return false;
HitZone resilience = m_Damage.GetResilienceHitZone();
if (!Check(resilience != null, "fixture casualty has actual native resilience hit zone")) return false;
float beforeResilience = resilience.GetHealth();
bool damageWasEnabled = m_Damage.IsDamageHandlingEnabled();
// ForceUnconsciousness uses HandleDamage(TRUE), which damage protection can
// suppress. Permit only this synchronous operation, then restore the flag
// before any assertion/cleanup path. Never restore health or life state.
m_Damage.EnableDamageHandling(true);
m_Damage.ForceUnconsciousness();
float afterResilience = resilience.GetHealth();
m_Damage.EnableDamageHandling(damageWasEnabled);
PrintFormat("ERR CASUALTY INCAPACITATION: resilienceBefore=%1 resilienceAfter=%2 damageOriginallyEnabled=%3 restored=%4", beforeResilience, afterResilience, damageWasEnabled, m_Damage.IsDamageHandlingEnabled() == damageWasEnabled);
return Check(afterResilience < beforeResilience && ERR_Actors.Alive(m_Casualty) && m_Damage.IsDamageHandlingEnabled() == damageWasEnabled, "single native incapacitation reduces actual resilience, preserves life and restores damage-protection flag");
}
protected bool BeginExit()
{
// Native unloading is a separate mechanism check, not a user-action claim.
vector exitTransform[4];
m_Casualty.GetWorldTransform(exitTransform);
exitTransform[3] = m_GroundPosition;
return Check(m_Access.GetOutVehicle_NoDoor(exitTransform, true, false), "unconscious cargo occupant accepts native ground exit");
}
protected bool ExitSettled()
{
return ERR_Actors.Alive(m_Casualty) && vector.Distance(m_Casualty.GetOrigin(), m_GroundPosition) < 4 && m_Seat.IsCompartmentAccessible();
}
protected vector ExpectedExit() { return m_GroundPosition; }
protected string ExitResultDetail() { return "unloaded survivor on ground and seat accessible"; }
// now is world milliseconds, matching the other native probes. Returns -1/0/1.
int Tick(float now)
{
if (now < m_Wait) return 0;
if (m_Step == 0)
{
PlayerController controller = GetGame().GetPlayerController();
if (!Check(Replication.IsServer() && controller && m_Mode, "server and local real caller available")) return -1;
m_Caller = controller.GetControlledEntity();
m_Vehicle = m_Mode.ExtractionVehicle();
if (!Check(ERR_Actors.CanFight(m_Caller) && !CompartmentAccessComponent.GetVehicleIn(m_Caller) && ERR_Actors.Alive(m_Vehicle), "caller on foot and authored helicopter intact")) return -1;
m_GroundPosition = ERR_Actors.Ground(m_Vehicle.GetOrigin() + "6 0 6");
m_Casualty = ERR_Actors.Spawn(m_Mode.Factions().PrisonerPrefab, m_GroundPosition);
if (!Check(m_Casualty != null, "independent casualty spawned on ground")) return -1;
m_Mode.Own(m_Casualty);
m_Access = SCR_CompartmentAccessComponent.Cast(m_Casualty.FindComponent(SCR_CompartmentAccessComponent));
m_Damage = SCR_CharacterDamageManagerComponent.Cast(m_Casualty.FindComponent(SCR_CharacterDamageManagerComponent));
if (!Check(m_Access && m_Damage, "native casualty components present")) return -1;
m_Access.GetOnCompartmentEntered().Insert(Entered);
m_Access.GetOnCompartmentLeft().Insert(Left);
m_Caller.GetWorldTransform(m_CallerTransform);
vector helperTransform[4];
m_Caller.GetWorldTransform(helperTransform);
helperTransform[3] = ERR_Actors.Ground(m_GroundPosition + "2 0 0");
ChimeraCharacter.Cast(m_Caller).Teleport(helperTransform);
m_CallerMoved = true;
m_Step = 1;
m_Wait = now + 1000;
return 0;
}
if (m_Step == 1)
{
if (!IncapacitateGroundCasualty()) return -1;
m_Deadline = now + 8000;
m_Step = 2;
return 0;
}
if (m_Step == 2)
{
if (!DownOnGround())
{
if (now >= m_Deadline) { Check(false, "native health-driven ground incapacitation before boarding deadline"); return -1; }
return 0;
}
if (m_Bench)
{
if (m_DownSettledAt == 0)
{
m_DownSettledAt = now + 5000;
m_Deadline = now + 13000;
Trace("incapacitated; begin five-second bench settling interval");
return 0;
}
if (now < m_DownSettledAt) return 0;
}
PrintFormat("ERR CASUALTY POSITIONS: caller=%1 casualty=%2 helicopter=%3", m_Caller.GetOrigin(), m_Casualty.GetOrigin(), m_Vehicle.GetOrigin());
if (!Check(ERR_Actors.CanFight(m_Caller) && vector.Distance(m_Caller.GetOrigin(), m_Casualty.GetOrigin()) < 4, "conscious real caller physically beside ground casualty")) return -1;
if (m_Bench) m_Seat = FindTransportBench();
else m_Seat = m_Access.FindFreeAndAccessibleCompartment(m_Vehicle, ECompartmentType.CARGO);
if (!Check(m_Seat && !SCR_PatientCompartmentSlot.Cast(m_Seat) && !m_Seat.GetOccupant(), "ordinary free cargo seat selected without patient-seat substitution")) return -1;
if (m_Bench) Print("ERR CASUALTY BENCH SOURCE: VehPart_MI8_seats_base.et sha256=1b9a2e55a9a1cb884886488b6e170f437b6c1a615b584449819b96a6c78ad973");
Trace("before selected boarding request");
PrintFormat("ERR CASUALTY LOAD: CanGetInVehicle=%1 state=%2 targetType=%3", m_Access.CanGetInVehicle(m_Seat.GetOwner()), CharacterControllerComponent.Cast(m_Casualty.FindComponent(CharacterControllerComponent)).GetLifeState(), m_Seat.GetType());
// Exactly the public server-to-owner primitive used by vanilla ambulance loading.
bool accepted;
if (m_Direct) accepted = m_Access.GetInVehicle(m_Seat.GetOwner(), m_Seat, true, -1, ECloseDoorAfterActions.INVALID, false);
else accepted = m_Access.MoveInVehicle(m_Vehicle, ECompartmentType.CARGO, false, m_Seat);
PrintFormat("ERR CASUALTY MECHANISM: direct=%1 bench=%2 accepted=%3", m_Direct, m_Bench, accepted);
Trace("immediately after selected boarding request");
m_NextDiagnostic = now + 1000;
if (!Check(accepted, "selected loading request accepted while already unconscious")) return -1;
m_Deadline = now + 8000;
m_Step = 3;
return 0;
}
if (m_Step == 3)
{
if (now >= m_NextDiagnostic) { Trace("awaiting physical occupancy"); m_NextDiagnostic = now + 1000; }
if (CompartmentAccessComponent.GetVehicleIn(m_Casualty) != m_Vehicle || m_Seat.GetOccupant() != m_Casualty)
{
if (now >= m_Deadline) { Check(false, "physical cargo occupancy after selected mechanism before deadline"); return -1; }
return 0;
}
if (!Check(ERR_Actors.Alive(m_Casualty) && !ERR_Actors.CanFight(m_Casualty), "living unconscious ground casualty now physically aboard")) return -1;
if (!Check(!m_Access.MoveInVehicle(m_Vehicle, ECompartmentType.CARGO, false, m_Seat), "occupied target seat rejects repeated request")) return -1;
ERR_PlayerRoster roster = new ERR_PlayerRoster();
UUID account = "00e77200-0000-0000-0000-000000000201";
if (!Check(roster.Bind(10201, account), "isolated casualty identity registered")) return -1;
roster.Spawned(10201, m_Casualty);
ERR_ExtractionService service = new ERR_ExtractionService(roster);
if (!Check(service.Assign(m_Vehicle) && service.Begin(m_Vehicle.GetOrigin() + "100 0 0", 50), "isolated boundary service initialized without moving mission helicopter")) return -1;
service.Tick(ERR_Phase.EXTRACTION);
if (!Check(service.DidEscape(account) && service.EscapedCount() == 1, "loaded unconscious identity satisfies physical extraction rule")) return -1;
if (!BeginExit()) return -1;
m_Deadline = now + 8000;
m_Step = 4;
return 0;
}
if (m_Step == 4)
{
if (now >= m_NextDiagnostic) { Trace("awaiting ground exit"); m_NextDiagnostic = now + 1000; }
if (CompartmentAccessComponent.GetVehicleIn(m_Casualty) || m_Seat.GetOccupant())
{
if (now >= m_Deadline) { Check(false, "native exit clears physical vehicle and seat before deadline"); return -1; }
return 0;
}
bool settled = ExitSettled();
if (!settled && now < m_Deadline) return 0;
PrintFormat("ERR CASUALTY EXIT POSITION: actual=%1 requested=%2 alive=%3 accessible=%4", m_Casualty.GetOrigin(), ExpectedExit(), ERR_Actors.Alive(m_Casualty), m_Seat.IsCompartmentAccessible());
if (!Check(settled, ExitResultDetail())) return -1;
Cleanup();
m_Step = 5;
return 1;
}
return 1;
}
}
#endif
@@ -0,0 +1,160 @@
#ifdef WORKBENCH
// Opt-in completion-boundary test of the REAL inherited stock remove action.
// It does not simulate held input, replace the action, or prove remote ownership.
class ERR_NativeCasualtyRemoveProbe : ERR_NativeCasualtyLoadProbe
{
protected SCR_RemoveCasualtyUserAction m_Remove;
protected UserActionContext m_Context;
protected int m_SavedDoor;
protected int m_SelectedDoor;
protected bool m_DoorChanged;
protected bool m_PendingAction;
protected bool m_TestBodyProtected;
protected bool m_RemoveStopped;
protected bool m_RemoveComplete;
protected vector m_AuthoredExit;
protected vector m_SeatedPosition;
protected vector m_HelperPosition;
protected float m_RemoveDeadline;
protected float m_RemoveDiagnostic;
void ERR_NativeCasualtyRemoveProbe(ERR_GameMode mode, bool direct = false, bool bench = false)
{
m_Mode = mode; m_Direct = true; m_Bench = true;
}
override void Cleanup()
{
if (m_Remove && m_DoorChanged) m_Remove.SetCompartmentDoorInfoIndex(m_SavedDoor);
m_DoorChanged = false; m_PendingAction = false; m_RemoveStopped = true;
super.Cleanup();
}
protected void FindAction(IEntity entity)
{
if (!entity || m_Remove) return;
BaseActionsManagerComponent manager = BaseActionsManagerComponent.Cast(entity.FindComponent(BaseActionsManagerComponent));
if (manager)
{
array<BaseUserAction> actions = {}; manager.GetActionsList(actions);
foreach (BaseUserAction action : actions)
{
SCR_RemoveCasualtyUserAction remove = SCR_RemoveCasualtyUserAction.Cast(action);
if (remove && remove.GetCompartmentSlot() == m_Seat && remove.GetContextIndex("door_l01_door") >= 0)
{
m_Remove = remove; return;
}
}
}
for (IEntity child = entity.GetChildren(); child; child = child.GetSibling()) FindAction(child);
}
protected void FindContext(IEntity entity)
{
if (!entity || m_Context) return;
BaseActionsManagerComponent manager = BaseActionsManagerComponent.Cast(entity.FindComponent(BaseActionsManagerComponent));
if (manager)
{
UserActionContext context = manager.GetContext("door_l01_door");
if (context)
{
array<BaseUserAction> actions = {}; context.GetActionsList(actions);
if (actions.Contains(m_Remove)) { m_Context = context; return; }
}
}
for (IEntity child = entity.GetChildren(); child; child = child.GetSibling()) FindContext(child);
}
protected CompartmentDoorInfo ResolveDoor()
{
BaseCompartmentManagerComponent manager = m_Seat.GetManager();
if (!manager) return null;
CompartmentDoorInfo info = manager.GetDoorInfo(m_SelectedDoor);
if (info) return info;
CompartmentDoorReference doorReference = manager.GetDoorReference(m_SelectedDoor);
if (!doorReference) return null;
BaseCompartmentManagerComponent referenced = doorReference.GetReferencedDoorCompartmentManager();
int referencedIndex = doorReference.GetAnimDoorIndex();
PrintFormat("ERR CASUALTY REMOVE DOOR REFERENCE: selected=%1 referencedIndex=%2 manager=%3", m_SelectedDoor, referencedIndex, referenced != null);
if (!referenced || referencedIndex < 0) return null;
return referenced.GetDoorInfo(referencedIndex);
}
protected override bool BeginExit()
{
FindAction(m_Vehicle);
if (!Check(m_Remove && BelongsToAircraft(m_Remove.GetOwner()) && m_Remove.GetActionDuration() == 1 && !m_Remove.HasLocalEffectOnly(), "stock bench RemoveCasualty action exists, duration one second, nonlocal effect")) return false;
FindContext(m_Vehicle);
if (!Check(m_Context && m_Context.GetContextName() == "door_l01_door", "actual exterior context contains selected stock seat action")) return false;
// Same native point-to-door selection used by SCR_CompartmentUserAction.
// Explicit cached index substitutes only for selecting a UI context, not input.
m_SelectedDoor = m_Seat.PickDoorIndexForPoint(m_Context.GetOrigin());
if (!Check(m_SelectedDoor >= 0, "actual action context resolves a relevant native compartment door")) return false;
CompartmentDoorInfo door = ResolveDoor();
if (!Check(door && door.GetExitTeleportPointInfo(), "selected native door exposes its authored teleport exit")) return false;
vector exitTransform[4]; door.GetExitTeleportPointInfo().GetWorldTransform(exitTransform);
m_AuthoredExit = exitTransform[3]; m_SeatedPosition = m_Casualty.GetOrigin();
if (!Check(vector.Distance(m_AuthoredExit, m_Vehicle.GetOrigin()) < 15, "authored exit resolves beside this physical helicopter")) return false;
m_SavedDoor = m_Remove.GetCompartmentDoorInfoIndex();
m_Remove.SetCompartmentDoorInfoIndex(m_SelectedDoor); m_DoorChanged = true;
vector outside = m_Context.GetOrigin() - m_Vehicle.GetOrigin(); outside[1] = 0;
if (!Check(outside.Length() > 0.1, "exterior context has a distinct outward direction")) return false;
outside.Normalize();
m_HelperPosition = ERR_Actors.Ground(m_Context.GetOrigin() + outside * 0.75);
vector helper[4]; m_Caller.GetWorldTransform(helper); helper[3] = m_HelperPosition;
ChimeraCharacter.Cast(m_Caller).Teleport(helper);
PrintFormat("ERR CASUALTY REMOVE SETUP: action=%1 context=%2 door=%3 contextOrigin=%4 exit=%5 seatedOrigin=%6 helperRequested=%7", m_Remove.GetActionID(), m_Context.GetContextName(), m_SelectedDoor, m_Context.GetOrigin(), m_AuthoredExit, m_SeatedPosition, m_HelperPosition);
m_PendingAction = true;
m_Wait = GetGame().GetWorld().GetWorldTime() + 1000;
return true;
}
static bool SafeExit(float horizontalDistance, float terrainClearance)
{
return horizontalDistance >= 0 && horizontalDistance <= 1.25 && terrainClearance >= -0.25 && terrainClearance <= 1.5;
}
protected override vector ExpectedExit() { return m_AuthoredExit; }
protected override string ExitResultDetail() { return "stock removal leaves a living incapacitated body beside authored door exit, outside compartment, on terrain, and seat accessible"; }
protected override bool ExitSettled()
{
if (!m_Casualty || !m_Access || !m_Seat) return false;
vector actual = m_Casualty.GetOrigin();
float height = actual[1] - GetGame().GetWorld().GetSurfaceY(actual[0], actual[2]);
CharacterAnimationComponent animation = CharacterAnimationComponent.Cast(m_Casualty.FindComponent(CharacterAnimationComponent));
bool safe = DownOnGround() && !m_Access.IsGettingOut() && !m_Access.IsInCompartment() && !m_Seat.GetOccupant() && m_Seat.IsCompartmentAccessible() && animation && !animation.PhysicsIsFalling() && SafeExit(vector.DistanceXZ(actual, m_AuthoredExit), height);
float now = GetGame().GetWorld().GetWorldTime();
if (now >= m_RemoveDiagnostic || safe)
{
PrintFormat("ERR CASUALTY REMOVE POSITION: actual=%1 aiming=%2 authored=%3 seated=%4 horizontalError=%5 terrainClearance=%6 safe=%7", actual, ChimeraCharacter.Cast(m_Casualty).AimingPosition(), m_AuthoredExit, m_SeatedPosition, vector.DistanceXZ(actual, m_AuthoredExit), height, safe);
Trace("stock door removal observation"); m_RemoveDiagnostic = now + 1000;
}
return safe;
}
override int Tick(float now)
{
if (m_RemoveComplete) return 1;
if (m_RemoveStopped) return -1;
if (m_RemoveDeadline == 0)
{
if (!Check(m_Mode && ERR_CasualtyLoadingRules.AircraftReady(m_Mode.ExtractionVehicle()), "production mission helicopter is physically grounded and stable before fixture")) return -1;
m_RemoveDeadline = now + 45000;
}
if (now > m_RemoveDeadline) { Check(false, "bounded 45-second stock removal fixture deadline"); return -1; }
if (m_Step == 4 && m_PendingAction)
{
if (now < m_Wait) return 0;
if (!Check(ERR_Actors.CanFight(m_Caller) && !CompartmentAccessComponent.GetVehicleIn(m_Caller) && vector.Distance(m_Caller.GetOrigin(), m_HelperPosition) < 1.5, "real conscious rescuer settled outside selected native door")) return -1;
bool shown = m_Remove.CanBeShown(m_Caller);
bool permitted = m_Remove.CanBePerformed(m_Caller);
PrintFormat("ERR CASUALTY REMOVE ACTION: shown=%1 performedAllowed=%2 reason=%3 cachedDoor=%4 caller=%5", shown, permitted, m_Remove.GetCannotPerformReason(), m_Remove.GetCompartmentDoorInfoIndex(), m_Caller.GetOrigin());
if (!Check(shown && permitted && m_Seat.GetOccupant() == m_Casualty && m_Remove.GetCompartmentDoorInfoIndex() == m_SelectedDoor, "stock native action visibility and permission accept actual rescuer and occupied casualty bench")) return -1;
m_PendingAction = false; // exactly one completion invocation, no native retry
m_Remove.PerformAction(m_Remove.GetOwner(), m_Caller);
Print("ERR CASUALTY REMOVE: invoked existing stock action completion once; held input and network dispatch not exercised");
m_Deadline = now + 8000;
}
int result = super.Tick(now);
if (!m_TestBodyProtected && m_Casualty && m_Damage)
{
// Only the fixture-owned casualty is protected from nearby initial guards.
// ForceUnconsciousness still uses its actual native health/life transition.
m_Damage.EnableDamageHandling(false); m_TestBodyProtected = true;
}
if (result == 1) m_RemoveComplete = true;
return result;
}
}
#endif
@@ -0,0 +1,293 @@
#ifdef WORKBENCH
// Only the helper's membership is synthetic. The casualty keeps its real native
// player controller/account/body and exercises the ordinary owner handshake.
class ERR_NativeCasualtyHelperAdapter : ERR_CasualtyLoadingService
{
protected ref ERR_PlayerLife m_HelperLife;
protected bool m_HelperEnabled;
void ERR_NativeCasualtyHelperAdapter(ERR_GameMode mode) { m_Mode = mode; }
void Helper(IEntity actor)
{
m_HelperLife = new ERR_PlayerLife();
m_HelperLife.Actor = actor; m_HelperLife.Connected = true; m_HelperLife.UsedLife = true;
m_HelperLife.ConnectionId = 15401;
m_HelperLife.AccountId = "00e77200-0000-0000-0000-000000001401";
}
void EnableHelper() { m_HelperEnabled = true; }
bool StrictParticipant(IEntity actor) { return super.CurrentLife(actor) != null; }
bool StoppedForProbe() { return m_Stopped; }
protected override ERR_PlayerLife CurrentLife(IEntity actor)
{
if (m_HelperEnabled && m_HelperLife && actor && actor == m_HelperLife.Actor) return m_HelperLife;
return super.CurrentLife(actor);
}
}
class ERR_NativeCasualtyServiceProbe
{
protected ERR_GameMode m_Mode;
protected IEntity m_Player;
protected IEntity m_Helper;
protected IEntity m_Blocker;
protected Vehicle m_Aircraft;
protected ERR_LoadCasualtyAction m_Action;
protected ref ERR_NativeCasualtyHelperAdapter m_Service;
protected ref ERR_NativeCasualtyGroundProbe m_GroundProbe;
protected bool m_GroundChecked;
protected ref array<BaseCompartmentSlot> m_Reserved = {};
protected BaseCompartmentSlot m_LoadedSeat;
protected float m_Deadline;
protected float m_Wait;
protected float m_PositionDeadline;
protected float m_NextDiagnostic;
protected vector m_RequestedPlayer;
protected vector m_RequestedHelper;
protected bool m_PlayerDamageSnapshot;
protected bool m_PlayerDamageOriginallyEnabled;
protected int m_SettleStart;
protected int m_Step;
protected int m_Resolutions;
protected bool m_Loaded;
protected bool m_Observing;
protected bool m_Stopped;
void ERR_NativeCasualtyServiceProbe(ERR_GameMode mode) { m_Mode = mode; }
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: casualty service " + detail); return true; }
Print("ERR SMOKE FAIL: casualty service " + detail, LogLevel.ERROR);
Stop(); return false;
}
protected void Resolved(IEntity actor, bool loaded)
{
m_Resolutions++;
m_Loaded = actor == m_Player && loaded;
}
protected void ReleaseFixtureReservations()
{
foreach (BaseCompartmentSlot seat : m_Reserved)
if (seat && m_Helper && seat.IsReservedBy(m_Helper)) seat.SetReserved(null);
m_Reserved.Clear();
}
void Stop()
{
if (m_Stopped) return;
m_Stopped = true;
if (m_GroundProbe) m_GroundProbe.Stop();
if (m_Service) { m_Service.Stop(); m_Service.OnResolved().Remove(Resolved); }
if (m_Mode) m_Mode.ERR_UseCasualtyFixture(null);
ReleaseFixtureReservations();
// Restore the original damage-handling flag only on this exact still-owned
// living player body. This never restores health or changes another actor.
if (m_PlayerDamageSnapshot && GetGame() && GetGame().GetPlayerController() && GetGame().GetPlayerController().GetControlledEntity() == m_Player && ERR_Actors.Alive(m_Player))
{
DamageManagerComponent damage = DamageManagerComponent.Cast(m_Player.FindComponent(DamageManagerComponent));
if (damage) damage.EnableDamageHandling(m_PlayerDamageOriginallyEnabled);
}
m_PlayerDamageSnapshot = false;
ERR_Actors.DeleteSpawned(m_Blocker); m_Blocker = null;
ERR_Actors.DeleteSpawned(m_Helper); m_Helper = null;
// This opt-in case ends by ordinary round reset. Never restore arbitrary
// health, force recovery, teleport the seated player, or delete its body.
}
protected ERR_LoadCasualtyAction FindAction()
{
BaseActionsManagerComponent manager = BaseActionsManagerComponent.Cast(m_Player.FindComponent(BaseActionsManagerComponent));
if (!manager) return null;
array<BaseUserAction> actions = {}; manager.GetActionsList(actions);
bool medical;
ERR_LoadCasualtyAction load;
foreach (BaseUserAction action : actions)
{
if (SCR_BandageUserAction.Cast(action)) medical = true;
ERR_LoadCasualtyAction candidate = ERR_LoadCasualtyAction.Cast(action);
if (candidate) load = candidate;
}
if (!Check(medical, "successor prisoner retains inherited native medicine actions")) return null;
return load;
}
protected void Diagnose()
{
if (!m_Player || !m_Helper || !m_Aircraft || !m_Service) return;
HelicopterControllerComponent aircraftController = HelicopterControllerComponent.Cast(m_Aircraft.FindComponent(HelicopterControllerComponent));
Physics physics = m_Aircraft.GetPhysics();
bool groundContact;
bool simulationAvailable;
float speed = -1;
float angularSpeed = -1;
if (aircraftController && aircraftController.GetSimulation())
{
simulationAvailable = true;
groundContact = aircraftController.GetSimulation().HasAnyGroundContact();
}
if (physics) { speed = physics.GetVelocity().Length(); angularSpeed = physics.GetAngularVelocity().Length(); }
vector transform[4]; m_Aircraft.GetWorldTransform(transform);
PrintFormat("ERR CASUALTY AIRCRAFT READINESS: alive=%1 controller=%2 simulation=%3 physics=%4 groundContact=%5 up=%6 speed=%7 angularSpeed=%8 position=%9", ERR_Actors.Alive(m_Aircraft), aircraftController != null, simulationAvailable, physics != null, groundContact, transform[1][1], speed, angularSpeed, m_Aircraft.GetOrigin());
CharacterControllerComponent playerController = CharacterControllerComponent.Cast(m_Player.FindComponent(CharacterControllerComponent));
CharacterControllerComponent helperController = CharacterControllerComponent.Cast(m_Helper.FindComponent(CharacterControllerComponent));
int playerLife = -1;
int helperLife = -1;
if (playerController) playerLife = playerController.GetLifeState();
if (helperController) helperLife = helperController.GetLifeState();
PrintFormat("ERR CASUALTY ACTOR READINESS: playerAlive=%1 playerConscious=%2 playerLife=%3 helperAlive=%4 helperConscious=%5 helperLife=%6 pairRange=%7 playerAircraftRange=%8 helperAircraftRange=%9", ERR_Actors.Alive(m_Player), ERR_Actors.CanFight(m_Player), playerLife, ERR_Actors.Alive(m_Helper), ERR_Actors.CanFight(m_Helper), helperLife, vector.Distance(m_Player.GetOrigin(), m_Helper.GetOrigin()), vector.Distance(m_Player.GetOrigin(), m_Aircraft.GetOrigin()), vector.Distance(m_Helper.GetOrigin(), m_Aircraft.GetOrigin()));
PrintFormat("ERR CASUALTY POSITION READINESS: playerRequested=%1 playerActual=%2 playerError=%3 helperRequested=%4 helperActual=%5 helperError=%6", m_RequestedPlayer, m_Player.GetOrigin(), vector.Distance(m_RequestedPlayer, m_Player.GetOrigin()), m_RequestedHelper, m_Helper.GetOrigin(), vector.Distance(m_RequestedHelper, m_Helper.GetOrigin()));
ERR_CasualtyGroundSupport.Parked(m_Aircraft, true);
PrintFormat("ERR CASUALTY SERVICE STATE: step=%1 player=%2 helper=%3 groundedTarget=%4 aircraftReady=%5 sight=%6 pending=%7 resolutions=%8 loaded=%9", m_Step, m_Player.GetOrigin(), m_Helper.GetOrigin(), ERR_CasualtyLoadingRules.GroundSettled(m_Player), ERR_CasualtyLoadingRules.AircraftReady(m_Aircraft), ERR_CasualtyLoadingRules.LineOfSight(m_Helper, m_Player), m_Service.PendingCount(), m_Resolutions, m_Loaded);
}
int Tick(float now)
{
if (m_Stopped) return -1;
if (m_Deadline > 0 && now > m_Deadline) { if (m_Player && m_Helper && m_Service) Diagnose(); Check(false, "bounded native fixture deadline"); return -1; }
if (m_Service && m_Observing) m_Service.Tick(now);
if (now < m_Wait) return 0;
if (m_Step == 0)
{
PlayerController controller = GetGame().GetPlayerController();
if (!Check(Replication.IsServer() && controller && m_Mode.Phase() == ERR_Phase.PRISON, "explicit local server fixture starts with real prisoner")) return -1;
m_Player = controller.GetControlledEntity(); m_Aircraft = m_Mode.ExtractionVehicle();
if (!Check(ERR_Actors.CanFight(m_Player) && m_Aircraft && !CompartmentAccessComponent.GetVehicleIn(m_Player), "real controlled casualty starts conscious on foot")) return -1;
m_Action = FindAction();
if (!Check(m_Action && m_Action.GetOwner() == m_Player && m_Action.GetActionDuration() == 5 && !m_Action.HasLocalEffectOnly(), "real successor prisoner owns five-second server action")) return -1;
DamageManagerComponent playerDamage = DamageManagerComponent.Cast(m_Player.FindComponent(DamageManagerComponent));
if (!Check(playerDamage != null, "fixture player damage manager available")) return -1;
m_PlayerDamageOriginallyEnabled = playerDamage.IsDamageHandlingEnabled();
m_PlayerDamageSnapshot = true;
playerDamage.EnableDamageHandling(false);
m_RequestedPlayer = ERR_Actors.Ground(m_Aircraft.GetOrigin() + "6 0 6");
m_RequestedHelper = ERR_Actors.Ground(m_RequestedPlayer + "0 0 2");
m_Helper = ERR_Actors.Spawn(m_Mode.Factions().PrisonerPrefab, m_RequestedHelper);
if (!Check(m_Helper != null, "separate physical helper spawned")) return -1;
m_Mode.Own(m_Helper);
DamageManagerComponent helperDamage = DamageManagerComponent.Cast(m_Helper.FindComponent(DamageManagerComponent));
if (!Check(helperDamage != null, "fixture helper damage manager available")) return -1;
helperDamage.EnableDamageHandling(false);
vector transform[4]; m_Player.GetWorldTransform(transform); transform[3] = m_RequestedPlayer;
ChimeraCharacter.Cast(m_Player).Teleport(transform);
m_Service = new ERR_NativeCasualtyHelperAdapter(m_Mode); m_Service.Helper(m_Helper);
m_Service.OnResolved().Insert(Resolved);
if (!Check(m_Service.StrictParticipant(m_Player) && !m_Service.StrictParticipant(m_Helper), "actual target passes strict native membership; synthetic helper does not")) return -1;
if (!Check(!m_Mode.CasualtyLoading().Begin(m_Helper, m_Player) && !m_Service.Begin(m_Helper, m_Player), "production and unadapted sample reject out-of-phase helper request")) return -1;
// Deliberate fixture phase setup, not evidence of a completed radio objective.
if (!Check(m_Mode.Transition(ERR_Phase.RADIO_SEARCH) && m_Mode.Transition(ERR_Phase.RADIO_DEFENCE) && m_Mode.Transition(ERR_Phase.EXTRACTION), "fixture enters extraction through explicit legal phase transitions")) return -1;
m_Deadline = now + 45000;
m_PositionDeadline = now + 10000;
m_NextDiagnostic = now;
m_Wait = now + 500; m_Step = 1; return 0;
}
if (m_Step == 1)
{
if (now >= m_NextDiagnostic) { Diagnose(); m_NextDiagnostic = now + 1000; }
bool actorsReady = ERR_Actors.CanFight(m_Player) && ERR_Actors.CanFight(m_Helper) && vector.Distance(m_RequestedPlayer, m_Player.GetOrigin()) <= 1.5 && vector.Distance(m_RequestedHelper, m_Helper.GetOrigin()) <= 1.5 && vector.Distance(m_Player.GetOrigin(), m_Helper.GetOrigin()) <= 3;
bool aircraftReady = ERR_CasualtyLoadingRules.AircraftReady(m_Aircraft);
if ((!actorsReady || !aircraftReady) && now < m_PositionDeadline) return 0;
if (!Check(actorsReady, "actual living conscious actors reached requested nearby ground positions before deadline")) return -1;
if (!Check(aircraftReady, "actual aircraft satisfies unchanged production grounded-motion safety before deadline")) return -1;
if (!m_GroundChecked)
{
if (!m_GroundProbe) m_GroundProbe = new ERR_NativeCasualtyGroundProbe(m_Mode);
int groundResult = m_GroundProbe.Tick(now);
if (groundResult < 0) { Check(false, "native ground-support qualification failed"); return -1; }
if (groundResult == 0) return 0;
m_GroundChecked = true;
}
if (!Check(!m_Service.Begin(m_Helper, m_Player), "conscious target is rejected before native incapacitation")) return -1;
SCR_CharacterDamageManagerComponent damage = SCR_CharacterDamageManagerComponent.Cast(m_Player.FindComponent(SCR_CharacterDamageManagerComponent));
if (!Check(damage != null, "real casualty has native health manager")) return -1;
HitZone resilience = damage.GetResilienceHitZone();
if (!Check(resilience && damage.GetPermitUnconsciousness(), "native resilience and unconsciousness policy available")) return -1;
float beforeResilience = resilience.GetHealth();
// Protection must not swallow the fixture's own true-damage request. Keep
// this synchronous interval limited to that one native health operation.
damage.EnableDamageHandling(true);
damage.ForceUnconsciousness();
float afterResilience = resilience.GetHealth();
damage.EnableDamageHandling(false);
PrintFormat("ERR CASUALTY INCAPACITATION: resilienceBefore=%1 resilienceAfter=%2", beforeResilience, afterResilience);
if (!Check(afterResilience < beforeResilience && ERR_Actors.Alive(m_Player), "single native resilience damage applied before restoring fixture protection")) return -1;
m_Step = 2; m_Wait = now + 500; return 0;
}
if (m_Step == 2)
{
if (!ERR_CasualtyLoadingRules.GroundSettled(m_Player))
{
if (now >= m_NextDiagnostic) { Diagnose(); m_NextDiagnostic = now + 1000; }
return 0;
}
if (!Check(ERR_CasualtyLoadingRules.Nearby(m_Mode, m_Helper, m_Player), "actual incapacitated target is settled with clear line of sight")) return -1;
if (!Check(!m_Mode.CasualtyLoading().CanLoad(m_Helper, m_Player) && !m_Service.Begin(m_Helper, m_Player) && m_Service.PendingCount() == 0, "strict production authorization rejects nonparticipant helper even in valid physical state")) return -1;
m_Service.EnableHelper();
if (!Check(m_Mode.ERR_UseCasualtyFixture(m_Service), "explicit fixture installs helper-only membership adapter")) return -1;
m_Observing = true; m_Service.Tick(now); m_SettleStart = System.GetTickCount();
if (!Check(!m_Service.CanLoad(m_Helper, m_Player), "new observation cannot bypass five-second settling window")) return -1;
m_Step = 3; return 0;
}
if (m_Step == 3)
{
if (System.GetTickCount() - m_SettleStart < 5500) return 0;
Diagnose();
if (!Check(m_Service.CanLoad(m_Helper, m_Player), "settled real casualty is eligible with only helper membership adapted")) return -1;
array<BaseCompartmentSlot> seats = {}; m_Mode.Extraction().Seats(seats);
foreach (BaseCompartmentSlot seat : seats)
{
if (!ERR_CasualtyLoadingRules.TransportBench(seat, m_Aircraft) || seat.GetOccupant() || seat.IsReserved()) continue;
seat.SetReserved(m_Helper); m_Reserved.Insert(seat);
}
if (!Check(!m_Reserved.IsEmpty() && !m_Service.CanLoad(m_Helper, m_Player) && !m_Service.Begin(m_Helper, m_Player), "native external reservations prevent loading when all child benches are reserved")) return -1;
ReleaseFixtureReservations();
vector middle = (ChimeraCharacter.Cast(m_Helper).AimingPosition() + ChimeraCharacter.Cast(m_Player).AimingPosition()) * 0.5;
m_Blocker = ERR_Actors.Spawn("{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et", middle);
if (!Check(m_Blocker != null, "successor-owned physical line-of-sight obstacle spawned")) return -1;
m_Mode.Own(m_Blocker);
vector low, high; m_Blocker.GetWorldBounds(low, high);
vector wall[4]; m_Blocker.GetWorldTransform(wall); wall[3] = wall[3] + middle - (low + high) * 0.5;
m_Blocker.SetWorldTransform(wall);
m_Step = 4; m_Wait = now + 500; return 0;
}
if (m_Step == 4)
{
if (!Check(!ERR_CasualtyLoadingRules.LineOfSight(m_Helper, m_Player) && !m_Service.CanLoad(m_Helper, m_Player) && !m_Service.Begin(m_Helper, m_Player) && m_Service.PendingCount() == 0, "physical wall blocks completion-time rescue authorization")) return -1;
ERR_Actors.DeleteSpawned(m_Blocker); m_Blocker = null;
m_Step = 5; m_Wait = now + 500; return 0;
}
if (m_Step == 5)
{
if (!Check(m_Action.CanBePerformedScript(m_Helper) && m_Service.PendingCount() == 0, "real action becomes performable after obstruction removal")) return -1;
// Invoke the real completion boundary. This does not simulate holding input
// for five seconds or claim a second authenticated network player.
m_Action.PerformAction(m_Player, m_Helper);
if (!Check(m_Service.PendingCount() == 1 && m_Resolutions == 0, "real owner handshake commits one pending physical load")) return -1;
array<BaseCompartmentSlot> seats = {}; m_Mode.Extraction().Seats(seats);
foreach (BaseCompartmentSlot seat : seats)
if (ERR_CasualtyLoadingRules.TransportBench(seat, m_Aircraft) && seat.IsReservedBy(m_Player)) m_LoadedSeat = seat;
if (!Check(m_LoadedSeat != null, "service reserves the actual selected child bench for the casualty")) return -1;
m_Action.PerformAction(m_Player, m_Helper);
if (!Check(m_Service.PendingCount() == 1 && m_Resolutions == 0, "duplicate completion cannot issue a second ticket")) return -1;
m_Step = 6; return 0;
}
if (m_Step == 6)
{
if (m_Service.PendingCount() > 0) return 0;
Diagnose();
if (!Check(m_Loaded && m_Resolutions == 1 && ERR_Actors.Alive(m_Player) && ERR_CasualtyLoadingRules.Down(m_Player) && CompartmentAccessComponent.GetVehicleIn(m_Player) == m_Aircraft && m_LoadedSeat.GetOccupant() == m_Player, "production owner handshake physically loads real living incapacitated player exactly once")) return -1;
if (!Check(!m_LoadedSeat.IsReserved() && m_Service.StrictParticipant(m_Player), "successful loading releases reservation and preserves actual player identity")) return -1;
m_Service.Stop(); m_Service.Stop(); m_Service.Tick(now);
if (!Check(m_Service.StoppedForProbe() && m_Service.PendingCount() == 0 && m_Resolutions == 1 && !m_Service.Begin(m_Helper, m_Player), "Stop is latched, idempotent and cannot repeat completion or restart loading")) return -1;
Stop(); return 1;
}
return 0;
}
}
modded class ERR_GameMode
{
protected ref ERR_NativeCasualtyHelperAdapter m_ERRNativeCasualtyAdapter;
bool ERR_UseCasualtyFixture(ERR_NativeCasualtyHelperAdapter service)
{
string enabled, outcome;
if (!Replication.IsServer() || !System.GetCLIParam("errBaseSmoke", enabled) || enabled != "1" || !System.GetCLIParam("errSmokeOutcome", outcome) || outcome != "casualty-service") return false;
m_ERRNativeCasualtyAdapter = service; return true;
}
override ERR_CasualtyLoadingService CasualtyLoading()
{
if (m_ERRNativeCasualtyAdapter) return m_ERRNativeCasualtyAdapter;
return super.CasualtyLoading();
}
}
#endif
@@ -0,0 +1,52 @@
#ifdef WORKBENCH
// Exercises native service dispatch, not remote RPC ownership or rendered UI.
class ERR_NativeContractsProbe
{
protected static bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: base contracts " + detail); return true; }
Print("ERR SMOKE FAIL: base contracts " + detail, LogLevel.ERROR);
return false;
}
static bool Run(ERR_GameMode mode, IEntity player)
{
if (!Check(mode.Factions() && mode.Factions().WorldReady() && mode.PreparationError().IsEmpty(), "catalog resolves hostile vanilla factions and successful preparation")) return false;
if (!Check(SCR_FactionManager.SGetFaction(player) == mode.PlayerFaction() && player.GetPrefabData().GetPrefabName() == mode.Factions().PrisonerPrefab, "physical prisoner matches injected faction and resource")) return false;
ERR_SiteRuntime site = mode.Sites().Find("everon-supply-1");
if (!Check(site && site.Group && site.Group.GetFaction() == mode.EnemyFaction() && site.Group.GetPrefabData().GetPrefabName() == mode.Factions().GuardGroupPrefab, "physical guard squad comes from injected catalog")) return false;
ERR_FactionCatalog isolated = ERR_FactionCatalog.Cast(SCR_BaseContainerTools.CreateInstanceFromPrefab("{BE50B97B8E2B36E6}Configs/ERR/VanillaFactions.conf"));
if (!isolated) return false;
string enemyKey = isolated.EnemyFactionKey;
isolated.EnemyFactionKey = isolated.PlayerFactionKey;
if (!Check(!isolated.Valid() && !isolated.WorldReady(), "same-side factions rejected")) return false;
isolated.EnemyFactionKey = "ERR_MISSING_FACTION";
if (!Check(!isolated.WorldReady(), "unknown world faction rejected")) return false;
isolated.EnemyFactionKey = enemyKey;
ResourceName prisoner = isolated.PrisonerPrefab;
isolated.PrisonerPrefab = "";
if (!Check(!isolated.Valid(), "missing prisoner resource rejected")) return false;
isolated.PrisonerPrefab = prisoner;
if (!Check(isolated.WorldReady(), "isolated catalog restored without mutating live configuration")) return false;
array<string> replies = {};
int original = mode.Settings().Get("radioDefenceSeconds");
mode.Admin().Execute(mode, false, "mission radio set 60", replies);
if (!Check(mode.Settings().Get("radioDefenceSeconds") == original && replies.Count() == 1 && replies[0] == "Administrator access required.", "unauthorized settings request rejected")) return false;
mode.Admin().Execute(mode, true, "mission radio set 60junk", replies);
if (!Check(mode.Settings().Get("radioDefenceSeconds") == original && replies[0].Contains("Invalid"), "partially numeric input rejected without mutation")) return false;
mode.Admin().Execute(mode, true, "mission radio set 0", replies);
if (!Check(mode.Settings().Get("radioDefenceSeconds") == original && replies[0].Contains("Invalid"), "out-of-range duration rejected without mutation")) return false;
mode.Admin().Execute(mode, true, " MISSION RADIO SET 60 ", replies);
if (!Check(mode.Settings().Get("radioDefenceSeconds") == 60 && replies[0] == "radioDefenceSeconds=60 saved", "normalized base command saves validated duration")) return false;
mode.Admin().Execute(mode, true, "mission radio show", replies);
if (!Check(replies.Count() == 1 && replies[0] == "radioDefenceSeconds=60", "base command reads saved duration")) return false;
mode.Admin().Execute(mode, true, "settings", replies);
if (!Check(replies.Count() == ERR_Settings.Keys().Count(), "base settings listing available")) return false;
mode.Admin().Execute(mode, true, "status", replies);
if (!Check(!replies.IsEmpty() && replies[0] == "Escape the prison", "base mission status available before companions")) return false;
mode.Admin().Execute(mode, true, "mission radio set " + original.ToString(), replies);
if (!Check(mode.Settings().Get("radioDefenceSeconds") == original, "test setting restored")) return false;
return true;
}
}
#endif
@@ -0,0 +1,60 @@
#ifdef WORKBENCH
class ERR_NativeDefinitionProbe
{
protected static bool Check(bool condition, string name)
{
if (condition) { Print("ERR SMOKE PASS: definition " + name); return true; }
Print("ERR SMOKE FAIL: definition " + name, LogLevel.ERROR);
return false;
}
static bool Run()
{
ERR_ScenarioDefinition definition = ERR_ScenarioDefinition.Cast(SCR_BaseContainerTools.CreateInstanceFromPrefab("{BC310DC0CD27F523}Configs/ERR/EveronSites.conf"));
if (!Check(definition && definition.Valid(), "authored configuration resolves")) return false;
ERR_SiteDefinition cache = definition.Find("everon-supply-1");
if (!cache) return false;
cache.Id = "everon-prison-1";
if (!Check(!definition.Valid(), "duplicate site identity rejected")) return false;
cache.Id = "everon-supply-1";
ERR_SiteStock documents = cache.Stock[3];
documents.TargetId = "missing-radio";
if (!Check(!definition.Valid(), "dangling document target rejected")) return false;
documents.TargetId = "everon-prison-1";
if (!Check(!definition.Valid(), "non-radio document target rejected")) return false;
documents.TargetId = "everon-radio-1";
documents.Count = 1;
if (!Check(!definition.Valid(), "insufficient guaranteed radio clues rejected")) return false;
documents.Count = 65;
if (!Check(!definition.Valid(), "unbounded stock rejected")) return false;
documents.Count = 2;
cache.RevealOnEscape = false;
if (!Check(!definition.Valid(), "unguaranteed clue route rejected")) return false;
cache.RevealOnEscape = true;
ERR_SiteDefinition alternate = definition.Find("everon-supply-2");
vector alternatePosition = alternate.Position;
alternate.Position = cache.Position + "10 0 0";
if (!Check(!definition.Valid(), "radio sources sharing one footprint rejected")) return false;
alternate.Position = alternatePosition;
string label = alternate.Label;
alternate.Label = "";
if (!Check(!definition.Valid(), "unnamed location rejected")) return false;
alternate.Label = label;
ERR_SiteDefinition radio = definition.Find("everon-radio-1");
float radioRadius = radio.DiscoverRadius;
radio.DiscoverRadius = 9;
if (!Check(!definition.Valid(), "radio identification beyond eight metres rejected")) return false;
radio.DiscoverRadius = 8;
if (!Check(definition.Valid(), "close physical radio identification allowed")) return false;
radio.DiscoverRadius = radioRadius;
radio.RevealOnEscape = true;
if (!Check(!definition.Valid(), "escape cannot reveal the radio directly")) return false;
radio.RevealOnEscape = false;
ERR_SiteProp prop = definition.Find("everon-outpost-1").Props[0];
vector offset = prop.Offset;
prop.Offset = "100 0 0";
if (!Check(!definition.Valid(), "unbounded scenery offset rejected")) return false;
prop.Offset = offset;
return Check(definition.Valid(), "valid configuration restored after isolated mutations");
}
}
#endif
@@ -0,0 +1,163 @@
#ifdef WORKBENCH
class ERR_NativeEnvironmentSample : ERR_EnvironmentService
{
bool Apply(TimeAndWeatherManagerEntity manager, int dateCode, int profile) { return StartOn(manager, dateCode, profile); }
}
// Explicit isolated environment smoke outcome. It temporarily changes the private
// native world, restores date/weather family/looping, then requests a round reset.
class ERR_NativeEnvironmentProbe
{
protected ERR_GameMode m_Mode;
protected TimeAndWeatherManagerEntity m_Manager;
protected ref ERR_NativeEnvironmentSample m_Sample;
protected ref ERR_Settings m_TestSettings;
protected bool m_Snapshot;
protected bool m_Restored;
protected bool m_Stopped;
protected bool m_OwnsSettings;
protected bool m_RestoreDateAccepted;
protected int m_Year;
protected int m_Month;
protected int m_Day;
protected string m_Weather;
protected bool m_Looping;
protected float m_Clock;
protected float m_DayDuration;
protected bool m_DayAdvances;
protected float m_Wait;
protected float m_Deadline;
protected int m_Step;
protected int m_Result;
void ERR_NativeEnvironmentProbe(ERR_GameMode mode) { m_Mode = mode; }
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: environment " + detail); return true; }
Print("ERR SMOKE FAIL: environment " + detail, LogLevel.ERROR);
m_Result = -1;
Stop();
return false;
}
protected string CurrentWeather()
{
if (!m_Manager || !m_Manager.GetCurrentWeatherState()) return "";
return m_Manager.GetCurrentWeatherState().GetStateName();
}
protected void Restore()
{
if (!m_Snapshot || m_Restored || !m_Manager) return;
m_Restored = true;
if (m_Sample) m_Sample.Stop();
m_RestoreDateAccepted = m_Manager.SetDate(m_Year, m_Month, m_Day, true);
m_Manager.ForceWeatherTo(m_Looping, m_Weather, 0, 24);
}
void Stop()
{
if (m_Stopped) return;
Restore();
m_Stopped = true;
if (m_OwnsSettings)
{
string path = "$profile:erri-native-environment-probe.json";
FileIO.DeleteFile(path); FileIO.DeleteFile(path + ".bak"); FileIO.DeleteFile(path + ".tmp");
m_OwnsSettings = false;
}
}
protected bool SettingsChecks()
{
string path = "$profile:erri-native-environment-probe.json";
if (!Check(!FileIO.FileExists(path) && !FileIO.FileExists(path + ".bak") && !FileIO.FileExists(path + ".tmp"), "environment fixture filenames are unused")) return false;
m_OwnsSettings = true;
JsonSaveContext json = new JsonSaveContext();
json.EnableTypeDiscriminator(false);
foreach (string key : ERR_Settings.Keys())
if (key != "startDateCode" && key != "weatherProfile") json.WriteValue(key, m_Mode.Settings().Get(key));
if (!Check(json.SaveToFile(path), "owned legacy settings fixture written")) return false;
m_TestSettings = new ERR_Settings(path);
JsonLoadContext unchanged = new JsonLoadContext();
if (!Check(m_TestSettings.Get("startDateCode") == 0 && m_TestSettings.Get("weatherProfile") == 0 && unchanged.LoadFromFile(path) && !unchanged.DoesKeyExist("startDateCode") && !unchanged.DoesKeyExist("weatherProfile"), "missing environment settings preserve authored defaults without rewriting legacy file")) return false;
if (!Check(m_TestSettings.Set("startDateCode", 1501231) && m_TestSettings.Set("weatherProfile", 2), "date and weather persist in owned fixture settings")) return false;
ERR_Settings reloaded = new ERR_Settings(path);
if (!Check(reloaded.Get("startDateCode") == 1501231 && reloaded.Get("weatherProfile") == 2 && reloaded.Get("radioDefenceSeconds") == m_Mode.Settings().Get("radioDefenceSeconds"), "odd maximum date code round-trips exactly and old settings survive")) return false;
if (!Check(!m_TestSettings.Set("startDateCode", 229) && !m_TestSettings.Set("weatherProfile", 4), "non-leap-century date and unknown profile cannot be saved")) return false;
int originalDate = m_Mode.Settings().Get("startDateCode");
int originalProfile = m_Mode.Settings().Get("weatherProfile");
array<string> replies = {};
m_Mode.Admin().Execute(m_Mode, false, "mission weather set rain", replies);
if (!Check(replies.Count() == 1 && replies[0].Contains("Administrator") && m_Mode.Settings().Get("startDateCode") == originalDate && m_Mode.Settings().Get("weatherProfile") == originalProfile, "unauthorized environment command preserves real settings")) return false;
replies.Clear();
ERR_EnvironmentCommands.Execute(m_TestSettings, m_Mode.Environment(), {"mission", "date", "set", "2000-02-29"}, replies);
if (!Check(m_TestSettings.Get("startDateCode") == 1000229 && replies.Count() == 1 && replies[0].Contains("next round"), "natural leap-date command saves only next-round settings")) return false;
replies.Clear();
ERR_EnvironmentCommands.Execute(m_TestSettings, m_Mode.Environment(), {"mission", "weather", "set", "clear"}, replies);
if (!Check(m_TestSettings.Get("weatherProfile") == 1 && replies.Count() == 1 && replies[0].Contains("Current round unchanged"), "named weather command saves only next-round settings")) return false;
replies.Clear();
ERR_EnvironmentCommands.Execute(m_TestSettings, m_Mode.Environment(), {"mission", "date", "set", "1900-00-00"}, replies);
if (!Check(m_TestSettings.Get("startDateCode") == 1000229 && replies[0].Contains("Invalid"), "zero month/day cannot masquerade as authored date")) return false;
return true;
}
int Tick(float now)
{
if (m_Result != 0) return m_Result;
if (m_Stopped) return -1;
if (!Replication.IsServer() || !m_Mode || !m_Mode.Active()) { Check(false, "requires active server authority"); return -1; }
if (m_Deadline > 0 && now > m_Deadline) { Check(false, "native environment request timed out"); return -1; }
if (now < m_Wait) return 0;
if (m_Step == 0)
{
ChimeraWorld world = ChimeraWorld.CastFrom(m_Mode.GetWorld());
if (world) m_Manager = world.GetTimeAndWeatherManager();
if (!Check(m_Manager && m_Manager.GetCurrentWeatherState() && m_Mode.Environment().Started(), "native environment manager and round service available")) return -1;
m_Manager.GetDate(m_Year, m_Month, m_Day);
m_Weather = CurrentWeather(); m_Looping = m_Manager.IsWeatherLooping();
m_Clock = m_Manager.GetTimeOfTheDay(); m_DayDuration = m_Manager.GetDayDuration(); m_DayAdvances = m_Manager.GetIsDayAutoAdvanced();
m_Snapshot = true;
m_Deadline = now + 15000;
if (!SettingsChecks()) return -1;
if (!Check(CurrentWeather() == m_Weather && m_Manager.GetYear() == m_Year && m_Manager.GetMonth() == m_Month && m_Manager.GetDay() == m_Day, "saving fixture environment settings does not mutate native world")) return -1;
for (int profile = 1; profile <= 3; profile++)
if (!Check(ERR_EnvironmentService.HasWeatherState(m_Manager, ERR_EnvironmentService.WeatherStateName(profile)), "selected vanilla weather family exists: " + ERR_EnvironmentService.ProfileName(profile))) return -1;
m_Sample = new ERR_NativeEnvironmentSample();
if (!Check(!m_Sample.Apply(m_Manager, 229, 1) && !m_Sample.Apply(m_Manager, 1000229, 4) && !m_Sample.Started(), "invalid date/profile rejected before native changes")) return -1;
if (!Check(m_Sample.Apply(m_Manager, 890601, 1), "clear profile and specific date requested once")) return -1;
m_Step = 1; m_Wait = now + 500;
return 0;
}
if (m_Step == 1)
{
if (!Check(CurrentWeather() == "Clear" && m_Manager.IsWeatherLooping() && m_Manager.GetYear() == 1989 && m_Manager.GetMonth() == 6 && m_Manager.GetDay() == 1, "native clear weather and requested date applied after frame")) return -1;
if (!Check(m_Sample.Apply(m_Manager, 1000229, 3) && m_Sample.DateCode() == 890601 && m_Sample.Profile() == 1 && CurrentWeather() == "Clear", "repeated start cannot apply another date or profile")) return -1;
m_Sample.Stop();
if (!Check(!m_Sample.Apply(m_Manager, 1000229, 3), "stopped environment cannot restart")) return -1;
m_Sample = new ERR_NativeEnvironmentSample();
if (!Check(m_Sample.Apply(m_Manager, 1000229, 2), "overcast and leap date requested on fresh service")) return -1;
m_Step = 2; m_Wait = now + 500;
return 0;
}
if (m_Step == 2)
{
if (!Check(CurrentWeather() == "Overcast" && m_Manager.GetYear() == 2000 && m_Manager.GetMonth() == 2 && m_Manager.GetDay() == 29, "native overcast and leap date accepted")) return -1;
m_Sample.Stop(); m_Sample = new ERR_NativeEnvironmentSample();
if (!Check(m_Sample.Apply(m_Manager, 0, 3), "rain profile preserves authored/current date when date override is disabled")) return -1;
m_Step = 3; m_Wait = now + 500;
return 0;
}
if (m_Step == 3)
{
if (!Check(CurrentWeather() == "Rainy" && m_Manager.GetYear() == 2000 && m_Manager.GetMonth() == 2 && m_Manager.GetDay() == 29, "native rainy family applied without changing date")) return -1;
float clockAdvance = m_Manager.GetTimeOfTheDay() - m_Clock;
if (clockAdvance < 0) clockAdvance += 24;
if (!Check(m_Manager.GetDayDuration() == m_DayDuration && m_Manager.GetIsDayAutoAdvanced() == m_DayAdvances && clockAdvance < 0.1, "environment does not reset clock or progression")) return -1;
Restore();
m_Step = 4; m_Wait = now + 500;
return 0;
}
if (m_Step == 4)
{
if (!Check(m_RestoreDateAccepted && m_Manager.GetYear() == m_Year && m_Manager.GetMonth() == m_Month && m_Manager.GetDay() == m_Day && CurrentWeather() == m_Weather && m_Manager.IsWeatherLooping() == m_Looping, "fixture restores original native date, weather family and looping")) return -1;
m_Result = 1; Stop();
return 1;
}
return 0;
}
}
#endif
@@ -0,0 +1,118 @@
#ifdef WORKBENCH
// Only environment-restart opts into this fixed private path. The runner must
// refuse all preexisting fixture files and require exactly two real rounds.
class ERR_NativeEnvironmentRestartProbe
{
static const string PATH = "$profile:erri-environment-restart.json";
static const string MARKER = "$profile:erri-environment-restart-round.json";
protected ERR_GameMode m_Mode;
protected TimeAndWeatherManagerEntity m_Manager;
protected bool m_Failed;
protected bool m_Stopped;
protected bool m_Complete;
protected int m_Round = 1;
protected int m_InitialDate;
protected string m_InitialWeather;
protected bool m_InitialLoop;
protected float m_Wait;
protected float m_Hour;
protected float m_Duration;
void ERR_NativeEnvironmentRestartProbe(ERR_GameMode mode)
{
m_Mode = mode;
ChimeraWorld world = ChimeraWorld.CastFrom(mode.GetWorld());
if (world) m_Manager = world.GetTimeAndWeatherManager();
if (!Check(m_Manager && m_Manager.GetCurrentWeatherState(), "world environment exists before production settings and service initialization")) return;
m_InitialDate = DateCode();
m_InitialWeather = Weather();
m_InitialLoop = m_Manager.IsWeatherLooping();
if (FileIO.FileExists(MARKER))
{
JsonLoadContext marker = new JsonLoadContext();
int stage;
if (!Check(marker.LoadFromFile(MARKER) && marker.ReadValue("completedRound", stage) && stage == 1 && FileIO.FileExists(PATH), "second instance sees first round's completed marker and saved settings")) return;
m_Round = 2;
}
else
{
if (!Check(!FileIO.FileExists(PATH) && !FileIO.FileExists(PATH + ".bak") && !FileIO.FileExists(PATH + ".tmp"), "first instance owns previously unused fixture settings paths")) return;
}
PrintFormat("ERR ENVIRONMENT RESTART INSTANCE: round=%1 authoredDate=%2 authoredWeather=%3", m_Round, m_InitialDate, m_InitialWeather);
}
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: environment restart " + detail); return true; }
m_Failed = true;
Print("ERR SMOKE FAIL: environment restart " + detail, LogLevel.ERROR);
return false;
}
protected int DateCode() { return (m_Manager.GetYear() - 1900) * 10000 + m_Manager.GetMonth() * 100 + m_Manager.GetDay(); }
protected string Weather()
{
WeatherState state = m_Manager.GetCurrentWeatherState();
if (!state) return "unavailable";
return state.GetStateName();
}
int Round() { return m_Round; }
void Stop() { m_Stopped = true; }
int Tick(float now)
{
if (m_Failed || m_Stopped) return -1;
if (m_Complete) return 1;
if (m_Wait == 0)
{
if (!Check(m_Mode.Environment().Started() && m_Mode.Time().Started(), "production round services started")) return -1;
if (!Check(m_Mode.Settings().Get("startTimeMinutes") == 840 && m_Mode.Settings().Get("timeMultiplier") == 1 && m_Mode.Time().StartMinutes() == 840 && m_Mode.Time().Multiplier() == 1, "both rounds retain original clock defaults and active progression settings")) return -1;
m_Hour = m_Manager.GetTimeOfTheDay();
m_Duration = m_Manager.GetDayDuration();
if (m_Round == 1)
{
if (!Check(m_Mode.Settings().Get("startDateCode") == 0 && m_Mode.Settings().Get("weatherProfile") == 0 && m_Mode.Environment().DateCode() == 0 && m_Mode.Environment().Profile() == 0, "first instance uses authored environment defaults")) return -1;
if (!Check(DateCode() == m_InitialDate && Weather() == m_InitialWeather && m_Manager.IsWeatherLooping() == m_InitialLoop, "default production startup preserves authored date and weather")) return -1;
array<string> replies = {};
if (!Check(ERR_EnvironmentCommands.Execute(m_Mode.Settings(), m_Mode.Environment(), {"mission", "date", "set", "2000-02-29"}, replies), "production date command handled")) return -1;
if (!Check(ERR_EnvironmentCommands.Execute(m_Mode.Settings(), m_Mode.Environment(), {"mission", "weather", "set", "overcast"}, replies), "production weather command handled")) return -1;
if (!Check(replies.Count() == 2 && replies[0].Contains("Saved for next round") && replies[1].Contains("Saved for next round"), "both commands report durable next-round saves")) return -1;
// This is the actual mode's settings owner, not a detached sample service.
ERR_Settings disk = new ERR_Settings(PATH);
if (!Check(disk.Get("startDateCode") == 1000229 && disk.Get("weatherProfile") == 2 && disk.Get("radioDefenceSeconds") == 180 && disk.Get("timeMultiplier") == 1, "production saves persist environment and preserve unrelated defaults")) return -1;
}
else
{
if (!Check(m_Mode.Settings().Get("startDateCode") == 1000229 && m_Mode.Settings().Get("weatherProfile") == 2 && m_Mode.Environment().DateCode() == 1000229 && m_Mode.Environment().Profile() == 2, "second real mode loads saved date and weather into production services")) return -1;
}
m_Wait = now + 2500;
return 0;
}
if (now < m_Wait) return 0;
if (!Check(m_Mode.Settings().Get("startDateCode") == 1000229 && m_Mode.Settings().Get("weatherProfile") == 2, "saved next-round settings remain intact")) return -1;
if (m_Round == 1)
{
if (!Check(m_Mode.Environment().DateCode() == 0 && m_Mode.Environment().Profile() == 0 && DateCode() == m_InitialDate && Weather() == m_InitialWeather && m_Manager.IsWeatherLooping() == m_InitialLoop, "saving real mode settings leaves current environment unchanged across native ticks")) return -1;
}
else if (!Check(DateCode() == 1000229 && Weather() == "Overcast" && m_Manager.IsWeatherLooping(), "second round applies exact leap date and selected native weather family")) return -1;
float advance = m_Manager.GetTimeOfTheDay() - m_Hour;
if (advance < 0) advance += 24;
if (!Check(advance > 0 && advance < 0.02 && m_Manager.GetIsDayAutoAdvanced() && m_Duration == ERR_TimeService.DayDuration(1) && m_Manager.GetDayDuration() == m_Duration, "native clock continues advancing without an environment-induced reset or rate change")) return -1;
if (m_Round == 1)
{
JsonSaveContext marker = new JsonSaveContext();
marker.EnableTypeDiscriminator(false);
marker.WriteValue("completedRound", 1);
if (!Check(marker.SaveToFile(MARKER), "first completed round leaves restart handoff marker")) return -1;
}
else
{
if (FileIO.FileExists(PATH)) FileIO.DeleteFile(PATH);
if (FileIO.FileExists(PATH + ".bak")) FileIO.DeleteFile(PATH + ".bak");
if (FileIO.FileExists(PATH + ".tmp")) FileIO.DeleteFile(PATH + ".tmp");
if (FileIO.FileExists(MARKER)) FileIO.DeleteFile(MARKER);
if (!Check(!FileIO.FileExists(PATH) && !FileIO.FileExists(PATH + ".bak") && !FileIO.FileExists(PATH + ".tmp") && !FileIO.FileExists(MARKER), "successful second round removes only fixture settings and marker files")) return -1;
}
m_Complete = true;
return 1;
}
}
#endif
@@ -0,0 +1,172 @@
#ifdef WORKBENCH
// Real vehicle/character compartments with an isolated synthetic identity roster.
// This is not twelve network clients, manual boarding, medical treatment or flight.
class ERR_NativeExtractionProbe
{
protected ERR_GameMode m_Mode;
protected Vehicle m_Vehicle;
protected ref ERR_PlayerRoster m_Roster = new ERR_PlayerRoster();
protected ref ERR_ExtractionService m_All;
protected ref ERR_ExtractionService m_Partial;
protected ref array<IEntity> m_Actors = {};
protected ref array<UUID> m_Accounts = {};
protected ref array<BaseCompartmentSlot> m_Seats = {};
protected vector m_Original[4];
protected int m_Step;
protected int m_Boarding;
protected int m_Resolutions;
protected float m_Wait;
protected UUID m_NewAccount = "00e77200-0000-0000-0000-000000000099";
void ERR_NativeExtractionProbe(ERR_GameMode mode) { m_Mode = mode; }
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: extraction " + detail); return true; }
Print("ERR SMOKE FAIL: extraction " + detail, LogLevel.ERROR);
return false;
}
protected void Resolved(ERR_ExtractionState state) { m_Resolutions++; }
protected IEntity Character(int index)
{
IEntity actor = ERR_Actors.Spawn(m_Mode.Factions().PrisonerPrefab, ERR_Actors.Ground(m_Original[3] + Vector(20 + index * 2, 0, 15)));
if (!actor) return null;
m_Mode.Own(actor);
m_Actors.Insert(actor);
DamageManagerComponent.Cast(actor.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
return actor;
}
protected void MoveHelicopter(bool outside)
{
vector transform[4];
m_Vehicle.GetWorldTransform(transform);
transform[3] = m_Original[3];
if (outside) transform[3] = m_Original[3] + "650 150 0";
m_Vehicle.SetWorldTransform(transform);
}
int Tick(float now)
{
if (now < m_Wait) return 0;
if (m_Step == 0)
{
if (!ERR_NativeTimeProbe.Run(m_Mode)) return -1;
m_Vehicle = m_Mode.ExtractionVehicle();
if (!Check(m_Vehicle && m_Mode.Extraction().Ready(), "authored transport is ready for twelve prisoners")) return -1;
int count = m_Mode.Extraction().Seats(m_Seats);
PrintFormat("ERR EXTRACTION CAPACITY: usable pilot/passenger positions=%1", count);
if (!Check(count >= 12 && !m_Mode.SetExtractionVehicle(m_Vehicle), "running mission cannot rebind its extraction vehicle")) return -1;
m_Vehicle.GetWorldTransform(m_Original);
DamageManagerComponent.Cast(m_Vehicle.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
for (int i = 0; i < 12; i++)
{
IEntity actor = Character(i);
UUID account = "00e77200-0000-0000-0000-" + (i + 1).ToString(12, true);
if (!Check(actor && m_Roster.Bind(10001 + i, account), "isolated participant created")) return -1;
m_Accounts.Insert(account);
m_Roster.Spawned(10001 + i, actor);
}
m_All = new ERR_ExtractionService(m_Roster);
if (!Check(m_All.Assign(m_Vehicle) && !m_All.Begin(m_Original[3], 0) && m_All.Begin(m_Original[3], 500), "invalid boundary rejected without preventing a valid service start")) return -1;
if (!Check(!m_All.Assign(m_Vehicle) && !m_All.Begin(m_Original[3], 500), "duplicate binding and start rejected")) return -1;
m_All.OnResolved().Insert(Resolved);
m_Step = 1;
m_Wait = now + 1000;
return 0;
}
if (m_Step == 1)
{
CompartmentAccessComponent access = CompartmentAccessComponent.Cast(m_Actors[m_Boarding].FindComponent(CompartmentAccessComponent));
if (!Check(access && access.GetInVehicle(m_Vehicle, m_Seats[m_Boarding], true, -1, ECloseDoorAfterActions.INVALID, false), "native boarding request accepted")) return -1;
m_Boarding++;
m_Wait = now + 500;
if (m_Boarding == 12) { m_Step = 2; m_Wait = now + 3000; }
return 0;
}
if (m_Step == 2)
{
for (int i = 0; i < 12; i++)
if (!Check(CompartmentAccessComponent.GetVehicleIn(m_Actors[i]) == m_Vehicle && m_Seats[i].GetOccupant() == m_Actors[i], "participant occupies a distinct physical seat")) return -1;
m_All.Tick(ERR_Phase.EXTRACTION);
if (!Check(m_All.State() == ERR_ExtractionState.WAITING && m_All.EscapedCount() == 0, "boarding inside boundary does not win")) return -1;
MoveHelicopter(true);
m_Step = 3;
m_Wait = now + 2500;
return 0;
}
if (m_Step == 3)
{
m_All.Tick(ERR_Phase.RADIO_SEARCH);
if (!Check(m_All.State() == ERR_ExtractionState.WAITING, "leaving before extraction phase does not win")) return -1;
m_All.Tick(ERR_Phase.EXTRACTION);
array<int> winners = {};
m_All.ConnectedWinners(winners);
if (!Check(m_All.EscapedCount() == 12 && m_All.LeftBehindCount() == 0 && winners.Count() == 12 && m_Resolutions == 1, "all twelve physical occupants escape exactly once")) return -1;
m_All.Tick(ERR_Phase.EXTRACTION);
if (!Check(m_Resolutions == 1 && !m_All.Assign(m_Vehicle), "resolved extraction neither repeats nor replaces its vehicle")) return -1;
MoveHelicopter(false);
m_Step = 4;
m_Wait = now + 2500;
return 0;
}
if (m_Step == 4)
{
SCR_CharacterDamageManagerComponent damage = SCR_CharacterDamageManagerComponent.Cast(m_Actors[0].FindComponent(SCR_CharacterDamageManagerComponent));
damage.EnableDamageHandling(true);
damage.Kill(Instigator.CreateInstigator(m_Actors[0]));
vector exitTransform[4];
m_Actors[1].GetWorldTransform(exitTransform);
exitTransform[3] = ERR_Actors.Ground(m_Original[3] + "30 0 0");
CompartmentAccessComponent access = CompartmentAccessComponent.Cast(m_Actors[1].FindComponent(CompartmentAccessComponent));
if (!Check(access.GetOutVehicle_NoDoor(exitTransform, false, false), "one survivor leaves the helicopter")) return -1;
CharacterControllerComponent.Cast(m_Actors[2].FindComponent(CharacterControllerComponent)).SetUnconscious(true);
m_Roster.Disconnect(10004);
IEntity newcomer = Character(12);
if (!Check(newcomer && m_Roster.Bind(10004, m_NewAccount), "departed connection ID reused by a distinct account")) return -1;
m_Roster.Spawned(10004, newcomer);
m_Partial = new ERR_ExtractionService(m_Roster);
if (!Check(m_Partial.Assign(m_Vehicle) && m_Partial.Begin(m_Original[3], 500), "isolated partial escape case starts")) return -1;
m_Step = 5;
m_Wait = now + 3000;
return 0;
}
if (m_Step == 5)
{
if (!Check(!ERR_Actors.Alive(m_Actors[0]) && !CompartmentAccessComponent.GetVehicleIn(m_Actors[1]), "dead occupant and left-behind survivor are physically distinct cases")) return -1;
if (!Check(ERR_Actors.Alive(m_Actors[2]) && !ERR_Actors.CanFight(m_Actors[2]) && CompartmentAccessComponent.GetVehicleIn(m_Actors[2]) == m_Vehicle, "unconscious passenger remains physically aboard")) return -1;
MoveHelicopter(true);
m_Step = 6;
m_Wait = now + 2500;
return 0;
}
if (m_Step == 6)
{
m_Partial.Tick(ERR_Phase.EXTRACTION);
array<int> winners = {};
m_Partial.ConnectedWinners(winners);
if (!Check(m_Partial.EscapedCount() == 10 && m_Partial.LeftBehindCount() == 3 && winners.Count() == 9, "partial escape counts only living aboard participants")) return -1;
if (!Check(m_Partial.DidEscape(m_Accounts[2]) && m_Partial.DidEscape(m_Accounts[3]) && !m_Partial.DidEscape(m_Accounts[0]) && !m_Partial.DidEscape(m_Accounts[1]) && !m_Partial.DidEscape(m_NewAccount), "unconscious and reserved occupants escape; dead and absent people do not")) return -1;
if (!Check(winners.Contains(10003) && !winners.Contains(10004), "recycled connection ID does not inherit another account's win")) return -1;
if (!Check(m_Roster.Bind(10040, m_Accounts[3]), "escaped reserved account can rebind to a new connection")) return -1;
m_Partial.ConnectedWinners(winners);
if (!Check(winners.Count() == 10 && winners.Contains(10040) && !winners.Contains(10004), "result follows escaped identity after rebinding")) return -1;
m_Partial.Tick(ERR_Phase.EXTRACTION);
if (!Check(m_Partial.EscapedCount() == 10 && m_Partial.LeftBehindCount() == 3, "terminal result snapshot remains stable")) return -1;
ERR_PlayerRoster emptyRoster = new ERR_PlayerRoster();
ERR_ExtractionService empty = new ERR_ExtractionService(emptyRoster);
if (!Check(empty.Assign(m_Vehicle) && empty.Begin(m_Original[3], 500), "empty-roster control starts")) return -1;
empty.Tick(ERR_Phase.EXTRACTION);
if (!Check(empty.State() == ERR_ExtractionState.WAITING, "unregistered occupants cannot win the mission")) return -1;
ERR_ExtractionService stopped = new ERR_ExtractionService(m_Roster);
if (!Check(stopped.Assign(m_Vehicle) && stopped.Begin(m_Original[3], 500), "stopped-service control starts")) return -1;
stopped.Stop();
stopped.Tick(ERR_Phase.EXTRACTION);
if (!Check(stopped.State() == ERR_ExtractionState.WAITING, "stopped extraction cannot resolve")) return -1;
if (!ERR_NativeResultProbe.Run(m_Partial, m_Accounts[3], m_Accounts[0]) || !ERR_NativeTimeProbe.VerifyProgression(m_Mode)) return -1;
MoveHelicopter(false);
for (int i = m_Actors.Count() - 1; i >= 0; i--) ERR_Actors.DeleteSpawned(m_Actors[i]);
m_Step = 7;
return 1;
}
return 0;
}
}
#endif
@@ -0,0 +1,75 @@
#ifdef WORKBENCH
// Read-only native assertions. Run after site preparation and faction init,
// before destructive vehicle fixtures. Does not load legacy full faction configs.
class ERR_NativeFactionCatalogProbe
{
protected static ResourceName UAZ() { return "{259EE7B78C51B624}Prefabs/Vehicles/Wheeled/UAZ469/UAZ469.et"; }
protected static ResourceName Mi8() { return "{DF5CCB7C0FF049F4}Prefabs/Vehicles/Helicopters/Mi8MT/Mi8MT_unarmed_transport.et"; }
protected static bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: faction-catalog " + detail); return true; }
Print("ERR SMOKE FAIL: faction-catalog " + detail, LogLevel.ERROR);
return false;
}
protected static bool Catalogs(SCR_Faction faction)
{
array<SCR_EntityCatalog> catalogs = {};
if (!Check(faction.GetAllFactionEntityCatalogs(catalogs) == 6, faction.GetFactionKey() + " retains all six catalog types")) return false;
SCR_EntityCatalog vehicles = faction.GetFactionEntityCatalogOfType(EEntityCatalogType.VEHICLE);
array<SCR_EntityCatalogEntry> entries = {};
if (!Check(vehicles && vehicles.GetEntityList(entries) == 2, faction.GetFactionKey() + " has exactly two supported vehicle entries")) return false;
bool uaz, mi8;
foreach (SCR_EntityCatalogEntry entry : entries)
{
if (!Check(entry != null, "vehicle entry exists")) return false;
ResourceName prefab = entry.GetPrefab();
PrintFormat("ERR FACTION VEHICLE: faction=%1 prefab=%2", faction.GetFactionKey(), prefab);
if (prefab == UAZ()) uaz = true;
else if (prefab == Mi8()) mi8 = true;
else return Check(false, "unexpected vehicle catalog entry " + prefab);
}
if (!Check(uaz && mi8 && vehicles.GetEntryWithPrefab(UAZ()) && vehicles.GetEntryWithPrefab(Mi8()), "catalog lookup resolves both unique mission vehicles")) return false;
array<EEntityCatalogType> retained = {EEntityCatalogType.CHARACTER, EEntityCatalogType.GROUP, EEntityCatalogType.WEAPONS_TRIPOD, EEntityCatalogType.ITEM, EEntityCatalogType.SUPPLY_CONTAINER_ITEM};
foreach (EEntityCatalogType kind : retained)
{
SCR_EntityCatalog catalog = faction.GetFactionEntityCatalogOfType(kind);
if (!Check(catalog != null, faction.GetFactionKey() + " non-vehicle catalog exists " + typename.EnumToString(EEntityCatalogType, kind))) return false;
catalog.GetEntityList(entries);
PrintFormat("ERR FACTION RETAINED: faction=%1 type=%2 entries=%3", faction.GetFactionKey(), typename.EnumToString(EEntityCatalogType, kind), entries.Count());
// Installed US/USSR supply-container catalogs are intentionally empty.
if (kind != EEntityCatalogType.SUPPLY_CONTAINER_ITEM && !Check(!entries.IsEmpty(), "retained native content catalog is initialized and nonempty")) return false;
}
return true;
}
protected static bool Items(SCR_Faction faction, ResourceName dressing, ResourceName saline, ResourceName radio)
{
SCR_EntityCatalog items = faction.GetFactionEntityCatalogOfType(EEntityCatalogType.ITEM);
return Check(items && items.GetEntryWithPrefab(dressing) && items.GetEntryWithPrefab(saline) && items.GetEntryWithPrefab(radio), faction.GetFactionKey() + " native dressing saline and backpack radio remain catalogued");
}
static bool Run(ERR_GameMode mode)
{
if (!Check(Replication.IsServer() && mode && mode.Active(), "active authority available")) return false;
FactionManager manager = GetGame().GetFactionManager();
if (!Check(manager != null, "native faction manager exists")) return false;
SCR_Faction us = SCR_Faction.Cast(manager.GetFactionByKey("US"));
SCR_Faction ussr = SCR_Faction.Cast(manager.GetFactionByKey("USSR"));
if (!Check(us && ussr && mode.PlayerFaction() == us && mode.EnemyFaction() == ussr, "mission retains US and USSR faction identities")) return false;
if (!Check(us.IsFactionEnemy(ussr) && ussr.IsFactionEnemy(us) && !us.IsFactionEnemy(us) && !ussr.IsFactionEnemy(ussr), "mutual enemy and same-faction relations preserved")) return false;
if (!Check(us.GetFactionRadioEncryptionKey() == "chickenNuggets" && ussr.GetFactionRadioEncryptionKey() == "coldBorscht" && us.GetFactionRadioFrequency() == 48000 && ussr.GetFactionRadioFrequency() == 42000, "native radio encryption and frequencies preserved")) return false;
if (!Check(us.GetCallsignInfo() && ussr.GetCallsignInfo(), "native faction callsign configurations resolve")) return false;
if (!Catalogs(us) || !Catalogs(ussr)) return false;
if (!Items(us, "{A81F501D3EF6F38E}Prefabs/Items/Medicine/FieldDressing_01/FieldDressing_US_01.et", "{00E36F41CA310E2A}Prefabs/Items/Medicine/SalineBag_01/SalineBag_US_01.et", "{9B6B61BB3FE3DFB0}Prefabs/Items/Equipment/Radios/Radio_ANPRC77.et")) return false;
if (!Items(ussr, "{C3F1FA1E2EC2B345}Prefabs/Items/Medicine/FieldDressing_01/FieldDressing_USSR_01.et", "{527D7C5D2E476BDC}Prefabs/Items/Medicine/SalineBag_01/SalineBag_USSR_01.et", "{54C68E438DD34265}Prefabs/Items/Equipment/Radios/Radio_R107M.et")) return false;
SCR_EntityCatalogManagerComponent catalogManager = SCR_EntityCatalogManagerComponent.GetInstance();
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(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;
Print("ERR FACTION RESULT: successor faction catalog scope and retained native support; no driving or multiplayer claim");
return true;
}
}
#endif
@@ -0,0 +1,247 @@
#ifdef WORKBENCH
// Native queue length is not exposed: SCR_AIGroup.GetSpawnQueueSize() is a
// compatibility stub returning zero. Observe dispatch on explicitly tagged
// disposable groups instead; every engine call still reaches unchanged super.
class ERR_NativeGuardDispatchTrace
{
static int Total;
}
modded class SCR_AIGroup
{
protected bool m_ERRGuardCancelTracked;
protected int m_ERRGuardCancelDispatches;
protected int m_ERRGuardCancelInitCalls;
protected int m_ERRGuardCancelRequestCalls;
void ERR_TrackGuardCancel()
{
m_ERRGuardCancelTracked = true;
PrintFormat("ERR GUARD CANCEL TRACK: group=%1 initCalls=%2 requests=%3 agents=%4 spawnImmediately=%5 slots=%6 lifecycle=%7", this, m_ERRGuardCancelInitCalls, m_ERRGuardCancelRequestCalls, GetAgentsCount(), GetSpawnImmediately(), m_aUnitPrefabSlots.Count(), GetLifecyclePolicy());
}
override void EOnInit(IEntity owner)
{
m_ERRGuardCancelInitCalls++;
if (m_ERRGuardCancelTracked) PrintFormat("ERR GUARD CANCEL LATE INIT: group=%1 initCalls=%2", this, m_ERRGuardCancelInitCalls);
super.EOnInit(owner);
}
override void RequestSpawn(int slotsWanted = -1, float observerRange = 0)
{
m_ERRGuardCancelRequestCalls++;
if (m_ERRGuardCancelTracked) PrintFormat("ERR GUARD CANCEL REQUEST: group=%1 request=%2 wanted=%3 range=%4 slots=%5", this, m_ERRGuardCancelRequestCalls, slotsWanted, observerRange, m_aUnitPrefabSlots.Count());
super.RequestSpawn(slotsWanted, observerRange);
}
int ERR_GuardCancelDispatches() { return m_ERRGuardCancelDispatches; }
// EOnInit assigns the cached utility before requesting members. The ignore-
// spawning branch instead invokes all-members init immediately; exclude it.
bool ERR_GuardCancelPendingInit() { return GetGroupUtilityComponent() && !m_bOnInitInvoked; }
override bool ExpandOneMember()
{
if (m_ERRGuardCancelTracked)
{
m_ERRGuardCancelDispatches++;
ERR_NativeGuardDispatchTrace.Total++;
PrintFormat("ERR GUARD CANCEL DISPATCH: group=%1 dispatch=%2 agents=%3 slots=%4 complete=%5", this, m_ERRGuardCancelDispatches, GetAgentsCount(), m_aUnitPrefabSlots.Count(), IsExpandComplete());
}
return super.ExpandOneMember();
}
}
// Assemble only the owned-group portion of PENDING preparation. No authored
// mission sites, stock, waypoints or live mission service are cloned or changed.
class ERR_NativeGuardCancelOwner : ERR_SiteService
{
protected int m_Cancellations;
void ERR_NativeGuardCancelOwner(ERR_GameMode mode) { m_Mode = mode; }
int Cancellations() { return m_Cancellations; }
protected override void CancelInitialGuardRequests()
{
m_Cancellations++;
PrintFormat("ERR GUARD CANCEL OWNER: pendingState=%1 calls=%2 ownedSites=%3", m_State, m_Cancellations, m_Sites.Count());
super.CancelInitialGuardRequests();
foreach (ERR_SiteRuntime site : m_Sites)
if (site.Group) PrintFormat("ERR GUARD CANCEL RETIRED: site=%1 group=%2 slots=%3 spawnImmediately=%4 agents=%5 complete=%6", site.Definition.Id, site.Group, site.Group.m_aUnitPrefabSlots.Count(), site.Group.GetSpawnImmediately(), site.Group.GetAgentsCount(), site.Group.IsExpandComplete());
}
bool Attach(SCR_AIGroup group, string label)
{
if (!group || m_Begun) return false;
m_Begun = true;
ERR_SiteRuntime site = new ERR_SiteRuntime();
site.Definition = new ERR_SiteDefinition();
site.Definition.Id = label;
site.Definition.Guarded = true;
site.Group = group;
if (group.m_aUnitPrefabSlots) site.InitialGuardCount = group.m_aUnitPrefabSlots.Count();
m_Sites.Insert(site);
m_Entities.Insert(group);
return site.InitialGuardCount == 6 && group.GetSpawnImmediately();
}
}
class ERR_NativeGuardCancelProbe
{
protected ERR_GameMode m_Mode;
protected ref ERR_NativeGuardCancelOwner m_EmptyOwner;
protected ref ERR_NativeGuardCancelOwner m_PartialOwner;
protected ref ERR_NativeGuardCancelOwner m_ControlOwner;
protected SCR_AIGroup m_Empty;
protected SCR_AIGroup m_Partial;
protected SCR_AIGroup m_Control;
protected ref array<IEntity> m_PartialMembers = {};
protected ref array<IEntity> m_DeletedMembers = {};
protected ref array<SCR_AIGroup> m_MainGroups = {};
protected int m_PartialDispatches;
protected int m_TotalBeforeDelete;
protected int m_Step;
protected float m_Deadline;
protected float m_ObserveUntil;
protected bool m_RolledBack;
void ERR_NativeGuardCancelProbe(ERR_GameMode mode) { m_Mode = mode; }
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: guard-cancel " + detail); return true; }
Print("ERR SMOKE FAIL: guard-cancel " + detail, LogLevel.ERROR);
Stop();
return false;
}
protected int Members(SCR_AIGroup group, out array<IEntity> members)
{
members.Clear();
if (!group) return 0;
array<AIAgent> agents = {};
group.GetAgents(agents);
foreach (AIAgent agent : agents)
{
if (!agent) continue;
IEntity actor = agent.GetControlledEntity();
if (actor && !members.Contains(actor)) members.Insert(actor);
}
return members.Count();
}
protected SCR_AIGroup NewGroup(vector offset)
{
ERR_SiteRuntime outpost = m_Mode.Sites().Find("everon-outpost-1");
if (!outpost || !outpost.Group) return null;
SCR_AIGroup group = SCR_AIGroup.Cast(ERR_Actors.Spawn(m_Mode.Factions().GuardGroupPrefab, ERR_Actors.Ground(outpost.Group.GetOrigin() + offset)));
if (group) group.ERR_TrackGuardCancel();
return group;
}
protected bool OriginalSitesIntact()
{
if (!m_Mode.Sites() || m_Mode.Sites().Prepare() != ERR_PrepareResult.READY) return false;
array<ERR_LocationComponent> locations = {};
m_Mode.Locations(locations);
array<SCR_AIGroup> groups = {};
foreach (ERR_LocationComponent location : locations)
{
ERR_SiteRuntime site = m_Mode.Sites().Find(location.Id());
if (site && site.Group) groups.Insert(site.Group);
}
if (groups.Count() != m_MainGroups.Count()) return false;
foreach (SCR_AIGroup group : m_MainGroups)
if (!group || !groups.Contains(group)) return false;
return true;
}
protected bool CancelledStable()
{
array<IEntity> emptyMembers = {};
array<IEntity> partialMembers = {};
if (!m_Empty || Members(m_Empty, emptyMembers) != 0 || m_Empty.ERR_GuardCancelDispatches() != 0) return false;
if (!m_Partial || Members(m_Partial, partialMembers) != m_PartialMembers.Count() || m_Partial.ERR_GuardCancelDispatches() != m_PartialDispatches) return false;
foreach (IEntity actor : m_PartialMembers)
if (!actor || !partialMembers.Contains(actor)) return false;
return true;
}
void Stop()
{
// Each owner rolls back at most once. ERR_Actors.DeleteSpawned retains its
// stock active-player protection; no player is assigned to these groups.
if (m_RolledBack) return;
m_RolledBack = true;
if (m_EmptyOwner) m_EmptyOwner.Rollback();
if (m_PartialOwner) m_PartialOwner.Rollback();
if (m_ControlOwner) m_ControlOwner.Rollback();
}
int Tick(float now)
{
if (m_Deadline == 0) m_Deadline = now + 90000;
if (now > m_Deadline || !m_Mode.Active()) { Check(false, "bounded fixture deadline or active mission lost"); return -1; }
if (m_Step == 0)
{
array<ERR_LocationComponent> locations = {};
m_Mode.Locations(locations);
foreach (ERR_LocationComponent location : locations)
{
ERR_SiteRuntime site = m_Mode.Sites().Find(location.Id());
if (site && site.Group) m_MainGroups.Insert(site.Group);
}
ERR_NativeGuardDispatchTrace.Total = 0;
m_EmptyOwner = new ERR_NativeGuardCancelOwner(m_Mode);
m_PartialOwner = new ERR_NativeGuardCancelOwner(m_Mode);
m_ControlOwner = new ERR_NativeGuardCancelOwner(m_Mode);
m_Empty = NewGroup("50 0 60");
if (!Check(m_EmptyOwner.Attach(m_Empty, "guard-cancel-empty"), "empty case owns one six-slot automatic native group")) return -1;
array<IEntity> initial = {};
if (!Check(m_Empty.ERR_GuardCancelPendingInit() && Members(m_Empty, initial) == 0 && m_Empty.ERR_GuardCancelDispatches() == 0 && !m_Empty.IsExpandComplete(), "empty case is cancelled before its first native member dispatch")) return -1;
m_EmptyOwner.Stop();
m_EmptyOwner.Stop();
if (!Check(m_EmptyOwner.Cancellations() == 1 && !m_Empty.GetSpawnImmediately() && m_Empty.m_aUnitPrefabSlots.IsEmpty(), "empty cancellation retires only this instance's spawn intent once")) return -1;
if (!Check(m_Empty && m_EmptyOwner.Owns(m_Empty) && m_EmptyOwner.Prepare() == ERR_PrepareResult.FAILED && !m_Mode.Sites().Owns(m_Empty), "repeated actual preparation stop retains only the disposable empty group for observation")) return -1;
m_Partial = NewGroup("-50 0 60");
if (!Check(m_PartialOwner.Attach(m_Partial, "guard-cancel-partial"), "partial case owns a separate six-slot automatic native group")) return -1;
m_Control = NewGroup("0 0 100");
if (!Check(m_ControlOwner.Attach(m_Control, "guard-cancel-control"), "uncancelled six-slot control uses the same native queue")) return -1;
m_Step = 1;
return 0;
}
if (m_Step == 1)
{
array<IEntity> emptyMembers = {};
if (!m_Empty || Members(m_Empty, emptyMembers) != 0 || m_Empty.ERR_GuardCancelDispatches() != 0)
{ Check(false, "empty group received late dispatch or members after cancellation"); return -1; }
if (!m_Partial || !m_Control) { Check(false, "disposable observation group disappeared before cancellation"); return -1; }
if (Members(m_Partial, m_PartialMembers) == 0) return 0;
if (!Check(m_PartialMembers.Count() < 6 && !m_Partial.IsExpandComplete() && m_Partial.ERR_GuardCancelDispatches() > 0, "native queue produced a real partial squad before cancellation")) return -1;
// The fixture tick runs after native dispatch returns, so the queue has
// finished re-enqueuing this partial fill. Do not purge from inside AddAgent.
m_PartialOwner.Stop();
m_PartialOwner.Stop();
if (!Check(m_PartialOwner.Cancellations() == 1 && !m_Partial.GetSpawnImmediately() && m_Partial.m_aUnitPrefabSlots.IsEmpty(), "partial cancellation retires future spawn intent without deleting present actors")) return -1;
m_PartialDispatches = m_Partial.ERR_GuardCancelDispatches();
if (!Check(m_PartialOwner.Prepare() == ERR_PrepareResult.FAILED && m_PartialOwner.Owns(m_Partial), "partial preparation stops idempotently while existing members remain")) return -1;
PrintFormat("ERR GUARD CANCEL: partialMembers=%1 dispatches=%2 observationSeconds=15", m_PartialMembers.Count(), m_PartialDispatches);
m_ObserveUntil = now + 15000;
m_Step = 2;
return 0;
}
if (m_Step == 2)
{
if (!CancelledStable()) { Check(false, "cancelled groups changed member identity/count or received another native dispatch"); return -1; }
array<IEntity> controlMembers = {};
int controlCount = Members(m_Control, controlMembers);
if (!m_Control || controlCount > 6) { Check(false, "uncancelled control vanished or exceeded its requested size"); return -1; }
if (now < m_ObserveUntil || controlCount != 6 || !m_Control.IsExpandComplete()) return 0;
if (!Check(m_Control.ERR_GuardCancelDispatches() >= 6 && m_Control.m_aUnitPrefabSlots.Count() == 6 && m_Control.GetSpawnImmediately(), "uncancelled native control reached six while cancelled groups stayed unchanged for at least fifteen seconds")) return -1;
if (!Check(OriginalSitesIntact(), "original mission site groups and ready service remain intact")) return -1;
foreach (IEntity partialMember : m_PartialMembers) m_DeletedMembers.Insert(partialMember);
foreach (IEntity controlMember : controlMembers) m_DeletedMembers.Insert(controlMember);
foreach (IEntity actor : m_DeletedMembers)
if (!Check(!ERR_Actors.Player(actor), "rollback target is a disposable non-player character")) return -1;
m_TotalBeforeDelete = ERR_NativeGuardDispatchTrace.Total;
Stop();
m_ObserveUntil = now + 6000;
m_Step = 3;
return 0;
}
if (m_Step == 3)
{
if (ERR_NativeGuardDispatchTrace.Total != m_TotalBeforeDelete) { Check(false, "native dispatch occurred after rollback"); return -1; }
if (now < m_ObserveUntil) return 0;
if (!Check(!m_Empty && !m_Partial && !m_Control, "each disposable group was deleted once by its real owner rollback")) return -1;
foreach (IEntity deleted : m_DeletedMembers)
if (!Check(!deleted, "each physical partial/control member is actually deleted after rollback")) return -1;
if (!Check(OriginalSitesIntact(), "original mission groups remain intact after rollback and six-second observation")) return -1;
return 1;
}
return 0;
}
}
#endif
@@ -0,0 +1,190 @@
#ifdef WORKBENCH
[BaseContainerProps(category: "Respawn")]
modded class ERR_SpawnLogic
{
// Test-only ownership setup. This does not change a life record, start a real
// spawn request, replace a controller's actor or bypass production finalization.
bool ERR_TestPendingActor(SCR_SpawnRequestComponent request, SCR_SpawnData data, IEntity actor)
{
if (!Replication.IsServer() || !request || !data || !actor || ERR_Actors.Player(actor)) return false;
int id = request.GetPlayerId();
if (m_Pending.Contains(id) || !m_Identities.Contains(id)) return false;
ERR_SpawnTicket ticket = new ERR_SpawnTicket();
ticket.ConnectionId = id; ticket.AccountId = m_Identities.Get(id);
ticket.Mode = ERR_GameMode.Get();
ticket.Controller = SCR_PlayerController.Cast(request.GetPlayerController());
ticket.Request = request; ticket.Data = data; ticket.Actor = actor;
m_SpawnTickets.Insert(ticket);
m_Pending.Insert(id);
m_AuthorizedPositions.Set(id, data.GetPosition());
return true;
}
void ERR_TestClearAdmission(int playerId) { CancelAdmission(playerId, ERR_ObserverReason.NONE); }
bool ERR_TestDelayedLossPreservesAdmission(int playerId, SCR_PlayerController controller)
{
ObserveAfterLoss(playerId, m_Identities.Get(playerId), controller);
return m_Pending.Contains(playerId) && m_AuthorizedPositions.Contains(playerId);
}
bool ERR_TestHasAdmission(int playerId)
{
if (m_Pending.Contains(playerId) || m_AuthorizedPositions.Contains(playerId)) return true;
foreach (ERR_SpawnTicket ticket : m_SpawnTickets)
if (ticket.ConnectionId == playerId) return true;
return false;
}
}
class ERR_NativeLifecycleProbe
{
protected ERR_GameMode m_Mode;
protected IEntity m_Player;
protected IEntity m_Blocker;
protected IEntity m_Preload;
protected IEntity m_Cancelled;
protected IEntity m_Drift;
protected SCR_PlayerController m_Controller;
protected SCR_FreeSpawnRequestComponent m_Request;
protected SCR_FreeSpawnHandlerComponent m_Handler;
protected ERR_SpawnLogic m_Logic;
protected ref SCR_FreeSpawnData m_Data;
protected ref ERR_SpawnPlacement m_Placement = new ERR_SpawnPlacement();
protected vector m_Position;
protected int m_Step;
protected float m_Wait;
void ERR_NativeLifecycleProbe(ERR_GameMode mode, IEntity player) { m_Mode = mode; m_Player = player; }
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: lifecycle " + detail); return true; }
Print("ERR SMOKE FAIL: lifecycle " + detail, LogLevel.ERROR);
return false;
}
protected IEntity Actor(vector position)
{
IEntity actor = ERR_Actors.Spawn(m_Mode.Factions().PrisonerPrefab, position);
if (actor)
{
m_Mode.Own(actor);
DamageManagerComponent.Cast(actor.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
}
return actor;
}
protected bool Rules()
{
UUID account = "00e77300-0000-0000-0000-000000000001";
UUID other = "00e77300-0000-0000-0000-000000000002";
for (int phase = ERR_Phase.PREPARING; phase <= ERR_Phase.DEFEAT; phase++)
{
bool permitted = phase >= ERR_Phase.PRISON && phase <= ERR_Phase.RADIO_DEFENCE;
if (!Check(ERR_SpawnFinalizationRules.Allows(phase, true, false, account, account, true, true, true) == permitted, "finalization phase matrix " + phase.ToString())) return false;
}
if (!Check(!ERR_SpawnFinalizationRules.Allows(ERR_Phase.PRISON, true, true, account, account, true, true, true), "spent life cannot finalize a delayed admission")) return false;
if (!Check(!ERR_SpawnFinalizationRules.Allows(ERR_Phase.PRISON, false, false, account, account, true, true, true), "disconnected account cannot finalize")) return false;
if (!Check(!ERR_SpawnFinalizationRules.Allows(ERR_Phase.PRISON, true, false, account, other, true, true, true), "recycled connection identity cannot inherit a pending admission")) return false;
if (!Check(!ERR_SpawnFinalizationRules.Allows(ERR_Phase.PRISON, true, false, account, account, false, true, true), "changed controller rejects stale admission")) return false;
if (!Check(!ERR_SpawnFinalizationRules.Allows(ERR_Phase.PRISON, true, false, account, account, true, false, true), "changed request rejects stale admission")) return false;
if (!Check(!ERR_SpawnFinalizationRules.Allows(ERR_Phase.PRISON, true, false, account, account, true, true, false), "existing controlled actor cannot be replaced by delayed admission")) return false;
return Check(ERR_SpawnFinalizationRules.PositionClose("0.3 0.2 0", "0 0 0") && !ERR_SpawnFinalizationRules.PositionClose("0 2 0", "0 0 0") && !ERR_SpawnFinalizationRules.PositionClose("0.7 0 0", "0 0 0"), "native settling tolerance rejects horizontal and vertical preload drift");
}
protected bool Finalization()
{
m_Controller = SCR_PlayerController.Cast(GetGame().GetPlayerController());
m_Request = SCR_FreeSpawnRequestComponent.Cast(m_Controller.FindComponent(SCR_FreeSpawnRequestComponent));
if (m_Request) m_Handler = SCR_FreeSpawnHandlerComponent.Cast(m_Request.GetHandlerComponent());
SCR_RespawnSystemComponent system = SCR_RespawnSystemComponent.GetInstance();
m_Logic = ERR_SpawnLogic.Cast(system.GetSpawnLogic());
if (!Check(m_Request && m_Handler && m_Logic, "real local free-spawn request, handler and admission logic exist")) return false;
int id = m_Controller.GetPlayerId();
ERR_PlayerLife life = m_Mode.Players().Get(id);
if (!Check(life && life.UsedLife && life.Actor == m_Player && m_Controller.GetControlledEntity() == m_Player && !m_Logic.ERR_TestHasAdmission(id), "real prisoner has a spent life and no pending admission")) return false;
m_Preload = Actor(m_Position);
m_Data = new SCR_FreeSpawnData(m_Mode.Factions().PrisonerPrefab, m_Position);
if (!Check(m_Preload && m_Logic.ERR_TestPendingActor(m_Request, m_Data, m_Preload), "isolated unpossessed actor receives an explicit test-owned admission ticket")) return false;
SCR_FreeSpawnData different = new SCR_FreeSpawnData(m_Mode.Factions().PrisonerPrefab, m_Position);
if (!Check(m_Handler.FinalizeRequest_S(m_Request, different, m_Preload) == SCR_ESpawnResult.SPAWN_NOT_ALLOWED && ERR_Actors.Alive(m_Preload), "different request payload cannot claim or delete the owned actor")) return false;
return Check(m_Handler.FinalizeRequest_S(m_Request, m_Data, m_Preload) == SCR_ESpawnResult.SPAWN_NOT_ALLOWED, "native finalization refuses the spent-life preload");
}
int Tick(float now)
{
if (now < m_Wait) return 0;
if (m_Step == 0)
{
if (!Rules()) return -1;
map<int, vector> reservations = new map<int, vector>();
vector first, second;
if (!Check(m_Placement.Prison(m_Mode.PrisonPosition(), reservations, first), "native prison has a clear candidate")) return -1;
reservations.Set(1, first);
if (!Check(m_Placement.Prison(m_Mode.PrisonPosition(), reservations, second) && vector.DistanceXZ(first, second) >= 1.2, "IDs one and thirteen receive distinct reserved slots")) return -1;
reservations.Set(13, second);
vector position;
int count = 2;
while (count < 16 && m_Placement.Prison(m_Mode.PrisonPosition(), reservations, position))
{
reservations.Set(100 + count, position);
count++;
}
PrintFormat("ERR SPAWN CAPACITY: remaining clear prison positions=%1 plus current controlled prisoner", count);
if (!Check(count >= 11 && !m_Placement.Prison(m_Mode.PrisonPosition(), reservations, position), "occupied and pending slots exhaust a bounded grid without overlap or outside fallback")) return -1;
vector prison = m_Mode.PrisonPosition();
foreach (int id, vector reserved : reservations)
if (!Check(Math.AbsFloat(reserved[0] - prison[0]) <= 4.6 && Math.AbsFloat(reserved[2] - prison[2]) <= 4.6, "candidate remains inside authored prison")) return -1;
reservations.Remove(1);
if (!Check(m_Placement.Prison(m_Mode.PrisonPosition(), reservations, position) && vector.DistanceXZ(position, first) < 0.1, "released pending reservation becomes available again")) return -1;
m_Position = first;
m_Blocker = Actor(first);
if (!Check(m_Blocker != null, "uncontrolled physical body created at a previously clear slot")) return -1;
m_Step = 1; m_Wait = now + 1000;
return 0;
}
if (m_Step == 1)
{
map<int, vector> empty = new map<int, vector>();
if (!Check(!m_Placement.Clear(m_Position, empty), "native character body blocks a slot even without a connected player ID")) return -1;
ERR_Actors.DeleteSpawned(m_Blocker);
m_Step = 2; m_Wait = now + 1000;
return 0;
}
if (m_Step == 2)
{
if (!Finalization()) return -1;
m_Step = 3; m_Wait = now + 1000;
return 0;
}
if (m_Step == 3)
{
int id = m_Controller.GetPlayerId();
ERR_PlayerLife life = m_Mode.Players().Get(id);
if (!Check(!m_Preload, "refused native preload was deleted after lifecycle processing")) return -1;
m_Logic.ERR_TestClearAdmission(id);
if (!Check(!m_Logic.ERR_TestHasAdmission(id) && life.UsedLife && life.Actor == m_Player && m_Controller.GetControlledEntity() == m_Player, "refusal releases reservations without changing life or controlled actor")) return -1;
if (!Check(m_Handler.FinalizeRequest_S(m_Request, m_Data, m_Player) == SCR_ESpawnResult.SPAWN_NOT_ALLOWED && ERR_Actors.Alive(m_Player), "unticketed repeated callback cannot delete or replace the controlled prisoner")) return -1;
m_Cancelled = Actor(m_Position);
if (!Check(m_Cancelled && m_Logic.ERR_TestPendingActor(m_Request, m_Data, m_Cancelled), "second isolated pending actor prepared for cancellation")) return -1;
if (!Check(m_Logic.ERR_TestDelayedLossPreservesAdmission(id, m_Controller) && m_Controller.ERR_ObserverState() == ERR_ObserverReason.NONE, "delayed loss callback preserves reacquired living controller and admission metadata")) return -1;
m_Logic.ERR_TestClearAdmission(id);
m_Step = 4; m_Wait = now + 1000;
return 0;
}
if (m_Step == 4)
{
int id = m_Controller.GetPlayerId();
ERR_PlayerLife life = m_Mode.Players().Get(id);
if (!Check(!m_Cancelled && !m_Logic.ERR_TestHasAdmission(id) && life.UsedLife && life.Actor == m_Player, "pending-request cancellation cleans its actor and never grants another life")) return -1;
m_Drift = Actor(m_Position);
if (!Check(m_Drift != null, "physical actor created for vertical drift check")) return -1;
vector transform[4]; m_Drift.GetWorldTransform(transform);
transform[3] = m_Position + "0 10 0";
ChimeraCharacter.Cast(m_Drift).Teleport(transform);
m_Step = 5; m_Wait = now + 750;
return 0;
}
if (m_Step == 5)
{
if (!Check(!ERR_SpawnFinalizationRules.PositionClose(m_Drift.GetOrigin(), m_Position), "actual vertically displaced character fails finalization position tolerance after native movement")) return -1;
ERR_Actors.DeleteSpawned(m_Drift);
m_Step = 6;
return 1;
}
return 0;
}
}
#endif
@@ -0,0 +1,225 @@
#ifdef WORKBENCH
// Opt-in native medical-effects fixture. Treatment enters at the real item's
// completion boundary; manual user actions/animations and remote clients are not
// simulated. Only fixture-owned characters are injured or deleted.
class ERR_NativeMedicalProbe
{
protected ERR_GameMode m_Mode;
protected IEntity m_Casualty;
protected IEntity m_Healer;
protected IEntity m_Dressing;
protected IEntity m_Saline;
protected IEntity m_SpareSaline;
protected SCR_CharacterDamageManagerComponent m_Damage;
protected CharacterControllerComponent m_Controller;
protected SCR_CharacterBloodHitZone m_Blood;
protected SCR_SalineStorageComponent m_SalineStorage;
protected SCR_InventoryStorageManagerComponent m_Inventory;
protected ref array<IEntity> m_Owned = {};
protected int m_Step;
protected int m_Result;
protected bool m_Stopped;
protected bool m_Recovered;
protected float m_Wait;
protected float m_Deadline;
protected float m_InfusionStarted;
protected float m_SeedBlood;
protected float m_PreInfusionBlood;
protected float m_RegenSpeed;
protected float m_NextDiagnostic;
void ERR_NativeMedicalProbe(ERR_GameMode mode) { m_Mode = mode; }
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: medical " + detail); return true; }
Print("ERR SMOKE FAIL: medical " + detail, LogLevel.ERROR);
Trace("failure");
m_Result = -1;
Stop();
return false;
}
protected IEntity Spawn(ResourceName prefab, vector position)
{
IEntity entity = ERR_Actors.Spawn(prefab, ERR_Actors.Ground(position));
if (entity) { m_Owned.Insert(entity); m_Mode.Own(entity); }
return entity;
}
protected int SalineEffects()
{
if (!m_Damage) return 0;
array<ref SCR_PersistentDamageEffect> effects = {};
m_Damage.GetPersistentEffects(effects);
int count;
foreach (SCR_PersistentDamageEffect effect : effects)
if (SCR_SalineDamageEffect.Cast(effect)) count++;
return count;
}
protected int AttachedBags()
{
if (!m_SalineStorage) return 0;
array<IEntity> items = {};
m_SalineStorage.GetAll(items);
return items.Count();
}
protected void Trace(string stage)
{
if (!m_Blood || !m_Controller || !m_Damage) return;
PrintFormat("ERR MEDICAL STATE: stage=%1 blood=%2 bloodState=%3 unconscious=%4 bleeding=%5 salineEffects=%6 attachedBags=%7", stage, m_Blood.GetHealth(), m_Blood.GetDamageState(), m_Controller.IsUnconscious(), m_Damage.IsBleeding(), SalineEffects(), AttachedBags());
}
void Stop()
{
if (m_Stopped) return;
m_Stopped = true;
// Items first, then characters; native scheduled saline deletion is null-safe.
for (int i = m_Owned.Count() - 1; i >= 0; i--) ERR_Actors.DeleteSpawned(m_Owned[i]);
m_Owned.Clear();
m_Casualty = null;
m_Healer = null;
m_Dressing = null;
m_Saline = null;
m_SpareSaline = null;
m_Damage = null;
m_Controller = null;
m_Blood = null;
m_SalineStorage = null;
m_Inventory = null;
}
int Tick(float now)
{
if (m_Result != 0) return m_Result;
if (m_Stopped) return -1;
if (!Replication.IsServer() || !m_Mode || !m_Mode.Active())
{
Check(false, "fixture requires the active authority");
return -1;
}
if (m_Deadline > 0 && now > m_Deadline)
{
Check(false, "native medical state timed out at step " + m_Step.ToString());
return -1;
}
if (now < m_Wait) return 0;
if (m_Step == 0)
{
// Keep fixture actors separate from the real prisoner spawn.
vector anchor = m_Mode.PrisonPosition() + "100 0 100";
m_Casualty = Spawn(m_Mode.Factions().PrisonerPrefab, anchor);
m_Healer = Spawn(m_Mode.Factions().PrisonerPrefab, anchor + "3 0 0");
m_Dressing = Spawn("{C3F1FA1E2EC2B345}Prefabs/Items/Medicine/FieldDressing_01/FieldDressing_USSR_01.et", anchor + "4 0 0");
m_Saline = Spawn("{527D7C5D2E476BDC}Prefabs/Items/Medicine/SalineBag_01/SalineBag_USSR_01.et", anchor + "5 0 0");
m_SpareSaline = Spawn("{527D7C5D2E476BDC}Prefabs/Items/Medicine/SalineBag_01/SalineBag_USSR_01.et", anchor + "6 0 0");
if (!Check(m_Casualty && m_Healer && m_Dressing && m_Saline && m_SpareSaline, "two isolated characters and finite stock-type medicine spawned")) return -1;
if (!Check(!ERR_Actors.Player(m_Casualty) && !ERR_Actors.Player(m_Healer), "neither medical actor is a real player")) return -1;
m_Deadline = now + 120000;
m_Wait = now + 1000;
m_Step = 1;
return 0;
}
if (!ERR_Actors.Alive(m_Casualty) || !ERR_Actors.Alive(m_Healer))
{
Check(false, "owned casualty or healer died unexpectedly");
return -1;
}
if (m_Step == 1)
{
m_Damage = SCR_CharacterDamageManagerComponent.Cast(m_Casualty.FindComponent(SCR_CharacterDamageManagerComponent));
m_Controller = CharacterControllerComponent.Cast(m_Casualty.FindComponent(CharacterControllerComponent));
m_SalineStorage = SCR_SalineStorageComponent.Cast(m_Casualty.FindComponent(SCR_SalineStorageComponent));
m_Inventory = SCR_InventoryStorageManagerComponent.Cast(m_Healer.FindComponent(SCR_InventoryStorageManagerComponent));
if (!Check(m_Damage && m_Controller && m_SalineStorage && m_Inventory, "native health, controller and medical storage components available")) return -1;
m_Blood = m_Damage.GetBloodHitZone();
if (!Check(m_Blood && !m_Controller.IsUnconscious() && !m_Damage.IsBleeding(), "casualty begins healthy and conscious")) return -1;
if (!Check(m_Inventory.TryInsertItem(m_Dressing) && m_Inventory.TryInsertItem(m_Saline) && m_Inventory.TryInsertItem(m_SpareSaline), "finite medical items enter healer inventory")) return -1;
SCR_ConsumableItemComponent dressing = SCR_ConsumableItemComponent.Cast(m_Dressing.FindComponent(SCR_ConsumableItemComponent));
SCR_ConsumableItemComponent saline = SCR_ConsumableItemComponent.Cast(m_Saline.FindComponent(SCR_ConsumableItemComponent));
if (!Check(dressing && saline, "stock items contain actual native consumable components")) return -1;
SCR_ConsumableBandage bandageEffect = SCR_ConsumableBandage.Cast(dressing.GetConsumableEffect());
SCR_ConsumableSalineBag salineEffect = SCR_ConsumableSalineBag.Cast(saline.GetConsumableEffect());
SCR_EConsumableFailReason reason;
if (!Check(bandageEffect && salineEffect && !bandageEffect.CanApplyEffect(m_Casualty, m_Healer, reason) && !salineEffect.CanApplyEffect(m_Casualty, m_Healer, reason), "healthy casualty rejects bandage and saline")) return -1;
array<HitZone> zones = {};
m_Damage.GetHitZonesOfGroupFromOwner(ECharacterHitZoneGroup.LEFTARM, zones);
SCR_CharacterHitZone wound;
foreach (HitZone zone : zones)
{
SCR_CharacterHitZone candidate = SCR_CharacterHitZone.Cast(zone);
if (candidate && candidate.GetMaxBleedingRate() > 0) { wound = candidate; break; }
}
if (!Check(wound != null, "native left-arm hit zone can bleed")) return -1;
wound.SetHealthScaled(0.5);
m_Damage.AddBleedingEffectOnHitZone(wound);
float threshold = m_Blood.GetDamageStateThreshold(ECharacterBloodState.UNCONSCIOUS) * m_Blood.GetMaxHealth();
float destroyed = m_Blood.GetDamageStateThreshold(ECharacterBloodState.DESTROYED) * m_Blood.GetMaxHealth();
if (!Check(threshold - destroyed > 200, "native blood thresholds permit a survivable recovery fixture")) return -1;
// Initial casualty condition only. All subsequent blood changes are native effects.
m_Blood.SetHealth(threshold - 100);
m_SeedBlood = m_Blood.GetHealth();
m_Wait = now + 5000;
m_Step = 2;
return 0;
}
if (m_Step == 2)
{
Trace("untreated blood loss");
if (!Check(m_Damage.IsBleeding() && m_Blood.GetHealth() < m_SeedBlood && m_Controller.IsUnconscious(), "native bleeding reduces real blood and causes unconsciousness")) return -1;
SCR_ConsumableItemComponent dressing = SCR_ConsumableItemComponent.Cast(m_Dressing.FindComponent(SCR_ConsumableItemComponent));
SCR_ConsumableBandage effect = SCR_ConsumableBandage.Cast(dressing.GetConsumableEffect());
SCR_EConsumableFailReason reason;
if (!Check(effect.CanApplyEffectToHZ(m_Casualty, m_Healer, ECharacterHitZoneGroup.LEFTARM, reason), "real dressing accepts bleeding arm")) return -1;
ItemUseParameters parameters = effect.GetAnimationParameters(m_Dressing, m_Casualty, ECharacterHitZoneGroup.LEFTARM);
if (!Check(parameters && effect.GetDeleteOnUse(), "native dressing parameters and finite consumption policy are valid")) return -1;
dressing.ApplyItemEffect(m_Casualty, m_Healer, parameters, m_Dressing, effect.GetDeleteOnUse());
m_Wait = now + 500;
m_Step = 3;
return 0;
}
if (m_Step == 3)
{
if (!Check(!m_Dressing && !m_Damage.IsBleeding() && m_Damage.GetGroupDamageOverTime(ECharacterHitZoneGroup.LEFTARM, EDamageType.BLEEDING) == 0, "dressing is consumed and native bleeding is stopped")) return -1;
if (!Check(m_Controller.IsUnconscious(), "bandage alone has not fabricated blood recovery")) return -1;
SCR_ConsumableItemComponent saline = SCR_ConsumableItemComponent.Cast(m_Saline.FindComponent(SCR_ConsumableItemComponent));
SCR_ConsumableSalineBag effect = SCR_ConsumableSalineBag.Cast(saline.GetConsumableEffect());
SCR_EConsumableFailReason reason;
if (!Check(effect && effect.CanApplyEffect(m_Casualty, m_Healer, reason) && !effect.GetDeleteOnUse(), "native saline accepts real blood loss and retains bag for infusion")) return -1;
ItemUseParameters parameters = effect.GetAnimationParameters(m_Saline, m_Casualty);
if (!Check(parameters != null, "native saline item parameters available")) return -1;
m_PreInfusionBlood = m_Blood.GetHealth();
m_RegenSpeed = effect.GetItemRegenSpeed();
saline.ApplyItemEffect(m_Casualty, m_Healer, parameters, m_Saline, effect.GetDeleteOnUse());
m_InfusionStarted = now;
m_Wait = now + 1000;
m_Step = 4;
return 0;
}
if (m_Step == 4)
{
array<IEntity> attached = {};
m_SalineStorage.GetAll(attached);
if (!Check(m_Saline && attached.Count() == 1 && attached.Contains(m_Saline) && m_Damage.GetGroupSalineBagged(ECharacterHitZoneGroup.RIGHTARM) && SalineEffects() == 1, "real saline bag occupies native treatment slot with one healing effect")) return -1;
SCR_ConsumableItemComponent spare = SCR_ConsumableItemComponent.Cast(m_SpareSaline.FindComponent(SCR_ConsumableItemComponent));
SCR_EConsumableFailReason reason;
if (!Check(!spare.GetConsumableEffect().CanApplyEffect(m_Casualty, m_Healer, reason) && reason == SCR_EConsumableFailReason.ALREADY_APPLIED && m_SpareSaline, "occupied treatment slot rejects another bag without consuming spare")) return -1;
m_NextDiagnostic = now;
m_Step = 5;
}
if (m_Step == 5)
{
if (now >= m_NextDiagnostic) { Trace("native infusion"); m_NextDiagnostic = now + 10000; }
if (!m_Controller.IsUnconscious()) m_Recovered = true;
if (m_Saline || AttachedBags() != 0 || SalineEffects() != 0 || m_Damage.GetGroupSalineBagged(ECharacterHitZoneGroup.RIGHTARM)) return 0;
float requiredGain = Math.Min(m_RegenSpeed * 60, (m_Blood.GetMaxHealth() - m_PreInfusionBlood) * 0.8);
if (!Check(m_RegenSpeed > 0 && m_Blood.GetHealth() - m_PreInfusionBlood >= requiredGain, "native infusion restores substantial blood without setting health again")) return -1;
if (!Check(m_Recovered && !m_Controller.IsUnconscious() && ERR_Actors.CanFight(m_Casualty), "casualty regains consciousness through native health recovery")) return -1;
if (!Check(now - m_InfusionStarted >= 85000 && !m_Saline && AttachedBags() == 0 && SalineEffects() == 0, "finite saline expires after native duration and releases treatment state")) return -1;
if (!Check(m_SpareSaline != null, "unused spare survives the completed infusion")) return -1;
InventoryItemComponent spareItem = InventoryItemComponent.Cast(m_SpareSaline.FindComponent(InventoryItemComponent));
if (!Check(spareItem && spareItem.GetParentSlot(), "rejected spare bag remains a physical inventory item")) return -1;
m_Result = 1;
Stop();
Print("ERR MEDICAL RESULT: native blood loss, finite treatment and recovery; not manual action or multiplayer acceptance");
return 1;
}
return 0;
}
}
#endif
@@ -0,0 +1,183 @@
#ifdef WORKBENCH
// Explicit disposable-world fixture: destroy all four authored radio documents,
// then move the real participant to the station. No replacement clues or actors
// are used to grant discovery; the normal site tick must identify the station.
class ERR_NativeObjectiveLossProbe
{
protected ERR_GameMode m_Mode;
protected IEntity m_Player;
protected ERR_SiteRuntime m_Radio;
protected ref array<IEntity> m_Documents = {};
protected IEntity m_NonParticipant;
protected IEntity m_Blocker;
protected bool m_Partial;
protected bool m_SettingSaved;
protected int m_RevealChance;
protected int m_Step;
protected int m_Identifications;
protected float m_Wait;
protected float m_Deadline;
void ERR_NativeObjectiveLossProbe(ERR_GameMode mode, IEntity player, bool partial)
{
m_Mode = mode; m_Player = player; m_Partial = partial;
m_Mode.OnLocationDiscovered().Insert(Discovered);
}
protected void Discovered(ERR_LocationComponent location, int previous, int current)
{
if (m_Radio && location == m_Radio.Location && current == 2) m_Identifications++;
}
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: objective-loss " + detail); return true; }
Print("ERR SMOKE FAIL: objective-loss " + detail, LogLevel.ERROR);
Stop();
return false;
}
protected void Move(vector position)
{
vector transform[4];
m_Player.GetWorldTransform(transform);
transform[3] = ERR_Actors.Ground(position);
ChimeraCharacter.Cast(m_Player).Teleport(transform);
}
protected void CollectDocuments(out array<IEntity> documents)
{
documents.Clear();
array<ERR_LocationComponent> locations = {};
m_Mode.Locations(locations);
array<IEntity> stock = {};
foreach (ERR_LocationComponent location : locations)
{
ERR_SiteRuntime site = m_Mode.Sites().Find(location.Id());
if (!site || !site.Container) continue;
ERR_Inventory.AppendUnique(InventoryStorageManagerComponent.Cast(site.Container.FindComponent(InventoryStorageManagerComponent)), stock);
}
ERR_Inventory.AppendUnique(InventoryStorageManagerComponent.Cast(m_Player.FindComponent(InventoryStorageManagerComponent)), stock);
foreach (IEntity item : stock)
{
ERR_DocumentComponent document = ERR_DocumentComponent.Cast(item.FindComponent(ERR_DocumentComponent));
if (document && document.TargetId() == m_Radio.Location.Id()) documents.Insert(item);
}
}
void Stop()
{
if (m_Mode)
{
m_Mode.OnLocationDiscovered().Remove(Discovered);
if (m_SettingSaved) m_Mode.Settings().Set("intelRevealChancePercent", m_RevealChance);
}
m_SettingSaved = false;
ERR_Actors.DeleteSpawned(m_NonParticipant);
ERR_Actors.DeleteSpawned(m_Blocker);
}
int Tick(float now)
{
if (m_Deadline == 0) m_Deadline = now + 60000;
if (now > m_Deadline || !m_Mode.Active() || !ERR_Actors.CanFight(m_Player))
{
Check(false, "fixture lost its active conscious participant or exceeded its deadline");
return -1;
}
if (now < m_Wait) return 0;
if (m_Step == 0)
{
m_Radio = m_Mode.Sites().Find("everon-radio-1");
if (!Check(m_Radio && m_Radio.Location && m_Radio.Location.Knowledge() == 0 && m_Radio.Definition.DiscoverRadius == 8, "authored station is initially unknown with bounded eight-metre discovery")) return -1;
CollectDocuments(m_Documents);
if (!Check(m_Documents.Count() == 4, "four distinct physical documents exist before deliberate loss")) return -1;
if (!Check(m_Mode.UnlockPrison(m_Player) && m_Mode.Phase() == ERR_Phase.RADIO_SEARCH, "real prisoner enters radio search through ordinary mission interface")) return -1;
if (m_Partial)
{
m_RevealChance = m_Mode.Settings().Get("intelRevealChancePercent");
m_SettingSaved = true;
if (!Check(m_Mode.Settings().Set("intelRevealChancePercent", 0), "isolated first document produces only partial knowledge")) return -1;
ERR_SiteRuntime cache = m_Mode.Sites().Find("everon-supply-1");
Move(cache.Container.GetOrigin() + "0 0 2");
InventoryStorageManagerComponent source = InventoryStorageManagerComponent.Cast(cache.Container.FindComponent(InventoryStorageManagerComponent));
SCR_InventoryStorageManagerComponent target = SCR_InventoryStorageManagerComponent.Cast(m_Player.FindComponent(SCR_InventoryStorageManagerComponent));
array<IEntity> stock = {};
ERR_Inventory.Items(source, stock);
IEntity selected;
foreach (IEntity item : stock) if (m_Documents.Contains(item)) { selected = item; break; }
BaseInventoryStorageComponent destination;
if (selected && target) destination = target.FindStorageForItem(selected);
if (!Check(destination && source.TryMoveItemToStorage(selected, destination), "one real document transfer accepted before the remaining clues are lost")) return -1;
m_Wait = now + 8000;
}
m_Step = 1;
return 0;
}
if (m_Step == 1)
{
int expected;
if (m_Partial) expected = 1;
if (!Check(m_Radio.Location.Knowledge() == expected, "ordinary clue processing leaves the requested unknown or partial station state")) return -1;
foreach (IEntity document : m_Documents) if (document) SCR_EntityHelper.DeleteEntityAndChildren(document);
m_Step = 2;
m_Wait = now + 1500;
return 0;
}
if (m_Step == 2)
{
foreach (IEntity removed : m_Documents)
if (!Check(!removed, "each original physical document is deleted")) return -1;
array<IEntity> remaining = {};
CollectDocuments(remaining);
if (!Check(remaining.IsEmpty() && m_Mode.Sites().Prepare() == ERR_PrepareResult.READY, "all targeted documents are absent and preparation does not replenish them")) return -1;
Move(m_Radio.Location.Position() + "0 0 20");
m_NonParticipant = ERR_Actors.Spawn(m_Mode.Factions().PrisonerPrefab, ERR_Actors.Ground(m_Radio.Location.Position() + "3 0 0"));
if (!Check(m_NonParticipant != null, "disposable unpossessed character created for authorization rejection")) return -1;
m_Mode.Own(m_NonParticipant);
if (!Check(!m_Mode.Sites().CanObserve(m_NonParticipant, m_Radio), "unpossessed character cannot identify the station")) return -1;
m_Step = 3;
m_Wait = now + 6000;
return 0;
}
if (m_Step == 3)
{
int expected;
if (m_Partial) expected = 1;
if (!Check(m_Radio.Location.Knowledge() == expected && !m_Mode.Sites().CanObserve(m_Player, m_Radio), "distant participant and nearby nonparticipant do not reveal the station")) return -1;
ERR_Actors.DeleteSpawned(m_NonParticipant);
m_Blocker = ERR_Actors.Spawn("{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et", ERR_Actors.Ground(m_Radio.Location.Position() + "-1.5 0 2"));
if (!Check(m_Blocker != null, "physical obstruction created before close approach")) return -1;
m_Mode.Own(m_Blocker);
Move(m_Radio.Location.Position() + "0 0 4");
m_Step = 4;
m_Wait = now + 6000;
return 0;
}
if (m_Step == 4)
{
int expected;
if (m_Partial) expected = 1;
PrintFormat("ERR OBJECTIVE LOSS: player=%1 radio=%2 knowledge=%3 visible=%4", m_Player.GetOrigin(), m_Radio.Location.Position(), m_Radio.Location.Knowledge(), m_Mode.Sites().CanObserve(m_Player, m_Radio));
if (!Check(m_Radio.Location.Knowledge() == expected && !m_Mode.Sites().CanObserve(m_Player, m_Radio), "a nearby station behind a wall remains unidentified")) return -1;
ERR_Actors.DeleteSpawned(m_Blocker);
m_Step = 5;
m_Wait = now + 6000;
return 0;
}
if (m_Step == 5)
{
TraceParam sight = new TraceParam();
sight.Start = m_Player.GetOrigin() + "0 1.5 0";
sight.End = m_Radio.Location.Position() + "0 1.3 0";
sight.Flags = TraceFlags.WORLD | TraceFlags.ENTS;
sight.LayerMask = TRACE_LAYER_CAMERA;
sight.Exclude = m_Player;
float fraction = m_Mode.GetWorld().TraceMove(sight, null);
PrintFormat("ERR OBJECTIVE LOSS FINAL: player=%1 radio=%2 knowledge=%3 identifications=%4 visible=%5", m_Player.GetOrigin(), m_Radio.Location.Position(), m_Radio.Location.Knowledge(), m_Identifications, m_Mode.Sites().CanObserve(m_Player, m_Radio));
PrintFormat("ERR OBJECTIVE LOSS SIGHT: fraction=%1 hit=%2 blockerRemaining=%3", fraction, sight.TraceEnt, m_Blocker);
if (!Check(m_Mode.Sites().CanObserve(m_Player, m_Radio) && m_Radio.Location.Knowledge() == 2 && m_Identifications == 1, "actual participant with clear close approach identifies the station once through normal discovery")) return -1;
array<IEntity> remaining = {};
CollectDocuments(remaining);
if (!Check(remaining.IsEmpty(), "physical discovery creates no replacement documents")) return -1;
if (!Check(m_Mode.RadioDefence().CanBegin(m_Player, m_Radio.Location) && m_Mode.TuneRadio(m_Player, m_Radio.Location) && m_Mode.Phase() == ERR_Phase.RADIO_DEFENCE, "identified station permits ordinary authorized tuning after complete document loss")) return -1;
Stop();
return 1;
}
return 0;
}
}
#endif
@@ -0,0 +1,238 @@
#ifdef WORKBENCH
// Exercise native task entries and detail text on the private test display.
// This is local presentation evidence, not remote task replication acceptance.
class ERR_NativeObjectiveProbe
{
protected static bool s_Failed;
protected static Widget s_Entry;
protected static Widget s_Detail;
protected static SCR_Task s_Task;
protected static SCR_TaskListEntryUIComponent s_EntryHandler;
protected static SCR_TaskListEntryDescriptionUIComponent s_DetailHandler;
protected static ref SCR_TaskListEntryDescriptionUIComponent s_ClosedHandler;
protected static float s_CloseReadyAt;
protected static SCR_Task s_IsolatedTask;
protected static bool s_IsolatedDeleteRequested;
protected static SCR_Task s_PreservedMissionTask;
protected static ref SCR_TaskExecutor s_RestoreExecutor;
static void BeginRound()
{
Stop();
s_Failed = false;
}
protected static bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: objective UI " + detail); return true; }
s_Failed = true;
Stop();
Print("ERR SMOKE FAIL: objective UI " + detail, LogLevel.ERROR);
return false;
}
static void Stop()
{
if (s_EntryHandler && s_DetailHandler) s_EntryHandler.GetOnTaskVisualChanged().Remove(s_DetailHandler.InitDescription);
if (s_Detail) s_Detail.RemoveFromHierarchy();
if (s_Entry) s_Entry.RemoveFromHierarchy();
s_Detail = null;
s_Entry = null;
s_DetailHandler = null;
s_EntryHandler = null;
s_Task = null;
s_ClosedHandler = null;
s_CloseReadyAt = 0;
SCR_TaskSystem tasks = SCR_TaskSystem.GetInstance();
if (tasks && s_IsolatedTask && !s_IsolatedDeleteRequested) tasks.DeleteTask(s_IsolatedTask);
if (tasks && s_PreservedMissionTask && s_RestoreExecutor && !s_PreservedMissionTask.IsTaskAssignedTo(s_RestoreExecutor))
tasks.AssignTask(s_PreservedMissionTask, s_RestoreExecutor, true);
s_IsolatedTask = null;
s_IsolatedDeleteRequested = false;
s_PreservedMissionTask = null;
s_RestoreExecutor = null;
}
// Emit unchanged task state: exercise actual invoker subscriptions without
// changing assignment, visibility, ownership or mission progression.
protected static void EmitTaskCallbacks(SCR_Task task)
{
task.GetOnTaskAssigneeAdded().Invoke(task, null, 0);
task.GetOnTaskAssigneeRemoved().Invoke(task, null, 0);
task.GetOnTaskUIVisibilityChanged().Invoke(task, task.GetTaskUIVisibility());
task.GetOnTaskVisibilityChanged().Invoke(task, task.GetTaskVisibility());
task.GetOnTaskOwnershipChanged().Invoke(task, task.GetTaskOwnership());
task.GetOnDisplayDataChanged().Invoke();
}
static bool BeginCloseReopen(ERR_GameMode mode, float now)
{
if (!Observe(mode)) return false;
// Reinitialization is the vanilla route when the selected task changes.
s_DetailHandler.InitDescription(s_Task);
s_DetailHandler.ERR_ProbeWatch();
EmitTaskCallbacks(s_Task);
if (!Check(s_DetailHandler.ERR_ProbeTaskCallbacks() == 5, "rebinding keeps one subscription to each native task event")) return false;
// Keep only the handler alive so a leaked event or queued overflow callback
// can be counted safely after actual widget destruction. No manual unbinding.
SCR_Task task = s_Task;
ref SCR_TaskListEntryDescriptionUIComponent closed = s_DetailHandler;
Stop();
s_ClosedHandler = closed;
s_ClosedHandler.ERR_ProbeClosed();
if (!Check(s_ClosedHandler.ERR_ProbeWasDetached() && s_ClosedHandler.ERR_ProbeHasNoTask(), "closing native widget uses production detach and releases its task")) return false;
EmitTaskCallbacks(task);
if (!Check(s_ClosedHandler.ERR_ProbeLateCallbacks() == 0, "closed native widget receives no task callbacks")) return false;
s_CloseReadyAt = now + 500;
return true;
}
// One normal smoke tick allows native 100ms overflow work to become due.
static int TickCloseReopen(ERR_GameMode mode, float now)
{
if (s_Failed || !s_ClosedHandler) return -1;
if (now < s_CloseReadyAt) return 0;
if (!Check(s_ClosedHandler.ERR_ProbeLateCallbacks() == 0, "closed native widget has no delayed overflow callbacks")) return -1;
if (!Observe(mode)) return -1;
if (!Check(s_DetailHandler != null, "details reopen through native layout and current objective binding")) return -1;
return 1;
}
// Dedicated objective-ui case: delete an owned test task before its panel.
// The real objective remains alive and regains its local assignment immediately.
static bool BeginDeletedTaskClose(ERR_GameMode mode, float now)
{
Stop();
SCR_TaskSystem tasks = SCR_TaskSystem.GetInstance();
s_PreservedMissionTask = mode.Objectives().Current();
s_RestoreExecutor = SCR_TaskSystem.TaskExecutorFromPlayerID(SCR_PlayerController.GetLocalPlayerId());
if (!Check(tasks && s_PreservedMissionTask && s_RestoreExecutor, "isolated deletion has a native task system, mission task and local executor")) return false;
s_IsolatedTask = tasks.CreateTask("{1D0F815858EE24AD}Prefabs/Tasks/BaseTask.et", "erri-mission-probe-teardown", "Objective cleanup probe", "Isolated native task for widget teardown.");
if (!Check(s_IsolatedTask && s_IsolatedTask != s_PreservedMissionTask, "deletion fixture owns a separate native task")) return false;
tasks.SetTaskUIVisibility(s_IsolatedTask, SCR_ETaskUIVisibility.LIST_ONLY);
// Vanilla permits one assigned task per executor. Cleanup restores the real one.
tasks.AssignTask(s_IsolatedTask, s_RestoreExecutor, true);
if (!Check(s_IsolatedTask.IsTaskAssignedTo(s_RestoreExecutor), "isolated task has an actual local assignee")) return false;
s_Detail = GetGame().GetWorkspace().CreateWidgets("{552EB1921B5C548C}UI/layouts/Task/TaskListEntryDescription.layout");
if (!Check(s_Detail != null, "isolated native details layout created")) return false;
s_Detail.SetVisible(false);
s_DetailHandler = SCR_TaskListEntryDescriptionUIComponent.Cast(s_Detail.FindHandler(SCR_TaskListEntryDescriptionUIComponent));
if (!Check(s_DetailHandler != null, "isolated native details handler attached")) return false;
s_DetailHandler.InitDescription(s_IsolatedTask);
s_DetailHandler.ERR_ProbeWatch();
s_IsolatedDeleteRequested = true;
tasks.DeleteTask(s_IsolatedTask);
// These are native weak references, not manually cleared probe variables.
if (!Check(s_IsolatedTask == null && s_DetailHandler.ERR_ProbeHasNoTask(), "native task deletion clears both task and bound-widget weak references")) return false;
s_ClosedHandler = s_DetailHandler;
s_Detail.RemoveFromHierarchy();
s_Detail = null;
s_DetailHandler = null;
s_ClosedHandler.ERR_ProbeClosed();
if (!Check(s_ClosedHandler.ERR_ProbeWasDetached(), "deleted-task panel closes through production detach")) return false;
tasks.AssignTask(s_PreservedMissionTask, s_RestoreExecutor, true);
if (!Check(mode.Objectives().Current() == s_PreservedMissionTask && s_PreservedMissionTask.IsTaskAssignedTo(s_RestoreExecutor), "real mission objective survives isolated deletion and assignment is restored")) return false;
s_CloseReadyAt = now + 500;
return true;
}
static int TickDeletedTaskClose(ERR_GameMode mode, float now)
{
if (s_Failed || !s_ClosedHandler) return -1;
if (now < s_CloseReadyAt) return 0;
if (!Check(s_ClosedHandler.ERR_ProbeLateCallbacks() == 0, "deleted-task panel has no delayed callbacks after weak references clear")) return -1;
if (!Check(s_PreservedMissionTask && mode.Objectives().Current() == s_PreservedMissionTask && s_PreservedMissionTask.IsTaskAssignedTo(s_RestoreExecutor), "real mission objective remains intact after delayed teardown check")) return -1;
Stop();
return 1;
}
static bool Observe(ERR_GameMode mode)
{
if (s_Failed) return false;
SCR_Task task = mode.Objectives().Current();
if (!Check(task != null, "current native task exists")) return false;
if (s_Task != task)
{
Stop();
s_Task = task;
s_Entry = GetGame().GetWorkspace().CreateWidgets("{B71975032150BAFB}UI/layouts/Task/TaskListEntry.layout");
s_Detail = GetGame().GetWorkspace().CreateWidgets("{552EB1921B5C548C}UI/layouts/Task/TaskListEntryDescription.layout");
if (!Check(s_Entry && s_Detail, "native list and detail layouts created")) return false;
s_Entry.SetVisible(false);
s_Detail.SetVisible(false);
s_EntryHandler = SCR_TaskListEntryUIComponent.Cast(s_Entry.FindHandler(SCR_TaskListEntryUIComponent));
s_DetailHandler = SCR_TaskListEntryDescriptionUIComponent.Cast(s_Detail.FindHandler(SCR_TaskListEntryDescriptionUIComponent));
if (!Check(s_EntryHandler && s_DetailHandler, "native handlers attached")) return false;
s_EntryHandler.InitTask(task);
s_DetailHandler.InitDescription(task);
// Same visual-change route used by the vanilla task list.
s_EntryHandler.GetOnTaskVisualChanged().Insert(s_DetailHandler.InitDescription);
}
RichTextWidget title = RichTextWidget.Cast(s_Entry.FindAnyWidget("m_wTaskTitle"));
RichTextWidget description = RichTextWidget.Cast(s_Detail.FindAnyWidget("m_wDescription"));
if (!Check(title && description && title.GetText() == task.GetTaskName() && description.GetText() == task.GetTaskDescription(), "open list and details contain current title and instructions")) return false;
Widget showMap = s_Detail.FindAnyWidget("m_wButtonShowMap");
if (!Check(showMap && !showMap.IsVisible(), "list-only task cannot expose an undiscovered map position")) return false;
SCR_TaskExecutor player = SCR_TaskSystem.TaskExecutorFromPlayerID(SCR_PlayerController.GetLocalPlayerId());
if (!Check(player && task.IsTaskAssignedTo(player), "actual local player receives the active objective")) return false;
if (!Check(ERR_ObjectiveText.Extraction(12500).Contains("12.5") && !ERR_ObjectiveText.Extraction(12500).Contains("9 kilometres"), "extraction instructions follow a nondefault boundary")) return false;
return true;
}
}
// Observers exist only in the opt-in native fixture. They do not unsubscribe or
// cancel work: a late callback is counted as failure before avoiding dead-widget
// dereferences, allowing the fixture to report the production cleanup defect.
modded class SCR_TaskListEntryDescriptionUIComponent
{
protected bool m_ERRProbeWatching;
protected bool m_ERRProbeClosed;
protected bool m_ERRProbeDetached;
protected int m_ERRProbeTaskCallbacks;
protected int m_ERRProbeLateCallbacks;
void ERR_ProbeWatch() { m_ERRProbeWatching = true; m_ERRProbeTaskCallbacks = 0; }
void ERR_ProbeClosed() { m_ERRProbeClosed = true; }
int ERR_ProbeTaskCallbacks() { return m_ERRProbeTaskCallbacks; }
int ERR_ProbeLateCallbacks() { return m_ERRProbeLateCallbacks; }
bool ERR_ProbeWasDetached() { return m_ERRProbeDetached; }
bool ERR_ProbeHasNoTask() { return m_Task == null; }
protected bool ERR_ProbeCallback(bool taskEvent, string kind)
{
if (!m_ERRProbeWatching) return false;
if (m_ERRProbeClosed)
{
m_ERRProbeLateCallbacks++;
PrintFormat("ERR OBJECTIVE LATE CALLBACK: kind=%1 count=%2 task=%3 detached=%4 root=%5", kind, m_ERRProbeLateCallbacks, m_Task != null, m_ERRProbeDetached, m_wRoot != null);
return true;
}
if (taskEvent) m_ERRProbeTaskCallbacks++;
return false;
}
override void HandlerDeattached(Widget w)
{
m_ERRProbeDetached = true;
super.HandlerDeattached(w);
}
protected override void UpdateAssignees(notnull SCR_Task task, SCR_TaskExecutor newAssignee = null, int requesterID = 0)
{
if (ERR_ProbeCallback(true, "UpdateAssignees")) return;
super.UpdateAssignees(task, newAssignee, requesterID);
}
protected override void OnTaskUIVisibilityChanged(SCR_Task task, SCR_ETaskUIVisibility visible)
{
if (ERR_ProbeCallback(true, "OnTaskUIVisibilityChanged")) return;
super.OnTaskUIVisibilityChanged(task, visible);
}
protected override void OnTaskVisibilityChanged(SCR_Task task, SCR_ETaskVisibility visible)
{
if (ERR_ProbeCallback(true, "OnTaskVisibilityChanged")) return;
super.OnTaskVisibilityChanged(task, visible);
}
protected override void OnTaskOwnershipChanged(SCR_Task task, SCR_ETaskOwnership ownership)
{
if (ERR_ProbeCallback(true, "OnTaskOwnershipChanged")) return;
super.OnTaskOwnershipChanged(task, ownership);
}
protected override void CheckOverflow(Widget w)
{
if (ERR_ProbeCallback(false, "CheckOverflow")) return;
super.CheckOverflow(w);
}
override void UpdateTask()
{
if (ERR_ProbeCallback(false, "UpdateTask")) return;
super.UpdateTask();
}
}
#endif
@@ -0,0 +1,203 @@
#ifdef WORKBENCH
// Observe existing physical guards. No teleporting, spawned soldiers or forced
// completion: a full ordered loop and physical movement must happen natively.
class ERR_NativePatrolProbe
{
protected ERR_GameMode m_Mode;
protected string m_SiteId;
protected ERR_SiteRuntime m_Site;
protected IEntity m_Actor;
protected vector m_Origin;
protected float m_Deadline;
protected int m_Completions;
protected int m_LastIndex = -1;
protected bool m_BadSequence;
protected bool m_Moved;
protected bool m_Started;
protected bool m_Prepared;
protected float m_AcquireDeadline;
protected bool m_Finished;
protected int m_LastQueueSample = -1;
protected ref map<string, int> m_QueueBounds = new map<string, int>();
void ERR_NativePatrolProbe(ERR_GameMode mode, string siteId = "everon-outpost-1") { m_Mode = mode; m_SiteId = siteId; }
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: patrol " + detail); return true; }
Print("ERR SMOKE FAIL: patrol " + detail, LogLevel.ERROR);
return false;
}
protected void Completed(AIWaypoint waypoint)
{
if (!m_Site) return;
int index = m_Site.PatrolWaypoints.Find(waypoint);
if (index < 0) return;
if (m_LastIndex >= 0 && index != (m_LastIndex + 1) % m_Site.PatrolWaypoints.Count()) m_BadSequence = true;
m_LastIndex = index;
m_Completions++;
PrintFormat("ERR PATROL COMPLETED: site=%1 index=%2 count=%3", m_Site.Definition.Id, index, m_Completions);
}
protected bool DefinitionChecks()
{
ERR_ScenarioDefinition definition = ERR_ScenarioDefinition.Cast(SCR_BaseContainerTools.CreateInstanceFromPrefab("{BC310DC0CD27F523}Configs/ERR/EveronSites.conf"));
if (!Check(definition && definition.Valid(), "authored definition resolves")) return false;
ERR_SiteDefinition site = definition.Find("everon-outpost-1");
if (!Check(site && site.Patrol && site.Patrol.Count() == 4, "four ordered outpost points authored")) return false;
ref array<ref ERR_SitePatrolPoint> savedRoute = site.Patrol;
site.Patrol = new array<ref ERR_SitePatrolPoint>();
site.Patrol.Insert(savedRoute[0]);
if (!Check(!definition.Valid(), "single-point route rejected")) return false;
for (int extra = 1; extra < 7; extra++) site.Patrol.Insert(savedRoute[extra % savedRoute.Count()]);
if (!Check(!definition.Valid(), "over-six-point route rejected")) return false;
site.Patrol.Clear();
site.Patrol.Insert(null);
site.Patrol.Insert(savedRoute[1]);
if (!Check(!definition.Valid(), "missing point rejected")) return false;
site.Patrol = savedRoute;
vector saved = site.Patrol[0].Offset;
site.Patrol[0].Offset = "61 0 0";
if (!Check(!definition.Valid(), "unbounded route rejected")) return false;
site.Patrol[0].Offset = "0 1 0";
if (!Check(!definition.Valid(), "floating route rejected")) return false;
site.Patrol[0].Offset = site.Patrol[1].Offset;
if (!Check(!definition.Valid(), "duplicate route points rejected")) return false;
site.Patrol[0].Offset = saved;
site.Guarded = false;
if (!Check(!definition.Valid(), "route without a physical guard rejected")) return false;
site.Guarded = true;
ERR_SiteDefinition prison = definition.Find("everon-prison-1");
prison.Patrol = site.Patrol;
if (!Check(!definition.Valid(), "prison patrol rejected; gate guards remain stationary")) return false;
prison.Patrol = null;
return Check(definition.Valid(), "isolated authoring mutations restored");
}
protected bool PreparedRoute(string id, bool deferDuplicateFailure = false)
{
ERR_SiteRuntime site = m_Mode.Sites().Find(id);
if (!Check(site && site.Group && site.PatrolCycle && site.PatrolWaypoints.Count() == 4, id + " existing group has one cycle and four native moves")) return false;
array<AIWaypoint> route = {};
site.PatrolCycle.GetWaypoints(route);
if (!Check(route.Count() == 4 && site.PatrolCycle.GetRerunCounter() == -1 && m_Mode.Sites().Owns(site.PatrolCycle), id + " finite owner holds repeating cycle")) return false;
for (int i = 0; i < route.Count(); i++)
{
vector expected = site.Definition.Position + site.Definition.GuardOffset + site.Definition.Patrol[i].Offset;
if (!Check(route[i] == site.PatrolWaypoints[i] && m_Mode.Sites().Owns(route[i]) && vector.DistanceXZ(route[i].GetOrigin(), expected) < 0.1, id + " authored point " + i.ToString())) return false;
}
array<AIWaypoint> queued = {};
site.Group.GetWaypoints(queued);
PrintFormat("ERR PATROL QUEUE: site=%1 count=%2 current=%3", id, queued.Count(), site.Group.GetCurrentWaypoint());
for (int diagnosticIndex = 0; diagnosticIndex < Math.Min(queued.Count(), 20); diagnosticIndex++)
{
AIWaypoint entry = queued[diagnosticIndex];
PrintFormat("ERR PATROL QUEUE ENTRY: site=%1 index=%2 point=%3 cycle=%4 current=%5 entity=%6", id, diagnosticIndex, site.PatrolWaypoints.Find(entry), entry == site.PatrolCycle, entry == site.Group.GetCurrentWaypoint(), entry);
}
// Diagnostic only: retain duplicate rejection at final acceptance, but first
// observe whether native completion events execute the apparent repetitions.
// GetWaypoints has no documented raw-queue/expanded-cycle distinction.
int repeated;
for (int j = 0; j < queued.Count(); j++)
{
if (!Check(queued[j] && (queued[j] == site.PatrolCycle || site.PatrolWaypoints.Contains(queued[j])), id + " queue contains only owned patrol references")) return false;
for (int k = 0; k < j; k++) if (queued[j] == queued[k]) repeated++;
}
int currentIndex = site.PatrolWaypoints.Find(site.Group.GetCurrentWaypoint());
string stateKey = id + ":" + currentIndex.ToString();
if (m_QueueBounds.Contains(stateKey))
{
if (!Check(queued.Count() <= m_QueueBounds.Get(stateKey), id + " queue does not grow at the same current route point")) return false;
}
else m_QueueBounds.Insert(stateKey, queued.Count());
PrintFormat("ERR PATROL QUEUE DIAGNOSTIC: site=%1 repeatedPairs=%2 deferred=%3 completions=%4", id, repeated, deferDuplicateFailure, m_Completions);
if (!deferDuplicateFailure && !Check(repeated == 0, id + " no duplicate queue references")) return false;
return true;
}
int Tick(float now)
{
if (m_Finished) return 1;
if (!m_Started)
{
if (!m_Prepared)
{
if (!DefinitionChecks() || !PreparedRoute("everon-supply-2", true) || !PreparedRoute("everon-outpost-1", true)) return -1;
ERR_SiteRuntime prison = m_Mode.Sites().Find("everon-prison-1");
if (!Check(prison && prison.Group && !prison.PatrolCycle && prison.PatrolWaypoints.IsEmpty(), "prison guards retain stationary orders")) return -1;
m_Site = m_Mode.Sites().Find(m_SiteId);
if (!Check(m_Site && m_Site.Group && m_Site.InitialGuardCount == 6, "selected native guard profile requests six physical members")) return -1;
m_AcquireDeadline = now + 15000;
m_Prepared = true;
}
if (!m_Site || !m_Site.Group) { Check(false, "original guard group disappeared before observation"); return -1; }
array<AIAgent> agents = {};
m_Site.Group.GetAgents(agents);
array<IEntity> ready = {};
foreach (AIAgent agent : agents)
{
if (!agent) continue;
IEntity actor = agent.GetControlledEntity();
if (ERR_Actors.CanFight(actor) && !ready.Contains(actor)) ready.Insert(actor);
}
if (ready.Count() > m_Site.InitialGuardCount) { Check(false, "original group contains excess living physical guards"); return -1; }
if (ready.Count() < m_Site.InitialGuardCount)
{
if (now < m_AcquireDeadline) return 0;
PrintFormat("ERR PATROL ACTOR TIMEOUT: site=%1 agents=%2 requested=%3 nativeComplete=%4", m_SiteId, agents.Count(), m_Site.InitialGuardCount, m_Site.Group.IsExpandComplete());
Check(false, "existing living physical guard unavailable after bounded readiness wait");
return -1;
}
m_Actor = ready[0];
if (!Check(ready.Count() == 6, "six distinct original living physical guards available")) return -1;
m_Origin = m_Actor.GetOrigin();
m_Site.Group.GetOnWaypointCompleted().Insert(Completed);
m_Deadline = now + 180000;
m_Started = true;
return 0;
}
if (m_Actor && vector.DistanceXZ(m_Origin, m_Actor.GetOrigin()) >= 8) m_Moved = true;
if (m_BadSequence) { Stop(); Check(false, "native route completion order"); return -1; }
if (m_Completions != m_LastQueueSample)
{
m_LastQueueSample = m_Completions;
if (!PreparedRoute(m_SiteId, true)) { Stop(); return -1; }
}
// Five completions prove traversal across a cycle boundary, from any starting point.
if (m_Completions >= 5 && m_Moved)
{
Stop();
if (!Check(true, "existing physical guard moved at least 8m and five native moves completed in authored cyclic order")) return -1;
if (!PreparedRoute(m_SiteId)) return -1;
m_Finished = true;
return 1;
}
if (now >= m_Deadline)
{
PrintFormat("ERR PATROL TIMEOUT: completions=%1 moved=%2 actor=%3 current=%4", m_Completions, m_Moved, m_Actor, m_Site.Group.GetCurrentWaypoint());
Stop(); Check(false, "native route traversal completed within 180s"); return -1;
}
return 0;
}
// Run last in a disposable mission: deliberately stops both route owners.
static bool StopChecks(ERR_GameMode mode)
{
ERR_SiteRuntime site = mode.Sites().Find("everon-outpost-1");
if (!site || !site.Group || !site.PatrolCycle) return false;
AIWaypoint search = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.SearchWaypoint(), ERR_Actors.Ground(site.Definition.Position + "40 0 0")));
if (!search) return false;
mode.Own(search);
site.Group.AddWaypointAt(search, 0);
mode.Sites().Stop();
mode.Sites().Stop();
array<AIWaypoint> queued = {};
site.Group.GetWaypoints(queued);
bool valid = !site.PatrolCycle && site.PatrolWaypoints.IsEmpty() && queued.Contains(search) && mode.Sites().Prepare() == ERR_PrepareResult.FAILED;
site.Group.RemoveWaypoint(search);
ERR_Actors.DeleteSpawned(search);
if (valid) Print("ERR SMOKE PASS: patrol stop clears owned routes once and preserves foreign Search");
else Print("ERR SMOKE FAIL: patrol stop ownership", LogLevel.ERROR);
return valid;
}
void Stop()
{
if (m_Site && m_Site.Group) m_Site.Group.GetOnWaypointCompleted().Remove(Completed);
}
void ~ERR_NativePatrolProbe() { Stop(); }
}
#endif
@@ -0,0 +1,210 @@
#ifdef WORKBENCH
// One real controller plus eleven disposable physical characters. This probes
// native inventory and transfer compatibility, not twelve clients or UI pickup.
class ERR_NativePrisonerProbe
{
protected ERR_GameMode m_Mode;
protected IEntity m_Player;
protected ref array<IEntity> m_Actors = {};
protected ref map<int, vector> m_Reservations = new map<int, vector>();
protected ref ERR_SpawnPlacement m_Placement = new ERR_SpawnPlacement();
protected ref map<string, int> m_Expected = new map<string, int>();
protected ERR_SiteRuntime m_Cache;
protected InventoryStorageManagerComponent m_Source;
protected SCR_InventoryStorageManagerComponent m_Inventory;
protected IEntity m_Document;
protected vector m_Original[4];
protected int m_Step;
protected int m_Checked;
protected float m_Wait;
void ERR_NativePrisonerProbe(ERR_GameMode mode, IEntity player)
{
m_Mode = mode; m_Player = player;
m_Expected.Set("Prefabs/Characters/HeadGear/Hat_Patrol_US_01/Hat_Patrol_US_01.et", 1);
m_Expected.Set("Prefabs/Characters/Uniforms/Jacket_US_BDU_rolledup.et", 1);
m_Expected.Set("Prefabs/Characters/Uniforms/Pants_US_BDU.et", 1);
m_Expected.Set("Prefabs/Characters/Footwear/CombatBoots_US_01.et", 1);
m_Expected.Set("Prefabs/Items/Equipment/Watches/Watch_SandY184A.et", 1);
m_Expected.Set("Prefabs/Items/Medicine/FieldDressing_01/FieldDressing_US_01.et", 2);
m_Expected.Set("Prefabs/Items/Medicine/Tourniquet_01/Tourniquet_US_01.et", 1);
m_Expected.Set("Prefabs/Items/Medicine/MorphineInjection_01/MorphineInjection_01.et", 1);
}
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: prisoner " + detail); return true; }
Print("ERR SMOKE FAIL: prisoner " + detail, LogLevel.ERROR);
Stop();
return false;
}
protected void Positions(string stage)
{
foreach (int index, IEntity actor : m_Actors)
{
if (!actor) continue;
vector lower, upper;
actor.GetWorldBounds(lower, upper);
PrintFormat("ERR PRISONER POSITION: stage=%1 index=%2 requested=%3 actual=%4 boundsMin=%5 boundsMax=%6", stage, index, m_Reservations.Get(index), actor.GetOrigin(), lower, upper);
}
}
protected bool Kit(IEntity actor, int index)
{
if (!Check(actor && actor.GetPrefabData() && actor.GetPrefabData().GetPrefabName() == "{3FF693715C9F8928}Prefabs/ERR/Prisoner.et", "character " + index.ToString() + " uses successor resource")) return false;
if (!Check(actor.FindComponent(SCR_CharacterDamageManagerComponent) && actor.FindComponent(CharacterControllerComponent) && actor.FindComponent(SCR_SalineStorageComponent) && actor.FindComponent(SCR_TourniquetStorageComponent) && actor.FindComponent(CompartmentAccessComponent), "character " + index.ToString() + " retains vanilla health, treatment and vehicle components")) return false;
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent));
if (!Check(inventory != null, "character inventory exists")) return false;
array<IEntity> items = {};
inventory.GetItems(items);
map<string, int> counts = new map<string, int>();
int identityItems;
array<IEntity> seen = {};
foreach (IEntity item : items)
{
if (!item || !item.GetPrefabData()) return Check(false, "inventory item has no resource identity");
ResourceName resource = item.GetPrefabData().GetPrefabName();
PrintFormat("ERR PRISONER ITEM: actor=%1 item=%2 resource=%3 duplicateReference=%4", index, item, resource, seen.Contains(item));
if (seen.Contains(item)) continue;
seen.Insert(item);
// Vanilla creates identification on possession, death or unconsciousness.
// Preserve this native identity record; it is not mission navigation equipment.
if (resource.EndsWith("Prefabs/Items/PersonalBelongings/PersonalBelongings_US.et"))
{
if (!Check(ERR_Actors.Player(actor) && item.FindComponent(SCR_IdentityInventoryItemComponent), "possessed prisoner retains native identification")) return false;
identityItems++;
continue;
}
// A non-equipment belt is authored by the rolled-up vanilla jacket itself.
if (resource.EndsWith("Prefabs/Characters/Vests/Vest_ALICE/Vest_ALICE_belt_dummy.et")) continue;
bool allowed = false;
foreach (string path, int expected : m_Expected)
{
if (!resource.EndsWith(path)) continue;
counts.Set(path, counts.Get(path) + 1);
allowed = true;
break;
}
if (!allowed) return Check(false, "unexpected starter inventory resource: " + resource);
}
int expectedIdentity;
if (ERR_Actors.Player(actor)) expectedIdentity = 1;
if (!Check(identityItems == expectedIdentity, "native identification is created exactly once for the possessed prisoner")) return false;
foreach (string path, int expected : m_Expected)
if (counts.Get(path) != expected) return Check(false, string.Format("character %1 expected %2 of %3, found %4", index, expected, path, counts.Get(path)));
PrintFormat("ERR PRISONER INVENTORY: character=%1 storedItems=%2", index, items.Count());
return Check(true, "character " + index.ToString() + " has exact clothing/watch/medical kit and no inherited navigation, radio, binoculars, tools, flares or weapons");
}
protected bool Contains(InventoryStorageManagerComponent inventory, IEntity item)
{
if (!inventory || !item) return false;
array<IEntity> items = {};
inventory.GetItems(items);
return items.Contains(item);
}
void Stop()
{
// Return borrowed stock when its source still exists; never spawn replacements.
if (m_Document && m_Cache && m_Cache.Container && m_Inventory && Contains(m_Inventory, m_Document))
{
BaseInventoryStorageComponent destination = BaseInventoryStorageComponent.Cast(m_Cache.Container.FindComponent(BaseInventoryStorageComponent));
if (destination) m_Inventory.TryMoveItemToStorage(m_Document, destination);
}
for (int i = 1; i < m_Actors.Count(); i++) ERR_Actors.DeleteSpawned(m_Actors[i]);
m_Reservations.Clear();
}
int Tick(float now)
{
if (now < m_Wait) return 0;
if (m_Step == 0)
{
if (!Check(m_Mode && m_Mode.Phase() == ERR_Phase.PRISON && ERR_Actors.Player(m_Player) && ERR_Actors.CanFight(m_Player), "fixture begins with a real conscious prisoner")) return -1;
m_Player.GetWorldTransform(m_Original);
m_Actors.Insert(m_Player);
m_Reservations.Set(0, m_Player.GetOrigin());
Positions("initial");
m_Step = 1;
m_Wait = now + 1000;
return 0;
}
if (m_Step == 1)
{
if (m_Actors.Count() < 12)
{
vector position;
bool available = m_Placement.Prison(m_Mode.PrisonPosition(), m_Reservations, position);
if (!available) Positions("no-clear-slot");
if (!Check(available, "bounded clear prison slot exists for next test character")) return -1;
IEntity actor = ERR_Actors.Spawn(m_Mode.Factions().PrisonerPrefab, position);
if (!Check(actor != null, "physical test prisoner created")) return -1;
m_Mode.Own(actor);
m_Reservations.Set(m_Actors.Count(), position);
m_Actors.Insert(actor);
DamageManagerComponent damage = DamageManagerComponent.Cast(actor.FindComponent(DamageManagerComponent));
if (!Check(damage != null, "test prisoner damage component exists")) return -1;
damage.EnableDamageHandling(false);
m_Wait = now + 500;
return 0;
}
Positions("all-spawned");
m_Step = 2; m_Wait = now + 1500;
return 0;
}
if (m_Step == 2)
{
if (m_Checked == 0) Positions("settled");
if (!Kit(m_Actors[m_Checked], m_Checked)) return -1;
m_Checked++;
if (m_Checked < 12) return 0;
m_Cache = m_Mode.Sites().Find("everon-supply-1");
if (!Check(m_Cache && m_Cache.Container, "authored clue source remains available")) return -1;
m_Source = InventoryStorageManagerComponent.Cast(m_Cache.Container.FindComponent(InventoryStorageManagerComponent));
m_Inventory = SCR_InventoryStorageManagerComponent.Cast(m_Player.FindComponent(SCR_InventoryStorageManagerComponent));
if (!Check(m_Source && m_Inventory, "source and player inventory managers are usable")) return -1;
array<IEntity> stock = {};
m_Source.GetItems(stock);
foreach (IEntity item : stock)
{
ERR_DocumentComponent document = ERR_DocumentComponent.Cast(item.FindComponent(ERR_DocumentComponent));
if (document && document.TargetId() == "everon-radio-1") { m_Document = item; break; }
}
if (!Check(m_Document && m_Inventory, "real finite radio document and player storage found")) return -1;
vector transform[4]; m_Player.GetWorldTransform(transform);
transform[3] = ERR_Actors.Ground(m_Cache.Container.GetOrigin() + "0 0 2");
ChimeraCharacter.Cast(m_Player).Teleport(transform);
m_Step = 3; m_Wait = now + 1000;
return 0;
}
if (m_Step == 3)
{
BaseInventoryStorageComponent destination = m_Inventory.FindStorageForItem(m_Document);
if (!Check(destination && m_Source.TryMoveItemToStorage(m_Document, destination), "minimal starter clothing accepts a real authored clue transfer")) return -1;
m_Step = 4; m_Wait = now + 1000;
return 0;
}
if (m_Step == 4)
{
if (!Check(Contains(m_Inventory, m_Document) && !Contains(m_Source, m_Document) && m_Mode.Phase() == ERR_Phase.PRISON, "document physically moved without duplication while prison phase remains active")) return -1;
BaseInventoryStorageComponent destination = BaseInventoryStorageComponent.Cast(m_Cache.Container.FindComponent(BaseInventoryStorageComponent));
if (!Check(destination && m_Inventory.TryMoveItemToStorage(m_Document, destination), "borrowed document returns to finite source")) return -1;
m_Step = 5; m_Wait = now + 1000;
return 0;
}
if (m_Step == 5)
{
if (!Check(Contains(m_Source, m_Document) && !Contains(m_Inventory, m_Document), "borrowed clue restored exactly once")) return -1;
Stop();
ChimeraCharacter.Cast(m_Player).Teleport(m_Original);
m_Step = 6; m_Wait = now + 1000;
return 0;
}
if (m_Step == 6)
{
for (int i = 1; i < m_Actors.Count(); i++)
if (!Check(!m_Actors[i], "disposable prisoner removed after native cleanup")) return -1;
ERR_PlayerLife life = m_Mode.Players().Get(GetGame().GetPlayerManager().GetPlayerIdFromControlledEntity(m_Player));
if (!Check(ERR_Actors.Player(m_Player) && ERR_Actors.Alive(m_Player) && life && life.UsedLife, "real prisoner and spent life preserved")) return -1;
m_Step = 7;
return 1;
}
return 0;
}
}
#endif
@@ -0,0 +1,367 @@
#ifdef WORKBENCH
// Only connection membership is simulated for the second character. Native
// actor mutations settle on later frames; isolated service timestamps stay exact.
class ERR_NativeRadioSample : ERR_RadioDefenceService
{
ref array<IEntity> Members = {};
int Connections = 2;
void ERR_NativeRadioSample(ERR_GameMode mode) { m_Mode = mode; }
protected override bool IsPlayer(IEntity actor) { return Members.Contains(actor); }
protected override int ConnectedPlayers() { return Connections; }
protected override void Players(out array<IEntity> players) { players.Copy(Members); }
}
class ERR_NativeRadioDefenceProbe
{
protected ERR_GameMode m_Mode;
protected IEntity m_Player;
protected IEntity m_Second;
protected ERR_LocationComponent m_Radio;
protected FactionAffiliationComponent m_Affiliation;
protected ref ERR_NativeRadioSample m_Service;
protected ref ERR_RadioDefenceService m_Authoritative;
protected int m_Starts;
protected int m_Completions;
protected int m_Changes;
protected int m_OriginalDuration;
protected vector m_Original[4];
protected bool m_OriginalUnconscious;
protected bool m_Snapshot;
protected bool m_SettingsChanged;
protected bool m_Restored;
protected bool m_RestoreSucceeded;
protected int m_Step;
protected int m_Result;
protected float m_Wait;
protected float m_StateDeadline;
protected float m_NextStateDiagnostic;
void ERR_NativeRadioDefenceProbe(ERR_GameMode mode, IEntity player, ERR_LocationComponent radio)
{
m_Mode = mode; m_Player = player; m_Radio = radio;
}
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: radio service " + detail); return true; }
Print("ERR SMOKE FAIL: radio service " + detail, LogLevel.ERROR);
m_Result = -1;
Restore();
return false;
}
protected void Started(ERR_LocationComponent location) { m_Starts++; }
protected void Changed(ERR_RadioDefenceService service) { m_Changes++; }
protected void Completed(ERR_LocationComponent location)
{
m_Completions++;
m_Service.Tick(1000000); // Re-entrant observer cannot complete twice.
}
protected void Move(IEntity actor, vector position)
{
vector transform[4]; actor.GetWorldTransform(transform);
transform[3] = ERR_Actors.Ground(position);
ChimeraCharacter.Cast(actor).Teleport(transform);
}
protected void Conscious(IEntity actor, bool value)
{
CharacterControllerComponent.Cast(actor.FindComponent(CharacterControllerComponent)).SetUnconscious(!value);
}
protected float Distance(string label)
{
float distance = vector.DistanceXZ(m_Player.GetOrigin(), m_Radio.Position());
PrintFormat("ERR RADIO POSITION: %1 actor=%2 radio=%3 distanceXZ=%4", label, m_Player.GetOrigin(), m_Radio.Position(), distance);
return distance;
}
protected void WaitFor(int step, float now)
{
m_Step = step;
m_Wait = now + 750;
m_StateDeadline = now + 12000;
m_NextStateDiagnostic = now;
}
protected void RecoveryDiagnostic(IEntity actor, string label)
{
CharacterControllerComponent controller;
if (actor) controller = CharacterControllerComponent.Cast(actor.FindComponent(CharacterControllerComponent));
int unconscious = -1;
int lifeState = -1;
if (controller)
{
unconscious = controller.IsUnconscious();
lifeState = controller.GetLifeState();
}
PrintFormat("ERR RADIO RECOVERY: %1 step=%2 alive=%3 canFight=%4 unconscious=%5 lifeState=%6 origin=%7", label, m_Step, ERR_Actors.Alive(actor), ERR_Actors.CanFight(actor), unconscious, lifeState, actor.GetOrigin());
}
// Native recovery includes controller/ragdoll state transitions beyond one
// frame. Poll the observed state without reissuing SetUnconscious or altering
// health. Dead/missing actors fail immediately; delayed recovery has a bound.
protected bool Recovered(IEntity actor, float now, string label)
{
if (!actor || !ERR_Actors.Alive(actor))
{
Check(false, label + " lost its living actor during recovery");
return false;
}
if (ERR_Actors.CanFight(actor))
{
RecoveryDiagnostic(actor, label + " ready");
return true;
}
if (now >= m_NextStateDiagnostic)
{
RecoveryDiagnostic(actor, label + " waiting");
m_NextStateDiagnostic = now + 1000;
}
if (now >= m_StateDeadline)
{
Check(false, label + " did not regain conscious native state within twelve seconds");
return false;
}
m_Wait = now + 250;
return false;
}
protected void AuthorizationDiagnostic()
{
int id = GetGame().GetPlayerManager().GetPlayerIdFromControlledEntity(m_Player);
ERR_PlayerLife life = m_Mode.Players().Get(id);
bool connected = life && life.Connected;
bool usedLife = life && life.UsedLife;
bool sameActor = life && life.Actor == m_Player;
bool friendly = m_Mode.PlayerFaction() && SCR_FactionManager.SGetFaction(m_Player) == m_Mode.PlayerFaction();
PrintFormat("ERR RADIO AUTH: active=%1 phase=%2 registered=%3 kind=%4 knowledge=%5 serviceState=%6 distance=%7", m_Mode.Active(), m_Mode.Phase(), m_Mode.HasLocation(m_Radio), m_Radio.Kind(), m_Radio.Knowledge(), m_Authoritative.State(), vector.DistanceXZ(m_Player.GetOrigin(), m_Radio.Position()));
PrintFormat("ERR RADIO AUTH: playerId=%1 connected=%2 usedLife=%3 sameActor=%4 friendly=%5 canFight=%6 canBegin=%7", id, connected, usedLife, sameActor, friendly, ERR_Actors.CanFight(m_Player), m_Authoritative.CanBegin(m_Player, m_Radio));
}
protected void Restore()
{
if (m_Restored) return;
m_Restored = true;
m_RestoreSucceeded = true;
if (m_Service)
{
m_Service.OnStarted().Remove(Started);
m_Service.OnProgressChanged().Remove(Changed);
m_Service.OnCompleted().Remove(Completed);
m_Service.Stop();
}
if (m_Authoritative) m_Authoritative.Stop();
if (!GetGame() || !Replication.IsServer()) return;
if (m_SettingsChanged && m_Mode && m_Mode.Settings())
m_RestoreSucceeded = m_Mode.Settings().Set("radioDefenceSeconds", m_OriginalDuration);
if (m_Snapshot && m_Player)
{
CharacterControllerComponent controller = CharacterControllerComponent.Cast(m_Player.FindComponent(CharacterControllerComponent));
ChimeraCharacter character = ChimeraCharacter.Cast(m_Player);
if (controller && character && ERR_Actors.Alive(m_Player))
{
controller.SetUnconscious(m_OriginalUnconscious);
character.Teleport(m_Original);
}
else m_RestoreSucceeded = false;
}
ERR_Actors.DeleteSpawned(m_Second);
m_Second = null;
}
// The parent must call this on cancellation/fixture timeout as well as failure.
// No fixture-owned callqueue is installed; no later mutation runs after Stop.
void Stop()
{
if (m_Result == 0) m_Result = -1;
Restore();
}
int Tick(float now)
{
if (m_Result != 0) return m_Result;
if (now < m_Wait) return 0;
if (!Replication.IsServer() || !m_Mode || !m_Player || !m_Radio || !m_Mode.Settings())
{
Check(false, "async fixture lost authoritative mission/player/radio");
return -1;
}
if (m_Step == 0)
{
if (!Check(m_Mode.Phase() == ERR_Phase.RADIO_SEARCH && m_Radio.Knowledge() == 2 && ERR_Actors.Player(m_Player), "identified radio and real controlled prisoner available")) return -1;
m_OriginalDuration = m_Mode.Settings().Get("radioDefenceSeconds");
m_Player.GetWorldTransform(m_Original);
CharacterControllerComponent controller = CharacterControllerComponent.Cast(m_Player.FindComponent(CharacterControllerComponent));
if (!Check(controller && ChimeraCharacter.Cast(m_Player), "controlled prisoner supports native state restoration")) return -1;
m_OriginalUnconscious = controller.IsUnconscious();
m_Snapshot = true;
if (!Check(m_Mode.Settings().Set("radioDefenceSeconds", 30), "isolated thirty-second duration saved")) return -1;
m_SettingsChanged = true;
m_Second = ERR_Actors.Spawn(m_Mode.Factions().PrisonerPrefab, ERR_Actors.Ground(m_Radio.Position() + "-2 0 0"));
if (!Check(m_Second != null, "second physical defender spawned")) return -1;
m_Mode.Own(m_Second);
DamageManagerComponent.Cast(m_Second.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
WaitFor(1, now);
return 0;
}
if (m_Step == 1)
{
if (!Check(ERR_Actors.CanFight(m_Second), "second physical defender settles with simulated connection membership")) return -1;
m_Authoritative = new ERR_RadioDefenceService(m_Mode);
if (!Check(!m_Authoritative.Begin(m_Second, m_Radio), "ordinary AI cannot tune even while alive and nearby")) return -1;
if (!Check(!m_Authoritative.Begin(m_Player, null), "missing radio rejected")) return -1;
Move(m_Player, m_Radio.Position() + "6 0 0");
WaitFor(2, now);
return 0;
}
if (m_Step == 2)
{
if (!Check(Distance("outside tuning range after deferred teleport") > 5 && !m_Authoritative.Begin(m_Player, m_Radio), "tuning outside five metres rejected")) return -1;
Move(m_Player, m_Radio.Position() + "2 0 0");
Conscious(m_Player, false);
WaitFor(3, now);
return 0;
}
if (m_Step == 3)
{
CharacterControllerComponent controller = CharacterControllerComponent.Cast(m_Player.FindComponent(CharacterControllerComponent));
if (!Check(Distance("unconscious tuning range after deferred teleport") <= 5 && controller.IsUnconscious() && !m_Authoritative.Begin(m_Player, m_Radio), "unconscious prisoner cannot tune")) return -1;
Conscious(m_Player, true);
WaitFor(4, now);
return 0;
}
if (m_Step == 4)
{
if (!Recovered(m_Player, now, "tuning prisoner")) return m_Result;
AuthorizationDiagnostic();
m_Service = new ERR_NativeRadioSample(m_Mode);
if (!Check(ERR_Actors.CanFight(m_Player) && m_Authoritative.CanBegin(m_Player, m_Radio), "registered friendly conscious prisoner remains authorized")) return -1;
if (!Check(!m_Service.Begin(m_Player, m_Radio), "unregistered defender cannot start the isolated service")) return -1;
m_Service.Members.Insert(m_Player); m_Service.Members.Insert(m_Second);
m_Affiliation = FactionAffiliationComponent.Cast(m_Second.FindComponent(FactionAffiliationComponent));
if (!Check(m_Affiliation != null, "physical defender has native faction affiliation")) return -1;
m_Affiliation.SetAffiliatedFaction(m_Mode.EnemyFaction());
WaitFor(5, now);
return 0;
}
if (m_Step == 5)
{
if (!Check(SCR_FactionManager.SGetFaction(m_Second) == m_Mode.EnemyFaction() && !m_Service.Begin(m_Second, m_Radio), "hostile controlled defender cannot tune")) return -1;
m_Affiliation.SetAffiliatedFaction(m_Mode.PlayerFaction());
WaitFor(6, now);
return 0;
}
if (m_Step == 6)
{
m_Service.OnStarted().Insert(Started);
m_Service.OnProgressChanged().Insert(Changed);
m_Service.OnCompleted().Insert(Completed);
if (!Check(m_Service.Begin(m_Player, m_Radio) && !m_Service.Begin(m_Player, m_Radio) && m_Starts == 1, "start event emitted once and duplicate start rejected")) return -1;
m_Service.Tick(1000);
if (!Check(m_Service.DefenderCount() == 2 && m_Service.Since() == 1000 && m_Service.Occupied(), "two conscious physical defenders share one timer")) return -1;
m_Affiliation.SetAffiliatedFaction(m_Mode.EnemyFaction());
WaitFor(7, now);
return 0;
}
if (m_Step == 7)
{
m_Service.Tick(1500);
if (!Check(!m_Service.CanDefend(m_Second) && m_Service.DefenderCount() == 1 && m_Service.Since() == 1000, "hostile actor cannot defend even with simulated connection membership")) return -1;
m_Affiliation.SetAffiliatedFaction(m_Mode.PlayerFaction());
WaitFor(8, now);
return 0;
}
if (m_Step == 8)
{
m_Service.Tick(1600);
int changed = m_Changes;
m_Service.Tick(6000);
if (!Check(m_Service.SecondsRemaining(6000) == 25 && m_Changes == changed, "elapsed countdown does not emit redundant progress events")) return -1;
Conscious(m_Player, false);
WaitFor(9, now);
return 0;
}
if (m_Step == 9)
{
m_Service.Tick(7000);
if (!Check(m_Service.DefenderCount() == 1 && m_Service.Since() == 1000, "second defender preserves progress while first is unconscious")) return -1;
Conscious(m_Second, false);
WaitFor(10, now);
return 0;
}
if (m_Step == 10)
{
m_Service.Tick(8000);
if (!Check(!m_Service.Occupied() && m_Service.Since() == -1 && m_Service.SecondsRemaining(8000) == 30, "all unconscious defenders reset accumulated time")) return -1;
Conscious(m_Player, true);
WaitFor(11, now);
return 0;
}
if (m_Step == 11)
{
if (!Recovered(m_Player, now, "first defender")) return m_Result;
m_Service.Tick(9000);
if (!Check(m_Service.DefenderCount() == 1 && m_Service.Since() == 9000, "conscious recovery starts a fresh continuous interval")) return -1;
Conscious(m_Second, true);
WaitFor(12, now);
return 0;
}
if (m_Step == 12)
{
if (!Recovered(m_Second, now, "second defender")) return m_Result;
m_Service.Tick(10000);
m_Service.Connections = 0; m_Service.Tick(11000);
if (!Check(m_Service.DefenderCount() == 0 && !m_Service.Occupied(), "empty server resets despite remaining character bodies")) return -1;
m_Service.Connections = 2; m_Service.Tick(12000);
Move(m_Player, m_Radio.Position() + "60.5 0 0");
WaitFor(13, now);
return 0;
}
if (m_Step == 13)
{
m_Service.Tick(13000);
if (!Check(Distance("outside defence radius after deferred teleport") >= 60 && !m_Service.CanDefend(m_Player) && m_Service.DefenderCount() == 1 && m_Service.Since() == 12000, "outside sixty metres excludes first defender without resetting second")) return -1;
DamageManagerComponent damage = DamageManagerComponent.Cast(m_Second.FindComponent(DamageManagerComponent));
damage.EnableDamageHandling(true);
SCR_CharacterDamageManagerComponent.Cast(damage).Kill(Instigator.CreateInstigator(m_Second));
WaitFor(14, now);
return 0;
}
if (m_Step == 14)
{
m_Service.Tick(14000);
if (!Check(!ERR_Actors.Alive(m_Second) && !m_Service.CanDefend(m_Second) && !m_Service.Occupied(), "dead defender cannot maintain transmission")) return -1;
Move(m_Player, m_Radio.Position() + "2 0 0");
WaitFor(15, now);
return 0;
}
if (m_Step == 15)
{
if (!Check(Distance("returned to radio after deferred teleport") <= 5 && ERR_Actors.CanFight(m_Player), "survivor returns physically before final transmission samples")) return -1;
m_Service.Tick(15000);
if (!Check(m_Mode.Settings().Set("radioDefenceSeconds", 45), "live duration increase saved")) return -1;
m_Service.Tick(45000);
if (!Check(m_Service.State() == ERR_RadioDefenceState.DEFENDING && m_Service.Since() == 15000 && m_Service.SecondsRemaining(45000) == 15, "live duration increase preserves accumulated occupancy")) return -1;
if (!Check(m_Mode.Settings().Set("radioDefenceSeconds", 30), "live duration reduction saved")) return -1;
m_Service.Tick(45000);
if (!Check(m_Service.State() == ERR_RadioDefenceState.COMPLETED && m_Completions == 1 && m_Service.SecondsRemaining(45000) == 0, "live duration reduction completes at exact continuous boundary")) return -1;
m_Service.Tick(46000); m_Service.Stop(); m_Service.Tick(47000);
if (!Check(m_Completions == 1 && !m_Service.Begin(m_Player, m_Radio), "completed service cannot restart or emit duplicate completion after stop")) return -1;
ERR_NativeRadioSample stopped = new ERR_NativeRadioSample(m_Mode);
stopped.Members.Insert(m_Player); stopped.OnCompleted().Insert(Completed);
if (!Check(stopped.Begin(m_Player, m_Radio), "isolated cancellation service begins")) return -1;
stopped.Tick(1000); stopped.Stop(); stopped.Stop(); stopped.Tick(60000);
if (!Check(stopped.State() == ERR_RadioDefenceState.STOPPED && !stopped.Occupied() && m_Completions == 1 && !stopped.Begin(m_Player, m_Radio), "stop cancels progress and future starts without completing")) return -1;
ERR_NativeRadioSample terminal = new ERR_NativeRadioSample(m_Mode);
terminal.Members.Insert(m_Player); terminal.OnCompleted().Insert(Completed);
if (!Check(terminal.Begin(m_Player, m_Radio), "isolated terminal-phase service begins")) return -1;
terminal.Tick(1000); terminal.PhaseChanged(ERR_Phase.RADIO_DEFENCE, ERR_Phase.DEFEAT); terminal.Tick(60000);
if (!Check(terminal.State() == ERR_RadioDefenceState.STOPPED && m_Completions == 1, "terminal phase event prevents later completion")) return -1;
if (!Check(m_Mode.Phase() == ERR_Phase.RADIO_SEARCH && m_Mode.RadioDefence().State() == ERR_RadioDefenceState.IDLE, "isolated event tests leave live objective ready for normal tuning")) return -1;
Restore();
WaitFor(90, now);
return 0;
}
if (m_Step == 90)
{
if (!m_OriginalUnconscious && !Recovered(m_Player, now, "restored prisoner")) return m_Result;
CharacterControllerComponent controller = CharacterControllerComponent.Cast(m_Player.FindComponent(CharacterControllerComponent));
PrintFormat("ERR RADIO RESTORE: actual=%1 original=%2 distance=%3", m_Player.GetOrigin(), m_Original[3], vector.Distance(m_Player.GetOrigin(), m_Original[3]));
if (!Check(m_RestoreSucceeded && m_Mode.Settings().Get("radioDefenceSeconds") == m_OriginalDuration && controller.IsUnconscious() == m_OriginalUnconscious && vector.Distance(m_Player.GetOrigin(), m_Original[3]) < 1.5, "isolated test restores live duration and physical player state after a world frame")) return -1;
m_Result = 1;
return 1;
}
Check(false, "unknown asynchronous fixture step");
return -1;
}
}
#endif
@@ -0,0 +1,96 @@
#ifdef WORKBENCH
class ERR_NativeRecoveryProbe
{
protected ERR_GameMode m_Mode;
protected IEntity m_Player;
protected CharacterControllerComponent m_Controller;
protected float m_Wait;
protected float m_RecoveryDeadline;
protected bool m_Stopped;
protected bool m_ChangedConsciousness;
protected bool m_InitiallyUnconscious;
protected int m_Step;
protected int m_Changes;
void ERR_NativeRecoveryProbe(ERR_GameMode mode, IEntity player)
{
m_Mode = mode; m_Player = player;
m_Controller = CharacterControllerComponent.Cast(player.FindComponent(CharacterControllerComponent));
if (m_Controller) m_InitiallyUnconscious = m_Controller.IsUnconscious();
mode.Recovery().OnChanged().Insert(Changed);
mode.OnStop().Insert(Stop);
}
protected void Changed(string message) { m_Changes++; }
void Stop()
{
if (m_Stopped) return;
m_Stopped = true;
if (m_Mode) m_Mode.OnStop().Remove(Stop);
if (m_Mode && m_Mode.Recovery()) m_Mode.Recovery().OnChanged().Remove(Changed);
if (m_ChangedConsciousness && m_Controller && ERR_Actors.Alive(m_Player)) m_Controller.SetUnconscious(m_InitiallyUnconscious);
}
protected bool Check(bool ok, string detail)
{
if (ok) { Print("ERR SMOKE PASS: recovery " + detail); return true; }
Stop();
Print("ERR SMOKE FAIL: recovery " + detail, LogLevel.ERROR);
return false;
}
int Tick(float now)
{
if (m_Stopped) return -1;
if (now < m_Wait) return 0;
if (m_Step == 0)
{
m_Mode.Recovery().Tick();
if (!Check(m_Controller && m_Mode.Recovery().Conscious() == 1 && m_Mode.RecoveryStatus().IsEmpty(), "real conscious prisoner is available without recovery warning")) return -1;
array<string> replies = {};
m_Mode.Admin().Execute(m_Mode, false, "mission abandon", replies);
if (!Check(replies.Count() == 1 && replies[0].Contains("Administrator") && m_Mode.Phase() == ERR_Phase.PRISON && m_Mode.EndReason().IsEmpty(), "unauthorized abandonment leaves round and result intact")) return -1;
m_Mode.Admin().Execute(m_Mode, true, "mission abandon now", replies);
if (!Check(m_Mode.Phase() == ERR_Phase.PRISON && m_Mode.EndReason().IsEmpty(), "malformed abandonment does not end a round")) return -1;
m_ChangedConsciousness = true;
m_Controller.SetUnconscious(true);
m_Step = 1; m_Wait = now + 1000;
return 0;
}
if (m_Step == 1)
{
m_Mode.Recovery().Tick();
if (!Check(ERR_Actors.Alive(m_Player) && !ERR_Actors.CanFight(m_Player) && m_Mode.Recovery().Incapacitated() == 1 && m_Mode.Recovery().Conscious() == 0, "native unconscious prisoner is counted as incapacitated, not dead")) return -1;
if (!Check(m_Mode.RecoveryStatus().Contains("No conscious prisoner") && m_Mode.Status().Contains("mission abandon") && ERR_ObjectiveText.Description(m_Mode).Contains(m_Mode.RecoveryStatus()), "server status and native objective copy explain the recovery state")) return -1;
if (!Check(m_Changes == 1, "recovery state publishes exactly one change")) return -1;
m_Mode.Recovery().Tick();
if (!Check(m_Changes == 1, "unchanged sampling emits no duplicate recovery event")) return -1;
m_Step = 2; m_Wait = now + 35000;
return 0;
}
if (m_Step == 2)
{
if (!Check(m_Mode.Phase() == ERR_Phase.PRISON && ERR_Actors.Alive(m_Player) && !ERR_Actors.CanFight(m_Player) && m_Mode.Players().HasSurvivor(), "all-incapacitated player survives beyond ordinary defeat grace")) return -1;
m_Controller.SetUnconscious(false);
m_RecoveryDeadline = now + 12000;
m_Step = 3; m_Wait = now + 1000;
return 0;
}
if (m_Step == 3)
{
if (!ERR_Actors.CanFight(m_Player) && now < m_RecoveryDeadline) return 0;
m_Mode.Recovery().Tick();
if (!Check(ERR_Actors.CanFight(m_Player) && m_Mode.RecoveryStatus().IsEmpty() && m_Mode.Recovery().Conscious() == 1 && m_Changes == 2, "native recovery clears the warning once")) return -1;
int id = GetGame().GetPlayerManager().GetPlayerIdFromControlledEntity(m_Player);
ERR_PlayerLife life = m_Mode.Players().Get(id);
if (!Check(life && life.UsedLife && life.Actor == m_Player, "recovery preserves original actor and spent life")) return -1;
m_Controller.SetUnconscious(true);
m_Step = 4; m_Wait = now + 1000;
return 0;
}
if (m_Step == 4)
{
m_Mode.Recovery().Tick();
if (!Check(!m_Mode.RecoveryStatus().IsEmpty() && m_Changes == 3, "second incapacitation restores warning before abandonment")) return -1;
return 1;
}
return 0;
}
}
#endif
@@ -0,0 +1,68 @@
#ifdef WORKBENCH
// Local controller and native presentation provider; no network clients are
// simulated. The extraction snapshot was produced by real occupied seats.
class ERR_NativeResultProbe
{
protected static Widget s_Content;
protected static bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: result " + detail); return true; }
if (s_Content) s_Content.RemoveFromHierarchy();
s_Content = null;
Print("ERR SMOKE FAIL: result " + detail, LogLevel.ERROR);
return false;
}
protected static bool NativeText(string title, string debrief)
{
if (!s_Content) return false;
TextWidget titleWidget = TextWidget.Cast(s_Content.FindAnyWidget("GameOver_State"));
TextWidget debriefWidget = TextWidget.Cast(s_Content.FindAnyWidget("GameOver_Description"));
return titleWidget && debriefWidget && titleWidget.GetText() == title && debriefWidget.GetText() == debrief;
}
static bool Run(ERR_ExtractionService extraction, UUID escapedAccount, UUID missedAccount)
{
SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerController());
if (!Check(controller && extraction && extraction.State() == ERR_ExtractionState.ESCAPED, "local controller and physical extraction snapshot exist")) return false;
int playerId = controller.GetPlayerId();
ERR_EscapeGameOverScreenInfo info = new ERR_EscapeGameOverScreenInfo();
array<int> staleIds = {playerId};
SCR_GameModeEndData staleData = SCR_GameModeEndData.Create(EGameOverTypes.VICTORY, staleIds);
array<int> copiedIds = {};
staleData.GetWinnerIds(copiedIds);
controller.ERR_ResetEscapeResult();
if (!Check(controller.ERR_EscapeResult() == ERR_PersonalEscapeResult.UNKNOWN && info.GetTitle(playerId, copiedIds) == "The helicopter escaped", "unknown controller ignores stale native end-data winner ID")) return false;
ResourceName layout = "{C4507D07F3C54831}UI/layouts/HUD/GameOver/EndScreen/EndScreenContent_NoImage.layout";
s_Content = GetGame().GetWorkspace().CreateWidgets(layout);
if (!Check(s_Content != null, "native result content widgets created")) return false;
s_Content.SetVisible(false);
SCR_GameOverScreenContentUIComponent contentHandler = SCR_GameOverScreenContentUIComponent.Cast(s_Content.FindHandler(SCR_GameOverScreenContentUIComponent));
if (!Check(contentHandler != null, "native result content handler attached")) return false;
SCR_GameOverScreenUIContentData content = new SCR_GameOverScreenUIContentData(layout, info, staleData, "STALE WINNER", string.Empty, "STALE DEBRIEF", ResourceName.Empty, ResourceName.Empty, null, string.Empty, string.Empty, string.Empty);
contentHandler.InitContent(content);
if (!Check(NativeText("The helicopter escaped", "Your personal escape result is not available."), "opened native content ignores a stale winner while personal result is unknown")) return false;
ERR_PlayerRoster roster = new ERR_PlayerRoster();
if (!Check(roster.Bind(playerId, escapedAccount), "isolated presentation account binds to local controller ID")) return false;
roster.Spawned(playerId, controller.GetControlledEntity());
controller.ERR_PublishEscapeResult(roster, extraction);
array<int> noWinners = {};
if (!Check(controller.ERR_EscapeResult() == ERR_PersonalEscapeResult.ESCAPED && info.GetTitle(playerId, noWinners) == "You escaped Everon", "identity snapshot publishes victory despite missing end-data ID")) return false;
if (!Check(NativeText("You escaped Everon", "You crossed the extraction boundary aboard the helicopter."), "already-open native content refreshes when personal victory arrives")) return false;
if (!Check(roster.Bind(playerId, missedAccount), "local presentation connection ID is recycled to non-escapee")) return false;
roster.Spawned(playerId, controller.GetControlledEntity());
controller.ERR_PublishEscapeResult(roster, extraction);
if (!Check(controller.ERR_EscapeResult() == ERR_PersonalEscapeResult.MISSED && info.GetTitle(playerId, copiedIds) == "The helicopter escaped" && info.GetDebriefing(playerId, copiedIds) == "You did not escape with the helicopter.", "recycled winner ID cannot grant escape or misdescribe dead occupants")) return false;
if (!Check(NativeText("The helicopter escaped", "You did not escape with the helicopter."), "native text refresh removes the previous account's win")) return false;
if (!Check(roster.Bind(playerId, escapedAccount), "escaped presentation account returns after simulated rebind")) return false;
controller.ERR_PublishEscapeResult(roster, extraction);
if (!Check(info.GetTitle(playerId, noWinners) == "You escaped Everon", "presentation follows returning account rather than copied end data")) return false;
controller.ERR_ResetEscapeResult();
if (!Check(controller.ERR_EscapeResult() == ERR_PersonalEscapeResult.UNKNOWN && info.GetTitle(playerId, copiedIds) == "The helicopter escaped", "round reset clears personal result despite stale winner ID")) return false;
if (!Check(NativeText("The helicopter escaped", "Your personal escape result is not available."), "round reset clears the open native result text")) return false;
s_Content.RemoveFromHierarchy();
s_Content = null;
ERR_GameMode mode = ERR_GameMode.Get();
controller.ERR_PublishEscapeResult(mode.Players(), mode.Extraction());
return true;
}
}
#endif
@@ -0,0 +1,58 @@
#ifdef WORKBENCH
// Read-only spatial survey of new authored site candidates. No world edits.
class ERR_NativeSiteSurvey
{
protected bool m_Blocked;
protected string m_Obstacle;
protected bool Obstacle(IEntity entity)
{
if (entity && entity.GetVObject())
{
m_Blocked = true;
m_Obstacle = entity.ToString();
if (entity.GetPrefabData()) m_Obstacle = entity.GetPrefabData().GetPrefabName();
}
return !m_Blocked;
}
void Area(string name, vector center)
{
int found, slopes, blocked;
float best = 10000;
for (int x = -80; x <= 80; x += 20)
for (int z = -80; z <= 80; z += 20)
{
vector position = ERR_Actors.Ground(center + Vector(x, 0, z));
float low = 10000;
float high = -10000;
for (int dx = -2; dx <= 2; dx += 2)
for (int dz = -2; dz <= 2; dz += 2)
{
float height = GetGame().GetWorld().GetSurfaceY(position[0] + dx, position[2] + dz);
low = Math.Min(low, height);
high = Math.Max(high, height);
}
best = Math.Min(best, high-low);
if (low < 1 || high - low > 1.2) { slopes++; continue; }
m_Blocked = false;
GetGame().GetWorld().QueryEntitiesByAABB(Vector(position[0]-1.3, low+0.1, position[2]-1.3), Vector(position[0]+1.3, high+2, position[2]+1.3), Obstacle);
if (m_Blocked)
{
blocked++;
if (blocked <= 2) PrintFormat("ERR SITE OBSTACLE: area=%1 position=%2 object=%3", name, position, m_Obstacle);
continue;
}
found++;
PrintFormat("ERR SITE CANDIDATE: area=%1 position=%2 elevationRange=%3", name, position, high-low);
}
PrintFormat("ERR SITE SURVEY COUNT: area=%1 clear=%2 slopes=%3 blocked=%4 bestSlope=%5", name, found, slopes, blocked, best);
}
void Run()
{
Area("west-supplies", "5900 0 7200");
Area("medical", "6320 0 7440");
Area("outpost", "6740 0 7320");
Print("ERR SMOKE PASS: candidate survey completed without authoring world changes");
Print("ERR SMOKE COMPLETE: read-only site survey");
}
}
#endif
@@ -0,0 +1,111 @@
// Opt-in local diagnostic only. It does not bypass validation or alter cleanup.
#ifdef WORKBENCH
modded class SCR_BaseItemSupportStationComponent
{
protected static int s_ERRStationTeardownLines;
protected static int s_ERRStationInitLines;
protected bool m_ERRStationInitSeen;
protected bool m_ERRStationInitModePresent;
protected bool m_ERRStationInitMainWorld;
protected bool m_ERRStationInitSiteOwned;
protected bool m_ERRStationInitInInventory;
protected ResourceName m_ERRStationInitParentPrefab;
protected bool m_ERRStationInDelete;
protected IEntity m_ERRStationDeleteOwner;
protected override void DelayedInit(IEntity owner)
{
// Preserve initialization and every native validation before observation.
super.DelayedInit(owner);
string enabled;
if (!System.GetCLIParam("errStationTeardownDiag", enabled) || enabled != "1" || !owner) return;
m_ERRStationInitSeen = true;
ERR_GameMode mode = ERR_GameMode.Get();
m_ERRStationInitModePresent = mode != null;
if (GetGame()) m_ERRStationInitMainWorld = owner.GetWorld() == GetGame().GetWorld();
ERR_SiteService sites;
if (mode) sites = mode.Sites();
m_ERRStationInitSiteOwned = sites && sites.Owns(owner);
InventoryItemComponent item = InventoryItemComponent.Cast(owner.FindComponent(InventoryItemComponent));
m_ERRStationInitInInventory = item && item.GetParentSlot();
IEntity parent = owner.GetParent();
if (parent && parent.GetPrefabData()) m_ERRStationInitParentPrefab = parent.GetPrefabData().GetPrefabName();
if (s_ERRStationInitLines >= 64) return;
s_ERRStationInitLines++;
ResourceName prefab;
if (owner.GetPrefabData()) prefab = owner.GetPrefabData().GetPrefabName();
PrintFormat("ERR STATION INIT OWNERSHIP: n=%1 prefab=%2 modePresent=%3 mainWorld=%4 siteOwned=%5 inInventory=%6 parentPrefab=%7", s_ERRStationInitLines, prefab, m_ERRStationInitModePresent, m_ERRStationInitMainWorld, m_ERRStationInitSiteOwned, m_ERRStationInitInInventory, m_ERRStationInitParentPrefab);
}
override void OnDelete(IEntity owner)
{
string enabled;
bool diagnose = System.GetCLIParam("errStationTeardownDiag", enabled) && enabled == "1";
if (diagnose)
{
m_ERRStationInDelete = true;
m_ERRStationDeleteOwner = owner;
}
// Preserve vanilla OnDelete, including its call to InitValidSetup.
super.OnDelete(owner);
m_ERRStationInDelete = false;
m_ERRStationDeleteOwner = null;
}
protected override bool InitValidSetup()
{
if (m_ERRStationInDelete && s_ERRStationTeardownLines < 64)
{
s_ERRStationTeardownLines++;
IEntity owner = m_ERRStationDeleteOwner;
string ownerName = "<deleted>";
ResourceName prefab;
if (owner)
{
ownerName = owner.GetName();
if (owner.GetPrefabData()) prefab = owner.GetPrefabData().GetPrefabName();
}
SCR_EntityCatalogManagerComponent catalog = SCR_EntityCatalogManagerComponent.GetInstance();
SCR_BaseGameMode baseMode;
if (GetGame()) baseMode = SCR_BaseGameMode.Cast(GetGame().GetGameMode());
int gameState = -1;
int phase = -1;
bool hasEndData;
if (baseMode)
{
gameState = baseMode.GetState();
hasEndData = baseMode.GetEndGameData() != null;
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);
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;
bool deleteSiteOwned;
bool deleteInInventory;
ResourceName deleteParentPrefab;
if (owner)
{
if (GetGame()) deleteMainWorld = owner.GetWorld() == GetGame().GetWorld();
ERR_GameMode deleteMode = ERR_GameMode.Cast(baseMode);
ERR_SiteService deleteSites;
if (deleteMode) deleteSites = deleteMode.Sites();
deleteSiteOwned = deleteSites && deleteSites.Owns(owner);
InventoryItemComponent deleteItem = InventoryItemComponent.Cast(owner.FindComponent(InventoryItemComponent));
deleteInInventory = deleteItem && deleteItem.GetParentSlot();
IEntity deleteParent = owner.GetParent();
if (deleteParent && deleteParent.GetPrefabData()) deleteParentPrefab = deleteParent.GetPrefabData().GetPrefabName();
}
detail += string.Format(" deleteMainWorld=%1 deleteSiteOwned=%2 deleteInInventory=%3 deleteParentPrefab=%4", deleteMainWorld, deleteSiteOwned, deleteInInventory, deleteParentPrefab);
Print(detail);
if (s_ERRStationTeardownLines == 64) Print("ERR STATION TEARDOWN DIAG: 64-line diagnostic limit reached; vanilla validation and cleanup continue unchanged.");
}
// Retain every original validation result and error message.
return super.InitValidSetup();
}
}
#endif
@@ -0,0 +1,117 @@
#ifdef WORKBENCH
class ERR_NativeSettingsReader : ERR_Settings
{
bool Probe(string path, out map<string, int> values) { return Read(path, values); }
}
// Runs inside the disposable native test profile. No original settings touched.
class ERR_NativeTimeProbe
{
protected static float s_EngineTime;
protected static float s_ClockHours;
protected static int s_Multiplier;
protected static bool s_Started;
static bool VerifyProgression(ERR_GameMode mode)
{
ChimeraWorld world = ChimeraWorld.CastFrom(mode.GetWorld());
TimeAndWeatherManagerEntity manager = world.GetTimeAndWeatherManager();
if (!Check(s_Started && manager && manager.GetIsDayAutoAdvanced(), "clock advancement remains enabled")) return false;
float elapsed = manager.GetEngineTime() - s_EngineTime;
if (!Check(elapsed >= 10, "progression sampled across at least ten engine seconds")) return false;
float actual = manager.GetTimeOfTheDay() - s_ClockHours;
if (actual < 0) actual += 24;
float expected = elapsed * s_Multiplier / 3600.0;
return Check(Math.AbsFloat(actual - expected) < 0.001, "native clock advances at configured rate without periodic resets");
}
protected static bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: mission time " + detail); return true; }
Print("ERR SMOKE FAIL: mission time " + detail, LogLevel.ERROR);
return false;
}
protected static bool WriteFixture(string path, string omit = "", bool clock = false, float minutes = 840, bool speed = false, float multiplier = 1)
{
JsonSaveContext json = new JsonSaveContext();
json.EnableTypeDiscriminator(false);
if (omit != "reportIntervalSeconds") json.WriteValue("reportIntervalSeconds", 20);
json.WriteValue("supportSquadLimit", 2);
json.WriteValue("responseRadiusMetres", 1500);
json.WriteValue("aiIntelChancePercent", 0);
json.WriteValue("intelRevealChancePercent", 100);
json.WriteValue("radioDefenceSeconds", 120);
if (clock) json.WriteValue("startTimeMinutes", minutes);
if (speed) json.WriteValue("timeMultiplier", multiplier);
return json.SaveToFile(path);
}
protected static bool SettingsMigration(string path)
{
if (!Check(WriteFixture(path), "legacy fixture created")) return false;
ERR_Settings legacy = new ERR_Settings(path);
if (!Check(legacy.Get("reportIntervalSeconds") == 20 && legacy.Get("radioDefenceSeconds") == 120 && legacy.Get("aiIntelChancePercent") == 0 && legacy.Get("intelRevealChancePercent") == 100, "existing six-key settings preserved")) return false;
if (!Check(legacy.Get("startTimeMinutes") == 840 && legacy.Get("timeMultiplier") == 1, "only absent time settings receive defaults")) return false;
JsonLoadContext unchanged = new JsonLoadContext();
if (!Check(unchanged.LoadFromFile(path) && !unchanged.DoesKeyExist("startTimeMinutes") && !unchanged.DoesKeyExist("timeMultiplier"), "loading does not rewrite legacy file")) return false;
if (!Check(legacy.Set("startTimeMinutes", 375) && legacy.Set("timeMultiplier", 4), "explicit save upgrades valid legacy settings")) return false;
ERR_Settings reloaded = new ERR_Settings(path);
if (!Check(reloaded.Get("startTimeMinutes") == 375 && reloaded.Get("timeMultiplier") == 4 && reloaded.Get("responseRadiusMetres") == 1500 && reloaded.Get("radioDefenceSeconds") == 120, "new keys and old values survive reload")) return false;
ERR_NativeSettingsReader reader = new ERR_NativeSettingsReader();
map<string, int> values;
if (!WriteFixture(path, "", true, 1440)) return false;
if (!Check(!reader.Probe(path, values), "present out-of-range time cannot silently default")) return false;
if (!WriteFixture(path, "", true, 375.5)) return false;
if (!Check(!reader.Probe(path, values), "fractional clock setting rejected")) return false;
if (!WriteFixture(path, "", false, 0, true, 0)) return false;
if (!Check(!reader.Probe(path, values), "zero multiplier rejected")) return false;
if (!WriteFixture(path, "reportIntervalSeconds")) return false;
if (!Check(!reader.Probe(path, values), "missing legacy setting remains invalid")) return false;
if (!WriteFixture(path, "", true, 375)) return false;
if (!Check(reader.Probe(path, values) && values.Get("startTimeMinutes") == 375 && values.Get("timeMultiplier") == 1, "partially upgraded file defaults only missing new field")) return false;
if (!WriteFixture(path, "", false, 0, true, 25)) return false;
ERR_Settings recovered = new ERR_Settings(path);
if (!Check(recovered.Get("startTimeMinutes") == 375 && recovered.Get("timeMultiplier") == 1 && recovered.Get("reportIntervalSeconds") == 20, "valid saved backup recovers invalid primary")) return false;
return true;
}
static bool Run(ERR_GameMode mode)
{
int minutes;
if (!Check(ERR_TimeService.ParseClock("00:00", minutes) && minutes == 0 && ERR_TimeService.ParseClock("23:59", minutes) && minutes == 1439 && ERR_TimeService.Clock(375) == "06:15", "clock parsing includes midnight and final minute")) return false;
if (!Check(!ERR_TimeService.ParseClock("24:00", minutes) && !ERR_TimeService.ParseClock("12:60", minutes) && !ERR_TimeService.ParseClock("1:00", minutes) && !ERR_TimeService.ParseClock("ab:cd", minutes) && !ERR_TimeService.ParseClock("06:15junk", minutes), "malformed clock commands rejected")) return false;
string path = "$profile:erri-native-time-probe.json";
if (!Check(!FileIO.FileExists(path) && !FileIO.FileExists(path + ".bak") && !FileIO.FileExists(path + ".tmp"), "isolated fixture paths unused")) return false;
bool migrated = SettingsMigration(path);
FileIO.DeleteFile(path); FileIO.DeleteFile(path + ".bak"); FileIO.DeleteFile(path + ".tmp");
if (!migrated) return false;
if (!Check(mode.Time() && mode.Time().Started(), "server starts world clock before prison phase")) return false;
ChimeraWorld world = ChimeraWorld.CastFrom(mode.GetWorld());
TimeAndWeatherManagerEntity manager = world.GetTimeAndWeatherManager();
if (!Check(manager && manager.GetIsDayAutoAdvanced() && Math.AbsFloat(manager.GetDayDuration() - ERR_TimeService.DayDuration(mode.Time().Multiplier())) < 0.1, "engine progression matches applied multiplier")) return false;
int originalTime = mode.Settings().Get("startTimeMinutes");
int originalSpeed = mode.Settings().Get("timeMultiplier");
array<string> replies = {};
mode.Admin().Execute(mode, false, "mission time set 06:15", replies);
if (!Check(mode.Settings().Get("startTimeMinutes") == originalTime && replies[0] == "Administrator access required.", "unauthorized clock mutation rejected")) return false;
mode.Admin().Execute(mode, true, "mission time set 24:00", replies);
if (!Check(mode.Settings().Get("startTimeMinutes") == originalTime && replies[0].Contains("Invalid"), "invalid clock command preserves setting")) return false;
float currentHours = manager.GetTimeOfTheDay();
float currentDuration = manager.GetDayDuration();
int appliedStart = mode.Time().StartMinutes();
int appliedSpeed = mode.Time().Multiplier();
mode.Admin().Execute(mode, true, "mission time set 06:15", replies);
bool savedTime = mode.Settings().Get("startTimeMinutes") == 375 && replies[0].Contains("next round");
mode.Admin().Execute(mode, true, "mission speed set 4", replies);
bool savedSpeed = mode.Settings().Get("timeMultiplier") == 4 && replies[0].Contains("next round");
bool idempotent = mode.Time().Start(mode) && Math.AbsFloat(manager.GetTimeOfTheDay() - currentHours) < 0.01 && manager.GetDayDuration() == currentDuration && mode.Time().StartMinutes() == appliedStart && mode.Time().Multiplier() == appliedSpeed;
mode.Admin().Execute(mode, true, "mission time show", replies);
bool shown = replies.Count() == 2 && replies[0].Contains("Clock") && replies[1] == "Next round: 06:15, 4x progression.";
bool restoredTime = mode.Settings().Set("startTimeMinutes", originalTime);
bool restoredSpeed = mode.Settings().Set("timeMultiplier", originalSpeed);
if (!Check(savedTime && savedSpeed && restoredTime && restoredSpeed, "authorized settings persist and test restores originals")) return false;
if (!Check(idempotent, "saved changes and repeated start leave current clock and speed intact")) return false;
if (!Check(shown, "admin sees current clock separately from next round settings")) return false;
s_EngineTime = manager.GetEngineTime();
s_ClockHours = manager.GetTimeOfTheDay();
s_Multiplier = mode.Time().Multiplier();
s_Started = true;
return true;
}
}
#endif
@@ -0,0 +1,146 @@
#ifdef WORKBENCH
// Explicit destructive test of successor-owned transport in the disposable
// scenario. Scripted occupants and engine input are not manual driving or MP.
class ERR_NativeTransportProbe
{
protected ERR_GameMode m_Mode;
protected ERR_SiteRuntime m_Site;
protected Vehicle m_Vehicle;
protected CarControllerComponent m_Controller;
protected SCR_FuelManagerComponent m_Fuel;
protected ref array<IEntity> m_Actors = {};
protected BaseCompartmentSlot m_Driver;
protected BaseCompartmentSlot m_Passenger;
protected int m_Step;
protected float m_Wait;
void ERR_NativeTransportProbe(ERR_GameMode mode) { m_Mode = mode; }
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);
return false;
}
protected bool DefinitionChecks()
{
ERR_ScenarioDefinition definition = ERR_ScenarioDefinition.Cast(SCR_BaseContainerTools.CreateInstanceFromPrefab("{BC310DC0CD27F523}Configs/ERR/EveronSites.conf"));
ERR_SiteDefinition site;
if (definition) site = definition.Find("everon-supply-2");
if (!Check(site && site.Transport && definition.Valid(), "authored transport definition resolves")) return false;
ERR_SiteTransport transport = site.Transport;
vector offset = transport.Offset;
transport.Offset = "61 0 0";
if (!Check(!definition.Valid(), "unbounded parking offset rejected")) return false;
transport.Offset = "0 1 0";
if (!Check(!definition.Valid(), "floating parking offset rejected")) return false;
transport.Offset = offset;
float radius = transport.ClearanceRadius;
transport.ClearanceRadius = 1;
if (!Check(!definition.Valid(), "undersized parking footprint rejected")) return false;
transport.ClearanceRadius = 9;
if (!Check(!definition.Valid(), "unbounded parking footprint rejected")) return false;
transport.ClearanceRadius = radius;
float range = transport.MaximumTerrainRange;
transport.MaximumTerrainRange = 2;
if (!Check(!definition.Valid(), "unsafe authoring slope allowance rejected")) return false;
transport.MaximumTerrainRange = range;
ERR_SiteDefinition radio = definition.Find("everon-radio-1");
radio.Transport = transport;
if (!Check(!definition.Valid(), "transport restricted to optional depot and outpost sites")) return false;
radio.Transport = null;
return Check(definition.Valid(), "valid authoring restored after isolated mutations");
}
protected IEntity Character(int index)
{
IEntity actor = ERR_Actors.Spawn(m_Mode.Factions().PrisonerPrefab, ERR_Actors.Ground(m_Vehicle.GetOrigin() + Vector(-8 - index * 2, 0, 0)));
if (!actor) return null;
m_Mode.Own(actor);
m_Actors.Insert(actor);
DamageManagerComponent damage = DamageManagerComponent.Cast(actor.FindComponent(DamageManagerComponent));
if (damage) damage.EnableDamageHandling(false);
return actor;
}
int Tick(float now)
{
if (now < m_Wait) return 0;
if (m_Step == 0)
{
if (!DefinitionChecks()) return -1;
m_Site = m_Mode.Sites().Find("everon-supply-2");
if (!Check(m_Site && m_Site.Transport && m_Mode.Sites().TransportCount() == 1 && m_Mode.Sites().Owns(m_Site.Transport), "exactly one authored transport belongs to the finite site service")) return -1;
m_Vehicle = m_Site.Transport;
m_Controller = CarControllerComponent.Cast(m_Vehicle.FindComponent(CarControllerComponent));
m_Fuel = SCR_FuelManagerComponent.Cast(m_Vehicle.FindComponent(SCR_FuelManagerComponent));
if (!Check(m_Controller && m_Fuel && m_Fuel.HasFuel() && m_Controller.GetPersistentHandBrake(), "wheeled transport starts parked with vanilla finite fuel")) return -1;
PrintFormat("ERR TRANSPORT FUEL: current=%1 maximum=%2", m_Fuel.GetTotalFuel(), m_Fuel.GetTotalMaxFuel());
BaseCompartmentManagerComponent manager = BaseCompartmentManagerComponent.Cast(m_Vehicle.FindComponent(BaseCompartmentManagerComponent));
array<BaseCompartmentSlot> seats = {};
if (manager) manager.GetCompartments(seats);
int usable;
foreach (BaseCompartmentSlot slot : seats)
{
if (!slot) continue;
ECompartmentType type = SCR_CompartmentAccessComponent.GetCompartmentType(slot);
if (type == ECompartmentType.PILOT) { usable++; if (!m_Driver) m_Driver = slot; }
if (type == ECompartmentType.CARGO) { usable++; if (!m_Passenger) m_Passenger = slot; }
}
PrintFormat("ERR TRANSPORT SEATS: usable driver/passenger positions=%1", usable);
if (!Check(m_Driver && m_Passenger && m_Driver != m_Passenger, "native driver and passenger compartments are distinct")) return -1;
DamageManagerComponent.Cast(m_Vehicle.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
IEntity driver = Character(0);
IEntity passenger = Character(1);
if (!Check(driver && passenger, "isolated physical occupants created")) return -1;
CompartmentAccessComponent access = CompartmentAccessComponent.Cast(driver.FindComponent(CompartmentAccessComponent));
if (!Check(access && access.GetInVehicle(m_Vehicle, m_Driver, true, -1, ECloseDoorAfterActions.INVALID, false), "native driver boarding request accepted")) return -1;
m_Step = 1;
m_Wait = now + 1500;
return 0;
}
if (m_Step == 1)
{
CompartmentAccessComponent access = CompartmentAccessComponent.Cast(m_Actors[1].FindComponent(CompartmentAccessComponent));
if (!Check(access && access.GetInVehicle(m_Vehicle, m_Passenger, true, -1, ECloseDoorAfterActions.INVALID, false), "native passenger boarding request accepted")) return -1;
m_Step = 2;
m_Wait = now + 2500;
return 0;
}
if (m_Step == 2)
{
if (!Check(m_Driver.GetOccupant() == m_Actors[0] && m_Passenger.GetOccupant() == m_Actors[1] && CompartmentAccessComponent.GetVehicleIn(m_Actors[0]) == m_Vehicle && CompartmentAccessComponent.GetVehicleIn(m_Actors[1]) == m_Vehicle, "driver and passenger physically occupy their native seats")) return -1;
if (!Check(m_Controller.IsEngineOn() || m_Controller.StartEngine(), "ordinary native engine start request accepted")) return -1;
m_Step = 3;
m_Wait = now + 6000;
return 0;
}
if (m_Step == 3)
{
if (!Check(m_Controller.IsEngineOn(), "native wheeled engine starts with available fuel")) return -1;
m_Fuel.SetTotalFuelPercentage(0);
if (!Check(m_Mode.Sites().Prepare() == ERR_PrepareResult.READY && m_Site.Transport == m_Vehicle, "preparation queries preserve the original vehicle after deliberate fuel depletion")) return -1;
m_Step = 4;
m_Wait = now + 5000;
return 0;
}
if (m_Step == 4)
{
if (!Check(!m_Fuel.HasFuel() && m_Fuel.GetTotalFuel() <= 0.001 && m_Mode.Sites().Prepare() == ERR_PrepareResult.READY && m_Mode.Sites().TransportCount() == 1 && m_Site.Transport == m_Vehicle, "empty transport stays empty and is not refilled or replaced")) return -1;
foreach (IEntity actor : m_Actors) ERR_Actors.DeleteSpawned(actor);
m_Actors.Clear();
ERR_Actors.DeleteSpawned(m_Vehicle);
m_Step = 5;
m_Wait = now + 3000;
return 0;
}
if (m_Step == 5)
{
if (!Check(!m_Site.Transport && m_Mode.Sites().TransportCount() == 0 && m_Mode.Sites().Prepare() == ERR_PrepareResult.READY, "lost transport remains absent after service ticks and preparation queries")) return -1;
ERR_ScenarioDefinition definition = ERR_ScenarioDefinition.Cast(SCR_BaseContainerTools.CreateInstanceFromPrefab("{BC310DC0CD27F523}Configs/ERR/EveronSites.conf"));
if (!Check(!m_Mode.Sites().Begin(definition, ERR_Supplies.Map()) && m_Mode.Sites().TransportCount() == 0, "duplicate initialization cannot replace lost transport")) return -1;
m_Mode.Sites().Stop();
if (!Check(m_Mode.Sites().Prepare() == ERR_PrepareResult.FAILED && !m_Mode.Sites().Begin(definition, ERR_Supplies.Map()), "stopped finite owner cannot reinitialize transport")) return -1;
m_Step = 6;
return 1;
}
return 0;
}
}
#endif
@@ -0,0 +1,36 @@
#ifdef WORKBENCH
// Authoring-only survey: omit transport in the fixture's in-memory definition so
// an invalid parking candidate cannot block inspecting nearby terrain/scenery.
modded class ERR_SiteService
{
override bool Begin(ERR_ScenarioDefinition definition, ResourceName fieldMap)
{
string outcome;
if (definition && System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "transport-survey")
foreach (ERR_SiteDefinition site : definition.Sites) site.Transport = null;
return super.Begin(definition, fieldMap);
}
}
class ERR_NativeTransportSurvey
{
static void Run(ERR_GameMode mode)
{
ERR_ScenarioDefinition definition = ERR_ScenarioDefinition.Cast(SCR_BaseContainerTools.CreateInstanceFromPrefab("{BC310DC0CD27F523}Configs/ERR/EveronSites.conf"));
ERR_SiteDefinition site = definition.Find("everon-supply-2");
int clear;
for (int x = -40; x <= 40; x += 8)
for (int z = -40; z <= 40; z += 8)
{
vector offset = Vector(x, 0, z);
if (offset.Length() < 8) continue;
vector position = site.Position + offset;
if (!mode.Sites().TransportClearance(position, site.Transport.ClearanceRadius, site.Transport.MaximumTerrainRange)) continue;
clear++;
PrintFormat("ERR PARKING CANDIDATE: offset=%1 position=%2", offset, ERR_Actors.Ground(position));
}
PrintFormat("ERR PARKING SURVEY: clear=%1; no scenery removed and no transport spawned", clear);
Print("ERR SMOKE PASS: transport parking survey completed");
Print("ERR SMOKE COMPLETE: bounded transport parking survey");
}
}
#endif
@@ -0,0 +1,59 @@
// Native task descriptions otherwise remain stale while their entry is open.
// Own only this scenario's additional binding and missing native detach cleanup.
modded class SCR_TaskListEntryDescriptionUIComponent
{
protected SCR_Task m_ERRObservedObjective;
// Entity references become null when a task is deleted; work ownership must survive.
protected bool m_ERROwnsObjectiveBinding;
override void InitDescription(notnull SCR_Task task)
{
// Native InitDescription deletes assignee labels; cancel their delayed work first.
ERR_UnbindObjectiveDetails();
super.InitDescription(task);
if (!ERR_GameMode.Get() || !task.GetTaskID().StartsWith("erri-mission-")) return;
m_ERRObservedObjective = task;
m_ERROwnsObjectiveBinding = true;
task.GetOnDisplayDataChanged().Insert(ERR_RefreshObjectiveDetails);
}
protected void ERR_RefreshObjectiveDetails()
{
if (m_wRoot && m_Task && m_Task == m_ERRObservedObjective) UpdateTask();
}
protected override void UpdateAssignees(notnull SCR_Task task, SCR_TaskExecutor newAssignee = null, int requesterID = 0)
{
// Native assignment refresh also deletes the old labels before rebuilding them.
if (m_ERROwnsObjectiveBinding && task == m_Task)
ERR_CancelObjectiveOverflow();
super.UpdateAssignees(task, newAssignee, requesterID);
}
protected void ERR_CancelObjectiveOverflow()
{
if (GetGame() && GetGame().GetCallqueue()) GetGame().GetCallqueue().Remove(CheckOverflow);
}
protected void ERR_UnbindObjectiveDetails()
{
if (!m_ERROwnsObjectiveBinding) return;
ERR_CancelObjectiveOverflow();
if (m_ERRObservedObjective) m_ERRObservedObjective.GetOnDisplayDataChanged().Remove(ERR_RefreshObjectiveDetails);
m_ERRObservedObjective = null;
m_ERROwnsObjectiveBinding = false;
}
override void HandlerDeattached(Widget w)
{
// Vanilla detaches widget/manager listeners but leaves these task listeners.
// Preserve that vanilla path for every task outside the successor scenario.
if (m_ERROwnsObjectiveBinding)
{
// These five native invokers are static and survive task entity deletion.
SCR_Task.GetOnTaskAssigneeAdded().Remove(UpdateAssignees);
SCR_Task.GetOnTaskAssigneeRemoved().Remove(UpdateAssignees);
SCR_Task.GetOnTaskUIVisibilityChanged().Remove(OnTaskUIVisibilityChanged);
SCR_Task.GetOnTaskVisibilityChanged().Remove(OnTaskVisibilityChanged);
SCR_Task.GetOnTaskOwnershipChanged().Remove(OnTaskOwnershipChanged);
if (m_TaskComponent) m_TaskComponent.GetOnTaskVisualChanged().Remove(OnTaskVisualChanged);
m_Task = null;
}
ERR_UnbindObjectiveDetails();
super.HandlerDeattached(w);
}
}
@@ -0,0 +1,64 @@
// Shared player-facing copy; reads replicated mission state and never changes it.
class ERR_ObjectiveText
{
static string Title(ERR_Phase phase)
{
if (phase == ERR_Phase.PRISON) return "Escape the prison";
if (phase == ERR_Phase.RADIO_SEARCH) return "Locate the radio station";
if (phase == ERR_Phase.RADIO_DEFENCE) return "Defend the radio transmission";
if (phase == ERR_Phase.EXTRACTION) return "Escape Everon by helicopter";
return "Escape Everon";
}
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);
}
static string RadioFeedbackMessage(ERR_RadioFeedback feedback)
{
if (feedback == ERR_RadioFeedback.WAITING)
return "Waiting for a conscious defender near the radio. Keep someone within 60 metres to start the transmission.";
if (feedback == ERR_RadioFeedback.TRANSMITTING)
return "Transmission in progress. Keep a conscious survivor within 60 metres of the radio. Leaving the area resets progress.";
if (feedback == ERR_RadioFeedback.INTERRUPTED)
return "Transmission interrupted. Progress has reset. Return a conscious survivor to within 60 metres of the radio to resume.";
return "Keep a conscious survivor within 60 metres of the radio until the transmission finishes. Leaving the area resets the defence timer.";
}
static string Description(ERR_GameMode mode, bool progress = true)
{
string description = PhaseDescription(mode, progress);
if (mode && mode.Active() && !mode.RecoveryStatus().IsEmpty()) description += "\n" + mode.RecoveryStatus();
return description;
}
protected static string PhaseDescription(ERR_GameMode mode, bool progress)
{
if (!mode) return "";
ERR_Phase phase = mode.Phase();
if (phase == ERR_Phase.PREPARING)
{
if (!mode.PreparationError().IsEmpty()) return "The mission could not start. An administrator can use /esc status for the cause. Spawning is disabled until the configuration is corrected and the scenario restarted.";
return "Preparing mission sites and equipment. You will spawn when the mission is ready.";
}
if (phase == ERR_Phase.PRISON)
return "Search the supply box inside the prison for weapons and a field map. Pick the gate lock, then get clear of the guards.";
if (phase == ERR_Phase.RADIO_SEARCH)
{
array<ERR_LocationComponent> locations = {};
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.";
}
if (phase == ERR_Phase.RADIO_DEFENCE)
{
if (!progress) return RadioFeedbackMessage(mode.RadioFeedback());
int seconds = mode.DefenceSecondsRemaining();
if (!mode.DefenceOccupied())
return string.Format("Transmission interrupted. Keep a conscious survivor within 60 metres of the radio for %1 seconds. Leaving resets progress.", seconds);
int displayed = Math.Ceil(seconds / 5.0) * 5;
return string.Format("Transmission in progress: up to %1 seconds remaining. Keep a conscious survivor within 60 metres of the radio. Leaving resets progress.", displayed);
}
if (phase == ERR_Phase.EXTRACTION) return Extraction(mode.EscapeRadius());
return "";
}
}
@@ -0,0 +1,60 @@
// Server-owned presentation adapter: mission rules decide progress, native tasks
// replicate it. Companion addons neither create nor advance these objectives.
class ERR_Objectives
{
protected ERR_GameMode m_Mode;
protected SCR_Task m_Current;
protected string m_LastDescription;
protected bool m_Stopped;
void ERR_Objectives(ERR_GameMode mode) { m_Mode = mode; }
SCR_Task Current() { return m_Current; }
static string Id(ERR_Phase phase) { return "erri-mission-" + phase.ToString(); }
void PhaseChanged(ERR_Phase previous, ERR_Phase current)
{
if (!Replication.IsServer() || m_Stopped) return;
SCR_TaskSystem tasks = SCR_TaskSystem.GetInstance();
if (!tasks) { Print("Rereintroduced: native task system missing", LogLevel.ERROR); return; }
if (m_Current)
{
SCR_ETaskState result = SCR_ETaskState.COMPLETED;
if (current == ERR_Phase.DEFEAT) result = SCR_ETaskState.FAILED;
tasks.SetTaskState(m_Current, result);
}
m_Current = null;
if (ERR_MissionRules.Terminal(current)) return;
string title = ERR_ObjectiveText.Title(current);
if (title.IsEmpty()) return;
m_Current = tasks.CreateTask("{1D0F815858EE24AD}Prefabs/Tasks/BaseTask.et", Id(current), title, Description());
if (!m_Current) { Print("Rereintroduced: objective creation failed", LogLevel.ERROR); return; }
// Location markers use the discovery registry. A task cannot expose an unknown site.
tasks.SetTaskUIVisibility(m_Current, SCR_ETaskUIVisibility.LIST_ONLY);
m_Mode.Own(m_Current);
m_LastDescription = "";
Tick(GetGame().GetWorld().GetWorldTime());
}
protected string Description() { return ERR_ObjectiveText.Description(m_Mode); }
void Tick(float now)
{
if (!Replication.IsServer() || m_Stopped || !m_Current) return;
string description = Description();
if (description != m_LastDescription)
{
m_Current.SetTaskDescription(description);
m_LastDescription = description;
}
SCR_TaskSystem tasks = SCR_TaskSystem.GetInstance();
if (!tasks) return;
array<int> players = {};
GetGame().GetPlayerManager().GetPlayers(players);
foreach (int playerId : players)
{
SCR_TaskExecutor executor = SCR_TaskSystem.TaskExecutorFromPlayerID(playerId);
if (executor && !m_Current.IsTaskAssignedTo(executor)) tasks.AssignTask(m_Current, executor, true);
}
}
void Stop() { m_Stopped = true; }
}
@@ -0,0 +1,225 @@
enum ERR_ObserverReason
{
NONE,
SPENT_LIFE,
LATE_EXTRACTION,
SPAWN_UNAVAILABLE
}
// A local follow camera. It never possesses its target or offers free flight.
[BaseContainerProps(), SCR_BaseManualCameraComponentTitle()]
class ERR_FollowSurvivorCameraComponent : SCR_BaseManualCameraComponent
{
override bool EOnCameraInit() { return true; }
override void EOnCameraFrame(SCR_ManualCameraParam param)
{
SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerController());
if (!controller) return;
IEntity target = controller.ERR_ObservedEntity();
vector aim = controller.ERR_ObserverAnchor() + "0 1.5 0";
vector position = aim + "0 4 -6";
IEntity exclude = target;
if (target)
{
IEntity vehicle = CompartmentAccessComponent.GetVehicleIn(target);
vector transform[4];
if (vehicle)
{
vehicle.GetWorldTransform(transform);
vector lower, upper;
vehicle.GetWorldBounds(lower, upper);
aim = (lower + upper) * 0.5;
position = aim + "0 5 0" - transform[2] * 10;
exclude = vehicle;
}
else
{
target.GetWorldTransform(transform);
aim = transform[3] + "0 1.5 0";
position = aim + "0 1.5 0" - transform[2] * 4;
}
}
TraceParam trace = new TraceParam();
trace.Start = aim;
trace.End = position;
trace.Flags = TraceFlags.WORLD | TraceFlags.ENTS;
trace.LayerMask = TRACE_LAYER_CAMERA;
trace.Exclude = exclude;
float fraction = GetCameraEntity().GetWorld().TraceMove(trace, null);
if (fraction < 1) position = vector.Lerp(aim, position, Math.Max(0.05, fraction - 0.05));
Math3D.DirectionAndUpMatrix(vector.Direction(position, aim), "0 1 0", param.transform);
param.transform[3] = position;
param.isDirty = true;
}
}
modded class SCR_PlayerController
{
[RplProp()]
protected ERR_ObserverReason m_ERRObserverReason;
[RplProp()]
protected RplId m_ERRObservedId = RplId.Invalid();
[RplProp()]
protected vector m_ERRObserverAnchor;
protected IEntity m_ERRServerObserved;
protected IEntity m_ERRServerObservedVehicle;
protected SCR_ManualCamera m_ERRObserverCamera;
protected bool m_ERRObserverMonitor;
protected bool m_ERRObserverReady;
protected float m_ERRLastCycle = -1000;
ERR_ObserverReason ERR_ObserverState() { return m_ERRObserverReason; }
vector ERR_ObserverAnchor() { return m_ERRObserverAnchor; }
IEntity ERR_ObservedEntity()
{
RplComponent component = RplComponent.Cast(Replication.FindItem(m_ERRObservedId));
if (!component) return null;
return component.GetEntity();
}
bool ERR_HasObserverCamera() { return m_ERRObserverCamera != null; }
void ERR_SetObserver(ERR_ObserverReason reason)
{
if (!Replication.IsServer()) return;
if (reason != ERR_ObserverReason.NONE && ERR_Actors.Alive(GetControlledEntity())) return;
m_ERRObserverReason = reason;
ERR_GameMode mode = ERR_GameMode.Get();
if (mode && reason != ERR_ObserverReason.NONE) m_ERRObserverAnchor = mode.PrisonPosition();
if (reason == ERR_ObserverReason.NONE) ERR_ReleaseObserved();
Replication.BumpMe();
ERR_UpdateObserverServer();
}
protected void ERR_StreamObserved(IEntity entity, bool normalStreaming)
{
RplIdentity identity = GetRplIdentity();
if (!identity.IsValid() || identity == RplIdentity.Local() || !entity) return;
RplComponent component = RplComponent.Cast(entity.FindComponent(RplComponent));
if (component) component.EnableStreamingConNode(identity, normalStreaming);
}
protected void ERR_ReleaseObserved()
{
ERR_StreamObserved(m_ERRServerObserved, true);
ERR_StreamObserved(m_ERRServerObservedVehicle, true);
m_ERRServerObserved = null;
m_ERRServerObservedVehicle = null;
m_ERRObservedId = RplId.Invalid();
}
void ERR_UpdateObserverServer(int direction = 0)
{
if (!Replication.IsServer() || m_ERRObserverReason == ERR_ObserverReason.NONE) return;
ERR_GameMode mode = ERR_GameMode.Get();
if (!mode || !mode.Active() || ERR_Actors.Alive(GetControlledEntity()))
{
m_ERRObserverReason = ERR_ObserverReason.NONE;
ERR_ReleaseObserved();
Replication.BumpMe();
return;
}
array<IEntity> survivors = {};
ERR_Actors.Players(survivors);
Faction friendly = mode.PlayerFaction();
for (int i = survivors.Count() - 1; i >= 0; i--)
if (SCR_FactionManager.SGetFaction(survivors[i]) != friendly) survivors.RemoveOrdered(i);
int index = survivors.Find(m_ERRServerObserved);
if (index < 0) index = 0;
else index += direction;
if (index < 0) index = survivors.Count() - 1;
if (index >= survivors.Count()) index = 0;
IEntity target;
if (!survivors.IsEmpty()) target = survivors[index];
if (target != m_ERRServerObserved)
{
ERR_ReleaseObserved();
m_ERRServerObserved = target;
if (target)
{
RplComponent component = RplComponent.Cast(target.FindComponent(RplComponent));
if (component) m_ERRObservedId = component.Id();
ERR_StreamObserved(target, false);
}
}
if (target)
{
m_ERRObserverAnchor = target.GetOrigin();
IEntity vehicle = CompartmentAccessComponent.GetVehicleIn(target);
if (vehicle != m_ERRServerObservedVehicle)
{
ERR_StreamObserved(m_ERRServerObservedVehicle, true);
m_ERRServerObservedVehicle = vehicle;
ERR_StreamObserved(vehicle, false);
}
}
Replication.BumpMe();
}
void ERR_CycleObserver(int direction)
{
if (this == GetGame().GetPlayerController()) Rpc(ERR_RequestObserverCycle, direction);
}
[RplRpc(RplChannel.Reliable, RplRcver.Server)]
protected void ERR_RequestObserverCycle(int direction)
{
if (!Replication.IsServer() || (direction != -1 && direction != 1)) return;
float now = GetGame().GetWorld().GetWorldTime();
if (now - m_ERRLastCycle < 250) return;
m_ERRLastCycle = now;
ERR_UpdateObserverServer(direction);
}
void ERR_StartObserverMonitor()
{
if (this != GetGame().GetPlayerController() || System.IsConsoleApp() || m_ERRObserverMonitor) return;
m_ERRObserverMonitor = true;
ERR_OnObserverMonitorStarted();
GetGame().GetCallqueue().CallLater(ERR_UpdateObserverClient, 250, true);
}
protected void ERR_OnObserverMonitorStarted() {}
protected void ERR_UpdateObserverClient()
{
ERR_GameMode mode = ERR_GameMode.Get();
bool observe = mode && mode.Active() && m_ERRObserverReason != ERR_ObserverReason.NONE && !ERR_Actors.Alive(GetControlledEntity());
if (!observe)
{
ERR_CloseObserverCamera();
return;
}
if (!m_ERRObserverCamera)
{
Resource resource = Resource.Load("{EB1210AAE1BB8F54}Prefabs/ERR/SpectatorCamera.et");
if (!resource || !resource.IsValid()) return;
EntitySpawnParams params = new EntitySpawnParams();
params.TransformMode = ETransformMode.WORLD;
params.Transform[3] = m_ERRObserverAnchor + "0 5 -6";
m_ERRObserverCamera = SCR_ManualCamera.Cast(GetGame().SpawnEntityPrefab(resource, GetGame().GetWorld(), params));
if (!m_ERRObserverCamera) return;
}
CameraManager manager = GetGame().GetCameraManager();
if (!manager || manager.CurrentCamera() != m_ERRObserverCamera) return;
SCR_RespawnSystemComponent respawn = SCR_RespawnSystemComponent.GetInstance();
if (respawn) respawn.DestroyLoadingPlaceholder();
if (!m_ERRObserverReady)
{
SCR_HintManagerComponent hints = SCR_HintManagerComponent.GetInstance();
if (hints) hints.ShowCustom("You are spectating the surviving prisoners until the next round.", "Spectating", 12);
m_ERRObserverReady = true;
}
}
protected void ERR_CloseObserverCamera()
{
m_ERRObserverReady = false;
if (!m_ERRObserverCamera) return;
if (GetGame().GetPlayerController() == this) m_ERRObserverCamera.TrySwitchToControlledEntityCamera();
m_ERRObserverCamera.Terminate();
delete m_ERRObserverCamera;
m_ERRObserverCamera = null;
}
void ~SCR_PlayerController()
{
if (GetGame())
{
GetGame().GetCallqueue().Remove(ERR_UpdateObserverClient);
ERR_CloseObserverCamera();
if (Replication.IsServer()) ERR_ReleaseObserved();
}
}
}
@@ -0,0 +1,130 @@
// Explicit local diagnostic launcher. Normal games and Workbench never request
// transitions from this file. No player, identity or audit state is fabricated.
#ifndef WORKBENCH
// Native Game is not Managed; this script-owned helper owns the callback.
class ERR_OrdinaryScenarioLaunchRequest : Managed
{
protected string m_Project;
protected ResourceName m_Header;
protected string m_Addon;
protected bool m_Queued;
protected bool m_Finished;
void Queue(string project, ResourceName header, string addon)
{
if (m_Queued || m_Finished) return;
m_Queued = true;
m_Project = project;
m_Header = header;
m_Addon = addon;
GetGame().GetCallqueue().CallLater(Attempt, 0, false);
}
void Cancel()
{
m_Finished = true;
if (GetGame() && GetGame().GetCallqueue()) GetGame().GetCallqueue().Remove(Attempt);
}
protected void Attempt()
{
if (m_Finished) return;
m_Finished = true;
if (!GameStateTransitions.IsInsideMainMenu() || ERR_GameMode.Get() || GameStateTransitions.IsTransitionRequestedOrInProgress())
{
Print("ERR ORDINARY: header launch refused outside idle main menu", LogLevel.ERROR);
return;
}
string addons;
if (!System.GetCLIParam("addons", addons) || addons != m_Addon)
{
Print("ERR ORDINARY: header launch addon selection mismatch", LogLevel.ERROR);
return;
}
ResourceName expected = m_Header;
MissionHeader header = MissionHeader.ReadMissionHeader(expected);
if (!header || header.GetWorldResourceName() != "{B6C516F86302C679}Worlds/ERR_Everon.ent" || header.GetWorldSystemsConfig() != "{C50579A2EC48E8EF}Configs/Systems/MissionSystems.conf")
{
Print("ERR ORDINARY: diagnostic mission header is invalid", LogLevel.ERROR);
return;
}
PrintFormat("ERR ORDINARY: requesting header project=%1", m_Project);
if (!GameStateTransitions.RequestScenarioChangeTransition(expected, string.Empty, addons))
Print("ERR ORDINARY: native header transition refused", LogLevel.ERROR);
}
}
modded class ArmaReforgerScripted
{
protected ref ERR_OrdinaryScenarioLaunchRequest m_ERRDiagnosticLaunchRequest;
protected bool m_ERRDiagnosticLaunchAttempted;
protected string m_ERRDiagnosticProject;
protected ResourceName ERR_DiagnosticHeader(string project)
{
if (project == "core") return "{AD62322D5213C440}Missions/ERR_Everon_Base.conf";
return string.Empty;
}
protected string ERR_DiagnosticAddon(string project)
{
if (project == "core") return "2528466DE220D91C";
return string.Empty;
}
override bool OnGameStart()
{
bool started = super.OnGameStart();
if (!started || !System.GetCLIParam("errOrdinaryScenario", m_ERRDiagnosticProject)) return started;
ResourceName expected = ERR_DiagnosticHeader(m_ERRDiagnosticProject);
if (expected.IsEmpty())
{
Print("ERR ORDINARY: invalid diagnostic project", LogLevel.ERROR);
return started;
}
// A loaded successor is inspected only; never replace an active mission.
if (ERR_GameMode.Get())
{
MissionHeader active = GetMissionHeader();
ResourceName systems = "{C50579A2EC48E8EF}Configs/Systems/MissionSystems.conf";
if (!active || active.GetHeaderResourceName() != expected || active.GetWorldSystemsConfig() != systems || GetSystemsConfig() != systems)
Print("ERR ORDINARY: active scenario header/systems mismatch", LogLevel.ERROR);
else
PrintFormat("ERR ORDINARY: header verified project=%1 systems=MissionSystems", m_ERRDiagnosticProject);
return started;
}
if (m_ERRDiagnosticLaunchAttempted) return started;
m_ERRDiagnosticLaunchAttempted = true;
// OnGameStart runs while the native state is finishing initialization. The
// one queued attempt requires that it actually reached the main menu.
m_ERRDiagnosticLaunchRequest = new ERR_OrdinaryScenarioLaunchRequest();
m_ERRDiagnosticLaunchRequest.Queue(m_ERRDiagnosticProject, expected, ERR_DiagnosticAddon(m_ERRDiagnosticProject));
return started;
}
override protected void OnBeforeWorldCleanup()
{
if (m_ERRDiagnosticLaunchRequest) m_ERRDiagnosticLaunchRequest.Cancel();
m_ERRDiagnosticLaunchRequest = null;
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
@@ -0,0 +1,61 @@
modded class SCR_PlayerController
{
protected ChatCommandInvoker m_ERRCommand;
protected float m_ERRLastRequest = -1000;
protected int m_ERRRegisterAttempts;
override void OnControlledEntityChanged(IEntity from, IEntity to)
{
super.OnControlledEntityChanged(from, to);
if (this == GetGame().GetPlayerController() && ERR_GameMode.Get()) ERR_Register();
}
protected void ERR_Register()
{
if (m_ERRCommand || m_ERRRegisterAttempts++ > 40) return;
SCR_ChatPanelManager manager = SCR_ChatPanelManager.GetInstance();
if (!manager) { GetGame().GetCallqueue().CallLater(ERR_Register, 250, false); return; }
m_ERRCommand = manager.GetCommandInvoker("esc");
if (m_ERRCommand) m_ERRCommand.Insert(ERR_OnCommand);
}
protected override void ERR_OnObserverMonitorStarted()
{
super.ERR_OnObserverMonitorStarted();
ERR_Register();
}
protected void ERR_OnCommand(SCR_ChatPanel panel, string arguments)
{
string localCommand = arguments.Trim(); localCommand.ToLower();
if (localCommand == "spectate next" || localCommand == "spectate previous")
{
int direction = 1;
if (localCommand == "spectate previous") direction = -1;
ERR_CycleObserver(direction);
return;
}
Rpc(ERR_Request, arguments);
}
[RplRpc(RplChannel.Reliable, RplRcver.Server)]
protected void ERR_Request(string arguments)
{
ERR_GameMode mode = ERR_GameMode.Get();
if (!Replication.IsServer() || !mode) return;
float now = GetGame().GetWorld().GetWorldTime();
if (arguments.Length() > 160 || now - m_ERRLastRequest < 250) return;
m_ERRLastRequest = now;
arguments = arguments.Trim(); arguments.ToLower();
array<string> replies = {};
mode.Admin().Execute(mode, SCR_Global.IsAdmin(GetPlayerId()), arguments, replies);
foreach (string reply : replies) ERR_Reply(reply);
}
protected void ERR_Reply(string text) { Rpc(ERR_Response, text); }
[RplRpc(RplChannel.Reliable, RplRcver.Owner)]
protected void ERR_Response(string text) { SCR_ChatComponent.RadioProtocolMessage(text); }
void ~SCR_PlayerController()
{
if (GetGame()) GetGame().GetCallqueue().Remove(ERR_Register);
if (m_ERRCommand) m_ERRCommand.Remove(ERR_OnCommand);
}
}
@@ -0,0 +1,56 @@
enum ERR_PersonalEscapeResult { UNKNOWN, ESCAPED, MISSED }
// Only the server resolves account identities. Clients receive their controller's
// result, never a persistent account ID or a reused end-data connection ID.
modded class SCR_PlayerController
{
[RplProp(onRplName: "ERR_OnEscapeResultReplicated")]
protected ERR_PersonalEscapeResult m_ERREscapeResult;
[RplProp(onRplName: "ERR_OnEscapeResultReplicated")]
protected RplId m_ERREscapeResultMission = RplId.Invalid();
protected static ref ScriptInvoker s_ERREscapeResultChanged = new ScriptInvoker();
static ScriptInvoker ERR_OnEscapeResultChanged() { return s_ERREscapeResultChanged; }
protected RplId ERR_CurrentResultMission()
{
ERR_GameMode mode = ERR_GameMode.Get();
if (!mode) return RplId.Invalid();
RplComponent component = RplComponent.Cast(mode.FindComponent(RplComponent));
if (!component) return RplId.Invalid();
return component.Id();
}
ERR_PersonalEscapeResult ERR_EscapeResult()
{
RplId mission = ERR_CurrentResultMission();
if (mission == RplId.Invalid() || mission != m_ERREscapeResultMission) return ERR_PersonalEscapeResult.UNKNOWN;
return m_ERREscapeResult;
}
void ERR_ResetEscapeResult()
{
if (!Replication.IsServer()) return;
m_ERREscapeResult = ERR_PersonalEscapeResult.UNKNOWN;
m_ERREscapeResultMission = ERR_CurrentResultMission();
Replication.BumpMe();
ERR_OnEscapeResultReplicated();
}
void ERR_PublishEscapeResult(ERR_PlayerRoster roster, ERR_ExtractionService extraction)
{
if (!Replication.IsServer()) return;
ERR_PersonalEscapeResult result = ERR_PersonalEscapeResult.UNKNOWN;
ERR_PlayerLife life;
if (roster) life = roster.Get(GetPlayerId());
if (life && life.Connected && life.ConnectionId == GetPlayerId() && life.UsedLife && extraction && extraction.State() == ERR_ExtractionState.ESCAPED)
{
result = ERR_PersonalEscapeResult.MISSED;
if (extraction.DidEscape(life.AccountId)) result = ERR_PersonalEscapeResult.ESCAPED;
}
m_ERREscapeResult = result;
m_ERREscapeResultMission = ERR_CurrentResultMission();
Replication.BumpMe();
ERR_OnEscapeResultReplicated();
}
protected void ERR_OnEscapeResultReplicated()
{
if (GetGame() && this == GetGame().GetPlayerController()) s_ERREscapeResultChanged.Invoke();
}
}
@@ -0,0 +1,41 @@
// Local presentation reads replicated state; it cannot advance objectives.
modded class SCR_PlayerController
{
protected int m_ERRGuidePhase = -1;
protected bool m_ERRGuideStarted;
protected string m_ERRGuideMessage;
override void OnControlledEntityChanged(IEntity from, IEntity to)
{
super.OnControlledEntityChanged(from, to);
ERR_StartGuide();
}
protected override void ERR_OnObserverMonitorStarted()
{
super.ERR_OnObserverMonitorStarted();
ERR_StartGuide();
}
protected void ERR_StartGuide()
{
if (this != GetGame().GetPlayerController() || !ERR_GameMode.Get() || m_ERRGuideStarted) return;
m_ERRGuideStarted = true;
GetGame().GetCallqueue().CallLater(ERR_UpdateGuide, 1000, true);
}
protected void ERR_UpdateGuide()
{
ERR_GameMode mode = ERR_GameMode.Get();
if (!mode) return;
if (mode.Phase() != ERR_Phase.PREPARING && !GetControlledEntity()) return;
string message = ERR_ObjectiveText.Description(mode, false);
if (m_ERRGuidePhase == mode.Phase() && m_ERRGuideMessage == message) return;
SCR_HintManagerComponent hints = SCR_HintManagerComponent.GetInstance();
if (!hints) return;
if (!message.IsEmpty()) hints.ShowCustom(message, ERR_ObjectiveText.Title(mode.Phase()), 15);
m_ERRGuidePhase = mode.Phase();
m_ERRGuideMessage = message;
}
void ~SCR_PlayerController()
{
if (GetGame()) GetGame().GetCallqueue().Remove(ERR_UpdateGuide);
}
}
@@ -0,0 +1,118 @@
// Round-owned identities survive controller IDs, death and reconnect attempts.
class ERR_PlayerLifeRules
{
static bool CanEnter(bool usedLife, int phase)
{
return !usedLife && phase >= ERR_Phase.PRISON && phase <= ERR_Phase.RADIO_DEFENCE;
}
}
class ERR_PlayerLife
{
UUID AccountId;
int ConnectionId;
bool Connected;
bool UsedLife;
IEntity Actor;
}
class ERR_PlayerRoster
{
protected ref map<UUID, ref ERR_PlayerLife> m_Lives = new map<UUID, ref ERR_PlayerLife>();
protected ref map<int, UUID> m_Connections = new map<int, UUID>();
protected ref ScriptInvoker m_OnChanged = new ScriptInvoker();
ScriptInvoker OnChanged() { return m_OnChanged; }
bool Bind(int playerId, UUID identity)
{
if (!Replication.IsServer() || playerId <= 0 || identity.IsNull()) return false;
ERR_PlayerLife life;
if (!m_Lives.Find(identity, life))
{
life = new ERR_PlayerLife();
life.AccountId = identity;
m_Lives.Insert(identity, life);
}
if (life.Connected && life.ConnectionId != playerId) return false;
// A recycled transient ID cannot inherit the previous connection's record.
UUID previous;
if (m_Connections.Find(playerId, previous) && previous != identity) Disconnect(playerId);
life.ConnectionId = playerId;
life.Connected = true;
m_Connections.Set(playerId, identity);
m_OnChanged.Invoke(playerId);
return true;
}
ERR_PlayerLife Get(int playerId)
{
UUID identity;
if (!m_Connections.Find(playerId, identity)) return null;
return m_Lives.Get(identity);
}
// The roster identity was bound by the audited connection lifecycle. Consumers
// validate its current body rather than requesting a second backend identity.
ERR_PlayerLife CurrentActor(IEntity actor)
{
if (!Replication.IsServer() || !actor || !GetGame() || !GetGame().GetPlayerManager()) return null;
PlayerManager manager = GetGame().GetPlayerManager();
int connection = manager.GetPlayerIdFromControlledEntity(actor);
if (connection <= 0 || manager.GetPlayerControlledEntity(connection) != actor) return null;
ERR_PlayerLife life = Get(connection);
if (!life || life.AccountId.IsNull() || !life.Connected || !life.UsedLife || life.ConnectionId != connection || life.Actor != actor) return null;
return life;
}
bool CanEnter(int playerId, int phase)
{
ERR_PlayerLife life = Get(playerId);
return life && life.Connected && ERR_PlayerLifeRules.CanEnter(life.UsedLife, phase);
}
void Spawned(int playerId, IEntity actor)
{
if (!Replication.IsServer() || !actor) return;
ERR_PlayerLife life = Get(playerId);
if (!life) return;
life.Actor = actor;
life.UsedLife = true;
m_OnChanged.Invoke(playerId);
}
void Disconnect(int playerId)
{
if (!Replication.IsServer()) return;
ERR_PlayerLife life = Get(playerId);
if (life) life.Connected = false;
m_Connections.Remove(playerId);
m_OnChanged.Invoke(playerId);
}
void Participants(out array<ref ERR_PlayerLife> lives)
{
lives.Clear();
foreach (UUID identity, ERR_PlayerLife life : m_Lives)
if (life.UsedLife) lives.Insert(life);
}
bool HasSurvivor()
{
// Native reconnect reservations retain the original actor until expiry.
// A reserved survivor must not lose the round while their client reconnects.
foreach (UUID identity, ERR_PlayerLife life : m_Lives)
if (life.UsedLife && ERR_Actors.Alive(life.Actor)) return true;
return false;
}
}
modded class SCR_ReconnectComponent
{
override bool HandlePlayerDisconnect(int playerId, KickCauseCode cause)
{
ERR_GameMode mode = ERR_GameMode.Get();
if (!mode || !mode.Active()) return super.HandlePlayerDisconnect(playerId, cause);
UUID identity = SCR_PlayerIdentityUtils.GetPlayerIdentityId(playerId);
if (identity.IsNull()) return super.HandlePlayerDisconnect(playerId, cause);
// Voluntary and involuntary departures use the same vanilla reservation,
// timeout, character handoff and expiry cleanup. No replacement life is issued.
SCR_ReconnectData data = StoreData(playerId);
if (!IsDataRelevant(data)) return false;
m_mReconnectData.Set(identity, data);
UpdateExpieryCheck();
return true;
}
}

Some files were not shown because too many files have changed in this diff Show More