Add configurable crash-site salvage; record Workshop 0.0.146
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", "largeRoadblockCount", "mainRoadWidthMetres", "civilianTrafficCount", "garageVehicleCount", "garageChancePercent", "garageRadiusMetres", "garageSpawnIntervalSeconds", "emptyRestartHours", "startMode", "radioDefenceSeconds", "aiIntelChancePercent", "intelRevealChancePercent", "radioIntelSiteCount", "startTimeMinutes", "timeMultiplier", "startDateCode", "weatherProfile", "viewDistanceMetres", "reportIntervalSeconds", "supportSquadLimit", "responseRadiusMetres", "convoyCount", "antiAirChancePercent", "antiPersonnelMineChancePercent", "heliTimerMinutes"};
|
||||
return {"crashLootMode", "crashSiteCount", "crashHelicopterChancePercent", "crashRifleCount", "crashMagazineCount", "crashAntiAirChancePercent", "crashRocketChancePercent", "aiDoctrine", "aiShadow", "automaticEquipmentChancePercent", "sarkaAngerPerCar", "motorbikeCount", "droneReconCount", "droneCooldownSeconds", "planeAltitudeMetres", "planeReplacementSeconds", "planeSectorSeconds", "fieldCampCount", "fieldHospitalCount", "startWeaponCount", "startMagazineCount", "objectGuardCount", "objectGuardEvery", "objectGuardHQMode", "hunterSpawnDistanceMetres", "roadblockCount", "largeRoadblockCount", "mainRoadWidthMetres", "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)
|
||||
{
|
||||
@@ -26,6 +26,7 @@ class ERR_AdminMenuFields
|
||||
}
|
||||
static string Display(string key, string value)
|
||||
{
|
||||
if (key == "crashLootMode") { if (value == "1") return "Random"; return "Custom"; }
|
||||
if (key == "aiDoctrine") { if (value == "1") return "Coordinated"; return "Current"; }
|
||||
if (key == "aiShadow") { if (value == "1") return "On"; return "Off"; }
|
||||
if (value == "-1" || value == "-2") return ERR_Settings.SelectionText(value.ToInt());
|
||||
@@ -60,6 +61,13 @@ class ERR_AdminMenuFields
|
||||
else if (key == "objectGuardHQMode") prefix = "mission objecthq";
|
||||
else if (key == "objectGuardCount") prefix = "mission objectguards";
|
||||
else if (key == "hunterSpawnDistanceMetres") prefix = "mission hunterdistance";
|
||||
else if (key == "crashLootMode") prefix = "mission crashloot";
|
||||
else if (key == "crashSiteCount") prefix = "mission crashsites";
|
||||
else if (key == "crashHelicopterChancePercent") prefix = "mission crashhelis";
|
||||
else if (key == "crashRifleCount") prefix = "mission crashrifles";
|
||||
else if (key == "crashMagazineCount") prefix = "mission crashammo";
|
||||
else if (key == "crashAntiAirChancePercent") prefix = "mission crashaa";
|
||||
else if (key == "crashRocketChancePercent") prefix = "mission crashrockets";
|
||||
else if (key == "fieldCampCount") prefix = "mission camps";
|
||||
else if (key == "fieldHospitalCount") prefix = "mission hospitals";
|
||||
else if (key == "roadblockCount") prefix = "mission roadblocks";
|
||||
|
||||
@@ -30,17 +30,18 @@ enum ERR_AdminPage
|
||||
CHECKPOINTS = 26,
|
||||
VEHICLE_POOL = 27,
|
||||
PACKS = 28,
|
||||
CRASH_SITES = 29,
|
||||
}
|
||||
|
||||
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", "VehiclesPage", "CheckpointsPage", "VehiclePoolPage", "PacksPage"}; }
|
||||
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", "OpenCheckpoints", "OpenVehiclePool", "OpenPacks"}; }
|
||||
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", "CheckpointsPage", "VehiclePoolPage", "PacksPage", "CrashSitesPage"}; }
|
||||
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", "OpenCheckpoints", "OpenVehiclePool", "OpenPacks", "OpenCrashSites"}; }
|
||||
static array<string> MainButtons() { return {"Main_Mission", "Main_Forces", "Main_HQ", "Main_Intel", "Main_Content", "Main_Audio", "Main_Status"}; }
|
||||
static array<string> Sidebars() { return {"Sidebar_Mission", "Sidebar_Forces", "Sidebar_HQ", "Sidebar_Intel", "Sidebar_Content", "Sidebar_Audio", "Sidebar_Status"}; }
|
||||
static int Group(int page)
|
||||
{
|
||||
array<int> groups = {0, 2, 6, 4, 1, 0, 3, 6, 0, 4, 5, 1, 1, 1, 2, 0, 4, 4, 4, 2, 4, 1, 2, 3, 0, 4, 1, 4, 1};
|
||||
array<int> groups = {0, 2, 6, 4, 1, 0, 3, 6, 0, 4, 5, 1, 1, 1, 2, 0, 4, 4, 4, 2, 4, 1, 2, 3, 0, 4, 1, 4, 1, 1};
|
||||
if (page < 0 || page >= groups.Count()) return -1;
|
||||
return groups[page];
|
||||
}
|
||||
|
||||
@@ -16,6 +16,13 @@ class ERR_CommandArguments
|
||||
if (section == "mission" && option == "dronecooldown") return "droneCooldownSeconds";
|
||||
if (section == "mission" && option == "plane") return "planeReconEnabled";
|
||||
if (section == "mission" && option == "camps") return "fieldCampCount";
|
||||
if (section == "mission" && option == "crashloot") return "crashLootMode";
|
||||
if (section == "mission" && option == "crashsites") return "crashSiteCount";
|
||||
if (section == "mission" && option == "crashhelis") return "crashHelicopterChancePercent";
|
||||
if (section == "mission" && option == "crashrifles") return "crashRifleCount";
|
||||
if (section == "mission" && option == "crashammo") return "crashMagazineCount";
|
||||
if (section == "mission" && option == "crashaa") return "crashAntiAirChancePercent";
|
||||
if (section == "mission" && option == "crashrockets") return "crashRocketChancePercent";
|
||||
if (section == "mission" && option == "hospitals") return "fieldHospitalCount";
|
||||
if (section == "mission" && option == "startweapons") return "startWeaponCount";
|
||||
if (section == "mission" && option == "startammo") return "startMagazineCount";
|
||||
@@ -44,6 +51,8 @@ class ERR_CommandArguments
|
||||
}
|
||||
static bool Selection(string key, string text, out int value)
|
||||
{
|
||||
if (key == "crashLootMode" && text == "random") { value = 1; return true; }
|
||||
if (key == "crashLootMode" && text == "custom") { value = 0; return true; }
|
||||
if (key == "aiDoctrine" && text == "current") { value = 0; return true; }
|
||||
if (key == "aiDoctrine" && text == "coordinated") { value = 1; return true; }
|
||||
if ((key == "aiShadow" || key == "restartOnEmpty" || key == "showAllSites") && text == "off") { value = 0; return true; }
|
||||
@@ -191,6 +200,13 @@ class ERR_AdminService
|
||||
if (key == "planeSectorSeconds") return "30-600; default 120. Applies next round.";
|
||||
if (key == "planeReconEnabled") return "0 disabled, 1 enabled if Cessna 172 and Propeller Flight Core are loaded. Applies next round. A clear airport departure area is required.";
|
||||
if (key == "fieldCampCount") return "1..16 field camps; default 12. Applies next round. Counts are requested: unsafe optional sites may be omitted.";
|
||||
if (key == "crashLootMode") return "Random (1, default): each crate rolls independently from poor pistol supplies to rich scoped-rifle/launcher loot. Custom (0): use configured rifle, ammo and launcher values. Next round.";
|
||||
if (key == "crashSiteCount") return "0..12; default 3. 0 disables sites. Finite wrecks and loot; no refills. Applies next round.";
|
||||
if (key == "crashHelicopterChancePercent") return "0..100; default 50. 0 = armored wrecks; 100 = helicopters. Applies next round.";
|
||||
if (key == "crashRifleCount") return "1..6; default 3. Random M21 / SVD rifles with fitted scopes. Custom loot mode only; next round.";
|
||||
if (key == "crashMagazineCount") return "1..8; default 4. Matching ammunition for each selected rifle. Custom loot mode only; next round.";
|
||||
if (key == "crashAntiAirChancePercent") return "0..100; default 25. One loaded Stinger, if installed and enabled. Custom loot mode only; next round.";
|
||||
if (key == "crashRocketChancePercent") return "0..100; default 50. One scoped RPG-7 with two matching rockets. Custom loot mode only; next round.";
|
||||
if (key == "fieldHospitalCount") return "1..8 field hospitals; default 6. Applies next round. The required radio-clue hospital is retained.";
|
||||
if (key == "objectGuardEvery") return "Guard every 1st, 2nd, 3rd or 4th eligible site; default every third. Nearby buildings share one site. Next round.";
|
||||
if (key == "objectGuardHQMode") return "0: all hold their posts; 1: half hold, half respond to HQ; 2: all may respond to HQ. All report contacts. Next round.";
|
||||
@@ -219,7 +235,7 @@ class ERR_AdminService
|
||||
}
|
||||
protected string RoadblockTiming(string key)
|
||||
{
|
||||
if (key == "radioIntelSiteCount" || key == "garageVehicleCount" || key == "garageChancePercent" || key == "garageRadiusMetres" || key == "garageSpawnIntervalSeconds") return " for next round";
|
||||
if (key == "crashLootMode" || key == "crashSiteCount" || key == "crashHelicopterChancePercent" || key == "crashRifleCount" || key == "crashMagazineCount" || key == "crashAntiAirChancePercent" || key == "crashRocketChancePercent" || 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" || key == "largeRoadblockCount" || key == "mainRoadWidthMetres") return " for next round";
|
||||
return "";
|
||||
|
||||
@@ -184,6 +184,7 @@ class ERR_AdminSession
|
||||
GetGame().ERR_ServerVehicles().Snapshot(values, m_VehicleAddon, m_VehiclePage, m_VehicleFilter, m_VehicleQuery, m_VehicleRole);
|
||||
GetGame().ERR_StartingSupplies().Snapshot(values);
|
||||
m_Mode.Admin().Execute(m_Mode, true, "status", status);
|
||||
if (m_Mode.CrashSites()) status.Insert(m_Mode.CrashSites().Status());
|
||||
m_Controller.ERR_AdminDeliverSnapshot(m_Token, true, values, status);
|
||||
}
|
||||
void Log(int cursor, bool latest)
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
// Curated compatible weapon/ammunition pairs. No dependency is added for AA.
|
||||
class ERR_CrashLoot
|
||||
{
|
||||
static ResourceName Rifle(bool west)
|
||||
{
|
||||
if (west) return "{81EB948E6414BD6F}Prefabs/Weapons/Rifles/M14/Rifle_M21_ARTII.et";
|
||||
return "{6415B7923DE28C1B}Prefabs/Weapons/Rifles/SVD/Rifle_SVD_PSO.et";
|
||||
}
|
||||
static ResourceName Magazine(bool west)
|
||||
{
|
||||
if (west) return "{627255315038152A}Prefabs/Weapons/Magazines/Magazine_762x51_M14_20rnd_SpecialBall.et";
|
||||
return "{9CCB46C6EE632C1A}Prefabs/Weapons/Magazines/Magazine_762x54_SVD_10rnd_Sniper.et";
|
||||
}
|
||||
static ResourceName Scope(bool west)
|
||||
{
|
||||
if (west) return "{D2018EDB1BBF4C88}Prefabs/Weapons/Attachments/Optics/Optic_ARTII/Optic_ARTII.et";
|
||||
return "{C850A33226B8F9C1}Prefabs/Weapons/Attachments/Optics/Optic_PSO1/Optic_PSO1.et";
|
||||
}
|
||||
static ResourceName RPG() { return "{E8A55396050E1762}Prefabs/Weapons/Launchers/RPG7/Launcher_RPG7_PGO7.et"; }
|
||||
static ResourceName Rocket() { return "{609E216CBF8D0B68}Prefabs/Weapons/Ammo/Ammo_Rocket_PG7VL.et"; }
|
||||
static bool Allowed(ResourceName prefab)
|
||||
{
|
||||
if (!GetGame().ERR_ItemSelections().Allows(prefab)) return false;
|
||||
Resource resource = Resource.Load(prefab);
|
||||
return resource && resource.IsValid();
|
||||
}
|
||||
static bool RifleAllowed(bool west) { return Allowed(Rifle(west)) && Allowed(Magazine(west)) && Allowed(Scope(west)); }
|
||||
// Roll quality once per site, then quantities within that quality. Never reroll on opening.
|
||||
static void RandomPlan(array<ResourceName> items)
|
||||
{
|
||||
QualityPlan(Math.RandomInt(0, 100), items);
|
||||
}
|
||||
static void QualityPlan(int quality, array<ResourceName> items)
|
||||
{
|
||||
items.Clear();
|
||||
if (quality < 30)
|
||||
{
|
||||
// Poor salvage can be only a few pistol magazines, without a weapon.
|
||||
if (!Allowed(ERR_Supplies.Magazine())) return;
|
||||
if (Math.RandomInt(0, 2) == 1 && Allowed(ERR_Supplies.Pistol())) items.Insert(ERR_Supplies.Pistol());
|
||||
int magazines = Math.RandomInt(1, 4);
|
||||
for (int i = 0; i < magazines; i++) items.Insert(ERR_Supplies.Magazine());
|
||||
return;
|
||||
}
|
||||
if (quality < 65) { Plan(1, Math.RandomInt(1, 4), 0, 0, items); return; }
|
||||
if (quality < 90) { Plan(Math.RandomInt(2, 4), Math.RandomInt(2, 6), 15, 35, items); return; }
|
||||
Plan(Math.RandomInt(4, 7), Math.RandomInt(4, 9), 50, 75, items);
|
||||
}
|
||||
static void Plan(int rifles, int magazines, int aaChance, int rocketChance, array<ResourceName> items)
|
||||
{
|
||||
items.Clear();
|
||||
bool west = RifleAllowed(true), east = RifleAllowed(false);
|
||||
for (int i = 0; i < rifles && (west || east); i++)
|
||||
{
|
||||
bool useWest = west && (!east || Math.RandomInt(0, 2) == 0);
|
||||
items.Insert(Rifle(useWest));
|
||||
for (int j = 0; j < magazines; j++) items.Insert(Magazine(useWest));
|
||||
}
|
||||
if (Math.RandomInt(0, 100) < rocketChance && Allowed(RPG()) && Allowed(Rocket())
|
||||
&& Allowed("{E5E9DBBF3BFB88C6}Prefabs/Weapons/Attachments/Optics/Optic_PGO7/Optic_PGO7V3.et"))
|
||||
{
|
||||
items.Insert(RPG()); items.Insert(Rocket()); items.Insert(Rocket());
|
||||
}
|
||||
// The inspected WCS Stinger is disposable and already contains one missile.
|
||||
if (Math.RandomInt(0, 100) < aaChance && Allowed(ERR_EnemyLoadoutCatalog.FIM)
|
||||
&& Allowed("{517333DE6AADE624}Prefabs/Weapons/Ammo/WCS/Ammo_Missile_FIM92.et")) items.Insert(ERR_EnemyLoadoutCatalog.FIM);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
class ERR_CrashSite
|
||||
{
|
||||
vector Position;
|
||||
IEntity Wreck;
|
||||
IEntity Smoke;
|
||||
IEntity Crate;
|
||||
ref array<IEntity> Stock = {};
|
||||
bool Ready;
|
||||
}
|
||||
|
||||
// Finite server-owned scenery. Small bounded steps never block mission preparation.
|
||||
class ERR_CrashSites
|
||||
{
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected ref array<ref ERR_CrashSite> m_Sites = {};
|
||||
protected ref ERR_CampTerrain m_Terrain = new ERR_CampTerrain();
|
||||
protected ref array<ResourceName> m_Loot = {};
|
||||
protected ERR_CrashSite m_Pending;
|
||||
protected InventoryStorageManagerComponent m_Inventory;
|
||||
protected IEntity m_Item;
|
||||
protected bool m_Accepted, m_ItemIssued, m_Started, m_Stopped;
|
||||
protected int m_Target, m_Consumed, m_Attempts, m_HeliChance, m_LootMode, m_Rifles, m_Magazines, m_AA, m_Rockets;
|
||||
protected int m_ItemIndex, m_ItemTicks, m_Skipped;
|
||||
void ERR_CrashSites(ERR_GameMode mode) { m_Mode = mode; }
|
||||
int Count() { return m_Sites.Count(); }
|
||||
ERR_CrashSite At(int index) { return m_Sites[index]; }
|
||||
string Status()
|
||||
{
|
||||
return string.Format("Crash sites: %1/%2 created; %3 unsuitable slots skipped. Finite loot; no refills.", Count(), m_Target, m_Skipped);
|
||||
}
|
||||
void Start()
|
||||
{
|
||||
if (m_Started || m_Stopped || !Replication.IsServer() || !m_Mode) return;
|
||||
m_Started = true;
|
||||
ERR_Settings settings = m_Mode.Settings();
|
||||
m_LootMode = settings.Get("crashLootMode");
|
||||
m_Target = settings.Get("crashSiteCount"); m_HeliChance = settings.Get("crashHelicopterChancePercent");
|
||||
m_Rifles = settings.Get("crashRifleCount"); m_Magazines = settings.Get("crashMagazineCount");
|
||||
m_AA = settings.Get("crashAntiAirChancePercent"); m_Rockets = settings.Get("crashRocketChancePercent");
|
||||
if (m_Target > 0) GetGame().GetCallqueue().CallLater(Step, 250, true);
|
||||
}
|
||||
protected bool Ready()
|
||||
{
|
||||
if (!m_Mode.Sites() || !GetGame().GetWorldEntity()) return false;
|
||||
ERR_ScenarioComponent scenario = ERR_ScenarioComponent.Cast(m_Mode.FindComponent(ERR_ScenarioComponent));
|
||||
return scenario && !scenario.BackgroundPending();
|
||||
}
|
||||
protected bool Separated(vector point)
|
||||
{
|
||||
foreach (ERR_CrashSite site : m_Sites) if (vector.DistanceXZ(site.Position, point) < 600) return false;
|
||||
if (m_Mode.Sites())
|
||||
for (int i = 0; i < m_Mode.Sites().Count(); i++)
|
||||
{
|
||||
ERR_SiteRuntime other = m_Mode.Sites().At(i);
|
||||
if (other && other.Definition && vector.DistanceXZ(other.Definition.Position, point) < 250) return false;
|
||||
}
|
||||
array<int> players = {}; GetGame().GetPlayerManager().GetPlayers(players);
|
||||
foreach (int id : players)
|
||||
{
|
||||
IEntity actor = GetGame().GetPlayerManager().GetPlayerControlledEntity(id);
|
||||
if (actor && vector.DistanceXZ(actor.GetOrigin(), point) < 400) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected bool Survey(out vector point, out float heading)
|
||||
{
|
||||
vector low, high; GetGame().GetWorldEntity().GetWorldBounds(low, high);
|
||||
point = Vector(Math.RandomFloat(low[0], high[0]), 0, Math.RandomFloat(low[2], high[2]));
|
||||
heading = Math.RandomFloat(0, 360);
|
||||
if (!Separated(point)) return false;
|
||||
SCR_AIWorld ai = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||
if (!ai || !ai.GetRoadNetworkManager()) return false;
|
||||
BaseRoad road; float distance;
|
||||
ai.GetRoadNetworkManager().GetClosestRoad(point, road, distance, skipNavlinks: true);
|
||||
if (road && distance < 20) return false;
|
||||
float floor;
|
||||
// Includes UH-1 tail (-8.8m), armor, smoke and the crate at x=6m plus access.
|
||||
if (!m_Terrain.Clear(point, heading, 11, 0.6, floor, 5)) return false;
|
||||
point = ERR_Actors.Ground(point);
|
||||
return true;
|
||||
}
|
||||
protected IEntity Spawn(ResourceName resource, vector position, vector angles)
|
||||
{
|
||||
IEntity entity = ERR_Actors.Spawn(resource, position, angles);
|
||||
if (entity) m_Mode.Own(entity);
|
||||
return entity;
|
||||
}
|
||||
protected bool Create(vector point, float heading)
|
||||
{
|
||||
if (m_LootMode == 1) ERR_CrashLoot.RandomPlan(m_Loot);
|
||||
else ERR_CrashLoot.Plan(m_Rifles, m_Magazines, m_AA, m_Rockets, m_Loot);
|
||||
if (m_Loot.IsEmpty() && m_LootMode == 0) return false;
|
||||
ERR_CrashSite site = new ERR_CrashSite(); site.Position = point;
|
||||
ResourceName wreck = "{56C7BD958478A96D}Prefabs/ERR/CrashArmorWreck.et";
|
||||
if (Math.RandomInt(0, 100) < m_HeliChance) wreck = ERR_CrashSiteResources.WreckPrefab();
|
||||
vector wreckPoint = point;
|
||||
if (wreck == ERR_CrashSiteResources.WreckPrefab())
|
||||
{
|
||||
vector minimum, maximum;
|
||||
if (ERR_CrashSiteResources.Bounds(minimum, maximum)) wreckPoint[1] = wreckPoint[1] - minimum[1];
|
||||
}
|
||||
site.Wreck = Spawn(wreck, wreckPoint, Vector(heading, 0, 0));
|
||||
vector cratePoint = ERR_Actors.Ground(point + ERR_CampTerrain.Rotate("6 0 0", heading));
|
||||
site.Crate = Spawn("{CD7E33406E1ACA1D}Prefabs/ERR/CrashLootCrate.et", cratePoint + "0 0.1 0", Vector(heading, 0, 0));
|
||||
site.Smoke = Spawn("{2608501780FEE1EA}Prefabs/ERR/CrashLootSmoke.et", point + "0 1 0", "0 0 0");
|
||||
if (site.Crate) m_Inventory = InventoryStorageManagerComponent.Cast(site.Crate.FindComponent(InventoryStorageManagerComponent));
|
||||
BaseInventoryStorageComponent storage;
|
||||
if (site.Crate) storage = BaseInventoryStorageComponent.Cast(site.Crate.FindComponent(BaseInventoryStorageComponent));
|
||||
if (!site.Wreck || !site.Crate || !site.Smoke || !m_Inventory || !storage)
|
||||
{
|
||||
ERR_Actors.DeleteSpawned(site.Smoke); ERR_Actors.DeleteSpawned(site.Crate); ERR_Actors.DeleteSpawned(site.Wreck);
|
||||
m_Inventory = null; return false;
|
||||
}
|
||||
ParticleEffectEntity smoke = ParticleEffectEntity.Cast(site.Smoke);
|
||||
if (smoke) { smoke.SetDeleteWhenStopped(false); smoke.Play(); }
|
||||
m_Inventory.AddStorage(storage);
|
||||
m_Sites.Insert(site); m_Pending = site; m_ItemIndex = 0;
|
||||
SCR_AIWorld ai = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||
if (ai) ai.RequestNavmeshRebuildEntity(site.Wreck);
|
||||
return true;
|
||||
}
|
||||
protected void Fill()
|
||||
{
|
||||
if (!m_Pending.Crate || !m_Inventory) { Finish(); return; }
|
||||
if (m_Item)
|
||||
{
|
||||
array<IEntity> items = {}; m_Inventory.GetItems(items);
|
||||
InventoryItemComponent itemComponent = InventoryItemComponent.Cast(m_Item.FindComponent(InventoryItemComponent));
|
||||
// A player may take the item between insertion and this confirmation tick.
|
||||
// Any attached inventory slot consumes the grant; never steal it back.
|
||||
if (items.Contains(m_Item) || (itemComponent && itemComponent.GetParentSlot()))
|
||||
{
|
||||
m_Pending.Stock.Insert(m_Item); m_Item = null; m_ItemIssued = false; m_ItemIndex++; m_ItemTicks = 0; m_Accepted = false;
|
||||
return;
|
||||
}
|
||||
m_ItemTicks++;
|
||||
if (m_ItemTicks > 12)
|
||||
{
|
||||
Print("Reintroduced [crash sites]: inventory rejected " + m_Loot[m_ItemIndex], LogLevel.WARNING);
|
||||
ERR_Actors.DeleteSpawned(m_Item); m_Item = null; m_ItemIssued = false; m_ItemIndex++; m_ItemTicks = 0; m_Accepted = false; return;
|
||||
}
|
||||
if (!m_Accepted) m_Accepted = m_Inventory.TryInsertItem(m_Item);
|
||||
return;
|
||||
}
|
||||
// A destroyed/consumed entity is still a spent grant, not a spawn retry.
|
||||
if (m_ItemIssued) { m_ItemIssued = false; m_ItemIndex++; m_ItemTicks = 0; m_Accepted = false; return; }
|
||||
if (m_ItemIndex >= m_Loot.Count()) { Finish(); return; }
|
||||
ResourceName resource = m_Loot[m_ItemIndex];
|
||||
if (!ERR_CrashLoot.Allowed(resource)) { m_ItemIndex++; return; }
|
||||
m_ItemIssued = true;
|
||||
m_Item = Spawn(resource, m_Pending.Crate.GetOrigin() + "0 0.4 0", "0 0 0");
|
||||
if (!m_Item) { m_ItemIssued = false; m_ItemIndex++; }
|
||||
}
|
||||
protected void Finish()
|
||||
{
|
||||
if (m_Item)
|
||||
{
|
||||
InventoryItemComponent itemComponent = InventoryItemComponent.Cast(m_Item.FindComponent(InventoryItemComponent));
|
||||
if (!itemComponent || !itemComponent.GetParentSlot()) ERR_Actors.DeleteSpawned(m_Item);
|
||||
}
|
||||
m_Item = null; m_ItemIssued = false;
|
||||
if (m_Pending) m_Pending.Ready = true;
|
||||
m_Pending = null; m_Inventory = null; m_Loot.Clear();
|
||||
}
|
||||
protected void Step()
|
||||
{
|
||||
if (m_Stopped || !Replication.IsServer() || !m_Mode || m_Mode != ERR_GameMode.Get() || ERR_MissionRules.Terminal(m_Mode.Phase())) { Stop(); return; }
|
||||
if (!m_Mode.Active()) return;
|
||||
if (m_Pending) { Fill(); return; }
|
||||
if (m_Consumed >= m_Target) { Stop(); return; }
|
||||
if (!Ready()) return;
|
||||
int began = System.GetTickCount();
|
||||
for (int sample = 0; sample < 8; sample++)
|
||||
{
|
||||
vector point; float heading;
|
||||
m_Attempts++;
|
||||
if (Survey(point, heading))
|
||||
{
|
||||
m_Consumed++; m_Attempts = 0;
|
||||
if (!Create(point, heading)) m_Skipped++;
|
||||
return;
|
||||
}
|
||||
if (m_Attempts >= 2000)
|
||||
{
|
||||
m_Consumed++; m_Skipped++; m_Attempts = 0;
|
||||
Print("Reintroduced [crash sites]: skipped slot after bounded terrain search", LogLevel.WARNING);
|
||||
return;
|
||||
}
|
||||
if (System.GetTickCount() - began >= 4) return;
|
||||
}
|
||||
}
|
||||
void Stop()
|
||||
{
|
||||
if (m_Stopped) return;
|
||||
m_Stopped = true;
|
||||
if (GetGame()) GetGame().GetCallqueue().Remove(Step);
|
||||
Finish();
|
||||
// All scenery and issued stock remain in the round's owned-entity cleanup.
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,7 @@ class ERR_GameMode : SCR_BaseGameMode
|
||||
ERR_Performance Performance() { return m_Performance; }
|
||||
protected ref ERR_CivilianTraffic m_CivilianTraffic;
|
||||
protected ref ERR_GarageVehicles m_GarageVehicles;
|
||||
protected ref ERR_CrashSites m_CrashSites;
|
||||
protected ref ERR_ForceLedger m_Forces = new ERR_ForceLedger();
|
||||
protected ref ERR_ForceSimulation m_ForceSimulation;
|
||||
protected ref array<AIAgent> m_ForceAgents = {};
|
||||
@@ -145,6 +146,7 @@ class ERR_GameMode : SCR_BaseGameMode
|
||||
ERR_CommandService Command() { return m_Command; }
|
||||
ERR_CivilianTraffic CivilianTraffic() { return m_CivilianTraffic; }
|
||||
ERR_GarageVehicles GarageVehicles() { return m_GarageVehicles; }
|
||||
ERR_CrashSites CrashSites() { return m_CrashSites; }
|
||||
ERR_Objectives Objectives() { return m_Objectives; }
|
||||
ERR_ExtractionService Extraction() { return m_Extraction; }
|
||||
string EndReason() { return m_EndReason; }
|
||||
@@ -551,6 +553,9 @@ class ERR_GameMode : SCR_BaseGameMode
|
||||
m_GarageVehicles = new ERR_GarageVehicles(this);
|
||||
OnStop().Insert(m_GarageVehicles.Stop);
|
||||
m_GarageVehicles.Start();
|
||||
m_CrashSites = new ERR_CrashSites(this);
|
||||
OnStop().Insert(m_CrashSites.Stop);
|
||||
m_CrashSites.Start();
|
||||
GetGame().GetCallqueue().CallLater(ServerTick, 1000, true);
|
||||
GetGame().GetCallqueue().CallLater(StartScenario, 1500, false);
|
||||
}
|
||||
|
||||
@@ -9,11 +9,18 @@ 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", "largeRoadblockCount", "mainRoadWidthMetres", "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"};
|
||||
return {"crashLootMode", "crashSiteCount", "crashHelicopterChancePercent", "crashRifleCount", "crashMagazineCount", "crashAntiAirChancePercent", "crashRocketChancePercent", "telemetryEnabled", "aiDoctrine", "aiShadow", "sarkaAngerPerCar", "motorbikeCount", "planeAltitudeMetres", "planeReplacementSeconds", "planeSectorSeconds", "planeReconEnabled", "droneReconCount", "droneCooldownSeconds", "fieldCampCount", "fieldHospitalCount", "startWeaponCount", "startMagazineCount", "objectGuardCount", "objectGuardEvery", "objectGuardHQMode", "hunterSpawnDistanceMetres", "roadblockCount", "largeRoadblockCount", "mainRoadWidthMetres", "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)
|
||||
{
|
||||
if (key == "crashLootMode") return value == 0 || value == 1;
|
||||
if (key == "crashSiteCount") return value >= 0 && value <= 12;
|
||||
if (key == "crashHelicopterChancePercent") return value >= 0 && value <= 100;
|
||||
if (key == "crashRifleCount") return value >= 1 && value <= 6;
|
||||
if (key == "crashMagazineCount") return value >= 1 && value <= 8;
|
||||
if (key == "crashAntiAirChancePercent") return value >= 0 && value <= 100;
|
||||
if (key == "crashRocketChancePercent") return value >= 0 && value <= 100;
|
||||
if (key == "telemetryEnabled" || key == "aiDoctrine" || key == "aiShadow") return value == 0 || value == 1;
|
||||
if (key == "radioIntelSiteCount") return value >= 0 && value <= 20;
|
||||
if (key == "sarkaAngerPerCar") return value >= 0 && value <= 30;
|
||||
@@ -127,6 +134,13 @@ class ERR_Settings
|
||||
|
||||
static int AddedDefault(string key)
|
||||
{
|
||||
if (key == "crashLootMode") return 1;
|
||||
if (key == "crashSiteCount") return 3;
|
||||
if (key == "crashHelicopterChancePercent") return 50;
|
||||
if (key == "crashRifleCount") return 3;
|
||||
if (key == "crashMagazineCount") return 4;
|
||||
if (key == "crashAntiAirChancePercent") return 25;
|
||||
if (key == "crashRocketChancePercent") return 50;
|
||||
if (key == "aiDoctrine" || key == "aiShadow") return 0;
|
||||
if (key == "radioIntelSiteCount") return 5;
|
||||
if (key == "sarkaAngerPerCar") return 3;
|
||||
@@ -172,6 +186,13 @@ class ERR_Settings
|
||||
void ERR_Settings(string path = PATH)
|
||||
{
|
||||
m_Path = path;
|
||||
m_Values.Set("crashLootMode", AddedDefault("crashLootMode"));
|
||||
m_Values.Set("crashSiteCount", AddedDefault("crashSiteCount"));
|
||||
m_Values.Set("crashHelicopterChancePercent", AddedDefault("crashHelicopterChancePercent"));
|
||||
m_Values.Set("crashRifleCount", AddedDefault("crashRifleCount"));
|
||||
m_Values.Set("crashMagazineCount", AddedDefault("crashMagazineCount"));
|
||||
m_Values.Set("crashAntiAirChancePercent", AddedDefault("crashAntiAirChancePercent"));
|
||||
m_Values.Set("crashRocketChancePercent", AddedDefault("crashRocketChancePercent"));
|
||||
m_Values.Set("telemetryEnabled", AddedDefault("telemetryEnabled"));
|
||||
m_Values.Set("aiDoctrine", AddedDefault("aiDoctrine"));
|
||||
m_Values.Set("aiShadow", AddedDefault("aiShadow"));
|
||||
|
||||
Reference in New Issue
Block a user