Add configurable nearby garage vehicles and fix roadblocks and fuel pumps; record Workshop 0.0.131
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -3,7 +3,7 @@ class ERR_AdminMenuFields
|
||||
{
|
||||
static array<string> Keys()
|
||||
{
|
||||
return {"aiDoctrine", "aiShadow", "automaticEquipmentChancePercent", "sarkaAngerPerCar", "motorbikeCount", "droneReconCount", "droneCooldownSeconds", "planeAltitudeMetres", "planeReplacementSeconds", "planeSectorSeconds", "fieldCampCount", "fieldHospitalCount", "startWeaponCount", "startMagazineCount", "objectGuardCount", "objectGuardEvery", "objectGuardHQMode", "hunterSpawnDistanceMetres", "roadblockCount", "civilianTrafficCount", "emptyRestartHours", "startMode", "radioDefenceSeconds", "aiIntelChancePercent", "intelRevealChancePercent", "radioIntelSiteCount", "startTimeMinutes", "timeMultiplier", "startDateCode", "weatherProfile", "viewDistanceMetres", "reportIntervalSeconds", "supportSquadLimit", "responseRadiusMetres", "convoyCount", "antiAirChancePercent", "antiPersonnelMineChancePercent", "heliTimerMinutes"};
|
||||
return {"aiDoctrine", "aiShadow", "automaticEquipmentChancePercent", "sarkaAngerPerCar", "motorbikeCount", "droneReconCount", "droneCooldownSeconds", "planeAltitudeMetres", "planeReplacementSeconds", "planeSectorSeconds", "fieldCampCount", "fieldHospitalCount", "startWeaponCount", "startMagazineCount", "objectGuardCount", "objectGuardEvery", "objectGuardHQMode", "hunterSpawnDistanceMetres", "roadblockCount", "civilianTrafficCount", "garageVehicleCount", "garageChancePercent", "garageRadiusMetres", "garageSpawnIntervalSeconds", "emptyRestartHours", "startMode", "radioDefenceSeconds", "aiIntelChancePercent", "intelRevealChancePercent", "radioIntelSiteCount", "startTimeMinutes", "timeMultiplier", "startDateCode", "weatherProfile", "viewDistanceMetres", "reportIntervalSeconds", "supportSquadLimit", "responseRadiusMetres", "convoyCount", "antiAirChancePercent", "antiPersonnelMineChancePercent", "heliTimerMinutes"};
|
||||
}
|
||||
static bool Available(string key, bool hq, bool intel)
|
||||
{
|
||||
@@ -64,6 +64,10 @@ class ERR_AdminMenuFields
|
||||
else if (key == "fieldHospitalCount") prefix = "mission hospitals";
|
||||
else if (key == "roadblockCount") prefix = "mission roadblocks";
|
||||
else if (key == "emptyRestartHours") prefix = "mission emptyhours";
|
||||
else if (key == "garageChancePercent") prefix = "mission garagechance";
|
||||
else if (key == "garageRadiusMetres") prefix = "mission garageradius";
|
||||
else if (key == "garageSpawnIntervalSeconds") prefix = "mission garageinterval";
|
||||
else if (key == "garageVehicleCount") prefix = "mission garagevehicles";
|
||||
else if (key == "civilianTrafficCount") prefix = "mission traffic";
|
||||
else if (key == "startMode") prefix = "mission start";
|
||||
else if (key == "radioDefenceSeconds") prefix = "mission radio";
|
||||
|
||||
@@ -26,17 +26,18 @@ enum ERR_AdminPage
|
||||
DRONES = 22,
|
||||
RADIO_INTEL = 23,
|
||||
TELEMETRY = 24,
|
||||
VEHICLES = 25,
|
||||
}
|
||||
|
||||
class ERR_AdminMenuPages
|
||||
{
|
||||
static array<string> Pages() { return {"SettingsPage", "HQPage", "JournalPage", "ModsPage", "FactionsPage", "WorldPage", "IntelPage", "StatusPage", "TerrainsPage", "ItemsPage", "TTSPage", "ForcesPage", "GuardsPage", "SitesPage", "PlanePage", "RecommendationsPage", "WeightsPage", "AllowedItemsPage", "ItemPresetsPage", "AIPage", "StarterPage", "SarkaPage", "DronesPage", "RadioIntelPage", "TelemetryPage"}; }
|
||||
static array<string> Navigation() { return {"OpenSettings", "OpenHQ", "OpenJournal", "OpenMods", "OpenFactions", "OpenWorld", "OpenIntel", "OpenStatus", "OpenTerrains", "OpenItems", "OpenTTS", "OpenForces", "OpenGuards", "OpenSites", "OpenPlane", "OpenRecommendations", "OpenWeights", "OpenAllowedItems", "OpenItemPresets", "OpenAI", "OpenStarter", "OpenSarka", "OpenDrones", "OpenRadioIntel", "OpenTelemetry"}; }
|
||||
static array<string> Pages() { return {"SettingsPage", "HQPage", "JournalPage", "ModsPage", "FactionsPage", "WorldPage", "IntelPage", "StatusPage", "TerrainsPage", "ItemsPage", "TTSPage", "ForcesPage", "GuardsPage", "SitesPage", "PlanePage", "RecommendationsPage", "WeightsPage", "AllowedItemsPage", "ItemPresetsPage", "AIPage", "StarterPage", "SarkaPage", "DronesPage", "RadioIntelPage", "TelemetryPage", "VehiclesPage"}; }
|
||||
static array<string> Navigation() { return {"OpenSettings", "OpenHQ", "OpenJournal", "OpenMods", "OpenFactions", "OpenWorld", "OpenIntel", "OpenStatus", "OpenTerrains", "OpenItems", "OpenTTS", "OpenForces", "OpenGuards", "OpenSites", "OpenPlane", "OpenRecommendations", "OpenWeights", "OpenAllowedItems", "OpenItemPresets", "OpenAI", "OpenStarter", "OpenSarka", "OpenDrones", "OpenRadioIntel", "OpenTelemetry", "OpenVehicles"}; }
|
||||
static array<string> MainButtons() { return {"Main_Mission", "Main_Forces", "Main_HQ", "Main_Intel", "Main_Items", "Main_Mods", "Main_Audio", "Main_Status"}; }
|
||||
static array<string> Sidebars() { return {"Sidebar_Mission", "Sidebar_Forces", "Sidebar_HQ", "Sidebar_Intel", "Sidebar_Items", "Sidebar_Mods", "Sidebar_Audio", "Sidebar_Status"}; }
|
||||
static int Group(int page)
|
||||
{
|
||||
array<int> groups = {0, 2, 7, 5, 1, 0, 3, 7, 0, 4, 6, 1, 1, 1, 2, 5, 4, 4, 4, 2, 4, 1, 2, 3, 0};
|
||||
array<int> groups = {0, 2, 7, 5, 1, 0, 3, 7, 0, 4, 6, 1, 1, 1, 2, 5, 4, 4, 4, 2, 4, 1, 2, 3, 0, 0};
|
||||
if (page < 0 || page >= groups.Count()) return -1;
|
||||
return groups[page];
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@ class ERR_CommandArguments
|
||||
if (section == "mission" && option == "objectguards") return "objectGuardCount";
|
||||
if (section == "mission" && option == "hunterdistance") return "hunterSpawnDistanceMetres";
|
||||
if (section == "mission" && option == "roadblocks") return "roadblockCount";
|
||||
if (section == "mission" && option == "garagechance") return "garageChancePercent";
|
||||
if (section == "mission" && option == "garageradius") return "garageRadiusMetres";
|
||||
if (section == "mission" && option == "garageinterval") return "garageSpawnIntervalSeconds";
|
||||
if (section == "mission" && option == "garagevehicles") return "garageVehicleCount";
|
||||
if (section == "mission" && option == "traffic") return "civilianTrafficCount";
|
||||
if (section == "mission" && option == "start") return "startMode";
|
||||
if (section == "mission" && option == "selfepi") return "selfEpinephrine";
|
||||
@@ -194,6 +198,10 @@ class ERR_AdminService
|
||||
if (key == "telemetryEnabled") return "Default on. Sends mission results and player identities to the configured statistics service. Disabling clears unsent events immediately.";
|
||||
if (key == "restartOnEmpty") return "On (1) restarts after the last disconnect; Off (0) preserves the round for reconnects. Default On. Applies immediately; normal round endings still apply.";
|
||||
if (key == "emptyRestartHours") return "0 disables idle restarts; 1..168 real hours continuously empty. Default 5. Applies immediately, measured from mission start or the last disconnect. Connected players, including spectators, prevent idle restarts.";
|
||||
if (key == "garageChancePercent") return "Garage selection chance (0-100%). Default 70%; 100 selects every suitable garage. Next round.";
|
||||
if (key == "garageRadiusMetres") return "Spawn radius (250-1500 metres). Distance around players; default 1000 m. Next round.";
|
||||
if (key == "garageSpawnIntervalSeconds") return "Time between spawns (5-60 seconds). Default 5 s. Increase to spread out spawning. Next round.";
|
||||
if (key == "garageVehicleCount") return "0..24 unclaimed garage vehicles near players; default 16. Used vehicles are kept; distant unused vehicles retire. Optional bikes. Applies next round.";
|
||||
if (key == "civilianTrafficCount") return "Set 0..6 civilian cars (default 3). Zero disables new traffic. Unclaimed cars retire only far from all players and out of sight; player-entered cars are preserved.";
|
||||
if (key == "showAllSites") return "0 disabled (default), 1 enabled. Show prepared sites at their positions as question marks; recovered intel identifies their types. Applies immediately to everyone.";
|
||||
if (key == "selfEpinephrine") return "0 disabled (default), 1 enabled. Unconscious players can press Space to use a carried epinephrine injector. Applies immediately; consumes the item and keeps its normal medical effect.";
|
||||
@@ -207,7 +215,7 @@ class ERR_AdminService
|
||||
}
|
||||
protected string RoadblockTiming(string key)
|
||||
{
|
||||
if (key == "radioIntelSiteCount") return " for next round";
|
||||
if (key == "radioIntelSiteCount" || key == "garageVehicleCount" || key == "garageChancePercent" || key == "garageRadiusMetres" || key == "garageSpawnIntervalSeconds") return " for next round";
|
||||
if (key == "aiDoctrine" || key == "aiShadow" || key == "droneReconCount" || key == "droneCooldownSeconds") return " for next round";
|
||||
if (key == "motorbikeCount" || key == "planeAltitudeMetres" || key == "planeReplacementSeconds" || key == "planeSectorSeconds" || key == "planeReconEnabled" || key == "fieldCampCount" || key == "fieldHospitalCount" || key == "startWeaponCount" || key == "startMagazineCount" || key == "objectGuardCount" || key == "objectGuardEvery" || key == "objectGuardHQMode" || key == "hunterSpawnDistanceMetres" || key == "roadblockCount") return " for next round";
|
||||
return "";
|
||||
|
||||
@@ -65,6 +65,7 @@ class ERR_GameMode : SCR_BaseGameMode
|
||||
protected ref ERR_Performance m_Performance;
|
||||
ERR_Performance Performance() { return m_Performance; }
|
||||
protected ref ERR_CivilianTraffic m_CivilianTraffic;
|
||||
protected ref ERR_GarageVehicles m_GarageVehicles;
|
||||
protected ref ERR_ForceLedger m_Forces = new ERR_ForceLedger();
|
||||
protected ref ERR_ForceSimulation m_ForceSimulation;
|
||||
protected ref array<AIAgent> m_ForceAgents = {};
|
||||
@@ -143,6 +144,7 @@ class ERR_GameMode : SCR_BaseGameMode
|
||||
ERR_ForceLedger Forces() { return m_Forces; }
|
||||
ERR_CommandService Command() { return m_Command; }
|
||||
ERR_CivilianTraffic CivilianTraffic() { return m_CivilianTraffic; }
|
||||
ERR_GarageVehicles GarageVehicles() { return m_GarageVehicles; }
|
||||
ERR_Objectives Objectives() { return m_Objectives; }
|
||||
ERR_ExtractionService Extraction() { return m_Extraction; }
|
||||
string EndReason() { return m_EndReason; }
|
||||
@@ -546,6 +548,9 @@ class ERR_GameMode : SCR_BaseGameMode
|
||||
m_Command = CreateCommandService();
|
||||
m_CivilianTraffic = new ERR_CivilianTraffic(this);
|
||||
OnStop().Insert(m_CivilianTraffic.Stop);
|
||||
m_GarageVehicles = new ERR_GarageVehicles(this);
|
||||
OnStop().Insert(m_GarageVehicles.Stop);
|
||||
m_GarageVehicles.Start();
|
||||
GetGame().GetCallqueue().CallLater(ServerTick, 1000, true);
|
||||
GetGame().GetCallqueue().CallLater(StartScenario, 1500, false);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ class ERR_Settings
|
||||
|
||||
static array<string> Keys()
|
||||
{
|
||||
return {"telemetryEnabled", "aiDoctrine", "aiShadow", "sarkaAngerPerCar", "motorbikeCount", "planeAltitudeMetres", "planeReplacementSeconds", "planeSectorSeconds", "planeReconEnabled", "droneReconCount", "droneCooldownSeconds", "fieldCampCount", "fieldHospitalCount", "startWeaponCount", "startMagazineCount", "objectGuardCount", "objectGuardEvery", "objectGuardHQMode", "hunterSpawnDistanceMetres", "roadblockCount", "civilianTrafficCount", "startMode", "keepWeaponOnUnconscious", "selfEpinephrine", "restartOnEmpty", "emptyRestartHours", "reportIntervalSeconds", "supportSquadLimit", "responseRadiusMetres", "aiIntelChancePercent", "intelRevealChancePercent", "radioIntelSiteCount", "showAllSites", "radioDefenceSeconds", "startTimeMinutes", "timeMultiplier", "startDateCode", "weatherProfile", "viewDistanceMetres", "antiAirChancePercent", "antiPersonnelMineChancePercent", "automaticEquipmentChancePercent", "automaticConvoyEnabled", "convoyCount", "heliTimerMinutes"};
|
||||
return {"telemetryEnabled", "aiDoctrine", "aiShadow", "sarkaAngerPerCar", "motorbikeCount", "planeAltitudeMetres", "planeReplacementSeconds", "planeSectorSeconds", "planeReconEnabled", "droneReconCount", "droneCooldownSeconds", "fieldCampCount", "fieldHospitalCount", "startWeaponCount", "startMagazineCount", "objectGuardCount", "objectGuardEvery", "objectGuardHQMode", "hunterSpawnDistanceMetres", "roadblockCount", "civilianTrafficCount", "garageVehicleCount", "garageChancePercent", "garageRadiusMetres", "garageSpawnIntervalSeconds", "startMode", "keepWeaponOnUnconscious", "selfEpinephrine", "restartOnEmpty", "emptyRestartHours", "reportIntervalSeconds", "supportSquadLimit", "responseRadiusMetres", "aiIntelChancePercent", "intelRevealChancePercent", "radioIntelSiteCount", "showAllSites", "radioDefenceSeconds", "startTimeMinutes", "timeMultiplier", "startDateCode", "weatherProfile", "viewDistanceMetres", "antiAirChancePercent", "antiPersonnelMineChancePercent", "automaticEquipmentChancePercent", "automaticConvoyEnabled", "convoyCount", "heliTimerMinutes"};
|
||||
}
|
||||
|
||||
static bool Valid(string key, int value)
|
||||
@@ -33,6 +33,10 @@ class ERR_Settings
|
||||
if (key == "objectGuardCount") return value >= 0 && value <= 16;
|
||||
if (key == "hunterSpawnDistanceMetres") return value >= 800 && value <= 3000;
|
||||
if (key == "roadblockCount") return value >= 0 && value <= 30;
|
||||
if (key == "garageChancePercent") return value >= 0 && value <= 100;
|
||||
if (key == "garageRadiusMetres") return value >= 250 && value <= 1500;
|
||||
if (key == "garageSpawnIntervalSeconds") return value >= 5 && value <= 60;
|
||||
if (key == "garageVehicleCount") return value >= 0 && value <= 24;
|
||||
if (key == "civilianTrafficCount") return value >= 0 && value <= 6;
|
||||
if (key == "startMode") return value == 0 || value == 1;
|
||||
if (key == "showAllSites") return value == 0 || value == 1;
|
||||
@@ -141,6 +145,10 @@ class ERR_Settings
|
||||
if (key == "hunterSpawnDistanceMetres") return 1000;
|
||||
if (key == "roadblockCount") return 4;
|
||||
if (key == "civilianTrafficCount") return 3;
|
||||
if (key == "garageChancePercent") return 70;
|
||||
if (key == "garageRadiusMetres") return 1000;
|
||||
if (key == "garageSpawnIntervalSeconds") return 5;
|
||||
if (key == "garageVehicleCount") return 16;
|
||||
if (key == "showAllSites") return 0;
|
||||
if (key == "startMode" || key == "selfEpinephrine") return 0;
|
||||
if (key == "telemetryEnabled" || key == "keepWeaponOnUnconscious" || key == "restartOnEmpty") return 1;
|
||||
@@ -164,6 +172,10 @@ class ERR_Settings
|
||||
m_Values.Set("aiDoctrine", AddedDefault("aiDoctrine"));
|
||||
m_Values.Set("aiShadow", AddedDefault("aiShadow"));
|
||||
m_Values.Set("sarkaAngerPerCar", AddedDefault("sarkaAngerPerCar"));
|
||||
m_Values.Set("garageChancePercent", AddedDefault("garageChancePercent"));
|
||||
m_Values.Set("garageRadiusMetres", AddedDefault("garageRadiusMetres"));
|
||||
m_Values.Set("garageSpawnIntervalSeconds", AddedDefault("garageSpawnIntervalSeconds"));
|
||||
m_Values.Set("garageVehicleCount", AddedDefault("garageVehicleCount"));
|
||||
m_Values.Set("motorbikeCount", AddedDefault("motorbikeCount"));
|
||||
m_Values.Set("planeAltitudeMetres", AddedDefault("planeAltitudeMetres"));
|
||||
m_Values.Set("planeReplacementSeconds", AddedDefault("planeReplacementSeconds"));
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
// World-authored pumps survive a game-mode takeover. Their native registrations
|
||||
// belong to the discarded manager and must move to the replacement on each peer.
|
||||
modded class SCR_SupportStationManagerComponent
|
||||
{
|
||||
void ERR_CollectTerrainStations(array<SCR_BaseSupportStationComponent> stations)
|
||||
{
|
||||
for (int i = 0; i < m_mSupportStations.Count(); i++)
|
||||
{
|
||||
array<SCR_BaseSupportStationComponent> registered = m_mSupportStations.GetElement(i);
|
||||
if (!registered) continue;
|
||||
foreach (SCR_BaseSupportStationComponent station : registered)
|
||||
if (station && !stations.Contains(station)) stations.Insert(station);
|
||||
}
|
||||
}
|
||||
override void OnPostInit(IEntity owner)
|
||||
{
|
||||
super.OnPostInit(owner);
|
||||
if (ERR_TerrainTakeoverMode.Cast(owner) && GetInstance() == this)
|
||||
GetGame().ERR_RestoreTerrainSupportStations(this);
|
||||
}
|
||||
}
|
||||
|
||||
modded class SCR_BaseSupportStationComponent
|
||||
{
|
||||
bool ERR_CanRestoreTerrainStation()
|
||||
{
|
||||
IEntity owner = GetOwner();
|
||||
return owner && GetGame() && owner.GetWorld() == GetGame().GetWorld()
|
||||
&& UsesRange() && m_eCurrentDamageState != EDamageState.DESTROYED;
|
||||
}
|
||||
void ERR_TerrainStationRestored(SCR_SupportStationManagerComponent manager) {}
|
||||
}
|
||||
|
||||
modded class SCR_BaseItemSupportStationComponent
|
||||
{
|
||||
override void ERR_TerrainStationRestored(SCR_SupportStationManagerComponent manager)
|
||||
{
|
||||
super.ERR_TerrainStationRestored(manager);
|
||||
// Item cleanup must remove the station from the manager it now belongs to.
|
||||
if (m_ERRItemStationInitialized && UsesRange()) m_ERRItemStationManager = manager;
|
||||
}
|
||||
}
|
||||
|
||||
modded class ArmaReforgerScripted
|
||||
{
|
||||
protected ref array<SCR_BaseSupportStationComponent> m_ERRTerrainSupportStations = {};
|
||||
void ERR_CaptureTerrainSupportStations(SCR_SupportStationManagerComponent original)
|
||||
{
|
||||
m_ERRTerrainSupportStations.Clear();
|
||||
if (original) original.ERR_CollectTerrainStations(m_ERRTerrainSupportStations);
|
||||
}
|
||||
void ERR_RestoreTerrainSupportStations(SCR_SupportStationManagerComponent manager)
|
||||
{
|
||||
if (!manager) return;
|
||||
int restored;
|
||||
foreach (SCR_BaseSupportStationComponent station : m_ERRTerrainSupportStations)
|
||||
{
|
||||
if (!station || !station.ERR_CanRestoreTerrainStation()) continue;
|
||||
manager.AddSupportStation(station);
|
||||
station.ERR_TerrainStationRestored(manager);
|
||||
restored++;
|
||||
}
|
||||
m_ERRTerrainSupportStations.Clear();
|
||||
PrintFormat("Reintroduced [terrain support]: retained %1 existing support stations", restored);
|
||||
}
|
||||
override protected void OnBeforeWorldCleanup()
|
||||
{
|
||||
m_ERRTerrainSupportStations.Clear();
|
||||
super.OnBeforeWorldCleanup();
|
||||
}
|
||||
}
|
||||
@@ -243,6 +243,7 @@ modded class ArmaReforgerScripted
|
||||
}
|
||||
}
|
||||
Print("Reintroduced [terrain takeover]: stopping original scenario; preserving radio and chat managers");
|
||||
ERR_CaptureTerrainSupportStations(SCR_SupportStationManagerComponent.Cast(m_ERRTerrainOriginal.FindComponent(SCR_SupportStationManagerComponent)));
|
||||
m_ERRTerrainOriginal.OnGameEnd();
|
||||
if (m_DataCollectorComponent && m_DataCollectorComponent.GetOwner() == m_ERRTerrainOriginal) m_DataCollectorComponent = null;
|
||||
SCR_EntityCatalogManagerComponent.ERR_ReleaseTerrainCatalog(m_ERRTerrainOriginal);
|
||||
|
||||
@@ -102,6 +102,7 @@ modded class ArmaReforgerScripted
|
||||
m_ERRClientTerrainTakeover = true;
|
||||
SCR_BaseGameMode mode = SCR_BaseGameMode.Cast(m_ERRClientOldManagers[0]);
|
||||
PrintFormat("Reintroduced [terrain client]: drain retained mode=%1 incoming=%2", mode, incoming);
|
||||
ERR_CaptureTerrainSupportStations(m_ERROldSupport);
|
||||
if (mode) mode.OnGameEnd();
|
||||
// Owner entities can already be invalid while native destruction is deferred.
|
||||
// Compare the captured component itself, never clear a newer singleton.
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
// Nearby garages are discovered incrementally. Each callback performs one
|
||||
// survey, maintenance, planning, resource-load or native-spawn step.
|
||||
class ERR_GarageVehicleRules
|
||||
{
|
||||
static bool Garage(string path)
|
||||
{
|
||||
path.ToLower();
|
||||
return path.EndsWith("/garage_e_01.et") || path.EndsWith("/houseaddon_garage_e_01.et");
|
||||
}
|
||||
static bool Envelope(vector low, vector high)
|
||||
{
|
||||
return ERR_ForceRules.FiniteVector(low) && ERR_ForceRules.FiniteVector(high)
|
||||
&& low[0] >= -1.05 && high[0] <= 1.05 && low[2] >= -2.4 && high[2] <= 2.4
|
||||
&& low[1] >= -0.3 && high[1] <= 1.9
|
||||
&& high[0] > low[0] && high[1] > low[1] && high[2] > low[2];
|
||||
}
|
||||
}
|
||||
class ERR_GarageSite
|
||||
{
|
||||
IEntity Garage;
|
||||
bool Consumed;
|
||||
bool Selected;
|
||||
float NextTry;
|
||||
}
|
||||
class ERR_ParkedVehicle
|
||||
{
|
||||
Vehicle Entity;
|
||||
ref ERR_GarageSite Site;
|
||||
vector Origin;
|
||||
float HiddenSince = -1;
|
||||
}
|
||||
class ERR_GarageVehicles
|
||||
{
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected ref array<ref ERR_GarageSite> m_Sites = {};
|
||||
protected ref array<ref ERR_ParkedVehicle> m_Parked = {};
|
||||
protected ref map<string, bool> m_KnownCells = new map<string, bool>();
|
||||
protected ref ERR_MotorbikeCatalog m_Bikes = new ERR_MotorbikeCatalog();
|
||||
protected ref array<ResourceName> m_LoadedNames = {};
|
||||
protected ref array<ref Resource> m_LoadedResources = {};
|
||||
protected ref ERR_GarageSite m_Pending;
|
||||
protected vector m_Position, m_Angles;
|
||||
protected ResourceName m_Prefab;
|
||||
protected bool m_PendingBike;
|
||||
protected int m_Stage, m_Work, m_SiteCursor, m_MaintainCursor, m_PlayerCursor;
|
||||
protected int m_Requested, m_Spawned, m_Claimed, m_BikeCandidate;
|
||||
protected int m_GarageChance, m_SpawnRadius, m_SpawnInterval, m_CleanupRadius;
|
||||
protected float m_NextSpawn, m_NextStep;
|
||||
protected bool m_Stopped, m_Started;
|
||||
int Spawned() { return m_Spawned; }
|
||||
void ERR_GarageVehicles(ERR_GameMode mode) { m_Mode = mode; }
|
||||
protected float Now() { return GetGame().GetWorld().GetWorldTime(); }
|
||||
protected bool Running() { return m_Mode && m_Mode == ERR_GameMode.Get() && m_Mode.Active(); }
|
||||
void Start()
|
||||
{
|
||||
if (!Replication.IsServer() || m_Stopped || m_Started) return;
|
||||
m_Started = true; m_Requested = m_Mode.Settings().Get("garageVehicleCount");
|
||||
m_GarageChance = m_Mode.Settings().Get("garageChancePercent");
|
||||
m_SpawnRadius = m_Mode.Settings().Get("garageRadiusMetres");
|
||||
m_SpawnInterval = m_Mode.Settings().Get("garageSpawnIntervalSeconds") * 1000;
|
||||
m_CleanupRadius = Math.Max(1400, m_SpawnRadius + 400);
|
||||
if (m_Requested <= 0 || m_GarageChance <= 0) { Stop(); return; }
|
||||
m_Bikes.Find();
|
||||
GetGame().GetCallqueue().CallLater(Step, 250, true);
|
||||
}
|
||||
protected bool Observers(array<vector> positions, array<IEntity> actors)
|
||||
{
|
||||
positions.Clear(); actors.Clear();
|
||||
array<int> ids = {}; GetGame().GetPlayerManager().GetPlayers(ids);
|
||||
SCR_EditorManagerCore editors = SCR_EditorManagerCore.Cast(SCR_EditorManagerCore.GetInstance(SCR_EditorManagerCore));
|
||||
foreach (int id : ids)
|
||||
{
|
||||
SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerManager().GetPlayerController(id));
|
||||
if (!controller) return false;
|
||||
SCR_EditorManagerEntity editor; if (editors) editor = editors.GetEditorManager(id);
|
||||
if (editor && editor.IsOpened()) return false;
|
||||
IEntity body = controller.GetControlledEntity();
|
||||
if (body)
|
||||
{
|
||||
if (!ERR_ForceRules.FiniteVector(body.GetOrigin())) return false;
|
||||
positions.Insert(body.GetOrigin()); actors.Insert(body);
|
||||
}
|
||||
if (controller.ERR_ObserverState() != ERR_ObserverReason.NONE)
|
||||
{
|
||||
IEntity watched = controller.ERR_ObservedEntity();
|
||||
if (!watched || !ERR_ForceRules.FiniteVector(watched.GetOrigin())) return false;
|
||||
positions.Insert(watched.GetOrigin()); actors.Insert(watched);
|
||||
}
|
||||
else if (!body) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected bool Nearby(vector position, array<vector> observers, float radius)
|
||||
{
|
||||
foreach (vector observer : observers) if (vector.DistanceXZ(position, observer) <= radius) return true;
|
||||
return false;
|
||||
}
|
||||
// Sample both ends/sides and roof, not just the hidden centre of a garage.
|
||||
// Open doors and windows can reveal a bay; no distance alone proves it hidden.
|
||||
protected bool Hidden(vector position, vector angles, array<IEntity> actors, IEntity ignored = null)
|
||||
{
|
||||
vector matrix[4]; Math3D.AnglesToMatrix(angles, matrix);
|
||||
foreach (IEntity actor : actors)
|
||||
{
|
||||
if (!actor) return false;
|
||||
array<IEntity> excluded = {actor};
|
||||
IEntity ride = CompartmentAccessComponent.GetVehicleIn(actor); if (ride) excluded.Insert(ride);
|
||||
for (int height = 0; height < 2; height++)
|
||||
for (int x = -1; x <= 1; x++)
|
||||
for (int z = -1; z <= 1; z++)
|
||||
{
|
||||
TraceParam ray = new TraceParam();
|
||||
ray.Start = actor.GetOrigin() + "0 1.7 0";
|
||||
ray.End = position + matrix[0] * (x * 1.1) + matrix[2] * (z * 2.5) + Vector(0, 0.5 + height * 1.3, 0);
|
||||
ray.Flags = TraceFlags.WORLD | TraceFlags.ENTS; ray.LayerMask = TRACE_LAYER_CAMERA;
|
||||
ray.ExcludeArray = excluded;
|
||||
float fraction = GetGame().GetWorld().TraceMove(ray, null);
|
||||
if (!ERR_ForceRules.Finite(fraction) || fraction >= 0.995 || (ignored && ERR_ConvoyRules.PartOf(ray.TraceEnt, ignored))) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected bool Filter(IEntity entity) { return entity && SCR_DestructibleBuildingEntity.Cast(entity); }
|
||||
protected bool Remember(IEntity entity)
|
||||
{
|
||||
if (m_Sites.Count() >= 2048) return false;
|
||||
if (!entity.GetPrefabData() || !ERR_GarageVehicleRules.Garage(entity.GetPrefabData().GetPrefabName())) return true;
|
||||
foreach (ERR_GarageSite site : m_Sites) if (site.Garage == entity) return true;
|
||||
ERR_GarageSite added = new ERR_GarageSite(); added.Garage = entity;
|
||||
added.Selected = Math.RandomInt(0, 100) < m_GarageChance;
|
||||
m_Sites.Insert(added);
|
||||
return true;
|
||||
}
|
||||
protected string CellKey(vector point) { return Math.Floor(point[0] / 500).ToString() + ":" + Math.Floor(point[2] / 500).ToString(); }
|
||||
protected void Discover(array<vector> observers, float now)
|
||||
{
|
||||
if (observers.IsEmpty() || m_Sites.Count() >= 2048 || m_KnownCells.Count() >= 16384) return;
|
||||
// Round-robin observers, nearest unvisited cell first. Each update queries
|
||||
// at most one 500m cell; teammates share already scanned cells.
|
||||
if (m_PlayerCursor >= observers.Count()) m_PlayerCursor = 0;
|
||||
vector observer = observers[m_PlayerCursor++];
|
||||
int cx = Math.Floor(observer[0] / 500), cz = Math.Floor(observer[2] / 500);
|
||||
for (int ring = 0; ring <= Math.Ceil(m_SpawnRadius / 500.0); ring++)
|
||||
for (int x = -ring; x <= ring; x++)
|
||||
for (int z = -ring; z <= ring; z++)
|
||||
{
|
||||
if (Math.Max(Math.AbsInt(x), Math.AbsInt(z)) != ring) continue;
|
||||
vector low = Vector((cx + x) * 500, 0, (cz + z) * 500);
|
||||
string key = CellKey(low);
|
||||
if (m_KnownCells.Contains(key) || !Nearby(low + "250 0 250", observers, m_CleanupRadius)) continue;
|
||||
m_KnownCells.Set(key, true);
|
||||
vector boundsLow, boundsHigh; GetGame().GetWorldEntity().GetWorldBounds(boundsLow, boundsHigh);
|
||||
low[1] = boundsLow[1] - 100; vector high = low + "500 0 500"; high[1] = boundsHigh[1] + 100;
|
||||
GetGame().GetWorld().QueryEntitiesByAABB(low, high, Remember, Filter, EQueryEntitiesFlags.ALL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
protected bool InUse(ERR_ParkedVehicle parked)
|
||||
{
|
||||
if (!parked.Entity) return false;
|
||||
// Movement also protects a towed, pushed or otherwise used car, even if a
|
||||
// compartment callback was missed. Never reclaim somebody's transport.
|
||||
if (vector.Distance(parked.Entity.GetOrigin(), parked.Origin) > 3) return true;
|
||||
array<BaseCompartmentSlot> seats = {};
|
||||
if (!ERR_ConvoyRules.CollectSeats(parked.Entity, seats)) return true;
|
||||
foreach (BaseCompartmentSlot seat : seats) if (seat.GetOccupant()) return true;
|
||||
return false;
|
||||
}
|
||||
protected void Claim(int index)
|
||||
{
|
||||
ERR_ParkedVehicle parked = m_Parked[index];
|
||||
parked.Site.Consumed = true;
|
||||
if (parked.Entity) m_Mode.Own(parked.Entity);
|
||||
m_Claimed++; m_Parked.Remove(index);
|
||||
}
|
||||
void PlayerEntered(IEntity actor, IEntity entered)
|
||||
{
|
||||
if (m_Stopped || !Replication.IsServer() || !ERR_Actors.Player(actor)) return;
|
||||
for (int i = m_Parked.Count() - 1; i >= 0; i--)
|
||||
if (m_Parked[i].Entity && ERR_ConvoyRules.PartOf(entered, m_Parked[i].Entity)) Claim(i);
|
||||
}
|
||||
protected void Maintain(array<vector> observers, array<IEntity> actors, float now, bool known)
|
||||
{
|
||||
if (m_Parked.IsEmpty()) return;
|
||||
if (m_MaintainCursor >= m_Parked.Count()) m_MaintainCursor = 0;
|
||||
int index = m_MaintainCursor++; ERR_ParkedVehicle parked = m_Parked[index];
|
||||
if (!parked.Entity) { parked.Site.Consumed = true; m_Parked.Remove(index); return; }
|
||||
if (InUse(parked)) { Claim(index); return; }
|
||||
if (!known || !ERR_CivilianTrafficRules.Far(parked.Entity.GetOrigin(), observers, m_CleanupRadius)
|
||||
|| !Hidden(parked.Entity.GetOrigin(), parked.Entity.GetYawPitchRoll(), actors, parked.Entity)) { parked.HiddenSince = -1; return; }
|
||||
if (parked.HiddenSince < 0) parked.HiddenSince = now;
|
||||
if (!ERR_CivilianTrafficRules.CleanupDue(now, parked.HiddenSince)) return;
|
||||
// Recheck occupancy immediately before deletion. Claimed cars are no longer
|
||||
// in this collection and cannot pass through the retirement path.
|
||||
if (InUse(parked)) { Claim(index); return; }
|
||||
SCR_GarbageSystem garbage = SCR_GarbageSystem.GetByEntityWorld(parked.Entity);
|
||||
if (garbage) garbage.UpdateBlacklist(parked.Entity, false);
|
||||
ERR_Actors.DeleteSpawned(parked.Entity); m_Parked.Remove(index);
|
||||
}
|
||||
protected bool Occupied(ERR_GarageSite site)
|
||||
{
|
||||
foreach (ERR_ParkedVehicle parked : m_Parked) if (parked.Site == site) return true;
|
||||
return false;
|
||||
}
|
||||
protected void Plan(array<vector> observers, array<IEntity> actors, float now)
|
||||
{
|
||||
if (observers.IsEmpty() || now < m_NextSpawn || m_Parked.Count() >= m_Requested || m_Parked.Count() + m_Claimed >= 128 || m_Sites.IsEmpty()) return;
|
||||
// Bounded planning work, even after a long tour of a large terrain.
|
||||
for (int candidate = 0; candidate < 8; candidate++)
|
||||
{
|
||||
if (m_SiteCursor >= m_Sites.Count()) m_SiteCursor = 0;
|
||||
ERR_GarageSite site = m_Sites[m_SiteCursor++];
|
||||
if (!site.Selected || site.Consumed || !site.Garage || now < site.NextTry || Occupied(site)) continue;
|
||||
vector position = site.Garage.CoordToParent("0 0 -0.5");
|
||||
if (!Nearby(position, observers, m_SpawnRadius)) continue;
|
||||
site.NextTry = now + 15000;
|
||||
if (!ERR_CivilianTrafficRules.Far(position, observers, 35) || !ERR_Actors.Alive(site.Garage)) continue;
|
||||
vector transform[4]; site.Garage.GetWorldTransform(transform);
|
||||
TraceParam floor = new TraceParam(); floor.Start = position + "0 1 0"; floor.End = position - "0 1 0";
|
||||
floor.Flags = TraceFlags.WORLD | TraceFlags.ENTS;
|
||||
float fraction = GetGame().GetWorld().TraceMove(floor, null);
|
||||
if (!ERR_ForceRules.Finite(fraction) || fraction >= 1 || floor.TraceNorm[1] < 0.98) return;
|
||||
position = vector.Lerp(floor.Start, floor.End, fraction) + "0 0.08 0";
|
||||
vector angles = site.Garage.GetYawPitchRoll();
|
||||
if (ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), position) || !Clear(position, transform) || !Hidden(position, angles, actors)) return;
|
||||
m_Pending = site; m_Position = position; m_Angles = angles;
|
||||
m_Prefab = ERR_CivilianTraffic.CarPrefab(Math.RandomInt(0, 2));
|
||||
m_PendingBike = m_Spawned % 4 == 3 && m_BikeCandidate < m_Bikes.Prefabs.Count();
|
||||
if (m_PendingBike) m_Prefab = m_Bikes.Prefabs[m_BikeCandidate];
|
||||
m_Stage = 1; return;
|
||||
}
|
||||
}
|
||||
protected void Pending(array<vector> observers, array<IEntity> actors)
|
||||
{
|
||||
if (m_Stage == 1)
|
||||
{
|
||||
if (!m_LoadedNames.Contains(m_Prefab))
|
||||
{
|
||||
Resource resource = Resource.Load(m_Prefab);
|
||||
if (!resource || !resource.IsValid()) { if (m_PendingBike) m_BikeCandidate++; m_Pending = null; return; }
|
||||
m_LoadedNames.Insert(m_Prefab); m_LoadedResources.Insert(resource);
|
||||
}
|
||||
m_Stage = 2; return;
|
||||
}
|
||||
ERR_GarageSite site = m_Pending; m_Pending = null;
|
||||
if (!site.Garage || site.Consumed || !ERR_Actors.Alive(site.Garage) || !Nearby(m_Position, observers, m_SpawnRadius)
|
||||
|| !ERR_CivilianTrafficRules.Far(m_Position, observers, 35) || !Hidden(m_Position, m_Angles, actors)) return;
|
||||
vector transform[4]; Math3D.AnglesToMatrix(m_Angles, transform);
|
||||
if (!Clear(m_Position, transform)) return;
|
||||
m_NextSpawn = Now() + m_SpawnInterval;
|
||||
IEntity entity = ERR_Actors.Spawn(m_Prefab, m_Position, m_Angles);
|
||||
Vehicle vehicle = Vehicle.Cast(entity);
|
||||
vector low, high; if (vehicle) vehicle.GetBounds(low, high);
|
||||
array<BaseCompartmentSlot> seats = {}; bool driver; bool armed;
|
||||
if (vehicle && ERR_ConvoyRules.CollectSeats(vehicle, seats))
|
||||
foreach (BaseCompartmentSlot seat : seats)
|
||||
{
|
||||
if (seat.GetType() == ECompartmentType.TURRET) armed = true;
|
||||
if (seat.GetType() == ECompartmentType.PILOT && seat.IsCompartmentAccessible() && !seat.GetOccupant()) driver = true;
|
||||
}
|
||||
if (!driver || armed || !ERR_GarageVehicleRules.Envelope(low, high) || !Clear(m_Position, transform, entity))
|
||||
{
|
||||
ERR_Actors.DeleteSpawned(entity); if (m_PendingBike) m_BikeCandidate++; return;
|
||||
}
|
||||
Faction civilian = GetGame().GetFactionManager().GetFactionByKey("CIV");
|
||||
if (civilian) ERR_Actors.SetFaction(vehicle, civilian);
|
||||
SCR_FuelManagerComponent fuel = SCR_FuelManagerComponent.Cast(vehicle.FindComponent(SCR_FuelManagerComponent));
|
||||
if (fuel) fuel.SetTotalFuelPercentage(Math.RandomFloat(0.3, 0.8));
|
||||
ERR_ParkedVehicle parked = new ERR_ParkedVehicle(); parked.Entity = vehicle; parked.Site = site; parked.Origin = m_Position;
|
||||
m_Parked.Insert(parked); m_Spawned++;
|
||||
SCR_GarbageSystem garbage = SCR_GarbageSystem.GetByEntityWorld(vehicle);
|
||||
if (garbage) garbage.UpdateBlacklist(vehicle, true);
|
||||
}
|
||||
protected void Step()
|
||||
{
|
||||
if (m_Stopped || !Replication.IsServer() || !Running()) return;
|
||||
float now = Now();
|
||||
if (now < m_NextStep) return;
|
||||
m_NextStep = now + 250; // Do not catch up missed work in one slow frame.
|
||||
array<vector> observers = {}; array<IEntity> actors = {};
|
||||
bool known = Observers(observers, actors);
|
||||
if (!known)
|
||||
{
|
||||
m_Pending = null;
|
||||
// Unknown cameras invalidate every retirement grace period immediately.
|
||||
foreach (ERR_ParkedVehicle parked : m_Parked) parked.HiddenSince = -1;
|
||||
Maintain(observers, actors, now, false); return;
|
||||
}
|
||||
if (m_Pending) { Pending(observers, actors); return; }
|
||||
m_Work = (m_Work + 1) % 3;
|
||||
if (m_Work == 0) Maintain(observers, actors, now, true);
|
||||
else if (m_Work == 1) Discover(observers, now);
|
||||
else Plan(observers, actors, now);
|
||||
}
|
||||
// Filled oriented body volume includes furniture, doors, other vehicles and
|
||||
// players. The garage itself is deliberately NOT excluded from collision.
|
||||
protected bool Clear(vector position, vector transform[4], IEntity ignored = null)
|
||||
{
|
||||
TraceOBB body = new TraceOBB();
|
||||
body.Start = position; body.End = position + "0 0.01 0";
|
||||
body.Mins = "-1.1 0.12 -2.5"; body.Maxs = "1.1 2 2.5";
|
||||
for (int i = 0; i < 3; i++) body.Mat[i] = transform[i];
|
||||
body.Flags = TraceFlags.WORLD | TraceFlags.ENTS | TraceFlags.OCEAN;
|
||||
body.Exclude = ignored;
|
||||
float fraction = GetGame().GetWorld().TraceMove(body, null);
|
||||
return ERR_ForceRules.Finite(fraction) && fraction >= 1;
|
||||
}
|
||||
void Stop()
|
||||
{
|
||||
m_Stopped = true;
|
||||
if (GetGame()) GetGame().GetCallqueue().Remove(Step);
|
||||
m_Pending = null; m_Sites.Clear();
|
||||
// No visible deletion at round end. World teardown owns remaining vehicles.
|
||||
}
|
||||
}
|
||||
@@ -915,6 +915,7 @@ modded class SCR_CompartmentAccessComponent
|
||||
if (mode) hq = ERR_HQ.Cast(mode.Command());
|
||||
if (hq && hq.Hunters()) hq.Hunters().PlayerEntered(GetOwner(), targetEntity);
|
||||
if (mode && mode.CivilianTraffic()) mode.CivilianTraffic().PlayerEntered(GetOwner(), targetEntity);
|
||||
if (mode && mode.GarageVehicles()) mode.GarageVehicles().PlayerEntered(GetOwner(), targetEntity);
|
||||
}
|
||||
super.OnCompartmentEntered(targetEntity, manager, mgrID, slotID, move);
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ class ERR_RecommendedAddons
|
||||
static void Snapshot(ERR_GameMode mode, array<string> values)
|
||||
{
|
||||
array<string> loaded = {}; GameProject.GetLoadedAddons(loaded);
|
||||
Entry(values, "bikes", loaded, {"6503091A7B264FC3", "61B84088181A8FA4", "629B2BA37EFFD577"}, {"WCS Motorbikes", "Motorbike", "WCS Armaments"}, mode.Settings().Get("motorbikeCount") > 0);
|
||||
Entry(values, "bikes", loaded, {"6503091A7B264FC3", "61B84088181A8FA4", "629B2BA37EFFD577"}, {"WCS Motorbikes", "Motorbike", "WCS Armaments"}, mode.Settings().Get("motorbikeCount") > 0 || (mode.Settings().Get("garageVehicleCount") > 0 && mode.Settings().Get("garageChancePercent") > 0));
|
||||
Entry(values, "plane", loaded, {"69BAEE0D439966FE", "69A8A34027DA65C5"}, {"Cessna 172", "Propeller Flight Core"}, mode.Settings().Get("planeReconEnabled") == 1);
|
||||
Entry(values, "weapons", loaded, {"BFC890A6F1603EC0"}, {"Phaseline Marksman Rifles"}, mode.Settings().Get("automaticEquipmentChancePercent") != 0);
|
||||
ERR_HQ hq = ERR_HQ.Cast(mode.Command());
|
||||
|
||||
@@ -4,7 +4,7 @@ class ERR_RoadblockRules
|
||||
{
|
||||
// Admission never waits longer for a high count. Remaining posts are built
|
||||
// by the same safe placement path in background, including after prison unlock.
|
||||
static int PropCount() { return 18; }
|
||||
static int PropCount() { return 8; }
|
||||
static int SearchMilliseconds() { return 12000; }
|
||||
static int SearchAttempts() { return 300; }
|
||||
static bool Road(float width, float length, float heightDifference)
|
||||
@@ -61,10 +61,7 @@ class ERR_Roadblocks
|
||||
protected ref array<ref Resource> m_Loaded = {};
|
||||
static const ResourceName COVER = "{21D83950DF36F250}Prefabs/Props/Military/Sandbags/Sandbag_01_long_burlap.et";
|
||||
static const ResourceName BARRIER = "{64C9C1D1C2C1B38D}Prefabs/Props/Infrastructure/Roadblocks/RoadBlock_01.et";
|
||||
static const ResourceName OBSTACLE = "{A46D45922F8CAD6C}Prefabs/Props/Military/Fortification/CzechHedgehog_01_painted.et";
|
||||
static const ResourceName SIGN = "{C1977A326F2AA011}Prefabs/Structures/Signs/Military/SignCheckpoint_01_stop.et";
|
||||
static const ResourceName SHELTER = "{01AE5FD77A9A4C21}Prefabs/Structures/Military/Camps/TentSmallUS_01/TentSmallUS_01.et";
|
||||
static const ResourceName TABLE = "{53506DCB398C7EBE}Prefabs/Props/Furniture/Table_01.et";
|
||||
void ERR_Roadblocks(ERR_GameMode mode) { m_Mode = mode; m_Mining = new ERR_RoadblockMining(mode); }
|
||||
void MineTick(array<ref ERR_HQUnit> units, array<IEntity> attempted, float now) { if (!m_Stopped) m_Mining.Tick(units, attempted, now); }
|
||||
bool Contains(SCR_AIGroup group) { return group && m_Groups.Contains(group); }
|
||||
@@ -93,7 +90,7 @@ class ERR_Roadblocks
|
||||
if (profile && profile.IsValid()) source = profile.GetResource().ToEntitySource();
|
||||
if (!source || !source.Get("m_aUnitPrefabSlots", m_Roster) || m_Roster.IsEmpty()) return false;
|
||||
// Preload before physical creation. A failed prefab never leaves half a post.
|
||||
array<ResourceName> resources = {COVER, BARRIER, OBSTACLE, SIGN, SHELTER, TABLE, "{EC9F1AA42D0B7863}Prefabs/ERR/PrisonPatrol.et", ERR_Resources.DefendWaypoint()};
|
||||
array<ResourceName> resources = {COVER, BARRIER, SIGN, "{EC9F1AA42D0B7863}Prefabs/ERR/PrisonPatrol.et", ERR_Resources.DefendWaypoint()};
|
||||
foreach (ResourceName actor : m_Roster) resources.Insert(actor);
|
||||
foreach (ResourceName prefab : resources)
|
||||
{
|
||||
@@ -142,7 +139,7 @@ class ERR_Roadblocks
|
||||
m_Right = Vector(m_Forward[2], 0, -m_Forward[0]);
|
||||
// Protected guard positions sit on the shoulders; approach barriers narrow
|
||||
// wider roads while preserving a straight vehicle passage on every width.
|
||||
m_Shoulder = Math.Max(road.GetWidth() * 0.5 + 1.5, 4);
|
||||
m_Shoulder = Math.Max(road.GetWidth() * 0.5 + 0.5, 3.2);
|
||||
m_Candidates++; m_CandidateStarted = System.GetTickCount();
|
||||
return true;
|
||||
}
|
||||
@@ -190,21 +187,13 @@ class ERR_Roadblocks
|
||||
// Footprints enclose the native model bounds, including small placement margins.
|
||||
for (int side = -1; side <= 1; side += 2)
|
||||
{
|
||||
// Two front walls, an approach wall and an outer return form
|
||||
// a sheltered bay for each pair. The rear stays open for guard access.
|
||||
// A short sandbag screen protects each pair without a wide outer bay.
|
||||
AddProp(covers, COVER, side * m_Shoulder, -2, m_Right, 1.9, 0.7, 1);
|
||||
AddProp(covers, COVER, side * m_Shoulder, 2, m_Right, 1.9, 0.7, 1);
|
||||
AddProp(covers, COVER, side * (m_Shoulder + 2.8), side * 4.8, m_Forward, 1.9, 0.7, 1);
|
||||
AddProp(covers, COVER, side * (m_Shoulder + 5.6), side * 2, m_Right, 1.9, 0.7, 1);
|
||||
// Paired stop-line barriers funnel traffic into a >=4m centre passage.
|
||||
AddProp(covers, BARRIER, side * 3.3, side * 8, m_Forward, 1.2, 0.4, 1.3);
|
||||
AddProp(covers, BARRIER, side * 5.9, side * 8, m_Forward, 1.2, 0.4, 1.3);
|
||||
AddProp(covers, OBSTACLE, side * (m_Shoulder + 4.5), side * 8, m_Forward, 1, 1, 1.6);
|
||||
AddProp(covers, SIGN, side * 3, side * 12, m_Forward * (side * 1.0), 0.45, 0.4, 0.8);
|
||||
// One barrier and sign per approach preserve a >=4m centre passage.
|
||||
AddProp(covers, BARRIER, side * 3.3, side * 6, m_Forward, 1.2, 0.4, 1.3);
|
||||
AddProp(covers, SIGN, side * 3, side * 8.5, m_Forward * (side * 1.0), 0.45, 0.4, 0.8);
|
||||
}
|
||||
// A small inspection/rest area behind one bay, away from the vehicle lane.
|
||||
AddProp(covers, TABLE, m_Shoulder + 3, -4.8, m_Forward, 0.9, 0.7, 1.1);
|
||||
AddProp(covers, SHELTER, m_Shoulder + 3, -8.5, m_Forward, 1.2, 2.4, 1.4);
|
||||
}
|
||||
|
||||
protected void AddProp(array<ref ERR_RoadblockProp> covers, ResourceName prefab, float across, float along, vector forward, float halfWidth, float halfDepth, float height)
|
||||
@@ -236,7 +225,7 @@ class ERR_Roadblocks
|
||||
m_BuildPositions.Clear(); m_BuildCovers.Clear();
|
||||
}
|
||||
// A free Game Master camera does not force-stream bare, non-editable props
|
||||
// with the nearby editable guards. Keep this bounded bank (<=540 props) on
|
||||
// with the nearby editable guards. Keep this bounded bank (<=240 props) on
|
||||
// clients, including late joiners, rather than tying it to the player's body.
|
||||
// Do not report a completed post if any physical prop lacks a network node.
|
||||
protected bool ShareProps()
|
||||
@@ -306,6 +295,10 @@ class ERR_Roadblocks
|
||||
m_BuildPositions.Clear(); m_BuildCovers.Clear();
|
||||
m_NextSearch = System.GetTickCount() + 2000;
|
||||
}
|
||||
protected vector GuardPosition(int side, int row)
|
||||
{
|
||||
return ERR_Actors.Ground(m_Centre + m_Right * (side * (m_Shoulder + 1.5)) + m_Forward * (side * (row - 1.5)));
|
||||
}
|
||||
// 0: reject, 1: awaiting native tile, 2: ready.
|
||||
protected int Plan(out array<vector> positions, out array<ref ERR_RoadblockProp> covers)
|
||||
{
|
||||
@@ -320,7 +313,7 @@ class ERR_Roadblocks
|
||||
{
|
||||
for (int row = -1; row <= 1; row += 2)
|
||||
{
|
||||
vector point = ERR_Actors.Ground(m_Centre + m_Right * (side * (m_Shoulder + 2.5)) + m_Forward * (side * (row - 1.5)));
|
||||
vector point = GuardPosition(side, row);
|
||||
if (!mesh.IsTileLoaded(point)) { mesh.LoadTileIn(point); return 1; }
|
||||
vector reachable;
|
||||
if (!mesh.GetReachablePoint(point, 1, reachable) || vector.DistanceXZ(point, reachable) > 1 || !m_Placement.Clear(point, reserved)) return 0;
|
||||
|
||||
Reference in New Issue
Block a user