Configure shared starting backpack contents from the admin menu
This commit is contained in:
@@ -140,7 +140,7 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
names.Insert("OpenSites"); names.Insert("SitesBack");
|
||||
names.Insert("OpenPlane"); names.Insert("PlaneBack");
|
||||
names.Insert("OpenAI"); names.Insert("AIBack");
|
||||
names.Insert("EquipmentOverview"); names.Insert("OpenAllowedItems"); names.Insert("OpenItemPresets"); names.Insert("OpenWeights"); names.Insert("OpenRecommendations"); names.Insert("RecommendationsBack"); names.Insert("RecommendedPlane"); names.Insert("RecommendedWeapons");
|
||||
names.Insert("OpenStarter"); names.Insert("EquipmentOverview"); names.Insert("OpenAllowedItems"); names.Insert("OpenItemPresets"); names.Insert("OpenWeights"); names.Insert("OpenRecommendations"); names.Insert("RecommendationsBack"); names.Insert("RecommendedPlane"); names.Insert("RecommendedWeapons");
|
||||
for (int density = 1; density <= 4; density++) names.Insert("GuardEvery_" + density.ToString());
|
||||
for (int policy = 0; policy <= 2; policy++) names.Insert("GuardHQ_" + policy.ToString());
|
||||
foreach (string key : ERR_AdminMenuFields.Keys())
|
||||
@@ -236,8 +236,8 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
if (widget) widget.SetText(value);
|
||||
}
|
||||
// Stable page order is also used by the shared navigation bar and native probe.
|
||||
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"}; }
|
||||
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"}; }
|
||||
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"}; }
|
||||
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"}; }
|
||||
protected void ShowPage(int page)
|
||||
{
|
||||
array<string> pages = Pages();
|
||||
@@ -247,11 +247,11 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
for (int i = 0; i < pages.Count(); i++)
|
||||
{
|
||||
GetRootWidget().FindAnyWidget(pages[i]).SetVisible(i == page);
|
||||
bool selected = i == page || ((page == 12 || page == 13) && i == 11) || ((page == 16 || page == 17 || page == 18) && i == 9) || ((page == 14 || page == 19) && i == 1);
|
||||
bool selected = i == page || ((page == 12 || page == 13) && i == 11) || ((page == 16 || page == 17 || page == 18 || page == 20) && i == 9) || ((page == 14 || page == 19) && i == 1);
|
||||
GetRootWidget().FindAnyWidget("Selected_" + navigation[i]).SetVisible(selected);
|
||||
ERR_SelectionStyle.Choice(SCR_ButtonTextComponent.GetButtonText(navigation[i], GetRootWidget()), selected);
|
||||
}
|
||||
GetRootWidget().FindAnyWidget("EquipmentNavigation").SetVisible(page == 9 || page == 16 || page == 17 || page == 18);
|
||||
GetRootWidget().FindAnyWidget("EquipmentNavigation").SetVisible(page == 9 || page == 16 || page == 17 || page == 18 || page == 20);
|
||||
GetRootWidget().FindAnyWidget("Selected_EquipmentOverview").SetVisible(page == 9);
|
||||
ERR_SelectionStyle.Choice(SCR_ButtonTextComponent.GetButtonText("EquipmentOverview", GetRootWidget()), page == 9);
|
||||
if (page == 2) m_LogPoll = 1;
|
||||
|
||||
@@ -68,11 +68,33 @@ class ERR_AdminSession
|
||||
return;
|
||||
}
|
||||
array<string> replies = {};
|
||||
if (!ItemCatalogCommand(arguments, replies) && !JournalCommand(arguments, replies) && !m_Tools.Execute(m_Mode, m_Controller, arguments, replies))
|
||||
if (!StartBackpackCommand(arguments, replies) && !ItemCatalogCommand(arguments, replies) && !JournalCommand(arguments, replies) && !m_Tools.Execute(m_Mode, m_Controller, arguments, replies))
|
||||
m_Mode.Admin().Execute(m_Mode, true, arguments, replies);
|
||||
foreach (string reply : replies) m_Controller.ERR_AdminDeliverReply(m_Token, reply, menu);
|
||||
if (menu) Snapshot(true);
|
||||
}
|
||||
protected bool StartBackpackCommand(string arguments, array<string> replies)
|
||||
{
|
||||
array<string> parts = {}; arguments.Split(" ", parts, true);
|
||||
if (parts.IsEmpty() || parts[0] != "starter") return false;
|
||||
ERR_StartBackpack supplies = GetGame().ERR_StartingSupplies();
|
||||
bool saved;
|
||||
if (parts.Count() == 3 && parts[1] == "mode" && (parts[2] == "default" || parts[2] == "custom")) saved = supplies.SetMode(parts[2] == "custom");
|
||||
else if (parts.Count() == 3 && parts[1] == "remove") saved = supplies.Remove(parts[2]);
|
||||
else if (parts.Count() == 5 && parts[1] == "set")
|
||||
{
|
||||
ERR_ServerItemCatalog catalog = GetGame().ERR_ServerItems(); catalog.Start(m_Mode);
|
||||
int count;
|
||||
if (catalog.Contains(parts[2], parts[3]) && ERR_CommandArguments.Integer(parts[4], count))
|
||||
{
|
||||
ERR_ServerItem item = catalog.Item(parts[3]);
|
||||
if (item) saved = supplies.Set(item.Prefab, count);
|
||||
}
|
||||
}
|
||||
if (saved) replies.Insert("Starting backpack saved for next round. Custom contents are used only when Custom is selected.");
|
||||
else replies.Insert("Not saved. Choose a catalog item and quantity 0-48; maximum 32 item types and 64 items total. Check configuration if read-only.");
|
||||
return true;
|
||||
}
|
||||
// Per-session paging: another administrator cannot change this menu's selection.
|
||||
protected bool ItemCatalogCommand(string arguments, array<string> replies)
|
||||
{
|
||||
@@ -135,6 +157,7 @@ class ERR_AdminSession
|
||||
m_Mode.Admin().Capabilities(values);
|
||||
ERR_RecommendedAddons.Snapshot(m_Mode, values);
|
||||
GetGame().ERR_ServerItems().Snapshot(m_Mode, values, m_ItemAddon, m_ItemPage);
|
||||
GetGame().ERR_StartingSupplies().Snapshot(values);
|
||||
m_Mode.Admin().Execute(m_Mode, true, "status", status);
|
||||
m_Controller.ERR_AdminDeliverSnapshot(m_Token, true, values, status);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ modded class ERR_GameMode
|
||||
string enabled;
|
||||
if (SCR_Global.IsEditMode() || !Replication.IsServer() || !System.GetCLIParam("errBaseSmoke", enabled) || enabled != "1") return;
|
||||
string nativeEndCase;
|
||||
array<string> independentFixtures = {"coordinated-ai", "optional-site-skip", "infantry-combat", "medical-combat", "hunter-faction", "unconscious-camera", "self-epinephrine", "objective-map", "town-garrisons", "site-garrisons", "radio-interaction", "enemy-loadout", "aggression", "native-end-active", "native-end-preparing", "generated-sites", "roadblocks", "mortar-station", "opening-tools", "airfield-markers", "air-logistics", "air-assault", "air-recon"};
|
||||
array<string> independentFixtures = {"coordinated-ai", "optional-site-skip", "infantry-combat", "medical-combat", "hunter-faction", "unconscious-camera", "self-epinephrine", "starter-backpack", "objective-map", "town-garrisons", "site-garrisons", "radio-interaction", "enemy-loadout", "aggression", "native-end-active", "native-end-preparing", "generated-sites", "roadblocks", "mortar-station", "opening-tools", "airfield-markers", "air-logistics", "air-assault", "air-recon"};
|
||||
if (System.GetCLIParam("errSmokeOutcome", nativeEndCase) && independentFixtures.Contains(nativeEndCase)) return;
|
||||
ERR_NativeObjectiveProbe.BeginRound();
|
||||
ERR_NativeProgressionIdentityProbe.BeginRound();
|
||||
|
||||
@@ -6,7 +6,7 @@ class ERR_ProfileConfig
|
||||
static const string VOICE = "$profile:EscapistsWelcomeVoice.json";
|
||||
static array<string> LegacyPaths()
|
||||
{
|
||||
return {ERR_Settings.PATH, ERR_ContentSettings.PATH, ERR_FactionSelection.PATH, ERR_TerrainPreference.PATH, ERR_ItemPreferences.PATH, VOICE};
|
||||
return {ERR_Settings.PATH, ERR_ContentSettings.PATH, ERR_FactionSelection.PATH, ERR_TerrainPreference.PATH, ERR_ItemPreferences.PATH, ERR_StartBackpack.PATH, VOICE};
|
||||
}
|
||||
static string Section(string path)
|
||||
{
|
||||
@@ -15,6 +15,7 @@ class ERR_ProfileConfig
|
||||
if (path == ERR_FactionSelection.PATH) return "factions";
|
||||
if (path == ERR_TerrainPreference.PATH) return "terrain";
|
||||
if (path == ERR_ItemPreferences.PATH) return "items";
|
||||
if (path == ERR_StartBackpack.PATH) return "startBackpack";
|
||||
if (path == VOICE) return "voice";
|
||||
return "";
|
||||
}
|
||||
@@ -86,6 +87,7 @@ class ERR_ProfileConfig
|
||||
if (section == "factions") return ERR_FactionSelection.Parse(json, roles);
|
||||
if (section == "terrain") return ERR_TerrainPreference.Parse(json, scenario);
|
||||
if (section == "items") return ERR_ItemPreferences.Parse(json, records);
|
||||
if (section == "startBackpack") return ERR_StartBackpack.Parse(json, records);
|
||||
string url;
|
||||
return section == "voice" && json.ReadValue("welcomeUrl", url) && (url.IsEmpty() || ERR_WelcomeVoiceRules.URL(url));
|
||||
}
|
||||
@@ -95,6 +97,7 @@ class ERR_ProfileConfig
|
||||
if (section == "factions") return "{\"escapists\":\"us\",\"enemy\":\"ussr\",\"independent\":\"fia\"}";
|
||||
if (section == "terrain") return "{\"scenario\":\"\"}";
|
||||
if (section == "items") return "{\"version\":1,\"records\":[\"active\"]}";
|
||||
if (section == "startBackpack") return "{\"version\":1,\"records\":[\"default\"]}";
|
||||
if (section == "voice") return "{\"welcomeUrl\":\"\"}";
|
||||
JsonSaveContext json = new JsonSaveContext(); json.EnableTypeDiscriminator(false);
|
||||
foreach (string key : ERR_Settings.Keys())
|
||||
|
||||
@@ -64,7 +64,7 @@ class ERR_ScenarioComponent : ScriptComponent
|
||||
#ifdef WORKBENCH
|
||||
string historicalFixture;
|
||||
if (!m_TerrainIndependent && System.GetCLIParam("errSmokeOutcome", historicalFixture) && historicalFixture != "normal-escape-route" && historicalFixture != "crash-start" && historicalFixture != "mounted-ammo" && historicalFixture != "alarm-hearing" && historicalFixture != "optional-site-skip" && historicalFixture != "infantry-combat" && historicalFixture != "site-garrisons" && historicalFixture != "generated-sites" && historicalFixture != "roadblocks" && historicalFixture != "opening-tools" && historicalFixture != "airfield-markers" && historicalFixture != "air-recon" && historicalFixture != "air-logistics" && historicalFixture != "air-assault")
|
||||
if (historicalFixture != "self-epinephrine") m_Definition = "{ACA98B44E2173605}Configs/ERR/Tests/LegacySites.conf";
|
||||
if (historicalFixture != "self-epinephrine" && historicalFixture != "starter-backpack") m_Definition = "{ACA98B44E2173605}Configs/ERR/Tests/LegacySites.conf";
|
||||
#endif
|
||||
m_RuntimeDefinition = ERR_ScenarioDefinition.Cast(SCR_BaseContainerTools.CreateInstanceFromPrefab(m_Definition));
|
||||
if (!m_RuntimeDefinition || !m_RuntimeDefinition.Valid()) { m_Failed = true; return ERR_PrepareResult.FAILED; }
|
||||
@@ -72,7 +72,7 @@ class ERR_ScenarioComponent : ScriptComponent
|
||||
// Existing deterministic probes intentionally use authored coordinates.
|
||||
string fixture;
|
||||
if (!m_TerrainIndependent && System.GetCLIParam("errSmokeOutcome", fixture) && fixture != "normal-escape-route" && fixture != "crash-start" && fixture != "mounted-ammo" && fixture != "alarm-hearing" && fixture != "optional-site-skip" && fixture != "infantry-combat" && fixture != "site-garrisons" && fixture != "generated-sites" && fixture != "roadblocks" && fixture != "airfield-markers" && fixture != "air-recon" && fixture != "air-logistics" && fixture != "air-assault")
|
||||
if (fixture != "self-epinephrine") foreach (ERR_SiteDefinition fixedSite : m_RuntimeDefinition.Sites) fixedSite.RandomClear = false;
|
||||
if (fixture != "self-epinephrine" && fixture != "starter-backpack") foreach (ERR_SiteDefinition fixedSite : m_RuntimeDefinition.Sites) fixedSite.RandomClear = false;
|
||||
if (!ERR_ConfigurePatrolHQFixture(fixture)) { m_Failed = true; return ERR_PrepareResult.FAILED; }
|
||||
#endif
|
||||
if (m_Progressive && !m_RuntimeDefinition.ConfigurePopulation(m_Definition, mode.Settings().Get("fieldCampCount"), mode.Settings().Get("fieldHospitalCount")))
|
||||
|
||||
@@ -40,6 +40,7 @@ class ERR_ServerItemCatalog
|
||||
foreach (ERR_ServerAddonItems addon : m_Addons) if (addon.Id == id) return addon;
|
||||
return null;
|
||||
}
|
||||
ERR_ServerItem Item(string key) { return m_Seen.Get(key); }
|
||||
bool Contains(string addonId, string itemId)
|
||||
{
|
||||
ERR_ServerAddonItems addon = Find(addonId);
|
||||
|
||||
@@ -5,6 +5,8 @@ class ERR_StockPlacement
|
||||
IEntity Item;
|
||||
InventoryStorageManagerComponent Inventory;
|
||||
bool Accepted;
|
||||
bool Optional;
|
||||
bool Skipped;
|
||||
}
|
||||
|
||||
class ERR_SiteRuntime
|
||||
@@ -428,14 +430,23 @@ class ERR_SiteService
|
||||
BaseInventoryStorageComponent storage = BaseInventoryStorageComponent.Cast(site.Container.FindComponent(BaseInventoryStorageComponent));
|
||||
if (!storage) return Fail("container storage: " + spec.Id);
|
||||
inventory.AddStorage(storage);
|
||||
if (spec.Stock)
|
||||
array<ref ERR_SiteStock> stockPlan = spec.Stock;
|
||||
bool customStart = spec.Kind == ERR_LocationKind.PRISON && GetGame().ERR_StartingSupplies().Custom();
|
||||
if (customStart)
|
||||
{
|
||||
foreach (ERR_SiteStock stock : spec.Stock)
|
||||
stockPlan = {};
|
||||
// Keep the route's shared map; replace default weapons/ammo with exact selections.
|
||||
ERR_SiteStock mapStock = new ERR_SiteStock(); mapStock.Prefab = ERR_Supplies.Map(); mapStock.Count = 1; stockPlan.Insert(mapStock);
|
||||
GetGame().ERR_StartingSupplies().Stock(stockPlan);
|
||||
}
|
||||
if (stockPlan)
|
||||
{
|
||||
foreach (ERR_SiteStock stock : stockPlan)
|
||||
{
|
||||
ResourceName resource = stock.Prefab;
|
||||
if (resource == ERR_Supplies.Map()) resource = fieldMap;
|
||||
int quantity = stock.Count;
|
||||
if (spec.Kind == ERR_LocationKind.PRISON)
|
||||
if (spec.Kind == ERR_LocationKind.PRISON && !customStart)
|
||||
{
|
||||
if (stock.Prefab == ERR_Supplies.Pistol()) quantity = startWeapons;
|
||||
else if (stock.Prefab == ERR_Supplies.Magazine()) quantity = startMagazines;
|
||||
@@ -444,6 +455,8 @@ class ERR_SiteService
|
||||
{
|
||||
// Created exactly once; insertion retries always use this same entity.
|
||||
IEntity item = Spawn(resource, site.Container.GetOrigin());
|
||||
if (!item && customStart && stock.Prefab != ERR_Supplies.Map())
|
||||
{ Print("Reintroduced [starting backpack]: unavailable item skipped: " + resource, LogLevel.WARNING); continue; }
|
||||
if (!item) return Fail("stock resource: " + spec.Id);
|
||||
if (!stock.TargetId.IsEmpty())
|
||||
{
|
||||
@@ -459,6 +472,7 @@ class ERR_SiteService
|
||||
else if (stock.Prefab == "{B05A816C0BF50802}Prefabs/Weapons/Explosives/Mine_PMN4/Mine_PMN4.et"
|
||||
&& site.DepotMines.Count() < 2) site.DepotMines.Insert(item);
|
||||
}
|
||||
placement.Optional = customStart && stock.Prefab != ERR_Supplies.Map();
|
||||
placement.Item = item;
|
||||
placement.Inventory = inventory;
|
||||
m_Placements.Insert(placement);
|
||||
@@ -643,10 +657,16 @@ class ERR_SiteService
|
||||
string firstRejected;
|
||||
foreach (ERR_StockPlacement placement : m_Placements)
|
||||
{
|
||||
if (placement.Skipped) continue;
|
||||
if (!placement.Item || !placement.Inventory) { Fail("stock disappeared during initialization"); return m_State; }
|
||||
array<IEntity> items = {};
|
||||
placement.Inventory.GetItems(items);
|
||||
if (items.Contains(placement.Item)) continue;
|
||||
if (placement.Optional && GetGame().GetWorld().GetWorldTime() > m_Deadline)
|
||||
{
|
||||
Print("Reintroduced [starting backpack]: item did not fit; omitted " + placement.Item.GetPrefabData().GetPrefabName(), LogLevel.WARNING);
|
||||
ERR_Actors.DeleteSpawned(placement.Item); placement.Skipped = true; continue;
|
||||
}
|
||||
pending++;
|
||||
if (firstRejected.IsEmpty() && placement.Item.GetPrefabData()) firstRejected = placement.Item.GetPrefabData().GetPrefabName();
|
||||
if (placement.Accepted) accepted++;
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
// Exact, finite shared starting supplies. Edits affect the next preparation only.
|
||||
class ERR_StartBackpack
|
||||
{
|
||||
static const string PATH = "$profile:EscapistsStartBackpack.json";
|
||||
protected ref array<string> m_Records = {"default"};
|
||||
protected bool m_ReadOnly;
|
||||
void ERR_StartBackpack()
|
||||
{
|
||||
if (!Replication.IsServer()) { m_ReadOnly = true; return; }
|
||||
array<string> loaded;
|
||||
if (Read(PATH, loaded) || Read(PATH + ".bak", loaded)) m_Records = loaded;
|
||||
else if (ERR_ProfileConfig.Exists(PATH) || ERR_ProfileConfig.Exists(PATH + ".bak")) m_ReadOnly = true;
|
||||
}
|
||||
static bool Valid(array<string> records)
|
||||
{
|
||||
if (!records || records.IsEmpty() || records.Count() > 33) return false;
|
||||
if (records[0] != "default" && records[0] != "custom") return false;
|
||||
set<string> seen = new set<string>(); int total;
|
||||
for (int i = 1; i < records.Count(); i++)
|
||||
{
|
||||
array<string> parts = {}; records[i].Split("|", parts, false);
|
||||
if (parts.Count() != 2 || parts[0].Length() > 512 || !parts[0].EndsWith(".et")) return false;
|
||||
string key = ERR_TerrainCatalog.Key(parts[0]); int count;
|
||||
if (!ERR_ItemPreferences.Guid(key) || seen.Contains(key)) return false;
|
||||
if (!ERR_CommandArguments.Integer(parts[1], count) || count < 1 || count > 48) return false;
|
||||
total += count; if (total > 64) return false;
|
||||
seen.Insert(key);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static bool Parse(JsonLoadContext json, out array<string> records)
|
||||
{
|
||||
int version; records = {};
|
||||
return json.ReadValue("version", version) && version == 1 && json.ReadValue("records", records) && Valid(records);
|
||||
}
|
||||
protected bool Read(string path, out array<string> records)
|
||||
{
|
||||
JsonLoadContext json = new JsonLoadContext();
|
||||
return ERR_ProfileConfig.Load(path, json) && Parse(json, records);
|
||||
}
|
||||
protected bool Save(array<string> records)
|
||||
{
|
||||
if (!Replication.IsServer() || m_ReadOnly || !Valid(records)) return false;
|
||||
JsonSaveContext json = new JsonSaveContext(); json.EnableTypeDiscriminator(false);
|
||||
json.WriteValue("version", 1); json.WriteValue("records", records);
|
||||
if (!ERR_ProfileConfig.Save(PATH, json)) return false;
|
||||
m_Records = records; return true;
|
||||
}
|
||||
bool Custom() { return m_Records[0] == "custom"; }
|
||||
bool SetMode(bool custom)
|
||||
{
|
||||
array<string> next = {}; next.Copy(m_Records);
|
||||
next[0] = "default"; if (custom) next[0] = "custom";
|
||||
return Save(next);
|
||||
}
|
||||
bool Set(ResourceName prefab, int quantity)
|
||||
{
|
||||
if (quantity < 0 || quantity > 48) return false;
|
||||
string key = ERR_TerrainCatalog.Key(prefab);
|
||||
if (!ERR_ItemPreferences.Guid(key)) return false;
|
||||
array<string> next = {m_Records[0]};
|
||||
for (int i = 1; i < m_Records.Count(); i++)
|
||||
{
|
||||
array<string> parts = {}; m_Records[i].Split("|", parts, false);
|
||||
if (ERR_TerrainCatalog.Key(parts[0]) != key) next.Insert(m_Records[i]);
|
||||
}
|
||||
if (quantity > 0) next.Insert(prefab + "|" + quantity.ToString());
|
||||
return Save(next);
|
||||
}
|
||||
bool Remove(string key)
|
||||
{
|
||||
for (int i = 1; i < m_Records.Count(); i++)
|
||||
{
|
||||
array<string> parts = {}; m_Records[i].Split("|", parts, false);
|
||||
if (ERR_TerrainCatalog.Key(parts[0]) == key) return Set(parts[0], 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void Stock(array<ref ERR_SiteStock> stock)
|
||||
{
|
||||
for (int i = 1; i < m_Records.Count(); i++)
|
||||
{
|
||||
array<string> parts = {}; m_Records[i].Split("|", parts, false);
|
||||
ERR_SiteStock item = new ERR_SiteStock(); item.Prefab = parts[0]; item.Count = parts[1].ToInt(); stock.Insert(item);
|
||||
}
|
||||
}
|
||||
void Snapshot(array<string> values)
|
||||
{
|
||||
values.Insert("starterMode=" + m_Records[0]);
|
||||
string state = "Saved for next round. The shared field map is always included. Add matching magazines separately.";
|
||||
if (m_ReadOnly) state = "Read-only: starting backpack configuration needs repair.";
|
||||
values.Insert("starterState=" + state);
|
||||
for (int i = 1; i < m_Records.Count(); i++)
|
||||
{
|
||||
array<string> parts = {}; m_Records[i].Split("|", parts, false);
|
||||
string key = ERR_TerrainCatalog.Key(parts[0]);
|
||||
ERR_ServerItem item = GetGame().ERR_ServerItems().Item(key);
|
||||
string label = FilePath.StripPath(parts[0]); if (item) label = item.Label;
|
||||
values.Insert("starterItem=" + key + "|" + ERR_ServerItemCatalog.Clean(label) + "|" + parts[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
modded class ArmaReforgerScripted
|
||||
{
|
||||
protected ref ERR_StartBackpack m_ERRStartBackpack;
|
||||
ERR_StartBackpack ERR_StartingSupplies()
|
||||
{
|
||||
if (!m_ERRStartBackpack) m_ERRStartBackpack = new ERR_StartBackpack();
|
||||
return m_ERRStartBackpack;
|
||||
}
|
||||
override protected void OnBeforeWorldCleanup()
|
||||
{
|
||||
m_ERRStartBackpack = null; super.OnBeforeWorldCleanup();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// Catalog choices come from the authenticated session, not client prefab paths.
|
||||
modded class ERR_AdminMenu
|
||||
{
|
||||
protected ref array<SCR_ButtonTextComponent> m_StarterButtons = {};
|
||||
protected ref array<string> m_StarterMods = {}, m_StarterCatalog = {}, m_StarterSaved = {};
|
||||
protected string m_StarterCatalogSignature, m_StarterSavedSignature, m_StarterAddon;
|
||||
protected int m_StarterMode = -1;
|
||||
override void OnMenuOpen()
|
||||
{
|
||||
super.OnMenuOpen();
|
||||
foreach (string name : {"StarterDefault", "StarterCustom", "StarterShow", "StarterPrevious", "StarterNext", "StarterAdd", "StarterRemove"})
|
||||
{
|
||||
SCR_ButtonTextComponent button = SCR_ButtonTextComponent.GetButtonText(name, GetRootWidget());
|
||||
if (!button) { Close(); return; }
|
||||
button.m_OnClicked.Insert(OnStarterButton); m_StarterButtons.Insert(button);
|
||||
}
|
||||
SCR_EditBoxComponent.GetEditBoxComponent("StarterQuantity", GetRootWidget()).SetValue("1");
|
||||
UpdateActions();
|
||||
}
|
||||
override void OnMenuClose()
|
||||
{
|
||||
foreach (SCR_ButtonTextComponent button : m_StarterButtons) if (button) button.m_OnClicked.Remove(OnStarterButton);
|
||||
m_StarterButtons.Clear(); m_StarterMods.Clear(); m_StarterCatalog.Clear(); m_StarterSaved.Clear();
|
||||
super.OnMenuClose();
|
||||
}
|
||||
protected override void UpdateActions()
|
||||
{
|
||||
super.UpdateActions();
|
||||
ERR_SelectionStyle.Pair(GetRootWidget(), "StarterCustom", "StarterDefault", m_StarterMode);
|
||||
foreach (SCR_ButtonTextComponent button : m_StarterButtons)
|
||||
{
|
||||
string name = button.GetRootWidget().GetName();
|
||||
bool enabled = m_Ready && m_StarterMode >= 0;
|
||||
if (name == "StarterDefault") enabled = enabled && m_StarterMode == 1;
|
||||
if (name == "StarterCustom") enabled = enabled && m_StarterMode == 0;
|
||||
if (name == "StarterShow") enabled = enabled && !m_StarterMods.IsEmpty();
|
||||
if (name == "StarterAdd") enabled = enabled && !m_StarterCatalog.IsEmpty();
|
||||
if (name == "StarterRemove") enabled = enabled && !m_StarterSaved.IsEmpty();
|
||||
if (name == "StarterPrevious") enabled = enabled && !m_StarterAddon.IsEmpty() && m_ShownItemPage > 0;
|
||||
if (name == "StarterNext") enabled = enabled && !m_StarterAddon.IsEmpty() && m_ShownItemPage < m_LastItemPage;
|
||||
button.SetEnabled(enabled);
|
||||
}
|
||||
}
|
||||
// Preserve selection across polls and only rebuild a dropdown when its data changes.
|
||||
protected void StarterCombo(string name, array<string> keys, array<string> labels, array<string> previous)
|
||||
{
|
||||
SCR_ComboBoxComponent combo = SCR_ComboBoxComponent.GetComboBoxComponent(name, GetRootWidget());
|
||||
if (!combo) return;
|
||||
int index = combo.GetCurrentIndex(); string selected;
|
||||
if (index >= 0 && index < previous.Count()) selected = previous[index];
|
||||
combo.ClearAll(); foreach (string label : labels) combo.AddItem(WidgetManager.Translate(label));
|
||||
combo.SetCurrentItem(Math.Max(0, keys.Find(selected)), false, false, false);
|
||||
previous.Copy(keys);
|
||||
}
|
||||
override void ReceiveSnapshot(bool authorized, array<string> values, array<string> status)
|
||||
{
|
||||
super.ReceiveSnapshot(authorized, values, status);
|
||||
if (!authorized || !values) return;
|
||||
if (m_StarterMods.Count() != m_ServerAddonKeys.Count()) StarterCombo("StarterMod", m_ServerAddonKeys, m_ServerAddonLabels, m_StarterMods);
|
||||
array<string> catalogKeys = {}, catalogLabels = {}, savedKeys = {}, savedLabels = {};
|
||||
string catalogSignature = m_ShownItemAddon, savedSignature;
|
||||
int total;
|
||||
foreach (string entry : values)
|
||||
{
|
||||
int divider = entry.IndexOf("="); if (divider < 0) continue;
|
||||
string field = entry.Substring(0, divider), value = entry.Substring(divider + 1, entry.Length() - divider - 1);
|
||||
array<string> parts = {}; value.Split("|", parts, false);
|
||||
if (field == "starterMode") { m_StarterMode = 0; if (value == "custom") m_StarterMode = 1; }
|
||||
if (field == "starterState") SetText("StarterState", value);
|
||||
if (field == "serverItem" && parts.Count() == 6)
|
||||
{ catalogKeys.Insert(parts[0]); catalogLabels.Insert(parts[1]); catalogSignature += value; }
|
||||
if (field == "starterItem" && parts.Count() == 3)
|
||||
{ savedKeys.Insert(parts[0]); savedLabels.Insert(parts[2] + " x " + WidgetManager.Translate(parts[1])); savedSignature += value; total += parts[2].ToInt(); }
|
||||
}
|
||||
if (catalogSignature != m_StarterCatalogSignature)
|
||||
{
|
||||
StarterCombo("StarterCatalogItem", catalogKeys, catalogLabels, m_StarterCatalog);
|
||||
m_StarterCatalogSignature = catalogSignature; m_StarterAddon = m_ShownItemAddon;
|
||||
SCR_ComboBoxComponent combo = SCR_ComboBoxComponent.GetComboBoxComponent("StarterMod", GetRootWidget());
|
||||
if (combo && !m_StarterAddon.IsEmpty()) combo.SetCurrentItem(Math.Max(0, m_StarterMods.Find(m_StarterAddon)), false, false, false);
|
||||
}
|
||||
if (savedSignature != m_StarterSavedSignature)
|
||||
{ StarterCombo("StarterSaved", savedKeys, savedLabels, m_StarterSaved); m_StarterSavedSignature = savedSignature; }
|
||||
string mode = "Default loadout selected"; if (m_StarterMode == 1) mode = "Custom loadout selected";
|
||||
SetText("StarterSummary", mode + " | Custom: " + savedKeys.Count().ToString() + "/32 types, " + total.ToString() + "/64 items + field map");
|
||||
string page = "Choose a mod, then Show items. Quantity 0 removes an item.";
|
||||
if (!m_StarterAddon.IsEmpty()) page = "Catalog page " + (m_ShownItemPage + 1).ToString() + "/" + (m_LastItemPage + 1).ToString() + " | Select an item above; enter its total desired quantity (0 removes).";
|
||||
SetText("StarterCatalogState", page); UpdateActions();
|
||||
}
|
||||
protected void OnStarterButton(SCR_ButtonBaseComponent button)
|
||||
{
|
||||
if (!button || !m_Ready || !IsFocused() || !m_Controller || m_Age > 6 || m_ClickDelay > 0) return;
|
||||
string name = button.GetRootWidget().GetName();
|
||||
if (name == "StarterDefault") { SubmitStarter("starter mode default"); return; }
|
||||
if (name == "StarterCustom") { SubmitStarter("starter mode custom"); return; }
|
||||
if (name == "StarterRemove")
|
||||
{
|
||||
int selected = SCR_ComboBoxComponent.GetComboBoxComponent("StarterSaved", GetRootWidget()).GetCurrentIndex();
|
||||
if (selected >= 0 && selected < m_StarterSaved.Count()) SubmitStarter("starter remove " + m_StarterSaved[selected]);
|
||||
return;
|
||||
}
|
||||
if (name == "StarterAdd")
|
||||
{
|
||||
int selected = SCR_ComboBoxComponent.GetComboBoxComponent("StarterCatalogItem", GetRootWidget()).GetCurrentIndex();
|
||||
int count; string quantity = SCR_EditBoxComponent.GetEditBoxComponent("StarterQuantity", GetRootWidget()).GetValue().Trim();
|
||||
if (!ERR_CommandArguments.Integer(quantity, count) || count < 0 || count > 48) { ReceiveReply("Enter a quantity from 0 to 48; 0 removes the item."); return; }
|
||||
if (selected >= 0 && selected < m_StarterCatalog.Count() && !m_StarterAddon.IsEmpty()) SubmitStarter("starter set " + m_StarterAddon + " " + m_StarterCatalog[selected] + " " + count.ToString());
|
||||
return;
|
||||
}
|
||||
int index = SCR_ComboBoxComponent.GetComboBoxComponent("StarterMod", GetRootWidget()).GetCurrentIndex();
|
||||
if (index < 0 || index >= m_StarterMods.Count()) return;
|
||||
string addon = m_StarterMods[index]; int page;
|
||||
if (name == "StarterPrevious" || name == "StarterNext")
|
||||
{
|
||||
addon = m_StarterAddon; page = m_ShownItemPage - 1;
|
||||
if (name == "StarterNext") page = m_ShownItemPage + 1;
|
||||
}
|
||||
SubmitStarter("items list " + addon + " " + Math.Clamp(page, 0, m_LastItemPage).ToString());
|
||||
}
|
||||
protected void SubmitStarter(string command)
|
||||
{
|
||||
m_ClickDelay = 0.3; SetText("Feedback", "Updating starting backpack...");
|
||||
m_Controller.ERR_AdminSubmit(command, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
#ifdef WORKBENCH
|
||||
modded class ERR_AdminMenu
|
||||
{
|
||||
bool ERR_StarterProbeBrowse()
|
||||
{
|
||||
ERR_ServerAddonItems addon = GetGame().ERR_ServerItems().Find("58d0fb3206b6f859");
|
||||
if (!addon) return false;
|
||||
foreach (int index, ERR_ServerItem item : addon.Items)
|
||||
if (item.Prefab == ERR_Supplies.Pistol())
|
||||
{ m_Controller.ERR_AdminSubmit("items list " + addon.Id + " " + (index / ERR_ServerItemCatalog.PAGE_SIZE).ToString(), true); return true; }
|
||||
return false;
|
||||
}
|
||||
bool ERR_StarterProbeQuantity()
|
||||
{
|
||||
int index = m_StarterCatalog.Find(ERR_TerrainCatalog.Key(ERR_Supplies.Pistol()));
|
||||
if (index < 0 || !m_Ready) return false;
|
||||
SCR_ComboBoxComponent.GetComboBoxComponent("StarterCatalogItem", GetRootWidget()).SetCurrentItem(index, false, false, false);
|
||||
SCR_EditBoxComponent.GetEditBoxComponent("StarterQuantity", GetRootWidget()).SetValue("1");
|
||||
OnStarterButton(SCR_ButtonTextComponent.GetButtonText("StarterAdd", GetRootWidget())); return true;
|
||||
}
|
||||
bool ERR_StarterProbeReady(int mode) { return m_Ready && m_StarterMode == mode; }
|
||||
bool ERR_StarterProbeSelect(bool custom)
|
||||
{
|
||||
ShowPage(20);
|
||||
string name = "StarterDefault"; if (custom) name = "StarterCustom";
|
||||
SCR_ButtonTextComponent button = SCR_ButtonTextComponent.GetButtonText(name, GetRootWidget());
|
||||
if (!button || !button.IsEnabled()) return false;
|
||||
OnStarterButton(button); return true;
|
||||
}
|
||||
}
|
||||
modded class ERR_GameMode
|
||||
{
|
||||
protected int m_ERRStarterStage, m_ERRStarterDeadline;
|
||||
protected float m_ERRStarterWait;
|
||||
protected IEntity m_ERRStarterContainer;
|
||||
protected override void EOnInit(IEntity owner)
|
||||
{
|
||||
super.EOnInit(owner);
|
||||
string outcome;
|
||||
if (!Replication.IsServer() || SCR_Global.IsEditMode() || !System.GetCLIParam("errSmokeOutcome", outcome) || outcome != "starter-backpack") return;
|
||||
m_ERRStarterDeadline = System.GetTickCount() + 360000;
|
||||
GetGame().GetCallqueue().CallLater(ERR_StarterProbeTick, 500, true);
|
||||
OnStop().Insert(ERR_StopStarterProbe);
|
||||
}
|
||||
protected void ERR_StopStarterProbe() { GetGame().GetCallqueue().Remove(ERR_StarterProbeTick); }
|
||||
protected bool ERR_StarterCheck(bool ok, string detail)
|
||||
{
|
||||
if (ok) { Print("ERR SMOKE PASS: starting backpack " + detail); return true; }
|
||||
Print("ERR SMOKE FAIL: starting backpack " + detail, LogLevel.ERROR); ERR_StopStarterProbe(); return false;
|
||||
}
|
||||
protected bool ERR_StarterInventory()
|
||||
{
|
||||
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(m_ERRStarterContainer.FindComponent(InventoryStorageManagerComponent));
|
||||
if (!inventory) return false;
|
||||
array<IEntity> items = {}; inventory.GetItems(items);
|
||||
int pistols, magazines, epinephrine, other;
|
||||
foreach (IEntity item : items)
|
||||
{
|
||||
if (!item || !item.GetPrefabData()) return false;
|
||||
ResourceName prefab = item.GetPrefabData().GetPrefabName();
|
||||
if (prefab == ERR_Supplies.Pistol()) pistols++;
|
||||
else if (prefab == ERR_Supplies.Magazine()) magazines++;
|
||||
else if (prefab == ERR_EnemyMedicalCatalog.EPI) epinephrine++;
|
||||
else other++;
|
||||
}
|
||||
PrintFormat("ERR STARTER STOCK: pistols=%1 magazines=%2 epinephrine=%3 other=%4", pistols, magazines, epinephrine, other);
|
||||
// Native enumeration includes the magazine loaded in each pistol, plus four spares.
|
||||
return pistols == 2 && magazines == 6 && epinephrine == 3 && other == 1;
|
||||
}
|
||||
protected void ERR_StarterProbeTick()
|
||||
{
|
||||
if (System.GetTickCount() > m_ERRStarterDeadline || !PreparationError().IsEmpty()) { ERR_StarterCheck(false, "deadline or preparation failure"); return; }
|
||||
if (!Active() || GetWorld().GetWorldTime() < m_ERRStarterWait) return;
|
||||
SCR_PlayerController pc = SCR_PlayerController.Cast(GetGame().GetPlayerController());
|
||||
if (!pc || !pc.GetControlledEntity() || !Players().CurrentActor(pc.GetControlledEntity())) return;
|
||||
if (m_ERRStarterStage == 0)
|
||||
{
|
||||
ERR_SiteRuntime prison = Sites().Find("everon-prison-1");
|
||||
if (!ERR_StarterCheck(prison && prison.Container, "physical shared backpack exists")) return;
|
||||
m_ERRStarterContainer = prison.Container;
|
||||
if (!ERR_StarterCheck(GetGame().ERR_StartingSupplies().Custom() && ERR_StarterInventory(), "saved exact custom loadout replaced default stock and retained one map")) return;
|
||||
ERR_AdminMenu.Open(); m_ERRStarterStage = 1; return;
|
||||
}
|
||||
ERR_AdminMenu menu = ERR_AdminMenu.GetOpenMenu();
|
||||
if (!menu) return;
|
||||
if (m_ERRStarterStage == 1)
|
||||
{
|
||||
if (!menu.ERR_StarterProbeReady(1)) return;
|
||||
if (!ERR_StarterCheck(menu.ERR_StarterProbeSelect(false), "real menu default button submitted authenticated command")) return;
|
||||
m_ERRStarterStage = 2; m_ERRStarterWait = GetWorld().GetWorldTime() + 1500; return;
|
||||
}
|
||||
if (m_ERRStarterStage == 2)
|
||||
{
|
||||
if (!menu.ERR_StarterProbeReady(0)) return;
|
||||
ERR_StartBackpack reload = new ERR_StartBackpack();
|
||||
if (!ERR_StarterCheck(!reload.Custom() && ERR_StarterInventory(), "default choice persisted without refilling current backpack")) return;
|
||||
if (!ERR_StarterCheck(menu.ERR_StarterProbeSelect(true), "real menu custom button submitted authenticated command")) return;
|
||||
m_ERRStarterStage = 3; m_ERRStarterWait = GetWorld().GetWorldTime() + 1500; return;
|
||||
}
|
||||
if (m_ERRStarterStage == 3)
|
||||
{
|
||||
if (!menu.ERR_StarterProbeReady(1) || !menu.ERR_StarterProbeBrowse()) return;
|
||||
m_ERRStarterStage = 4; m_ERRStarterWait = GetWorld().GetWorldTime() + 1500; return;
|
||||
}
|
||||
if (m_ERRStarterStage == 4)
|
||||
{
|
||||
if (!menu.ERR_StarterProbeQuantity()) return;
|
||||
m_ERRStarterStage = 5; m_ERRStarterWait = GetWorld().GetWorldTime() + 1500; return;
|
||||
}
|
||||
ERR_StartBackpack reload = new ERR_StartBackpack();
|
||||
array<ref ERR_SiteStock> planned = {}; reload.Stock(planned);
|
||||
int pistols;
|
||||
foreach (ERR_SiteStock item : planned) if (item.Prefab == ERR_Supplies.Pistol()) pistols = item.Count;
|
||||
if (!ERR_StarterCheck(reload.Custom() && planned.Count() == 3 && pistols == 1 && ERR_StarterInventory(), "actual quantity menu saved next-round stock to disk without changing current backpack")) return;
|
||||
if (!ERR_StarterCheck(!reload.Set(ERR_Supplies.Pistol(), 49), "oversized quantity rejected")) return;
|
||||
menu.Close(); ERR_StopStarterProbe(); Transition(ERR_Phase.DEFEAT);
|
||||
Print("ERR SMOKE COMPLETE: starting backpack exact physical stock, map, menu mode RPC, disk reload and current-round isolation; scripted local fixture");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user