Simplify content controls and add vehicle roles; record Workshop 0.0.144
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
Discover installed mod vehicles and save independent garage, traffic, patrol and convoy assignments. Retire the superseded RHS 1.9 overrides. Include native menu, crew/driving and publication evidence.
This commit is contained in:
@@ -29,6 +29,7 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
protected bool m_ScrollToEnd;
|
||||
protected int m_LogCursor = -1;
|
||||
|
||||
protected void OpenVehicleAddon(string addon) {}
|
||||
static ERR_AdminMenu GetOpenMenu()
|
||||
{
|
||||
if (!GetGame() || !GetGame().GetMenuManager()) return null;
|
||||
@@ -132,7 +133,9 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
protected void OnBack()
|
||||
{
|
||||
if (!IsFocused()) return;
|
||||
foreach (string key : ERR_AdminMenuFields.Keys())
|
||||
array<string> editFields = ERR_AdminMenuFields.Keys();
|
||||
editFields.InsertAll({"ModsSearch", "ItemsSearch", "VehiclesSearch", "ItemsPresetName"});
|
||||
foreach (string key : editFields)
|
||||
{
|
||||
SCR_EditBoxComponent edit = SCR_EditBoxComponent.GetEditBoxComponent(key, GetRootWidget());
|
||||
if (edit && edit.IsInWriteMode()) return;
|
||||
|
||||
@@ -28,23 +28,25 @@ enum ERR_AdminPage
|
||||
TELEMETRY = 24,
|
||||
VEHICLES = 25,
|
||||
CHECKPOINTS = 26,
|
||||
VEHICLE_POOL = 27,
|
||||
PACKS = 28,
|
||||
}
|
||||
|
||||
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"}; }
|
||||
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"}; }
|
||||
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 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> 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, 7, 5, 1, 0, 3, 7, 0, 4, 6, 1, 1, 1, 2, 5, 4, 4, 4, 2, 4, 1, 2, 3, 0, 0, 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};
|
||||
if (page < 0 || page >= groups.Count()) return -1;
|
||||
return groups[page];
|
||||
}
|
||||
static int MainPage(int group)
|
||||
{
|
||||
array<int> pages = {ERR_AdminPage.MISSION, ERR_AdminPage.FORCES, ERR_AdminPage.HQ, ERR_AdminPage.INTEL, ERR_AdminPage.ITEMS, ERR_AdminPage.MODS, ERR_AdminPage.SPEECH, ERR_AdminPage.STATUS};
|
||||
array<int> pages = {ERR_AdminPage.MISSION, ERR_AdminPage.FORCES, ERR_AdminPage.HQ, ERR_AdminPage.INTEL, ERR_AdminPage.MODS, ERR_AdminPage.SPEECH, ERR_AdminPage.STATUS};
|
||||
if (group < 0 || group >= pages.Count()) return -1;
|
||||
return pages[group];
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ class ERR_AdminSession
|
||||
protected int m_Cursor;
|
||||
protected string m_ItemAddon;
|
||||
protected int m_ItemPage;
|
||||
protected int m_ItemFilter;
|
||||
protected string m_ItemQuery;
|
||||
protected float m_LastRequest = -1000;
|
||||
protected float m_LastSnapshot = -1000;
|
||||
protected float m_LastLog = -1000;
|
||||
@@ -68,7 +70,7 @@ class ERR_AdminSession
|
||||
return;
|
||||
}
|
||||
array<string> replies = {};
|
||||
if (!StartBackpackCommand(arguments, replies) && !ItemCatalogCommand(arguments, replies) && !JournalCommand(arguments, replies) && !m_Tools.Execute(m_Mode, m_Controller, arguments, replies))
|
||||
if (!StartBackpackCommand(arguments, replies) && !ItemCatalogCommand(arguments, replies) && !VehicleCatalogCommand(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);
|
||||
@@ -107,6 +109,22 @@ class ERR_AdminSession
|
||||
if (parts.IsEmpty() || parts[0] != "items") return false;
|
||||
int page;
|
||||
ERR_ServerItemCatalog catalog = GetGame().ERR_ServerItems(); catalog.Start(m_Mode);
|
||||
if (parts.Count() == 4 && parts[1] == "mod" && catalog.Find(parts[2]) && (parts[3] == "0" || parts[3] == "1"))
|
||||
{
|
||||
if (GetGame().ERR_ItemSelections().SetSwitch(parts[2], "mod", parts[3] == "1")) replies.Insert("Mod selection saved for future random equipment and civilian vehicles. Individual choices are remembered.");
|
||||
else replies.Insert("Could not save mod selection. Previous choices retained.");
|
||||
return true;
|
||||
}
|
||||
if (parts.Count() >= 5 && parts[1] == "browse" && catalog.Find(parts[2]))
|
||||
{
|
||||
int filter;
|
||||
if (!ERR_CommandArguments.Integer(parts[3], page) || page < 0 || page > 2730 || !ERR_CommandArguments.Integer(parts[4], filter) || filter < 0 || filter > 2) return true;
|
||||
string query;
|
||||
for (int q = 5; q < parts.Count(); q++) { if (!query.IsEmpty()) query += " "; query += parts[q]; }
|
||||
if (query.Length() > 64) return true;
|
||||
m_ItemAddon = parts[2]; m_ItemPage = page; m_ItemFilter = filter; m_ItemQuery = query;
|
||||
return true;
|
||||
}
|
||||
if (parts.Count() == 4 && parts[1] == "preset")
|
||||
{
|
||||
if (GetGame().ERR_ItemSelections().Preset(parts[2], parts[3])) replies.Insert("Preset " + parts[2] + " completed: " + parts[3] + ". Future random equipment uses the saved selection.");
|
||||
@@ -129,7 +147,7 @@ class ERR_AdminSession
|
||||
}
|
||||
if (parts.Count() != 4 || parts[1] != "list" || !catalog.Find(parts[2]) || !ERR_CommandArguments.Integer(parts[3], page) || page < 0 || page > 2730)
|
||||
{ replies.Insert("Choose a loaded server mod in Items, then press Show items."); return true; }
|
||||
m_ItemAddon = parts[2]; m_ItemPage = page;
|
||||
m_ItemAddon = parts[2]; m_ItemPage = page; m_ItemFilter = 0; m_ItemQuery = "";
|
||||
return true;
|
||||
}
|
||||
protected bool JournalCommand(string arguments, array<string> replies)
|
||||
@@ -162,7 +180,8 @@ class ERR_AdminSession
|
||||
if (m_Mode.Discovery()) values.Insert("radioIntelProgress=" + m_Mode.Discovery().RadioIntelStatus());
|
||||
m_Mode.Admin().Capabilities(values);
|
||||
ERR_RecommendedAddons.Snapshot(m_Mode, values);
|
||||
GetGame().ERR_ServerItems().Snapshot(m_Mode, values, m_ItemAddon, m_ItemPage);
|
||||
GetGame().ERR_ServerItems().Snapshot(m_Mode, values, m_ItemAddon, m_ItemPage, m_ItemFilter, m_ItemQuery);
|
||||
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);
|
||||
m_Controller.ERR_AdminDeliverSnapshot(m_Token, true, values, status);
|
||||
@@ -209,4 +228,38 @@ class ERR_AdminSession
|
||||
m_Tools.Stop();
|
||||
if (m_Mode) m_Mode.OnStop().Remove(Stop);
|
||||
}
|
||||
|
||||
protected string m_VehicleAddon;
|
||||
protected int m_VehiclePage;
|
||||
protected int m_VehicleFilter;
|
||||
protected int m_VehicleRole;
|
||||
protected string m_VehicleQuery;
|
||||
protected bool VehicleCatalogCommand(string arguments, array<string> replies)
|
||||
{
|
||||
array<string> parts = {}; arguments.Split(" ", parts, true);
|
||||
if (parts.IsEmpty() || parts[0] != "vehicles") return false;
|
||||
ERR_ServerVehicleCatalog catalog = GetGame().ERR_ServerVehicles(); catalog.Start();
|
||||
if (parts.Count() == 6 && parts[1] == "role" && catalog.Find(parts[2]))
|
||||
{
|
||||
int role;
|
||||
if (!ERR_CommandArguments.Integer(parts[3], role) || role < 0 || role > 4 || (parts[5] != "0" && parts[5] != "1")) return true;
|
||||
if (parts[4] != "*" && (!catalog.Contains(parts[2], parts[4]) || (parts[5] == "1" && !catalog.Capable(parts[4], role)))) return true;
|
||||
if (GetGame().ERR_ItemSelections().SetVehicleRole(parts[2], parts[4], role, parts[5] == "1")) replies.Insert("Vehicle assignment saved for future spawns. Existing vehicles stay.");
|
||||
else replies.Insert("Could not save assignment. Previous choices retained.");
|
||||
return true;
|
||||
}
|
||||
if (parts.Count() >= 6 && parts[1] == "browse" && catalog.Find(parts[2]))
|
||||
{
|
||||
int page, filter, role;
|
||||
if (!ERR_CommandArguments.Integer(parts[3], page) || page < 0 || page > 2730 || !ERR_CommandArguments.Integer(parts[4], filter) || filter < 0 || filter > 2 || !ERR_CommandArguments.Integer(parts[5], role) || role < 0 || role > 4) return true;
|
||||
string query;
|
||||
for (int q = 6; q < parts.Count(); q++) { if (!query.IsEmpty()) query += " "; query += parts[q]; }
|
||||
if (query.Length() > 64) return true;
|
||||
m_VehicleAddon = parts[2]; m_VehiclePage = page; m_VehicleFilter = filter; m_VehicleQuery = query; m_VehicleRole = role;
|
||||
replies.Insert("Vehicle list updated. Assignments apply to future spawns.");
|
||||
return true;
|
||||
}
|
||||
replies.Insert("Choose a mod in Content / Vehicles, then choose a use, then assign vehicles.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,13 @@ modded class ERR_AdminMenu
|
||||
override void OnMenuOpen()
|
||||
{
|
||||
super.OnMenuOpen();
|
||||
Widget list = GetRootWidget().FindAnyWidget("ModList");
|
||||
Widget list = GetRootWidget().FindAnyWidget("PackList");
|
||||
foreach (string pack : ERR_ContentRegistry.Ids())
|
||||
{
|
||||
Widget row = GetGame().GetWorkspace().CreateWidgets("{F092D45656A8E02B}UI/layouts/ERR/ContentRow.layout", list);
|
||||
if (!row) { Close(); return; }
|
||||
m_ModRows.Set(pack, row);
|
||||
row.FindAnyWidget("EditItems").SetVisible(false); row.FindAnyWidget("EditVehicles").SetVisible(false);
|
||||
TextWidget label = TextWidget.Cast(row.FindAnyWidget("PackLabel"));
|
||||
if (label) label.SetText(ERR_ContentRegistry.Label(pack));
|
||||
SCR_ButtonTextComponent enable = SCR_ButtonTextComponent.GetButtonText("Enable", row);
|
||||
|
||||
@@ -41,6 +41,12 @@ class ERR_ItemPreferences
|
||||
for (int i = 0; i < 16; i++) if ("0123456789abcdef".IndexOf(value.Substring(i, 1)) < 0) return false;
|
||||
return true;
|
||||
}
|
||||
static bool RoleKey(string key)
|
||||
{
|
||||
if (key.Length() != 3 && key.Length() != 18) return false;
|
||||
if (key.Substring(0, 1) != "r" || "01234".IndexOf(key.Substring(1, 1)) < 0) return false;
|
||||
return key.Substring(2, key.Length() - 2) == "*" || Guid(key.Substring(2, key.Length() - 2));
|
||||
}
|
||||
static bool WeightKey(string key)
|
||||
{
|
||||
return key == "w*" || (key.Length() == 17 && key.Substring(0, 1) == "w" && Guid(key.Substring(1, 16)));
|
||||
@@ -67,7 +73,7 @@ class ERR_ItemPreferences
|
||||
{
|
||||
if (parts.Count() != 4 || !Name(parts[0]) || !Guid(parts[1])) return false;
|
||||
if (WeightKey(parts[2])) { if (!WeightValue(parts[3])) return false; }
|
||||
else if ((parts[2] != "*" && !Guid(parts[2])) || (parts[3] != "0" && parts[3] != "1")) return false;
|
||||
else if ((parts[2] != "*" && parts[2] != "mod" && parts[2] != "v*" && !RoleKey(parts[2]) && !Guid(parts[2]) && !(parts[2].Length() == 17 && parts[2].StartsWith("v") && Guid(parts[2].Substring(1, 16)))) || (parts[3] != "0" && parts[3] != "1")) return false;
|
||||
}
|
||||
}
|
||||
if (names.Count() > 17) return false;
|
||||
@@ -137,13 +143,91 @@ class ERR_ItemPreferences
|
||||
key = addon + "|*";
|
||||
return !m_Rules.Contains(key) || m_Rules.Get(key) == 1;
|
||||
}
|
||||
bool ModEnabled(string addon)
|
||||
{
|
||||
string key = addon + "|mod";
|
||||
return !m_Rules.Contains(key) || m_Rules.Get(key) == 1;
|
||||
}
|
||||
bool VehicleEnabled(string addon, string item)
|
||||
{
|
||||
string key = addon + "|v" + item;
|
||||
if (m_Rules.Contains(key)) return m_Rules.Get(key) == 1;
|
||||
key = addon + "|v*";
|
||||
return !m_Rules.Contains(key) || m_Rules.Get(key) == 1;
|
||||
}
|
||||
bool SetSwitch(string addon, string key, bool enabled)
|
||||
{
|
||||
if (!Guid(addon) || (key != "mod" && key != "v*" && !(key.Length() == 17 && key.StartsWith("v") && Guid(key.Substring(1, 16))))) return false;
|
||||
array<string> next = {};
|
||||
foreach (string row : m_Records)
|
||||
{
|
||||
array<string> parts = {}; row.Split("|", parts, false);
|
||||
if (parts.Count() == 4 && parts[0] == "active" && parts[1] == addon && (parts[2] == key || (key == "v*" && parts[2].StartsWith("v")))) continue;
|
||||
next.Insert(row);
|
||||
}
|
||||
string value = "0"; if (enabled) value = "1";
|
||||
next.Insert("active|" + addon + "|" + key + "|" + value);
|
||||
return Save(next);
|
||||
}
|
||||
bool SetVehicleRole(string addon, string item, int role, bool enabled)
|
||||
{
|
||||
if (!Guid(addon) || (item != "*" && !Guid(item)) || role < 0 || role > 4) return false;
|
||||
string prefix = "r" + role.ToString(), key = prefix + item;
|
||||
array<string> next = {};
|
||||
foreach (string row : m_Records)
|
||||
{
|
||||
array<string> parts = {}; row.Split("|", parts, false);
|
||||
if (parts.Count() == 4 && parts[0] == "active" && parts[1] == addon)
|
||||
{
|
||||
if (parts[2] == key || (item == "*" && parts[2].StartsWith(prefix))) continue;
|
||||
if (enabled && (parts[2] == "v" + item || (item == "*" && parts[2].StartsWith("v")))) continue;
|
||||
}
|
||||
next.Insert(row);
|
||||
}
|
||||
string value = "0"; if (enabled) value = "1";
|
||||
next.Insert("active|" + addon + "|" + key + "|" + value);
|
||||
if (enabled) next.Insert("active|" + addon + "|v" + item + "|1");
|
||||
return Save(next);
|
||||
}
|
||||
// -1 means inherit the mission's defaults. Shared-source explicit exclusions win.
|
||||
int VehicleRole(string addon, string item, int role)
|
||||
{
|
||||
string prefix = addon + "|r" + role.ToString();
|
||||
if (m_Rules.Contains(prefix + item)) return m_Rules.Get(prefix + item);
|
||||
if (m_Rules.Contains(prefix + "*")) return m_Rules.Get(prefix + "*");
|
||||
return -1;
|
||||
}
|
||||
bool AllowsVehicleRole(ResourceName prefab, int role, bool defaultEnabled)
|
||||
{
|
||||
if (role < 0 || role > 4 || !AllowsVehicle(prefab)) return false;
|
||||
string item = ERR_TerrainCatalog.Key(prefab);
|
||||
bool enabled = defaultEnabled;
|
||||
array<string> sources = SCR_AddonTool.GetResourceAddons(prefab);
|
||||
foreach (string project : sources)
|
||||
{
|
||||
if (!m_Loaded.Contains(project)) continue;
|
||||
int rule = VehicleRole(m_Loaded.Get(project), item, role);
|
||||
if (rule == 0) return false;
|
||||
if (rule == 1) enabled = true;
|
||||
}
|
||||
return enabled;
|
||||
}
|
||||
bool AllowsVehicle(ResourceName prefab)
|
||||
{
|
||||
string item = ERR_TerrainCatalog.Key(prefab);
|
||||
if (item.IsEmpty()) return false;
|
||||
array<string> sources = SCR_AddonTool.GetResourceAddons(prefab);
|
||||
foreach (string project : sources)
|
||||
if (m_Loaded.Contains(project) && (!ModEnabled(m_Loaded.Get(project)) || !VehicleEnabled(m_Loaded.Get(project), item))) return false;
|
||||
return true;
|
||||
}
|
||||
bool Allows(ResourceName prefab)
|
||||
{
|
||||
string item = ERR_TerrainCatalog.Key(prefab);
|
||||
if (item.IsEmpty()) return true;
|
||||
array<string> sources = SCR_AddonTool.GetResourceAddons(prefab);
|
||||
foreach (string project : sources)
|
||||
if (m_Loaded.Contains(project) && !Enabled(m_Loaded.Get(project), item)) return false;
|
||||
if (m_Loaded.Contains(project) && (!ModEnabled(m_Loaded.Get(project)) || !Enabled(m_Loaded.Get(project), item))) return false;
|
||||
return true;
|
||||
}
|
||||
bool Set(string addon, string item, bool enabled)
|
||||
@@ -153,7 +237,7 @@ class ERR_ItemPreferences
|
||||
foreach (string row : m_Records)
|
||||
{
|
||||
array<string> parts = {}; row.Split("|", parts, false);
|
||||
if (parts.Count() == 4 && parts[0] == "active" && parts[1] == addon && (item == "*" || parts[2] == item) && !WeightKey(parts[2])) continue;
|
||||
if (parts.Count() == 4 && parts[0] == "active" && parts[1] == addon && (parts[2] == item || (item == "*" && (parts[2] == "*" || Guid(parts[2]))))) continue;
|
||||
next.Insert(row);
|
||||
}
|
||||
string value = "0"; if (enabled) value = "1";
|
||||
@@ -225,6 +309,6 @@ class ERR_ItemPreferences
|
||||
{
|
||||
foreach (string row : m_Records) if (row != "active" && row.IndexOf("|") < 0) values.Insert("itemPreset=" + row);
|
||||
if (m_ReadOnly) values.Insert("itemsStorage=Read-only: preference file needs repair");
|
||||
else values.Insert("itemsStorage=Auto-saved; applies to future random equipment. Presets include selections and probability weights for all mods.");
|
||||
else values.Insert("itemsStorage=Auto-saved for future spawns. Presets include mod switches, item and vehicle-role choices, and equipment weights.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ modded class ERR_AdminMenu
|
||||
{
|
||||
GetRootWidget().Update();
|
||||
PrintFormat("ERR MOD MENU: page=%1 rows=%2 saved=%3 active=%4", m_Page, m_ModRows.Count(), m_ModSaved.Count(), m_ModActive.Count());
|
||||
if (m_Page != 3 || m_ModRows.Count() != ERR_ContentRegistry.Ids().Count()) return false;
|
||||
if (m_Page != ERR_AdminPage.PACKS || m_ModRows.Count() != ERR_ContentRegistry.Ids().Count()) return false;
|
||||
foreach (string pack, Widget row : m_ModRows)
|
||||
{
|
||||
TextWidget text = TextWidget.Cast(row.FindAnyWidget("PackState"));
|
||||
@@ -286,7 +286,7 @@ class ERR_NativeAdminMenuProbe
|
||||
Pass("all thirteen setting descriptions fit their native text widgets");
|
||||
System.MakeScreenshot("$logs:err-admin-open");
|
||||
if (!menu.ERR_ProbeNavigation(0)) return Fail("Esc entry did not open Mission page with shared navigation");
|
||||
menu.ERR_ProbePage(3);
|
||||
menu.ERR_ProbePage(ERR_AdminPage.PACKS);
|
||||
if (!menu.ERR_ProbeMods()) return Fail("mod rows missing state or collapsed");
|
||||
Print("ERR MOD CAPTURE READY");
|
||||
menu.ERR_ProbePage(0);
|
||||
@@ -335,7 +335,7 @@ class ERR_NativeAdminMenuProbe
|
||||
if (m_Mode.Settings().Get("radioDefenceSeconds") != m_RadioSeconds) return Fail("test setting restoration failed");
|
||||
Pass("test setting restored without resetting menu drafts");
|
||||
System.MakeScreenshot("$logs:err-admin-hq");
|
||||
menu.ERR_ProbePage(3);
|
||||
menu.ERR_ProbePage(ERR_AdminPage.PACKS);
|
||||
menu.ERR_ProbeMod(!m_RhsSaved);
|
||||
m_Wait = now + 2000; m_Stage = 11; return 0;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ class ERR_ServerItem
|
||||
{
|
||||
ResourceName Prefab;
|
||||
string Label;
|
||||
string SearchLabel;
|
||||
bool Eligible;
|
||||
}
|
||||
class ERR_ServerAddonItems
|
||||
@@ -74,6 +75,7 @@ class ERR_ServerItemCatalog
|
||||
if (addon.Label.IsEmpty()) addon.Label = Clean(addon.Project);
|
||||
m_Addons.Insert(addon);
|
||||
}
|
||||
SortAddons(m_Addons);
|
||||
Queue(manager.GetEntityCatalogOfType(EEntityCatalogType.ITEM, false));
|
||||
array<Faction> all = {}; factions.GetFactionsList(all);
|
||||
foreach (Faction faction : all)
|
||||
@@ -109,6 +111,7 @@ class ERR_ServerItemCatalog
|
||||
item = new ERR_ServerItem(); item.Prefab = prefab; item.Eligible = eligible;
|
||||
item.Label = Clean(entry.GetEntityName());
|
||||
if (item.Label.IsEmpty()) item.Label = Clean(FilePath.StripPath(prefab.GetPath()));
|
||||
item.SearchLabel = WidgetManager.Translate(item.Label);
|
||||
m_Seen.Set(key, item);
|
||||
array<string> sources = SCR_AddonTool.GetResourceAddons(prefab);
|
||||
bool attributed;
|
||||
@@ -119,16 +122,51 @@ class ERR_ServerItemCatalog
|
||||
if (m_Cursor >= m_Pending.Count())
|
||||
{
|
||||
GetGame().GetCallqueue().Remove(Scan);
|
||||
foreach (ERR_ServerAddonItems sortedAddon : m_Addons) SortItems(sortedAddon.Items);
|
||||
PrintFormat("Reintroduced [server items]: addons=%1 uniqueItems=%2 unattributed=%3 truncated=%4", m_Addons.Count(), m_Seen.Count(), m_Unattributed, m_Truncated);
|
||||
}
|
||||
}
|
||||
static void SortAddons(array<ref ERR_ServerAddonItems> addons)
|
||||
{
|
||||
array<string> order = {}; map<string, ref ERR_ServerAddonItems> entries = new map<string, ref ERR_ServerAddonItems>();
|
||||
foreach (ERR_ServerAddonItems addon : addons)
|
||||
{
|
||||
string key = addon.Label + "|" + addon.Id; key.ToLower(); order.Insert(key); entries.Set(key, addon);
|
||||
}
|
||||
order.Sort(); addons.Clear();
|
||||
foreach (string key : order) addons.Insert(entries.Get(key));
|
||||
}
|
||||
static void SortItems(array<ref ERR_ServerItem> items)
|
||||
{
|
||||
array<string> order = {}; map<string, ref ERR_ServerItem> entries = new map<string, ref ERR_ServerItem>();
|
||||
foreach (ERR_ServerItem item : items)
|
||||
{
|
||||
string key = item.SearchLabel + "|" + ERR_TerrainCatalog.Key(item.Prefab); key.ToLower(); order.Insert(key); entries.Set(key, item);
|
||||
}
|
||||
order.Sort(); items.Clear();
|
||||
foreach (string key : order) items.Insert(entries.Get(key));
|
||||
}
|
||||
static string Flag(bool value)
|
||||
{
|
||||
if (value) return "1";
|
||||
return "0";
|
||||
}
|
||||
static bool Matches(string label, string path, bool enabled, int filter, string query)
|
||||
{
|
||||
if ((filter == 1 && !enabled) || (filter == 2 && enabled)) return false;
|
||||
if (query.IsEmpty()) return true;
|
||||
string haystack = label + " " + path; haystack.ToLower(); query.ToLower();
|
||||
array<string> words = {}; query.Split(" ", words, true);
|
||||
foreach (string word : words) if (!haystack.Contains(word)) return false;
|
||||
return true;
|
||||
}
|
||||
static string Share(int tickets, int total)
|
||||
{
|
||||
if (total <= 0 || tickets <= 0) return "0%";
|
||||
float percent = Math.Round(tickets * 10000.0 / total) / 100.0;
|
||||
return percent.ToString() + "%";
|
||||
}
|
||||
void Snapshot(ERR_GameMode mode, array<string> values, string selected, int page)
|
||||
void Snapshot(ERR_GameMode mode, array<string> values, string selected, int page, int filter = 0, string query = "")
|
||||
{
|
||||
Start(mode);
|
||||
ERR_ItemPreferences preferences = GetGame().ERR_ItemSelections();
|
||||
@@ -151,7 +189,7 @@ class ERR_ServerItemCatalog
|
||||
foreach (ERR_ServerAddonItems addon : m_Addons)
|
||||
{
|
||||
string defaultEnabled = "0"; if (preferences.Enabled(addon.Id, "*")) defaultEnabled = "1";
|
||||
values.Insert("serverMod=" + addon.Id + "|" + addon.Label + "|" + addon.Items.Count().ToString() + "|" + addon.EnabledCount.ToString() + "|" + defaultEnabled);
|
||||
values.Insert("serverMod=" + addon.Id + "|" + addon.Label + "|" + addon.Items.Count().ToString() + "|" + addon.EnabledCount.ToString() + "|" + defaultEnabled + "|" + Flag(preferences.ModEnabled(addon.Id)));
|
||||
}
|
||||
string state = "Catalog complete";
|
||||
if (!m_Started || m_Cursor < m_Pending.Count()) state = "Scanning inventory catalogs";
|
||||
@@ -159,24 +197,28 @@ class ERR_ServerItemCatalog
|
||||
if (m_Unattributed > 0) state += "; " + m_Unattributed.ToString() + " items could not be attributed";
|
||||
values.Insert("itemsScan=" + state);
|
||||
int extraChance = mode.Settings().Runtime("automaticEquipmentChancePercent");
|
||||
values.Insert("weightChance=" + extraChance.ToString() + "% current chance per eligible soldier. The Spawn chances page controls which extra item is chosen; inventory fit can still prevent a grant.");
|
||||
values.Insert("weightChance=" + extraChance.ToString() + "% current chance per eligible soldier. Advanced weights controls which extra item is chosen; inventory fit can still prevent a grant.");
|
||||
ERR_ServerAddonItems chosen = Find(selected);
|
||||
if (!chosen) return;
|
||||
values.Insert("modWeight=" + chosen.Id + "|" + preferences.WeightSetting(chosen.Id, "*").ToString() + "|" + Share(chosen.TicketWeight, totalWeight));
|
||||
int lastPage = Math.Max(0, (chosen.Items.Count() - 1) / PAGE_SIZE);
|
||||
array<ref ERR_ServerItem> matches = {};
|
||||
foreach (ERR_ServerItem candidate : chosen.Items)
|
||||
if (Matches(candidate.SearchLabel, candidate.Prefab.GetPath(), preferences.Allows(candidate.Prefab) && (!mode.Content() || mode.Content().Allows(candidate.Prefab)), filter, query)) matches.Insert(candidate);
|
||||
int lastPage = Math.Max(0, (matches.Count() - 1) / PAGE_SIZE);
|
||||
page = Math.Clamp(page, 0, lastPage);
|
||||
values.Insert("itemsPage=" + selected + "|" + page.ToString() + "|" + lastPage.ToString() + "|" + chosen.Items.Count().ToString());
|
||||
int end = Math.Min(chosen.Items.Count(), (page + 1) * PAGE_SIZE);
|
||||
values.Insert("itemsPage=" + selected + "|" + page.ToString() + "|" + lastPage.ToString() + "|" + matches.Count().ToString());
|
||||
int end = Math.Min(matches.Count(), (page + 1) * PAGE_SIZE);
|
||||
for (int i = page * PAGE_SIZE; i < end; i++)
|
||||
{
|
||||
ERR_ServerItem item = chosen.Items[i];
|
||||
ERR_ServerItem item = matches[i];
|
||||
string stateText = "Catalog item; not in automatic equipment pool";
|
||||
if (item.Eligible) stateText = "Eligible for automatic equipment";
|
||||
if (mode.Content() && !mode.Content().Allows(item.Prefab)) stateText = "Blocked by current content-pack selection";
|
||||
string key = ERR_TerrainCatalog.Key(item.Prefab);
|
||||
string enabled = "0";
|
||||
if (preferences.Enabled(selected, key)) enabled = "1";
|
||||
if (!preferences.Allows(item.Prefab)) stateText = "Disabled by item/mod selection";
|
||||
if (!preferences.Allows(item.Prefab)) stateText = "Disabled by a source mod or item selection";
|
||||
if (!preferences.ModEnabled(selected)) stateText = "Mod disabled - item choice is remembered";
|
||||
string effective = "0";
|
||||
if (preferences.Allows(item.Prefab) && (!mode.Content() || mode.Content().Allows(item.Prefab))) effective = "1";
|
||||
values.Insert("itemWeight=" + key + "|" + preferences.WeightSetting(selected, key).ToString() + "|" + Share(ERR_AutomaticEquipmentCatalog.TicketWeight(item.Prefab), totalWeight));
|
||||
@@ -189,6 +231,12 @@ modded class ArmaReforgerScripted
|
||||
{
|
||||
protected ref ERR_ServerItemCatalog m_ERRServerItems;
|
||||
protected ref ERR_ItemPreferences m_ERRItemSelections;
|
||||
protected ref ERR_ServerVehicleCatalog m_ERRServerVehicles;
|
||||
ERR_ServerVehicleCatalog ERR_ServerVehicles()
|
||||
{
|
||||
if (!m_ERRServerVehicles) m_ERRServerVehicles = new ERR_ServerVehicleCatalog();
|
||||
return m_ERRServerVehicles;
|
||||
}
|
||||
ERR_ItemPreferences ERR_ItemSelections()
|
||||
{
|
||||
if (!m_ERRItemSelections) m_ERRItemSelections = new ERR_ItemPreferences();
|
||||
@@ -202,7 +250,8 @@ modded class ArmaReforgerScripted
|
||||
override protected void OnBeforeWorldCleanup()
|
||||
{
|
||||
if (m_ERRServerItems) m_ERRServerItems.Stop();
|
||||
m_ERRServerItems = null; m_ERRItemSelections = null;
|
||||
if (m_ERRServerVehicles) m_ERRServerVehicles.Stop();
|
||||
m_ERRServerItems = null; m_ERRItemSelections = null; m_ERRServerVehicles = null;
|
||||
super.OnBeforeWorldCleanup();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,21 @@ modded class ERR_AdminMenu
|
||||
protected ref array<string> m_ItemPresets = {};
|
||||
protected ref map<string, int> m_ItemSaved = new map<string, int>();
|
||||
protected ref map<string, int> m_AddonSelectionStates = new map<string, int>();
|
||||
protected ref map<string, int> m_ServerModEnabled = new map<string, int>();
|
||||
protected ref map<string, string> m_ServerModLabels = new map<string, string>();
|
||||
protected ref map<string, int> m_ServerModItemCounts = new map<string, int>();
|
||||
protected ref map<string, int> m_ServerModVehicleCounts = new map<string, int>();
|
||||
protected string m_ServerScanState;
|
||||
protected string m_ItemRowSignature;
|
||||
protected string m_LastItemChoice;
|
||||
protected int m_LastItemFilter = -1;
|
||||
protected string m_ShownItemAddon;
|
||||
protected int m_ShownItemPage;
|
||||
protected int m_LastItemPage;
|
||||
override void OnMenuOpen()
|
||||
{
|
||||
super.OnMenuOpen();
|
||||
foreach (string name : {"ItemsShow", "ItemsPrevious", "ItemsNext", "ItemsEnableAll", "ItemsDisableAll", "ItemsPresetSave", "ItemsPresetLoad", "ItemsPresetDelete"})
|
||||
foreach (string name : {"ItemsShow", "ItemsClear", "ItemsPrevious", "ItemsNext", "ItemsEnableAll", "ItemsDisableAll", "ItemsPresetSave", "ItemsPresetLoad", "ItemsPresetDelete"})
|
||||
{
|
||||
SCR_ButtonTextComponent button = SCR_ButtonTextComponent.GetButtonText(name, GetRootWidget());
|
||||
if (!button) { Close(); return; }
|
||||
@@ -24,6 +32,12 @@ modded class ERR_AdminMenu
|
||||
if (name == "ItemsEnableAll") ERR_SelectionStyle.Action(button, true);
|
||||
button.m_OnClicked.Insert(OnItemsButton); m_ItemButtons.Insert(button);
|
||||
}
|
||||
foreach (string filterName : {"ItemsFilter", "ModsFilter"})
|
||||
{
|
||||
SCR_ComboBoxComponent filter = SCR_ComboBoxComponent.GetComboBoxComponent(filterName, GetRootWidget());
|
||||
if (filterName == "ModsFilter") filter.AddItem("With items / vehicles");
|
||||
filter.AddItem("All"); filter.AddItem("Enabled"); filter.AddItem("Disabled"); filter.SetCurrentItem(0, false, false, false);
|
||||
}
|
||||
ERR_SelectionStyle.Status(GetRootWidget(), "ItemsSelectionState", -1);
|
||||
UpdateActions();
|
||||
}
|
||||
@@ -31,8 +45,10 @@ modded class ERR_AdminMenu
|
||||
{
|
||||
foreach (SCR_ButtonTextComponent button : m_ItemButtons) if (button) button.m_OnClicked.Remove(OnItemsButton);
|
||||
ClearItemRows(); m_ItemPresets.Clear();
|
||||
foreach (string addon, Widget row : m_ServerAddonRows) SCR_ButtonTextComponent.GetButtonText("Enable", row).m_OnClicked.Remove(OnLoadedModEdit);
|
||||
m_AddonSelectionStates.Clear();
|
||||
foreach (string addon, Widget row : m_ServerAddonRows)
|
||||
foreach (string action : {"Enable", "Disable", "EditItems", "EditVehicles"}) SCR_ButtonTextComponent.GetButtonText(action, row).m_OnClicked.Remove(OnLoadedModEdit);
|
||||
m_AddonSelectionStates.Clear(); m_ServerModEnabled.Clear(); m_ServerModLabels.Clear(); m_ServerModItemCounts.Clear(); m_ServerModVehicleCounts.Clear();
|
||||
m_LastItemChoice = ""; m_LastItemFilter = -1; m_ItemRowSignature = "";
|
||||
m_ItemButtons.Clear(); m_ServerAddonRows.Clear(); m_ServerAddonKeys.Clear(); m_ServerAddonLabels.Clear();
|
||||
m_ShownItemAddon = ""; m_ShownItemPage = 0; m_LastItemPage = 0;
|
||||
super.OnMenuClose();
|
||||
@@ -49,11 +65,22 @@ modded class ERR_AdminMenu
|
||||
bool ready = m_Ready && !m_ServerAddonKeys.IsEmpty();
|
||||
SCR_ComboBoxComponent combo = SCR_ComboBoxComponent.GetComboBoxComponent("ItemsMod", GetRootWidget());
|
||||
if (combo) combo.SetEnabled(ready);
|
||||
foreach (string addon, Widget loadedRow : m_ServerAddonRows) SCR_ButtonTextComponent.GetButtonText("Enable", loadedRow).SetEnabled(ready);
|
||||
foreach (string addon, Widget loadedRow : m_ServerAddonRows)
|
||||
{
|
||||
bool hasItems = m_ServerModItemCounts.Get(addon) > 0;
|
||||
bool hasVehicles = m_ServerModVehicleCounts.Get(addon) > 0;
|
||||
bool selectedMod = m_ServerModEnabled.Get(addon) == 1;
|
||||
SCR_ButtonTextComponent.GetButtonText("Enable", loadedRow).SetEnabled(ready && (hasItems || hasVehicles) && !selectedMod);
|
||||
SCR_ButtonTextComponent.GetButtonText("Disable", loadedRow).SetEnabled(ready && (hasItems || hasVehicles) && selectedMod);
|
||||
SCR_ButtonTextComponent.GetButtonText("EditItems", loadedRow).SetEnabled(ready && hasItems);
|
||||
SCR_ButtonTextComponent.GetButtonText("EditVehicles", loadedRow).SetEnabled(ready && hasVehicles);
|
||||
ERR_SelectionStyle.Pair(loadedRow, "Enable", "Disable", m_ServerModEnabled.Get(addon));
|
||||
}
|
||||
bool rowReady = ItemSelectionReady();
|
||||
foreach (string key, Widget row : m_ItemRows)
|
||||
{
|
||||
SCR_ButtonTextComponent.GetButtonText("Enable", row).SetEnabled(ready && m_ItemSaved.Contains(key) && m_ItemSaved.Get(key) == 0);
|
||||
SCR_ButtonTextComponent.GetButtonText("Disable", row).SetEnabled(ready && m_ItemSaved.Contains(key) && m_ItemSaved.Get(key) == 1);
|
||||
SCR_ButtonTextComponent.GetButtonText("Enable", row).SetEnabled(rowReady && m_ItemSaved.Contains(key) && m_ItemSaved.Get(key) == 0);
|
||||
SCR_ButtonTextComponent.GetButtonText("Disable", row).SetEnabled(rowReady && m_ItemSaved.Contains(key) && m_ItemSaved.Get(key) == 1);
|
||||
int selected = -1; if (m_ItemSaved.Contains(key)) selected = m_ItemSaved.Get(key);
|
||||
ERR_SelectionStyle.Pair(row, "Enable", "Disable", selected);
|
||||
}
|
||||
@@ -65,7 +92,7 @@ modded class ERR_AdminMenu
|
||||
if (button.GetRootWidget().GetName() == "ItemsPrevious") enabled = ready && m_ShownItemPage > 0;
|
||||
if (button.GetRootWidget().GetName() == "ItemsNext") enabled = ready && m_ShownItemPage < m_LastItemPage;
|
||||
string name = button.GetRootWidget().GetName();
|
||||
if (name == "ItemsEnableAll" || name == "ItemsDisableAll") enabled = ready && !m_ShownItemAddon.IsEmpty();
|
||||
if (name == "ItemsEnableAll" || name == "ItemsDisableAll") enabled = rowReady;
|
||||
if (name == "ItemsPresetLoad" || name == "ItemsPresetDelete") enabled = ready && !m_ItemPresets.IsEmpty();
|
||||
button.SetEnabled(enabled);
|
||||
}
|
||||
@@ -74,7 +101,7 @@ modded class ERR_AdminMenu
|
||||
{
|
||||
array<string> keys = {}, labels = {};
|
||||
array<string> itemRows = {}, presets = {};
|
||||
string scan, shown;
|
||||
string scan, shown, signature;
|
||||
int page, last, total;
|
||||
if (authorized && values)
|
||||
foreach (string entry : values)
|
||||
@@ -87,9 +114,11 @@ modded class ERR_AdminMenu
|
||||
if (field == "itemPreset") presets.Insert(value);
|
||||
if (field == "itemsPage" && parts.Count() == 4)
|
||||
{ shown = parts[0]; page = parts[1].ToInt(); last = parts[2].ToInt(); total = parts[3].ToInt(); }
|
||||
if (field == "serverItem" && parts.Count() == 6) itemRows.Insert(value);
|
||||
if (field != "serverMod" || parts.Count() != 5 || keys.Contains(parts[0])) continue;
|
||||
if (field == "serverItem" && parts.Count() == 6) { itemRows.Insert(value); signature += parts[0]; }
|
||||
if (field == "vehicleMod" && parts.Count() == 5) m_ServerModVehicleCounts.Set(parts[0], parts[2].ToInt());
|
||||
if (field != "serverMod" || parts.Count() != 6 || keys.Contains(parts[0])) continue;
|
||||
keys.Insert(parts[0]); labels.Insert(parts[1]);
|
||||
m_ServerModEnabled.Set(parts[0], parts[5].ToInt()); m_ServerModLabels.Set(parts[0], parts[1]); m_ServerModItemCounts.Set(parts[0], parts[2].ToInt());
|
||||
int aggregate = ERR_SelectionStyle.Aggregate(parts[2].ToInt(), parts[3].ToInt(), parts[4] == "1");
|
||||
m_AddonSelectionStates.Set(parts[0], aggregate);
|
||||
Widget row = m_ServerAddonRows.Get(parts[0]);
|
||||
@@ -98,13 +127,11 @@ modded class ERR_AdminMenu
|
||||
row = GetGame().GetWorkspace().CreateWidgets("{F092D45656A8E02B}UI/layouts/ERR/ContentRow.layout", GetRootWidget().FindAnyWidget("ModList"));
|
||||
if (!row) continue;
|
||||
m_ServerAddonRows.Set(parts[0], row);
|
||||
row.FindAnyWidget("Disable").SetVisible(false);
|
||||
SCR_ButtonTextComponent edit = SCR_ButtonTextComponent.GetButtonText("Enable", row);
|
||||
edit.SetText("Edit items"); edit.m_OnClicked.Insert(OnLoadedModEdit);
|
||||
foreach (string action : {"Enable", "Disable", "EditItems", "EditVehicles"}) SCR_ButtonTextComponent.GetButtonText(action, row).m_OnClicked.Insert(OnLoadedModEdit);
|
||||
}
|
||||
ERR_SelectionStyle.Status(row, "SelectionState", aggregate);
|
||||
ERR_SelectionStyle.Status(row, "SelectionState", parts[5].ToInt());
|
||||
TextWidget.Cast(row.FindAnyWidget("PackLabel")).SetText(parts[1]);
|
||||
TextWidget.Cast(row.FindAnyWidget("PackState")).SetText("Loaded on server | " + parts[2] + " catalogued items | Edit future random equipment");
|
||||
TextWidget.Cast(row.FindAnyWidget("PackState")).SetText(parts[3] + " / " + parts[2] + " items enabled");
|
||||
}
|
||||
bool changed = keys.Count() != m_ServerAddonKeys.Count();
|
||||
if (!changed) for (int i = 0; i < keys.Count(); i++) if (keys[i] != m_ServerAddonKeys[i] || labels[i] != m_ServerAddonLabels[i]) changed = true;
|
||||
@@ -118,14 +145,16 @@ modded class ERR_AdminMenu
|
||||
combo.SetCurrentItem(Math.Max(0, keys.Find(draft)), false, false, false);
|
||||
m_ServerAddonKeys.Copy(keys); m_ServerAddonLabels.Copy(labels);
|
||||
}
|
||||
SetText("ItemsScan", scan);
|
||||
m_ServerScanState = scan;
|
||||
SetText("ItemsHelp", "Extra equipment | " + scan);
|
||||
if (!shown.IsEmpty())
|
||||
{
|
||||
bool moved = shown != m_ShownItemAddon || page != m_ShownItemPage;
|
||||
bool moved = shown != m_ShownItemAddon || page != m_ShownItemPage || signature != m_ItemRowSignature;
|
||||
m_ItemRowSignature = signature;
|
||||
m_ShownItemAddon = shown; m_ShownItemPage = page; m_LastItemPage = last;
|
||||
int selected = keys.Find(shown); string title = shown;
|
||||
if (selected >= 0) title = labels[selected];
|
||||
SetText("ItemsPageState", title + " | " + total.ToString() + " items | Page " + (page + 1).ToString() + "/" + (last + 1).ToString());
|
||||
SetText("ItemsPageState", total.ToString() + " matching items | Page " + (page + 1).ToString() + " of " + (last + 1).ToString());
|
||||
if (moved)
|
||||
{
|
||||
ClearItemRows();
|
||||
@@ -150,11 +179,11 @@ modded class ERR_AdminMenu
|
||||
TextWidget.Cast(row.FindAnyWidget("PackLabel")).SetText(WidgetManager.Translate(parts[1]));
|
||||
m_ItemSaved.Set(parts[0], parts[4].ToInt());
|
||||
ERR_SelectionStyle.Status(row, "SelectionState", parts[5].ToInt());
|
||||
string selection = "Disabled"; if (parts[4] == "1") selection = "Enabled";
|
||||
TextWidget.Cast(row.FindAnyWidget("PackState")).SetText(selection + " here | " + parts[3] + "\n" + parts[2]);
|
||||
TextWidget.Cast(row.FindAnyWidget("PackState")).SetText(parts[3] + " | " + FilePath.StripPath(parts[2]));
|
||||
}
|
||||
string empty; if (itemRows.IsEmpty()) empty = "No registered inventory items found for this mod.";
|
||||
string empty; if (itemRows.IsEmpty()) empty = "No items match. Clear search or choose All to see more.";
|
||||
SetText("ItemsText", empty);
|
||||
GetRootWidget().FindAnyWidget("ItemsText").SetVisible(!empty.IsEmpty());
|
||||
ScrollLayoutWidget scroll = ScrollLayoutWidget.Cast(GetRootWidget().FindAnyWidget("ItemsScroll"));
|
||||
if (moved && scroll) scroll.SetSliderPos(0, 0);
|
||||
}
|
||||
@@ -175,9 +204,15 @@ modded class ERR_AdminMenu
|
||||
{
|
||||
if (!button || !m_Ready || !IsFocused() || !m_Controller || m_Age > 6 || m_ClickDelay > 0) return;
|
||||
string name = button.GetRootWidget().GetName();
|
||||
if (name == "ItemsClear")
|
||||
{
|
||||
SCR_EditBoxComponent.GetEditBoxComponent("ItemsSearch", GetRootWidget()).SetValue("");
|
||||
SCR_ComboBoxComponent.GetComboBoxComponent("ItemsFilter", GetRootWidget()).SetCurrentItem(0, false, false, false);
|
||||
BrowseItems(0); return;
|
||||
}
|
||||
if (name == "ItemsEnableAll" || name == "ItemsDisableAll")
|
||||
{
|
||||
if (m_ShownItemAddon.IsEmpty()) return;
|
||||
if (!ItemSelectionReady()) return;
|
||||
string value = "0"; if (name == "ItemsEnableAll") value = "1";
|
||||
SubmitItems("items set " + m_ShownItemAddon + " * " + value); return;
|
||||
}
|
||||
@@ -206,23 +241,73 @@ modded class ERR_AdminMenu
|
||||
if (button.GetRootWidget().GetName() == "ItemsPrevious") page = Math.Max(0, page - 1);
|
||||
if (button.GetRootWidget().GetName() == "ItemsNext") page = Math.Min(m_LastItemPage, page + 1);
|
||||
}
|
||||
SubmitItems("items list " + addon + " " + page.ToString());
|
||||
if (name == "ItemsShow") page = 0;
|
||||
BrowseItems(page);
|
||||
}
|
||||
protected bool ItemSelectionReady()
|
||||
{
|
||||
SCR_ComboBoxComponent combo = SCR_ComboBoxComponent.GetComboBoxComponent("ItemsMod", GetRootWidget());
|
||||
if (!m_Ready || !combo) return false;
|
||||
int index = combo.GetCurrentIndex();
|
||||
return index >= 0 && index < m_ServerAddonKeys.Count() && m_ServerAddonKeys[index] == m_ShownItemAddon;
|
||||
}
|
||||
protected void BrowseItems(int page)
|
||||
{
|
||||
SCR_ComboBoxComponent combo = SCR_ComboBoxComponent.GetComboBoxComponent("ItemsMod", GetRootWidget());
|
||||
int index = combo.GetCurrentIndex(); if (index < 0 || index >= m_ServerAddonKeys.Count()) return;
|
||||
int filter = SCR_ComboBoxComponent.GetComboBoxComponent("ItemsFilter", GetRootWidget()).GetCurrentIndex();
|
||||
string query = SCR_EditBoxComponent.GetEditBoxComponent("ItemsSearch", GetRootWidget()).GetValue().Trim();
|
||||
if (query.Length() > 64) { ReceiveReply("Search can contain up to 64 characters."); return; }
|
||||
m_LastItemChoice = m_ServerAddonKeys[index]; m_LastItemFilter = filter;
|
||||
SubmitItems("items browse " + m_LastItemChoice + " " + page.ToString() + " " + filter.ToString() + " " + query);
|
||||
UpdateActions();
|
||||
}
|
||||
override void OnMenuUpdate(float tDelta)
|
||||
{
|
||||
super.OnMenuUpdate(tDelta);
|
||||
if (!m_Ready || !IsFocused() || m_ClickDelay > 0 || m_Age > 6) return;
|
||||
if (m_Page == ERR_AdminPage.ALLOWED_ITEMS)
|
||||
{
|
||||
SCR_ComboBoxComponent combo = SCR_ComboBoxComponent.GetComboBoxComponent("ItemsMod", GetRootWidget());
|
||||
int index = combo.GetCurrentIndex();
|
||||
int filter = SCR_ComboBoxComponent.GetComboBoxComponent("ItemsFilter", GetRootWidget()).GetCurrentIndex();
|
||||
if (index >= 0 && index < m_ServerAddonKeys.Count() && (m_ServerAddonKeys[index] != m_LastItemChoice || filter != m_LastItemFilter)) BrowseItems(0);
|
||||
}
|
||||
if (m_Page != ERR_AdminPage.ALLOWED_ITEMS) m_LastItemChoice = "";
|
||||
if (m_Page != ERR_AdminPage.MODS) return;
|
||||
string query = SCR_EditBoxComponent.GetEditBoxComponent("ModsSearch", GetRootWidget()).GetValue();
|
||||
int filter = SCR_ComboBoxComponent.GetComboBoxComponent("ModsFilter", GetRootWidget()).GetCurrentIndex();
|
||||
int shown;
|
||||
foreach (string addon, Widget row : m_ServerAddonRows)
|
||||
{
|
||||
bool visible = ERR_ServerItemCatalog.Matches(m_ServerModLabels.Get(addon), addon, m_ServerModEnabled.Get(addon) == 1, Math.Max(0, filter - 1), query);
|
||||
if (filter == 0 && m_ServerModItemCounts.Get(addon) == 0 && m_ServerModVehicleCounts.Get(addon) == 0) visible = false;
|
||||
row.SetVisible(visible); if (visible) shown++;
|
||||
string details = m_ServerModItemCounts.Get(addon).ToString() + " items / " + m_ServerModVehicleCounts.Get(addon).ToString() + " vehicles";
|
||||
if (m_ServerModItemCounts.Get(addon) == 0 && m_ServerModVehicleCounts.Get(addon) == 0) details = "No selectable items or vehicles";
|
||||
TextWidget.Cast(row.FindAnyWidget("PackState")).SetText(details);
|
||||
}
|
||||
SetText("ModsCount", shown.ToString() + " of " + m_ServerAddonRows.Count().ToString() + " loaded mods | " + m_ServerScanState);
|
||||
}
|
||||
protected void OnLoadedModEdit(SCR_ButtonBaseComponent button)
|
||||
{
|
||||
if (!button || !m_Ready || !IsFocused() || !m_Controller || m_Age > 6 || m_ClickDelay > 0) return;
|
||||
foreach (string addon, Widget row : m_ServerAddonRows)
|
||||
{
|
||||
if (button.GetRootWidget() != row.FindAnyWidget("Enable")) continue;
|
||||
SCR_ComboBoxComponent combo = SCR_ComboBoxComponent.GetComboBoxComponent("ItemsMod", GetRootWidget());
|
||||
if (combo) combo.SetCurrentItem(m_ServerAddonKeys.Find(addon), false, false, false);
|
||||
ShowPage(ERR_AdminPage.ALLOWED_ITEMS); SubmitItems("items list " + addon + " 0"); return;
|
||||
if (button.GetRootWidget() == row.FindAnyWidget("Enable")) { SubmitItems("items mod " + addon + " 1"); return; }
|
||||
if (button.GetRootWidget() == row.FindAnyWidget("Disable")) { SubmitItems("items mod " + addon + " 0"); return; }
|
||||
if (button.GetRootWidget() == row.FindAnyWidget("EditVehicles")) { OpenVehicleAddon(addon); return; }
|
||||
if (button.GetRootWidget() != row.FindAnyWidget("EditItems")) continue;
|
||||
SCR_ComboBoxComponent.GetComboBoxComponent("ItemsMod", GetRootWidget()).SetCurrentItem(m_ServerAddonKeys.Find(addon), false, false, false);
|
||||
SCR_EditBoxComponent.GetEditBoxComponent("ItemsSearch", GetRootWidget()).SetValue("");
|
||||
SCR_ComboBoxComponent.GetComboBoxComponent("ItemsFilter", GetRootWidget()).SetCurrentItem(0, false, false, false);
|
||||
ShowPage(ERR_AdminPage.ALLOWED_ITEMS); BrowseItems(0); return;
|
||||
}
|
||||
}
|
||||
protected void SubmitItems(string command)
|
||||
{
|
||||
m_ClickDelay = 0.3;
|
||||
SetText("Feedback", "Saving selection on the server...");
|
||||
SetText("Feedback", "Waiting for server confirmation...");
|
||||
m_Controller.ERR_AdminSubmit(command, true);
|
||||
}
|
||||
protected void ClearItemRows()
|
||||
@@ -237,7 +322,7 @@ modded class ERR_AdminMenu
|
||||
}
|
||||
protected void OnItemRowButton(SCR_ButtonBaseComponent button)
|
||||
{
|
||||
if (!button || !m_Ready || !IsFocused() || !m_Controller || m_Age > 6 || m_ClickDelay > 0 || m_ShownItemAddon.IsEmpty()) return;
|
||||
if (!button || !m_Ready || !IsFocused() || !m_Controller || m_Age > 6 || m_ClickDelay > 0 || !ItemSelectionReady()) return;
|
||||
foreach (string key, Widget row : m_ItemRows)
|
||||
{
|
||||
string value;
|
||||
|
||||
@@ -5,6 +5,7 @@ modded class ERR_AdminMenu
|
||||
protected ref map<string, Widget> m_WeightRows = new map<string, Widget>();
|
||||
protected ref map<string, int> m_WeightSaved = new map<string, int>();
|
||||
protected ref array<string> m_WeightMods = {};
|
||||
protected string m_WeightRowSignature;
|
||||
protected string m_WeightAddon;
|
||||
protected int m_WeightPage = -1;
|
||||
override void OnMenuOpen()
|
||||
@@ -68,7 +69,8 @@ modded class ERR_AdminMenu
|
||||
m_WeightMods.Copy(m_ServerAddonKeys);
|
||||
combo.SetCurrentItem(Math.Max(0, m_ServerAddonKeys.Find(m_ShownItemAddon)), false, false, false);
|
||||
}
|
||||
bool moved = m_WeightAddon != m_ShownItemAddon || m_WeightPage != m_ShownItemPage;
|
||||
bool moved = m_WeightAddon != m_ShownItemAddon || m_WeightPage != m_ShownItemPage || m_WeightRowSignature != m_ItemRowSignature;
|
||||
m_WeightRowSignature = m_ItemRowSignature;
|
||||
if (moved)
|
||||
{
|
||||
ClearWeightRows(); m_WeightAddon = m_ShownItemAddon; m_WeightPage = m_ShownItemPage;
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
// Server discovery never instantiates a vehicle. New entries are opt-in per use.
|
||||
class ERR_VehicleEntry
|
||||
{
|
||||
ResourceName Prefab;
|
||||
int Defaults;
|
||||
int Capabilities;
|
||||
}
|
||||
class ERR_ServerVehicleCatalog
|
||||
{
|
||||
protected ref array<ref ERR_ServerAddonItems> m_Addons = {};
|
||||
protected ref map<string, ref ERR_VehicleEntry> m_Entries = new map<string, ref ERR_VehicleEntry>();
|
||||
protected ref map<string, ref ERR_ServerItem> m_Items = new map<string, ref ERR_ServerItem>();
|
||||
protected ref array<ResourceName> m_Pending = {};
|
||||
protected ref set<string> m_Queued = new set<string>();
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected int m_Cursor;
|
||||
protected bool m_Started;
|
||||
protected bool m_Truncated;
|
||||
static string RoleName(int role)
|
||||
{
|
||||
if (role == 0) return "Garages / yards";
|
||||
if (role == 1) return "Civilian traffic";
|
||||
if (role == 2) return "Enemy patrols";
|
||||
if (role == 3) return "Convoy armed escorts";
|
||||
return "Convoy supply trucks";
|
||||
}
|
||||
static string RoleHint(int role)
|
||||
{
|
||||
if (role == 0) return "Garages / yards: unarmed vehicles that fit the parking space.";
|
||||
if (role == 1) return "Traffic: vehicles with AI driving support; road size limits apply.";
|
||||
if (role == 2) return "Enemy patrols: driver and crew, up to 15 seats; your enemy faction supplies the soldiers.";
|
||||
if (role == 3) return "Convoy escorts: driver and gunner required. The specialized BMP slot stays unchanged.";
|
||||
return "Convoy supply slot: AI vehicle with cargo storage; normal supply capacity checks apply.";
|
||||
}
|
||||
static bool Candidate(string path)
|
||||
{
|
||||
path.ToLower();
|
||||
return path.EndsWith(".et") && path.Contains("vehicle") && !path.Contains("wreck")
|
||||
&& !path.Contains("_base") && !path.Contains("/parts/") && !path.Contains("/turrets/");
|
||||
}
|
||||
protected void Queue(ResourceName prefab, string path = "")
|
||||
{
|
||||
string key = ERR_TerrainCatalog.Key(prefab);
|
||||
if (key.IsEmpty() || m_Queued.Contains(key)) return;
|
||||
if (m_Pending.Count() >= 8192) { m_Truncated = true; return; }
|
||||
m_Queued.Insert(key); m_Pending.Insert(prefab);
|
||||
}
|
||||
protected void Search(ResourceName prefab, string path = "") { if (Candidate(prefab)) Queue(prefab); }
|
||||
protected void Catalog(SCR_EntityCatalog catalog)
|
||||
{
|
||||
if (!catalog) return;
|
||||
array<SCR_EntityCatalogEntry> entries = {}; catalog.GetEntityList(entries);
|
||||
foreach (SCR_EntityCatalogEntry entry : entries) if (entry && entry.IsEnabled()) Queue(entry.GetPrefab());
|
||||
}
|
||||
void Start()
|
||||
{
|
||||
if (m_Started || !Replication.IsServer()) return;
|
||||
m_Mode = ERR_GameMode.Get();
|
||||
SCR_EntityCatalogManagerComponent manager = SCR_EntityCatalogManagerComponent.GetInstance();
|
||||
if (!m_Mode || !manager || !GetGame().GetFactionManager()) return;
|
||||
m_Started = true;
|
||||
array<string> loaded = {}; GameProject.GetLoadedAddons(loaded);
|
||||
foreach (string guid : loaded)
|
||||
{
|
||||
ERR_ServerAddonItems addon = new ERR_ServerAddonItems(); addon.Id = guid; addon.Id.ToLower();
|
||||
addon.Project = GameProject.GetAddonID(guid); addon.Label = ERR_ServerItemCatalog.Clean(GameProject.GetAddonTitle(guid));
|
||||
if (addon.Label.IsEmpty()) addon.Label = addon.Project;
|
||||
m_Addons.Insert(addon);
|
||||
}
|
||||
ERR_ServerItemCatalog.SortAddons(m_Addons);
|
||||
ERR_ParkedCarCatalog cars = new ERR_ParkedCarCatalog(); cars.Find();
|
||||
foreach (ResourceName parked : cars.Prefabs) Seed(parked, 0);
|
||||
foreach (ResourceName road : cars.RoadPrefabs) Seed(road, 1);
|
||||
Seed(ERR_CivilianTraffic.CarPrefab(2), 1);
|
||||
foreach (ResourceName tractor : cars.Tractors) Seed(tractor, 0);
|
||||
ERR_MotorbikeCatalog bikes = new ERR_MotorbikeCatalog(); bikes.Find();
|
||||
foreach (ResourceName bike : bikes.Prefabs) Seed(bike, 0);
|
||||
Seed(ERR_HunterFleetRules.VehiclePrefab(0), 2); Seed(ERR_HunterFleetRules.VehiclePrefab(2), 2);
|
||||
Seed(ERR_ConvoyResources.VehiclePrefab(0), 3); Seed(ERR_ConvoyResources.VehiclePrefab(1), 4);
|
||||
Catalog(manager.GetEntityCatalogOfType(EEntityCatalogType.VEHICLE, false));
|
||||
array<Faction> factions = {}; GetGame().GetFactionManager().GetFactionsList(factions);
|
||||
foreach (Faction faction : factions)
|
||||
{
|
||||
SCR_Faction nativeFaction = SCR_Faction.Cast(faction);
|
||||
if (nativeFaction) Catalog(nativeFaction.GetFactionEntityCatalogOfType(EEntityCatalogType.VEHICLE, false));
|
||||
}
|
||||
// Also find complete vehicle prefabs from mods without faction catalogs.
|
||||
foreach (ERR_ServerAddonItems project : m_Addons)
|
||||
{
|
||||
SearchResourcesFilter filter = new SearchResourcesFilter(); filter.fileExtensions = {"et"}; filter.recursive = true;
|
||||
filter.rootPath = "$" + project.Project + ":Prefabs";
|
||||
ResourceDatabase.SearchResources(filter, Search);
|
||||
}
|
||||
GetGame().GetCallqueue().CallLater(Scan, 50, true); m_Mode.OnStop().Insert(Stop);
|
||||
}
|
||||
void Stop()
|
||||
{
|
||||
if (GetGame()) GetGame().GetCallqueue().Remove(Scan);
|
||||
if (m_Mode) m_Mode.OnStop().Remove(Stop);
|
||||
m_Mode = null; m_Started = false; m_Cursor = 0;
|
||||
m_Addons.Clear(); m_Entries.Clear(); m_Items.Clear(); m_Pending.Clear(); m_Queued.Clear();
|
||||
}
|
||||
protected void Seed(ResourceName prefab, int role)
|
||||
{
|
||||
string key = ERR_TerrainCatalog.Key(prefab);
|
||||
ERR_VehicleEntry entry = m_Entries.Get(key);
|
||||
if (!entry) { entry = new ERR_VehicleEntry(); entry.Prefab = prefab; m_Entries.Set(key, entry); }
|
||||
entry.Defaults = entry.Defaults | (1 << role);
|
||||
// Authored defaults remain available while asynchronous discovery completes.
|
||||
entry.Capabilities = entry.Capabilities | (1 << role);
|
||||
Queue(prefab);
|
||||
}
|
||||
protected bool Has(Resource resource, typename component)
|
||||
{
|
||||
IEntitySource source = SCR_BaseContainerTools.FindEntitySource(resource);
|
||||
if (!source) return false;
|
||||
array<IEntityComponentSource> matches = {};
|
||||
SCR_BaseContainerTools.FindComponentSourcesOfClass(source, component, true, matches);
|
||||
return !matches.IsEmpty();
|
||||
}
|
||||
protected void Scan()
|
||||
{
|
||||
if (!m_Mode || m_Mode != ERR_GameMode.Get() || !Replication.IsServer()) { Stop(); return; }
|
||||
int end = Math.Min(m_Cursor + 4, m_Pending.Count());
|
||||
while (m_Cursor < end)
|
||||
{
|
||||
ResourceName prefab = m_Pending[m_Cursor++]; string key = ERR_TerrainCatalog.Key(prefab);
|
||||
Resource resource = Resource.Load(prefab);
|
||||
if (!resource || !resource.IsValid()) continue;
|
||||
bool wheeled = Has(resource, VehicleWheeledSimulation);
|
||||
bool tracked = Has(resource, VehicleTrackedSimulation);
|
||||
if (!wheeled && !tracked) continue;
|
||||
bool ai = (Has(resource, ChimeraAIVehicleControlComponent) || Has(resource, AICarMovementComponent)) && Has(resource, SCR_AIVehicleUsageComponent);
|
||||
ERR_VehicleEntry entry = m_Entries.Get(key);
|
||||
if (!entry) { entry = new ERR_VehicleEntry(); entry.Prefab = prefab; m_Entries.Set(key, entry); }
|
||||
int capabilities;
|
||||
if (wheeled) capabilities = 1;
|
||||
if (ai)
|
||||
{
|
||||
capabilities = capabilities | 12;
|
||||
if (wheeled) capabilities = capabilities | 2;
|
||||
if (Has(resource, InventoryStorageManagerComponent)) capabilities = capabilities | 16;
|
||||
}
|
||||
entry.Capabilities = capabilities;
|
||||
ERR_ServerItem item = new ERR_ServerItem(); item.Prefab = prefab; item.Eligible = true;
|
||||
item.Label = FilePath.StripPath(prefab.GetPath()); item.Label.Replace(".et", ""); item.Label.Replace("_", " "); item.SearchLabel = item.Label;
|
||||
m_Items.Set(key, item);
|
||||
array<string> sources = SCR_AddonTool.GetResourceAddons(prefab);
|
||||
foreach (ERR_ServerAddonItems addon : m_Addons) if (sources.Contains(addon.Project)) addon.Items.Insert(item);
|
||||
}
|
||||
if (m_Cursor >= m_Pending.Count())
|
||||
{
|
||||
GetGame().GetCallqueue().Remove(Scan);
|
||||
foreach (ERR_ServerAddonItems sorted : m_Addons) ERR_ServerItemCatalog.SortItems(sorted.Items);
|
||||
PrintFormat("Reintroduced [server vehicles]: inspected=%1 groundVehicles=%2 truncated=%3", m_Cursor, m_Items.Count(), m_Truncated);
|
||||
}
|
||||
}
|
||||
ERR_ServerAddonItems Find(string id)
|
||||
{
|
||||
foreach (ERR_ServerAddonItems addon : m_Addons) if (addon.Id == id) return addon;
|
||||
return null;
|
||||
}
|
||||
bool Contains(string addonId, string itemId)
|
||||
{
|
||||
ERR_ServerAddonItems addon = Find(addonId); if (!addon) return false;
|
||||
foreach (ERR_ServerItem item : addon.Items) if (ERR_TerrainCatalog.Key(item.Prefab) == itemId) return true;
|
||||
return false;
|
||||
}
|
||||
void Reject(ResourceName prefab, int role)
|
||||
{
|
||||
ERR_VehicleEntry entry = m_Entries.Get(ERR_TerrainCatalog.Key(prefab));
|
||||
if (!entry || role < 0 || role > 4) return;
|
||||
entry.Capabilities = entry.Capabilities & ~(1 << role);
|
||||
PrintFormat("Reintroduced [vehicle assignment]: rejected %1 for %2: physical fit, driver, crew seats or storage unavailable; excluded until next round", prefab, RoleName(role));
|
||||
}
|
||||
bool Capable(string key, int role)
|
||||
{
|
||||
ERR_VehicleEntry entry = m_Entries.Get(key);
|
||||
return role >= 0 && role < 5 && entry && (entry.Capabilities & (1 << role)) != 0;
|
||||
}
|
||||
bool Allowed(ResourceName prefab, int role)
|
||||
{
|
||||
Start(); string key = ERR_TerrainCatalog.Key(prefab); ERR_VehicleEntry entry = m_Entries.Get(key);
|
||||
return Capable(key, role) && GetGame().ERR_ItemSelections().AllowsVehicleRole(prefab, role, (entry.Defaults & (1 << role)) != 0);
|
||||
}
|
||||
ResourceName Choose(int role, ResourceName preferred, bool otherDefaults = true)
|
||||
{
|
||||
Start(); array<ResourceName> choices = {};
|
||||
if (!preferred.IsEmpty() && Allowed(preferred, role)) choices.Insert(preferred);
|
||||
foreach (string key, ERR_VehicleEntry entry : m_Entries)
|
||||
if ((entry.Defaults & (1 << role)) == 0 && Allowed(entry.Prefab, role) && !choices.Contains(entry.Prefab)) choices.Insert(entry.Prefab);
|
||||
// If the preferred default was disabled, another enabled default may serve.
|
||||
if (choices.IsEmpty() && otherDefaults)
|
||||
foreach (string key, ERR_VehicleEntry entry : m_Entries)
|
||||
if (Allowed(entry.Prefab, role)) choices.Insert(entry.Prefab);
|
||||
if (choices.IsEmpty()) return "";
|
||||
return choices[Math.RandomInt(0, choices.Count())];
|
||||
}
|
||||
void Snapshot(array<string> values, string selected, int page, int filter, string query, int role = 0)
|
||||
{
|
||||
Start(); ERR_ItemPreferences preferences = GetGame().ERR_ItemSelections();
|
||||
foreach (ERR_ServerAddonItems addon : m_Addons)
|
||||
{
|
||||
if (addon.Items.IsEmpty()) continue;
|
||||
int enabled;
|
||||
foreach (ERR_ServerItem item : addon.Items) if (Allowed(item.Prefab, role)) enabled++;
|
||||
values.Insert("vehicleMod=" + addon.Id + "|" + addon.Label + "|" + addon.Items.Count().ToString() + "|" + enabled.ToString() + "|" + ERR_ServerItemCatalog.Flag(preferences.ModEnabled(addon.Id)));
|
||||
}
|
||||
string scan = "Vehicle discovery complete";
|
||||
if (!m_Started || m_Cursor < m_Pending.Count()) scan = "Discovering vehicles from loaded mods";
|
||||
if (m_Truncated) scan += "; scan limit reached";
|
||||
values.Insert("vehiclesScan=" + scan);
|
||||
ERR_ServerAddonItems chosen = Find(selected); if (!chosen) return;
|
||||
array<ref ERR_ServerItem> matches = {};
|
||||
foreach (ERR_ServerItem candidate : chosen.Items)
|
||||
if (ERR_ServerItemCatalog.Matches(candidate.SearchLabel, candidate.Prefab.GetPath(), Allowed(candidate.Prefab, role), filter, query)) matches.Insert(candidate);
|
||||
int last = Math.Max(0, (matches.Count() - 1) / ERR_ServerItemCatalog.PAGE_SIZE); page = Math.Clamp(page, 0, last);
|
||||
values.Insert("vehiclesPage=" + selected + "|" + page.ToString() + "|" + last.ToString() + "|" + matches.Count().ToString() + "|" + role.ToString());
|
||||
int end = Math.Min(matches.Count(), (page + 1) * ERR_ServerItemCatalog.PAGE_SIZE);
|
||||
for (int i = page * ERR_ServerItemCatalog.PAGE_SIZE; i < end; i++)
|
||||
{
|
||||
ERR_ServerItem item = matches[i]; string key = ERR_TerrainCatalog.Key(item.Prefab); ERR_VehicleEntry entry = m_Entries.Get(key);
|
||||
bool capable = Capable(key, role), effective = Allowed(item.Prefab, role);
|
||||
int rule = preferences.VehicleRole(selected, key, role);
|
||||
bool selectedChoice = rule == 1 || (rule == -1 && (entry.Defaults & (1 << role)) != 0);
|
||||
string use = RoleName(role) + ": not assigned";
|
||||
if (selectedChoice) use = RoleName(role) + ": selected; fit and crew checked at spawn";
|
||||
if (!preferences.ModEnabled(selected)) use = "Mod disabled - assignments remembered";
|
||||
else if (selectedChoice && !effective) use = "Disabled by another source mod or vehicle switch";
|
||||
if (!capable) use = "Unavailable for this use: missing support or failed spawn checks";
|
||||
values.Insert("serverVehicle=" + key + "|" + item.Label + "|" + ERR_ServerItemCatalog.Clean(item.Prefab.GetPath()) + "|" + use + "|" + ERR_ServerItemCatalog.Flag(selectedChoice) + "|" + ERR_ServerItemCatalog.Flag(effective) + "|" + ERR_ServerItemCatalog.Flag(capable));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_VehicleSelection
|
||||
{
|
||||
static bool Allowed(ResourceName prefab) { return !prefab.IsEmpty() && GetGame().ERR_ItemSelections().AllowsVehicle(prefab); }
|
||||
static ResourceName Choose(array<ResourceName> pool, int role = -1)
|
||||
{
|
||||
array<ResourceName> enabled = {};
|
||||
foreach (ResourceName prefab : pool) if ((role < 0 && Allowed(prefab)) || (role >= 0 && RoleAllowed(prefab, role))) enabled.Insert(prefab);
|
||||
if (enabled.IsEmpty()) return "";
|
||||
return enabled[Math.RandomInt(0, enabled.Count())];
|
||||
}
|
||||
static ResourceName ForRole(int role, ResourceName preferred, bool otherDefaults = true) { return GetGame().ERR_ServerVehicles().Choose(role, preferred, otherDefaults); }
|
||||
static bool RoleAllowed(ResourceName prefab, int role) { return !prefab.IsEmpty() && GetGame().ERR_ServerVehicles().Allowed(prefab, role); }
|
||||
static bool PatrolSeats(int drivers, int turrets, int cargo, int total)
|
||||
{
|
||||
return drivers == 1 && turrets >= 0 && cargo >= 0 && turrets + cargo >= 1 && total == drivers + turrets + cargo && total <= 15;
|
||||
}
|
||||
// A mod cannot enlarge the existing 4m x 9m x 3.5m road deployment envelope.
|
||||
static bool RoadFit(Vehicle vehicle)
|
||||
{
|
||||
if (!vehicle || (!vehicle.FindComponent(ChimeraAIVehicleControlComponent) && !vehicle.FindComponent(AICarMovementComponent)) || !vehicle.FindComponent(SCR_AIVehicleUsageComponent)) return false;
|
||||
vector low, high; vehicle.GetBounds(low, high);
|
||||
return ERR_ForceRules.FiniteVector(low) && ERR_ForceRules.FiniteVector(high)
|
||||
&& low[0] >= -2 && high[0] <= 2 && low[2] >= -4.5 && high[2] <= 4.5 && high[1] - low[1] > 0 && high[1] - low[1] <= 3.5;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
modded class ERR_AdminMenu
|
||||
{
|
||||
protected ref array<string> m_VehicleMods = {};
|
||||
protected ref array<string> m_VehicleLabels = {};
|
||||
protected ref array<SCR_ButtonTextComponent> m_VehicleButtons = {};
|
||||
protected ref map<string, Widget> m_VehicleRows = new map<string, Widget>();
|
||||
protected ref map<string, int> m_VehicleSaved = new map<string, int>();
|
||||
protected ref map<string, int> m_VehicleStates = new map<string, int>();
|
||||
protected string m_ShownVehicleAddon;
|
||||
protected string m_LastVehicleChoice;
|
||||
protected string m_VehicleRowSignature;
|
||||
protected int m_ShownVehiclePage;
|
||||
protected int m_LastVehiclePage;
|
||||
protected int m_LastVehicleFilter = -1;
|
||||
protected int m_LastVehicleRole = -1;
|
||||
protected int m_ShownVehicleRole;
|
||||
protected ref map<string, int> m_VehicleCapable = new map<string, int>();
|
||||
override void OnMenuOpen()
|
||||
{
|
||||
super.OnMenuOpen();
|
||||
foreach (string name : {"VehiclesShow", "VehiclesClear", "VehiclesPrevious", "VehiclesNext", "VehiclesEnableAll", "VehiclesDisableAll"})
|
||||
{
|
||||
SCR_ButtonTextComponent button = SCR_ButtonTextComponent.GetButtonText(name, GetRootWidget());
|
||||
if (!button) { Close(); return; }
|
||||
button.m_OnClicked.Insert(OnVehiclesButton); m_VehicleButtons.Insert(button);
|
||||
}
|
||||
SCR_ComboBoxComponent filter = SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesFilter", GetRootWidget());
|
||||
filter.AddItem("All"); filter.AddItem("Enabled"); filter.AddItem("Disabled"); filter.SetCurrentItem(0, false, false, false);
|
||||
SCR_ComboBoxComponent roles = SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesRole", GetRootWidget());
|
||||
for (int role = 0; role < 5; role++) roles.AddItem("Use for: " + ERR_ServerVehicleCatalog.RoleName(role));
|
||||
roles.SetCurrentItem(0, false, false, false);
|
||||
UpdateActions();
|
||||
}
|
||||
protected void ClearVehicleRows()
|
||||
{
|
||||
foreach (string key, Widget row : m_VehicleRows)
|
||||
{
|
||||
SCR_ButtonTextComponent.GetButtonText("Enable", row).m_OnClicked.Remove(OnVehicleRowButton);
|
||||
SCR_ButtonTextComponent.GetButtonText("Disable", row).m_OnClicked.Remove(OnVehicleRowButton);
|
||||
row.RemoveFromHierarchy();
|
||||
}
|
||||
m_VehicleRows.Clear(); m_VehicleSaved.Clear(); m_VehicleCapable.Clear();
|
||||
}
|
||||
override void OnMenuClose()
|
||||
{
|
||||
foreach (SCR_ButtonTextComponent button : m_VehicleButtons) button.m_OnClicked.Remove(OnVehiclesButton);
|
||||
m_LastVehicleChoice = ""; m_LastVehicleFilter = -1; m_LastVehicleRole = -1; m_ShownVehicleAddon = ""; m_VehicleRowSignature = "";
|
||||
ClearVehicleRows(); m_VehicleButtons.Clear(); m_VehicleMods.Clear(); m_VehicleLabels.Clear(); m_VehicleStates.Clear();
|
||||
super.OnMenuClose();
|
||||
}
|
||||
protected override void UpdateActions()
|
||||
{
|
||||
super.UpdateActions();
|
||||
bool ready = VehicleSelectionReady();
|
||||
foreach (SCR_ButtonTextComponent button : m_VehicleButtons)
|
||||
{
|
||||
string name = button.GetRootWidget().GetName(); bool enabled = ready;
|
||||
if (name == "VehiclesShow" || name == "VehiclesClear") enabled = m_Ready && !m_VehicleMods.IsEmpty();
|
||||
if (name == "VehiclesPrevious") enabled = ready && m_ShownVehiclePage > 0;
|
||||
if (name == "VehiclesNext") enabled = ready && m_ShownVehiclePage < m_LastVehiclePage;
|
||||
button.SetEnabled(enabled);
|
||||
}
|
||||
foreach (string key, Widget row : m_VehicleRows)
|
||||
{
|
||||
SCR_ButtonTextComponent.GetButtonText("Enable", row).SetEnabled(ready && m_VehicleCapable.Get(key) == 1 && m_VehicleSaved.Get(key) == 0);
|
||||
SCR_ButtonTextComponent.GetButtonText("Disable", row).SetEnabled(ready && m_VehicleSaved.Get(key) == 1);
|
||||
ERR_SelectionStyle.Pair(row, "Enable", "Disable", m_VehicleSaved.Get(key));
|
||||
if (!m_Ready) ERR_SelectionStyle.Status(row, "SelectionState", -1);
|
||||
}
|
||||
int state = -1; if (ready && m_VehicleStates.Contains(m_ShownVehicleAddon)) state = m_VehicleStates.Get(m_ShownVehicleAddon);
|
||||
ERR_SelectionStyle.Status(GetRootWidget(), "VehiclesSelectionState", state);
|
||||
}
|
||||
override void ReceiveSnapshot(bool authorized, array<string> values, array<string> status)
|
||||
{
|
||||
super.ReceiveSnapshot(authorized, values, status);
|
||||
if (!authorized || !values) return;
|
||||
array<string> keys = {}, labels = {}, rows = {}; string shown, signature, scan; int page, last, total, role;
|
||||
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 == "vehiclesScan") scan = value;
|
||||
if (field == "vehicleMod" && parts.Count() == 5)
|
||||
{
|
||||
keys.Insert(parts[0]); labels.Insert(parts[1]);
|
||||
m_VehicleStates.Set(parts[0], ERR_SelectionStyle.Aggregate(parts[2].ToInt(), parts[3].ToInt(), parts[4] == "1"));
|
||||
}
|
||||
if (field == "vehiclesPage" && parts.Count() == 5) { shown = parts[0]; page = parts[1].ToInt(); last = parts[2].ToInt(); total = parts[3].ToInt(); role = parts[4].ToInt(); }
|
||||
if (field == "serverVehicle" && parts.Count() == 7) { rows.Insert(value); signature += parts[0]; }
|
||||
}
|
||||
SCR_ComboBoxComponent combo = SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesMod", GetRootWidget());
|
||||
if (keys.Count() != m_VehicleMods.Count())
|
||||
{
|
||||
combo.ClearAll(); foreach (string label : labels) combo.AddItem(label);
|
||||
m_VehicleMods.Copy(keys); m_VehicleLabels.Copy(labels); combo.SetCurrentItem(Math.Max(0, keys.Find(m_ShownVehicleAddon)), false, false, false);
|
||||
}
|
||||
if (!shown.IsEmpty())
|
||||
{
|
||||
bool moved = shown != m_ShownVehicleAddon || page != m_ShownVehiclePage || signature != m_VehicleRowSignature || role != m_ShownVehicleRole;
|
||||
SetText("VehiclesHelp", ERR_ServerVehicleCatalog.RoleHint(role));
|
||||
m_ShownVehicleRole = role; m_ShownVehicleAddon = shown; m_ShownVehiclePage = page; m_LastVehiclePage = last; m_VehicleRowSignature = signature;
|
||||
if (moved) ClearVehicleRows();
|
||||
string scanNote; if (scan != "Vehicle discovery complete") scanNote = " (scanning)";
|
||||
SetText("VehiclesPageState", total.ToString() + " matching vehicles | Page " + (page + 1).ToString() + " of " + (last + 1).ToString() + scanNote);
|
||||
foreach (string data : rows)
|
||||
{
|
||||
array<string> parts = {}; data.Split("|", parts, false); Widget row = m_VehicleRows.Get(parts[0]);
|
||||
if (!row)
|
||||
{
|
||||
row = GetGame().GetWorkspace().CreateWidgets("{93A8120CBE7DA469}UI/layouts/ERR/ItemRow.layout", GetRootWidget().FindAnyWidget("VehiclesContent"));
|
||||
if (!row) continue;
|
||||
m_VehicleRows.Set(parts[0], row);
|
||||
SCR_ButtonTextComponent.GetButtonText("Enable", row).m_OnClicked.Insert(OnVehicleRowButton);
|
||||
SCR_ButtonTextComponent.GetButtonText("Disable", row).m_OnClicked.Insert(OnVehicleRowButton);
|
||||
}
|
||||
TextWidget.Cast(row.FindAnyWidget("PackLabel")).SetText(WidgetManager.Translate(parts[1]));
|
||||
TextWidget.Cast(row.FindAnyWidget("PackState")).SetText(parts[3]);
|
||||
m_VehicleCapable.Set(parts[0], parts[6].ToInt()); m_VehicleSaved.Set(parts[0], parts[4].ToInt()); ERR_SelectionStyle.Status(row, "SelectionState", parts[5].ToInt());
|
||||
}
|
||||
string empty; if (rows.IsEmpty()) empty = "No vehicles match. Clear search or choose All to see more.";
|
||||
SetText("VehiclesText", empty);
|
||||
GetRootWidget().FindAnyWidget("VehiclesText").SetVisible(!empty.IsEmpty());
|
||||
if (moved) ScrollLayoutWidget.Cast(GetRootWidget().FindAnyWidget("VehiclesScroll")).SetSliderPos(0, 0);
|
||||
}
|
||||
else if (keys.IsEmpty()) SetText("VehiclesText", "Discovering ground vehicles on this server...");
|
||||
UpdateActions();
|
||||
}
|
||||
protected override void OpenVehicleAddon(string addon)
|
||||
{
|
||||
int index = m_VehicleMods.Find(addon); if (index < 0) return;
|
||||
SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesMod", GetRootWidget()).SetCurrentItem(index, false, false, false);
|
||||
SCR_EditBoxComponent.GetEditBoxComponent("VehiclesSearch", GetRootWidget()).SetValue("");
|
||||
SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesFilter", GetRootWidget()).SetCurrentItem(0, false, false, false);
|
||||
ShowPage(ERR_AdminPage.VEHICLE_POOL); BrowseVehicles(0);
|
||||
}
|
||||
protected bool VehicleSelectionReady()
|
||||
{
|
||||
SCR_ComboBoxComponent combo = SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesMod", GetRootWidget());
|
||||
if (!m_Ready || !combo) return false;
|
||||
int index = combo.GetCurrentIndex();
|
||||
return index >= 0 && index < m_VehicleMods.Count() && m_VehicleMods[index] == m_ShownVehicleAddon && SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesRole", GetRootWidget()).GetCurrentIndex() == m_ShownVehicleRole;
|
||||
}
|
||||
protected void BrowseVehicles(int page)
|
||||
{
|
||||
int index = SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesMod", GetRootWidget()).GetCurrentIndex();
|
||||
if (index < 0 || index >= m_VehicleMods.Count()) return;
|
||||
int filter = SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesFilter", GetRootWidget()).GetCurrentIndex();
|
||||
string query = SCR_EditBoxComponent.GetEditBoxComponent("VehiclesSearch", GetRootWidget()).GetValue().Trim();
|
||||
if (query.Length() > 64) { ReceiveReply("Search can contain up to 64 characters."); return; }
|
||||
m_LastVehicleChoice = m_VehicleMods[index]; m_LastVehicleFilter = filter;
|
||||
m_LastVehicleRole = SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesRole", GetRootWidget()).GetCurrentIndex();
|
||||
SubmitItems("vehicles browse " + m_LastVehicleChoice + " " + page.ToString() + " " + filter.ToString() + " " + m_LastVehicleRole.ToString() + " " + query);
|
||||
UpdateActions();
|
||||
}
|
||||
override void OnMenuUpdate(float tDelta)
|
||||
{
|
||||
super.OnMenuUpdate(tDelta);
|
||||
if (!m_Ready || !IsFocused() || m_ClickDelay > 0 || m_Age > 6 || m_Page != ERR_AdminPage.VEHICLE_POOL) return;
|
||||
int index = SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesMod", GetRootWidget()).GetCurrentIndex();
|
||||
int filter = SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesFilter", GetRootWidget()).GetCurrentIndex();
|
||||
if (index >= 0 && index < m_VehicleMods.Count() && (m_VehicleMods[index] != m_LastVehicleChoice || filter != m_LastVehicleFilter || SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesRole", GetRootWidget()).GetCurrentIndex() != m_LastVehicleRole)) BrowseVehicles(0);
|
||||
}
|
||||
protected void OnVehiclesButton(SCR_ButtonBaseComponent button)
|
||||
{
|
||||
if (!button || !m_Ready || !IsFocused() || !m_Controller || m_Age > 6 || m_ClickDelay > 0) return;
|
||||
string name = button.GetRootWidget().GetName();
|
||||
if (name == "VehiclesEnableAll" || name == "VehiclesDisableAll")
|
||||
{
|
||||
if (!VehicleSelectionReady()) return;
|
||||
string value = "0"; if (name == "VehiclesEnableAll") value = "1";
|
||||
SubmitItems("vehicles role " + m_ShownVehicleAddon + " " + m_ShownVehicleRole.ToString() + " * " + value); return;
|
||||
}
|
||||
if (name == "VehiclesClear")
|
||||
{
|
||||
SCR_EditBoxComponent.GetEditBoxComponent("VehiclesSearch", GetRootWidget()).SetValue("");
|
||||
SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesFilter", GetRootWidget()).SetCurrentItem(0, false, false, false);
|
||||
}
|
||||
int page;
|
||||
if (name == "VehiclesPrevious") page = Math.Max(0, m_ShownVehiclePage - 1);
|
||||
if (name == "VehiclesNext") page = Math.Min(m_LastVehiclePage, m_ShownVehiclePage + 1);
|
||||
BrowseVehicles(page);
|
||||
}
|
||||
protected void OnVehicleRowButton(SCR_ButtonBaseComponent button)
|
||||
{
|
||||
if (!button || !m_Ready || !IsFocused() || !m_Controller || m_Age > 6 || m_ClickDelay > 0 || !VehicleSelectionReady()) return;
|
||||
foreach (string key, Widget row : m_VehicleRows)
|
||||
{
|
||||
string value;
|
||||
if (button.GetRootWidget() == row.FindAnyWidget("Enable")) value = "1";
|
||||
if (button.GetRootWidget() == row.FindAnyWidget("Disable")) value = "0";
|
||||
if (!value.IsEmpty()) { SubmitItems("vehicles role " + m_ShownVehicleAddon + " " + m_ShownVehicleRole.ToString() + " " + key + " " + value); return; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -209,20 +209,28 @@ class ERR_CivilianTraffic
|
||||
m_Catalog.Find(false);
|
||||
// Keep the transport van in traffic; premium Rally cars stay in parked pools.
|
||||
ResourceName prefab = CarPrefab(2);
|
||||
if (Math.RandomInt(0, 5) != 0) prefab = m_Catalog.RoadPrefabs[Math.RandomInt(0, m_Catalog.RoadPrefabs.Count())];
|
||||
if (Math.RandomInt(0, 5) != 0 || !ERR_VehicleSelection.RoleAllowed(prefab, 1)) prefab = ERR_VehicleSelection.Choose(m_Catalog.RoadPrefabs, 1);
|
||||
if (prefab.IsEmpty() && ERR_VehicleSelection.RoleAllowed(CarPrefab(2), 1)) prefab = CarPrefab(2);
|
||||
prefab = ERR_VehicleSelection.ForRole(1, prefab);
|
||||
if (prefab.IsEmpty()) return false;
|
||||
array<ResourceName> required = {prefab, DriverPrefab(), ERR_ConvoyResources.GroupPrefab(), ERR_Resources.MoveWaypoint()};
|
||||
foreach (ResourceName name : required) { Resource loaded = Resource.Load(name); if (!loaded || !loaded.IsValid()) return false; }
|
||||
// Loading can take time: check every observer again immediately before spawn.
|
||||
if (!Observers(observers, actors) || observers.IsEmpty() || !ERR_CivilianTrafficRules.Far(route[0], observers, 1000) || !Hidden(route[0], actors)) return false;
|
||||
ERR_ConvoyPlacement finalPlacement = new ERR_ConvoyPlacement();
|
||||
if (!finalPlacement.Clear(route[0], angles[0])) return false;
|
||||
if (!finalPlacement.Clear(route[0], angles[0]) || !ERR_VehicleSelection.RoleAllowed(prefab, 1)) return false;
|
||||
ERR_CivilianCar traffic = new ERR_CivilianCar(); traffic.Route.Copy(route);
|
||||
traffic.Number = m_Serial + 1; traffic.Prefab = prefab;
|
||||
traffic.Deadline = now + 120000; traffic.ProgressAt = now; traffic.ProgressPosition = route[0];
|
||||
traffic.Car = Vehicle.Cast(ERR_Actors.Spawn(prefab, route[0], angles));
|
||||
m_Serial++; // Rejected mod vehicles still consume one of the 64 native creation attempts.
|
||||
if (!ERR_VehicleSelection.RoadFit(traffic.Car))
|
||||
{
|
||||
GetGame().ERR_ServerVehicles().Reject(prefab, 1); ERR_Actors.DeleteSpawned(traffic.Car); return false;
|
||||
}
|
||||
traffic.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.GroupPrefab(), route[0]));
|
||||
// Retain failed partial creations for the same distant-only cleanup policy.
|
||||
m_Cars.Insert(traffic); m_Serial++; traffic.Protect(true);
|
||||
m_Cars.Insert(traffic); traffic.Protect(true);
|
||||
Faction civilian = GetGame().GetFactionManager().GetFactionByKey("CIV");
|
||||
if (traffic.Group) { traffic.Group.SetFaction(civilian); traffic.Group.SetDeleteWhenEmpty(false); }
|
||||
if (!traffic.Car || !ERR_ConvoyRules.EmptyCrewTemplate(traffic.Group)) { traffic.Finish("creation failed: vehicle or crew group"); return false; }
|
||||
@@ -231,7 +239,7 @@ class ERR_CivilianTraffic
|
||||
if (!ERR_ConvoyRules.CollectSeats(traffic.Car, seats)) { traffic.Finish("creation failed: compartments unavailable"); return false; }
|
||||
foreach (BaseCompartmentSlot seat : seats)
|
||||
if (seat.GetType() == ECompartmentType.PILOT && seat.IsCompartmentAccessible() && !seat.GetOccupant()) { traffic.Seat = seat; break; }
|
||||
if (!traffic.Seat) { traffic.Finish("creation failed: no accessible driver seat"); return false; }
|
||||
if (!traffic.Seat) { GetGame().ERR_ServerVehicles().Reject(prefab, 1); traffic.Finish("creation failed: no accessible driver seat"); return false; }
|
||||
AIGroup group = traffic.Group;
|
||||
traffic.Driver = traffic.Seat.SpawnCharacterInCompartment(DriverPrefab(), group, ERR_ConvoyResources.GroupPrefab());
|
||||
traffic.Protect(true);
|
||||
|
||||
@@ -404,6 +404,16 @@ class ERR_ConvoyService
|
||||
ERR_ConvoyTask Dispatch(ERR_ConvoyReceipt receipt, array<vector> positions, array<vector> angles, ERR_ForceRoute route, float now)
|
||||
{
|
||||
if (!Current() || !ERR_ForceRules.Finite(now) || now < 0 || !ERR_ConvoyRules.Plan(positions, angles, route) || !ERR_ConvoyResources.Available() || !ERR_ConvoyRules.GroundClear(positions, angles)) return null;
|
||||
// Choose once before consuming a finite receipt. Empty assigned pools defer dispatch.
|
||||
array<ResourceName> selected = {};
|
||||
for (int slot = 0; slot < 6; slot++)
|
||||
{
|
||||
ResourceName prefab = ERR_ConvoyResources.VehiclePrefab(slot);
|
||||
if (slot == 1) prefab = ERR_VehicleSelection.ForRole(4, prefab);
|
||||
if (slot == 0 || slot == 3 || slot == 5) prefab = ERR_VehicleSelection.ForRole(3, prefab);
|
||||
if (slot != 2 && prefab.IsEmpty()) return null;
|
||||
selected.Insert(prefab);
|
||||
}
|
||||
// The server role selection owns the memberless group affiliation.
|
||||
if (!m_Mode.EnemyFaction() || !m_Pool.Consume(receipt)) return null;
|
||||
ERR_ConvoyTask task = NewTask(); task.Receipt = receipt; task.Route = route; task.Deadline = now + 30000;
|
||||
@@ -414,10 +424,13 @@ class ERR_ConvoyService
|
||||
if (index == 2) { unit.Released = true; continue; }
|
||||
unit.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.GroupPrefab(), positions[index]));
|
||||
if (unit.Group) m_Mode.Own(unit.Group);
|
||||
unit.Entity = Vehicle.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.VehiclePrefab(index), positions[index], angles[index]));
|
||||
unit.Entity = Vehicle.Cast(ERR_Actors.Spawn(selected[index], positions[index], angles[index]));
|
||||
if (unit.Entity) m_Mode.Own(unit.Entity);
|
||||
if (!unit.Group || !unit.Entity || unit.Group.GetFaction() != m_Mode.EnemyFaction() || !ERR_ConvoyRules.EmptyCrewTemplate(unit.Group) || !FillCrew(unit))
|
||||
{ task.Stop(m_Mode, "finite convoy startup failed; receipt consumed without retry"); return task; }
|
||||
bool assigned = selected[index] != ERR_ConvoyResources.VehiclePrefab(index);
|
||||
bool compatible = !assigned || (ERR_VehicleSelection.RoadFit(unit.Entity) && (index != 1 || unit.Entity.FindComponent(InventoryStorageManagerComponent)));
|
||||
int role = 3; if (index == 1) role = 4;
|
||||
if (!compatible || !unit.Group || !unit.Entity || unit.Group.GetFaction() != m_Mode.EnemyFaction() || !ERR_ConvoyRules.EmptyCrewTemplate(unit.Group) || !FillCrew(unit))
|
||||
{ if (assigned) GetGame().ERR_ServerVehicles().Reject(selected[index], role); task.Stop(m_Mode, "finite convoy startup failed; receipt consumed without retry"); return task; }
|
||||
FactionAffiliationComponent affiliation = FactionAffiliationComponent.Cast(unit.Entity.FindComponent(FactionAffiliationComponent));
|
||||
if (affiliation) affiliation.SetAffiliatedFaction(m_Mode.EnemyFaction());
|
||||
AICarMovementComponent movement = AICarMovementComponent.Cast(unit.Entity.FindComponent(AICarMovementComponent));
|
||||
|
||||
@@ -309,15 +309,24 @@ class ERR_GarageVehicles
|
||||
}
|
||||
if (ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), position) || !Clear(position, transform, null, site.Farm) || !Hidden(position, angles, actors, null, site.Farm)) return;
|
||||
m_Pending = site; m_Position = position; m_Angles = angles;
|
||||
m_Prefab = m_Cars.Prefabs[Math.RandomInt(0, m_Cars.Prefabs.Count())];
|
||||
if (site.Farm) m_Prefab = m_Cars.Tractors[0];
|
||||
m_PendingBike = !site.Farm && m_Spawned % 4 == 3 && m_BikeCandidate < m_Bikes.Prefabs.Count();
|
||||
if (m_PendingBike) m_Prefab = m_Bikes.Prefabs[m_BikeCandidate];
|
||||
m_Prefab = ERR_VehicleSelection.Choose(m_Cars.Prefabs, 0);
|
||||
if (site.Farm) m_Prefab = ERR_VehicleSelection.Choose(m_Cars.Tractors, 0);
|
||||
m_PendingBike = !site.Farm && (m_Prefab.IsEmpty() || m_Spawned % 4 == 3) && m_BikeCandidate < m_Bikes.Prefabs.Count();
|
||||
if (m_PendingBike)
|
||||
{
|
||||
if (ERR_VehicleSelection.RoleAllowed(m_Bikes.Prefabs[m_BikeCandidate], 0)) m_Prefab = m_Bikes.Prefabs[m_BikeCandidate];
|
||||
else { m_BikeCandidate++; m_PendingBike = false; }
|
||||
}
|
||||
ResourceName preferred = m_Prefab;
|
||||
m_Prefab = ERR_VehicleSelection.ForRole(0, preferred, false);
|
||||
if (m_Prefab != preferred) m_PendingBike = false;
|
||||
if (m_Prefab.IsEmpty()) { m_Pending = null; return; }
|
||||
m_Stage = 1; return;
|
||||
}
|
||||
}
|
||||
protected void Pending(array<vector> observers, array<IEntity> actors)
|
||||
{
|
||||
if (!ERR_VehicleSelection.RoleAllowed(m_Prefab, 0)) { m_Pending = null; return; }
|
||||
if (m_Stage == 1)
|
||||
{
|
||||
if (!m_LoadedNames.Contains(m_Prefab))
|
||||
@@ -349,6 +358,7 @@ class ERR_GarageVehicles
|
||||
if (site.Farm) fits = ERR_GarageVehicleRules.FarmEnvelope(low, high);
|
||||
if (!driver || armed || !fits || !Clear(m_Position, transform, entity, site.Farm))
|
||||
{
|
||||
if (!driver || armed || !fits) GetGame().ERR_ServerVehicles().Reject(m_Prefab, 0);
|
||||
ERR_Actors.DeleteSpawned(entity); if (m_PendingBike) m_BikeCandidate++; return;
|
||||
}
|
||||
// Empty vehicles start unaffiliated; vanilla assigns the first occupant's
|
||||
|
||||
@@ -62,6 +62,7 @@ class ERR_HunterVehicle : ERR_ConvoyVehicle
|
||||
bool DeliveredSearch;
|
||||
float PendingObservedAt = -1;
|
||||
bool TroopTransport;
|
||||
bool AssignedVehicle;
|
||||
bool Motorbike;
|
||||
bool Sarka;
|
||||
bool OpeningPatrol;
|
||||
@@ -513,10 +514,16 @@ class ERR_HunterService : ERR_ConvoyService
|
||||
{
|
||||
if (!ERR_SarkaRules.Seats(drivers.Count(), turrets.Count(), cargo.Count(), all.Count())) return false;
|
||||
}
|
||||
else if (hunter.AssignedVehicle)
|
||||
{
|
||||
if (!ERR_VehicleSelection.PatrolSeats(drivers.Count(), turrets.Count(), cargo.Count(), all.Count())) return false;
|
||||
hunter.TroopTransport = turrets.IsEmpty();
|
||||
}
|
||||
else if (!ERR_HunterFleetRules.Seats(hunter.TroopTransport, drivers.Count(), turrets.Count(), cargo.Count(), all.Count())) return false;
|
||||
unit.Seats.Insert(drivers[0]);
|
||||
foreach (BaseCompartmentSlot turret : turrets) unit.Seats.Insert(turret);
|
||||
int medicIndex = unit.Seats.Count();
|
||||
if (hunter.AssignedVehicle && cargo.IsEmpty()) medicIndex = unit.Seats.Count() - 1;
|
||||
foreach (BaseCompartmentSlot passenger : cargo) unit.Seats.Insert(passenger);
|
||||
AIGroup nativeGroup = unit.Group;
|
||||
for (int i = 0; i < unit.Seats.Count(); i++)
|
||||
@@ -547,15 +554,18 @@ class ERR_HunterService : ERR_ConvoyService
|
||||
unit.OpeningPatrol = Deployed() < 3; unit.PatrolHome = position;
|
||||
unit.Source = "aggression-uaz-" + (Deployed() + 1).ToString();
|
||||
if (unit.TroopTransport) unit.Source = "aggression-truck-" + (Deployed() + 1).ToString();
|
||||
ResourceName vehiclePrefab = ERR_HunterFleetRules.VehiclePrefab(Deployed());
|
||||
ResourceName originalPrefab = ERR_HunterFleetRules.VehiclePrefab(Deployed());
|
||||
ResourceName vehiclePrefab = ERR_VehicleSelection.ForRole(2, originalPrefab);
|
||||
if (vehiclePrefab.IsEmpty()) { m_NextSpawn = now + 30000; return false; }
|
||||
unit.AssignedVehicle = vehiclePrefab != originalPrefab;
|
||||
unit.Deadline = now + 120000; unit.LaunchedAt = now; m_Hunters.Insert(unit);
|
||||
CommitReserve(unit, now);
|
||||
unit.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.GroupPrefab(), position));
|
||||
unit.Entity = Vehicle.Cast(ERR_Actors.Spawn(vehiclePrefab, position, angles));
|
||||
if (unit.Group) m_Mode.Own(unit.Group);
|
||||
if (unit.Entity) m_Mode.Own(unit.Entity);
|
||||
if (!unit.Group || !unit.Entity || !ERR_ConvoyRules.EmptyCrewTemplate(unit.Group) || !FillCrew(unit))
|
||||
{ unit.Release(m_Mode); ERR_HQ.Event("HUNTER FAILED", unit.Source + " native vehicle/crew spawn failed; allowance consumed"); return false; }
|
||||
if (!unit.Group || !unit.Entity || !ERR_ConvoyRules.EmptyCrewTemplate(unit.Group) || (unit.AssignedVehicle && !ERR_VehicleSelection.RoadFit(unit.Entity)) || !FillCrew(unit))
|
||||
{ GetGame().ERR_ServerVehicles().Reject(vehiclePrefab, 2); unit.Release(m_Mode); ERR_HQ.Event("HUNTER FAILED", unit.Source + " native vehicle/crew spawn failed; allowance consumed"); return false; }
|
||||
unit.Recovery = new ERR_HunterCrewRecovery(unit);
|
||||
unit.Recovery.Update(m_Mode, now);
|
||||
FactionAffiliationComponent affiliation = FactionAffiliationComponent.Cast(unit.Entity.FindComponent(FactionAffiliationComponent));
|
||||
|
||||
Reference in New Issue
Block a user