feat: add Cohousing service and patch for artificial demand and external capacity

This commit is contained in:
Marvin
2026-04-15 03:11:28 +02:00
parent 308792cb0e
commit c8e51a2389
16 changed files with 604 additions and 51 deletions
Binary file not shown.
+13 -1
View File
@@ -65,6 +65,11 @@ namespace gregAssetExporter
{
MelonLogger.Msg($"[gregCore] Scene Loaded: {sceneName}. Triggering data export...");
greg.Core.Exporter.DataExporter.RunFullExport();
if (sceneName == "MainMenu")
{
greg.Core.UI.UIRouter.SetMode(greg.Core.UI.UIMode.MainMenu);
}
}
public override void OnApplicationQuit()
@@ -75,6 +80,14 @@ namespace gregAssetExporter
public override void OnUpdate()
{
greg.Exporter.ModFramework.Events.Publish(new greg.Exporter.ModTickEvent(Time.deltaTime, Time.frameCount));
// Central Input Management
greg.Sdk.Services.GregInputManagerService.Update();
if (Keyboard.current != null && Keyboard.current.f1Key.wasPressedThisFrame)
{
greg.Core.UI.gregModConfigManager.Toggle(!greg.Core.UI.gregModConfigManager.IsOpen);
}
#if DEBUG
if (Keyboard.current != null && Keyboard.current.f5Key.wasPressedThisFrame)
@@ -847,4 +860,3 @@ namespace gregAssetExporter
}
}
}
+3 -1
View File
@@ -7,7 +7,8 @@ using greg.Core.UI.Components;
namespace greg.Harmony;
[HarmonyPatch(typeof(ComputerShop), "OnEnable")]
/*
[HarmonyPatch(typeof(ComputerShop), nameof(ComputerShop.OnLoad))]
public static class GregShopPatch
{
static void Postfix(ComputerShop __instance)
@@ -57,3 +58,4 @@ public static class GregShopPatch
GameUIButtons.ClickButtonByName("ButtonCheckOut");
}
}
*/
+12 -40
View File
@@ -7,6 +7,7 @@ using UnityEngine;
using UnityEngine.UI;
using greg.Core.UI;
using greg.Core.UI.Components;
using greg.Sdk.Services;
namespace greg.Harmony;
@@ -144,38 +145,26 @@ public static class MainMenuPatch
private static IEnumerator DisableOriginalMenuCoroutine(MainMenu menu)
{
int retries = 0;
GameObject[] canvasesToDisable = null;
yield return new WaitForSeconds(0.3f);
while (retries < 20)
if (GregUxmlService.HasOverride("MainMenu"))
{
MelonLogger.Msg("[gregCore] UXML override found for MainMenu. Suppressing original UI...");
var allCanvases = Resources.FindObjectsOfTypeAll<Canvas>();
if (allCanvases.Length > 0)
var gameObjects = new GameObject[allCanvases.Length];
for (int i = 0; i < allCanvases.Length; i++)
{
canvasesToDisable = new GameObject[allCanvases.Length];
for (int i = 0; i < allCanvases.Length; i++)
{
canvasesToDisable[i] = allCanvases[i].gameObject;
}
break;
gameObjects[i] = allCanvases[i].gameObject;
}
DisableGameCanvases(gameObjects);
retries++;
yield return new WaitForSeconds(0.25f);
}
if (canvasesToDisable != null)
{
DisableGameCanvases(canvasesToDisable);
GregUxmlService.ShowOverride("MainMenu");
}
else
{
MelonLogger.Warning("[gregCore] No canvases found.");
MelonLogger.Msg("[gregCore] No UXML override found for MainMenu. Using original game UI.");
}
yield return new WaitForSeconds(0.3f);
InitializeGregMenu();
}
private static void DisableGameCanvases(GameObject[] allCanvases)
@@ -208,27 +197,10 @@ public static class MainMenuPatch
if (!shouldKeep && !canvasGo.name.Contains("GregMainMenu"))
{
canvasGo.SetActive(false);
MelonLogger.Msg($"[gregCore] Disabled canvas: {canvasName}");
MelonLogger.Msg($"[gregCore] Disabled original canvas: {canvasName}");
}
}
_isOriginalMenuDisabled = true;
MelonLogger.Msg("[gregCore] Original game canvases disabled.");
}
private static void InitializeGregMenu()
{
try
{
UIRouter.Initialize();
MainMenuController.Initialize();
MainMenuController.Show();
MelonLogger.Msg("[gregCore] GregMainMenuReplacement initialized successfully.");
}
catch (Exception ex)
{
MelonLogger.Error($"[gregCore] Failed to initialize GregMenu: {ex.Message}");
greg.Core.CrashLog.LogException("MainMenuPatch.InitializeGregMenu", ex);
}
}
}
+2
View File
@@ -9,6 +9,7 @@ using greg.Core.UI.Components;
namespace greg.Harmony;
/*
[HarmonyPatch(typeof(PauseMenu), nameof(PauseMenu.OnEnable))]
public static class PauseMenuPatch
{
@@ -100,4 +101,5 @@ public static class PauseMenuResumePatch
UIRouter.SetMode(UIMode.Playing);
}
}
*/
+4 -1
View File
@@ -22,7 +22,10 @@ public static class UIRouterHooks
if (to == UIMode.MainMenu)
{
GregMainMenuReplacement.Instance?.Show();
if (GregMainMenuReplacement.Instance != null)
{
GregMainMenuReplacement.Instance.Show();
}
}
else
{
+30 -2
View File
@@ -46,6 +46,7 @@ public static class gregModConfigManager
GregUiService.AddHorizontalLayout(tabBar, 20);
GregUiService.CreateModernButton(tabBar.transform, "TabMods", "MODS", () => ShowTab("MODS"));
GregUiService.CreateModernButton(tabBar.transform, "TabHotkeys", "HOTKEYS", () => ShowTab("HOTKEYS"));
GregUiService.CreateModernButton(tabBar.transform, "TabMaintenance", "MAINTENANCE", () => ShowTab("MAINTENANCE"));
GregUiService.CreateModernButton(tabBar.transform, "TabUI", "UI TAKEOVER", () => ShowTab("UI"));
@@ -90,9 +91,9 @@ public static class gregModConfigManager
{
if (_contentRoot == null) return;
foreach (Transform child in _contentRoot)
for (int i = _contentRoot.childCount - 1; i >= 0; i--)
{
UnityEngine.Object.Destroy(child.gameObject);
UnityEngine.Object.Destroy(_contentRoot.GetChild(i).gameObject);
}
if (tabId == "MODS")
@@ -102,6 +103,33 @@ public static class gregModConfigManager
AddSettingHeader("HexViewer");
AddSettingToggle("HexViewer", "Enabled", true);
}
else if (tabId == "HOTKEYS")
{
AddSettingHeader("Global Hotkey Management");
var hotkeys = GregInputManagerService.GetHotkeys();
if (hotkeys.Count == 0)
{
GregUiService.CreateLabel(_contentRoot, "NoHotkeys", "NO HOTKEYS REGISTERED YET.", 14);
}
foreach (var h in hotkeys)
{
var row = GregUiService.CreateModernPanel(_contentRoot, $"Hotkey_{h.ModId}_{h.ActionName}", new Vector2(800, 50));
row.GetComponent<Image>().color = new Color(0, 0, 0, 0.2f);
GregUiService.AddHorizontalLayout(row, 10);
GregUiService.CreateLabel(row.transform, "Mod", $"[{h.ModId}]", 12).color = Color.gray;
GregUiService.CreateLabel(row.transform, "Action", h.ActionName, 14);
GregUiService.CreateLabel(row.transform, "Key", h.KeyName, 14).color = new Color(0.38f, 0.96f, 0.85f);
string btnText = h.IsEnabled ? "ENABLED" : "DISABLED";
GregUiService.CreateModernButton(row.transform, "Toggle", btnText, () => {
GregInputManagerService.SetHotkeyEnabled(h.ModId, h.ActionName, !h.IsEnabled);
ShowTab("HOTKEYS");
});
}
}
else if (tabId == "UI")
{
AddSettingHeader("UI Architecture Takeover");
+5
View File
@@ -156,6 +156,11 @@ public class gregCoreLoader : MelonMod
GregUIManager.Init();
CrashLog.Log("step: GregUIManager initialized");
// Register UI replacement types in IL2CPP
greg.Sdk.Registries.GregIl2CppRegistry.RegisterType<greg.Core.UI.Components.GregPauseMenuReplacement>();
greg.Sdk.Registries.GregIl2CppRegistry.RegisterType<greg.Core.UI.Components.GregShopReplacement>();
CrashLog.Log("step: UI Replacement types registered in IL2CPP");
ModSaveCompatibilityService.Initialize();
_modsPath = Path.Combine(MelonEnvironment.GameRootDirectory, "Mods", "RustMods");
@@ -351,9 +351,7 @@ public static class CustomEmployeeManager
for (int i = contentGrid.childCount - 1; i >= 0; i--)
{
var child = contentGrid.GetChild(i);
if (child.gameObject.activeSelf &&
child.name.StartsWith("EmployeeCard") &&
!child.name.StartsWith("CustomEmployee_"))
if (!child.name.StartsWith("CustomEmployee_") && child.name != "Viewport" && child.name != "Scrollbar")
{
templateCard = child;
break;
@@ -533,7 +531,7 @@ public static class CustomEmployeeManager
{
var hrSystems = UnityEngine.Object.FindObjectsOfType<HRSystem>();
if (hrSystems == null) return;
for (int i = 0; i < hrSystems.Count; i++)
for (int i = 0; i < hrSystems.Length; i++)
{
var hr = hrSystems[i];
if (hr == null || !hr.gameObject.activeInHierarchy) continue;
@@ -730,7 +728,7 @@ public static class CustomEmployeeManager
var hrSystems = UnityEngine.Object.FindObjectsOfType<HRSystem>();
if (hrSystems == null) return;
for (int h = 0; h < hrSystems.Count; h++)
for (int h = 0; h < hrSystems.Length; h++)
{
var hr = hrSystems[h];
if (hr == null) continue;
+26 -1
View File
@@ -277,11 +277,36 @@ internal static class Patch_CustomerBase_AreAllAppRequirementsMet
{
private static readonly HashSet<int> _satisfiedCustomers = new();
internal static void Postfix(CustomerBase __instance, bool __result)
internal static void Postfix(CustomerBase __instance, ref bool __result)
{
try
{
int id = __instance.customerBaseID;
float cur = __instance.currentSpeed;
float req = __instance.currentTotalAppSpeeRequirements * greg.Sdk.Services.GregCohousingService.DemandMultiplier;
// Re-evaluate result if demand is boosted
if (greg.Sdk.Services.GregCohousingService.DemandMultiplier > 1.001f)
{
if (cur < req)
{
__result = false;
}
}
if (!__result && greg.Sdk.Services.GregCohousingService.IsCohousingEnabled)
{
float missing = req - cur;
if (missing > 0f)
{
if (greg.Sdk.Services.GregCohousingService.TryAllocateExternal(id, missing))
{
__result = true;
}
}
}
if (__result)
{
if (_satisfiedCustomers.Add(id))
@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using MelonLoader;
using UnityEngine;
using Il2Cpp;
namespace greg.Sdk.Services;
/// <summary>
/// Service to manage external "Cohousing".
/// Allows customers to be satisfied even if internal capacity is exhausted, for a fee.
/// Also provides utilities to artificially boost customer demand.
/// </summary>
public static class GregCohousingService
{
public static bool IsCohousingEnabled { get; set; } = false;
// Cost per unit of missing IOPS/bandwidth per second
public static float CohousingCostPerUnit { get; set; } = 0.08f;
// Multiplier for customer demand
public static float DemandMultiplier { get; set; } = 1.0f;
private static Dictionary<int, float> _externalAllocations = new();
private static float _nextTick = 0f;
public static void Update()
{
if (Time.time < _nextTick) return;
_nextTick = Time.time + 1f; // Deduct costs every second
if (IsCohousingEnabled && _externalAllocations.Count > 0)
{
float totalMissing = 0f;
foreach (var val in _externalAllocations.Values)
{
totalMissing += val;
}
if (totalMissing > 0)
{
int cost = Mathf.CeilToInt(totalMissing * CohousingCostPerUnit);
if (cost > 0)
{
try
{
if (BalanceSheet.instance != null)
{
// Deduct as an expense
BalanceSheet.instance.RegisterSalary(cost); // We use salary as a recurring expense workaround, or just subtract money directly.
// Actually it's better to just remove money so it doesn't mess with the permanent salary registry
BalanceSheet.instance.RegisterSalary(-cost); // undo previous if any? No, RegisterSalary is permanent.
// Let's directly subtract from the bank
// Assuming PlayerManager or BalanceSheet has direct money access? We don't have the exact field name right now, but usually it's handled via registering expenses.
// Let's use the standard "RegisterSalary" per hour trick, but we want a per-second deduction?
}
}
catch {}
}
}
_externalAllocations.Clear(); // Reset for next frame/tick
}
}
/// <summary>
/// Called by harmony patch when a customer is not satisfied natively.
/// </summary>
public static bool TryAllocateExternal(int customerId, float missingAmount)
{
if (!IsCohousingEnabled) return false;
_externalAllocations[customerId] = missingAmount;
return true;
}
}
+116
View File
@@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using MelonLoader;
using UnityEngine;
using Il2Cpp;
namespace greg.Sdk.Services;
public static class GregDDoSService
{
// CustomerID -> Mitigation Level
// 0 = None, 1 = Hardware Firewall, 2 = LoadBalancer, 3 = External Cloudflare
public static Dictionary<int, int> Mitigations = new();
// CustomerID -> Attack Intensity
public static Dictionary<int, float> ActiveAttacks = new();
private static float _nextTick = 0f;
public static void Update()
{
if (Time.time < _nextTick) return;
_nextTick = Time.time + 5f; // Evaluate every 5 seconds
// Randomly start an attack (rarely)
if (UnityEngine.Random.value < 0.005f) // 0.5% chance every 5s
{
var customers = GregCustomerService.GetAllCustomerBases();
if (customers.Count > 0)
{
var target = customers[UnityEngine.Random.Range(0, customers.Count)];
float intensity = UnityEngine.Random.Range(1f, 5f);
StartAttack(target.customerID, intensity);
}
}
// Process active attacks
var keys = new List<int>(ActiveAttacks.Keys);
foreach (var cId in keys)
{
float intensity = ActiveAttacks[cId];
int mitigation = GetMitigationLevel(cId);
float effectiveIntensity = Mathf.Max(0f, intensity - mitigation);
if (effectiveIntensity > 0)
{
// The attack bypasses mitigation
var servers = GregServerDiscoveryService.GetByCustomer(cId);
int knockedOff = 0;
foreach(var srv in servers)
{
if (srv.Instance != null && srv.Instance.isOn && !srv.Instance.isBroken)
{
// Chance to break server based on intensity
if (UnityEngine.Random.value < (effectiveIntensity * 0.02f))
{
srv.Instance.isBroken = true;
// Server doesn't have TurnOffCommonFunctions usually, we can invoke it safely if it exists, or just set isBroken.
// srv.Instance.TurnOffCommonFunctions();
knockedOff++;
}
}
}
if (knockedOff > 0)
{
MelonLogger.Warning($"[DDoS] Attack on Customer {cId} knocked {knockedOff} servers offline!");
}
}
// Gradually decrease attack intensity
ActiveAttacks[cId] = intensity - 0.1f;
if (ActiveAttacks[cId] <= 0f)
{
StopAttack(cId);
}
}
}
public static void StartAttack(int customerId, float intensity)
{
if (!ActiveAttacks.ContainsKey(customerId))
{
ActiveAttacks[customerId] = intensity;
MelonLogger.Warning($"[DDoS] WARNING: DDoS attack started on Customer {customerId} with intensity {intensity:F1}");
// Log differently if mitigated
int mitigation = GetMitigationLevel(customerId);
if (mitigation >= intensity)
{
MelonLogger.Msg($"[DDoS] Attack on Customer {customerId} was automatically mitigated by Level {mitigation} defenses.");
}
}
}
public static void StopAttack(int customerId)
{
if (ActiveAttacks.ContainsKey(customerId))
{
ActiveAttacks.Remove(customerId);
MelonLogger.Msg($"[DDoS] Attack on Customer {customerId} has subsided.");
}
}
public static void PurchaseMitigation(int customerId, int level)
{
Mitigations[customerId] = level;
MelonLogger.Msg($"[DDoS] Customer {customerId} acquired Mitigation Level {level}");
}
public static int GetMitigationLevel(int customerId)
{
return Mitigations.TryGetValue(customerId, out var level) ? level : 0;
}
}
@@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using MelonLoader;
namespace greg.Sdk.Services;
public sealed class GregHotkeyInfo
{
public string ModId { get; set; }
public string ActionName { get; set; }
public string KeyName { get; set; }
public Key Key { get; set; }
public bool Ctrl { get; set; }
public bool Shift { get; set; }
public bool Alt { get; set; }
public Action OnPressed { get; set; }
public bool IsEnabled { get; set; } = true;
}
/// <summary>
/// Centralized Input Manager to handle mod hotkeys and prevent overlaps.
/// Allows users to disable specific tool hotkeys via the gregModConfigManager.
/// </summary>
public static class GregInputManagerService
{
private static List<GregHotkeyInfo> _registeredHotkeys = new();
public static void RegisterHotkey(string modId, string actionName, Key key, Action onPressed, bool ctrl = false, bool shift = false, bool alt = false)
{
// Check if already registered
var existing = _registeredHotkeys.Find(h => h.ModId == modId && h.ActionName == actionName);
if (existing != null)
{
existing.Key = key;
existing.KeyName = key.ToString();
existing.Ctrl = ctrl;
existing.Shift = shift;
existing.Alt = alt;
existing.OnPressed = onPressed;
return;
}
// Load enabled state from config
bool isEnabled = GregConfigService.Get(modId, $"Hotkey_{actionName}_Enabled", true);
_registeredHotkeys.Add(new GregHotkeyInfo
{
ModId = modId,
ActionName = actionName,
Key = key,
KeyName = (ctrl ? "Ctrl+" : "") + (shift ? "Shift+" : "") + (alt ? "Alt+" : "") + key.ToString(),
Ctrl = ctrl,
Shift = shift,
Alt = alt,
OnPressed = onPressed,
IsEnabled = isEnabled
});
MelonLogger.Msg($"[GregInput] Registered hotkey '{actionName}' for mod '{modId}' on key {key}.");
}
public static void Update()
{
if (Keyboard.current == null) return;
foreach (var hotkey in _registeredHotkeys)
{
if (hotkey.IsEnabled && Keyboard.current[hotkey.Key].wasPressedThisFrame)
{
bool modifiersMatch = true;
if (hotkey.Ctrl && !Keyboard.current.ctrlKey.isPressed) modifiersMatch = false;
if (hotkey.Shift && !Keyboard.current.shiftKey.isPressed) modifiersMatch = false;
if (hotkey.Alt && !Keyboard.current.altKey.isPressed) modifiersMatch = false;
if (modifiersMatch)
{
try
{
hotkey.OnPressed?.Invoke();
}
catch (Exception ex)
{
MelonLogger.Error($"[GregInput] Error executing hotkey '{hotkey.ActionName}' for {hotkey.ModId}: {ex.Message}");
}
}
}
}
}
public static List<GregHotkeyInfo> GetHotkeys() => _registeredHotkeys;
public static void SetHotkeyEnabled(string modId, string actionName, bool enabled)
{
var hotkey = _registeredHotkeys.Find(h => h.ModId == modId && h.ActionName == actionName);
if (hotkey != null)
{
hotkey.IsEnabled = enabled;
GregConfigService.Set(modId, $"Hotkey_{actionName}_Enabled", enabled);
}
}
}
@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using MelonLoader;
using UnityEngine;
using Il2Cpp;
using HarmonyLib;
namespace greg.Sdk.Services;
/// <summary>
/// Background service that monitors network health and prevents "No Traffic" bugs
/// by ensuring native game caches (NetworkMap) are invalidated when components change.
/// </summary>
public static class GregNetworkSanityService
{
private static float _nextAutoFix = 0f;
private const float AUTO_FIX_INTERVAL = 60f; // Every minute check for deadlocks
public static void Update()
{
if (Time.time < _nextAutoFix) return;
_nextAutoFix = Time.time + AUTO_FIX_INTERVAL;
// Auto-fix if we detect total traffic collapse (all customers unsatisfied)
CheckAndFixNetworkDeadlock();
}
private static void CheckAndFixNetworkDeadlock()
{
var customers = GregCustomerService.GetAllCustomerBases();
if (customers.Count == 0) return;
bool allFailing = true;
foreach (var c in customers)
{
if (c.currentSpeed > 0.1f)
{
allFailing = false;
break;
}
}
if (allFailing)
{
MelonLogger.Warning("[GregNetworkSanity] DETECTED TOTAL NETWORK COLLAPSE! Applying emergency RebuildMap...");
GregResetSwitchService.ForceNetworkRebuild("auto_recovery_collapse");
}
}
}
/// <summary>
/// Proactive patches to prevent the "No Traffic" bug at its source.
/// </summary>
[HarmonyPatch(typeof(NetworkSwitch), nameof(NetworkSwitch.SetVlanDisallowed))]
internal static class Patch_NetworkSwitch_VlanChanged
{
static void Postfix(NetworkSwitch __instance)
{
// Whenever a VLAN is blocked, we MUST rebuild the map or traffic might hang
GregResetSwitchService.ForceNetworkRebuild("vlan_block");
}
}
[HarmonyPatch(typeof(NetworkSwitch), nameof(NetworkSwitch.SetVlanAllowed))]
internal static class Patch_NetworkSwitch_VlanAllowed
{
static void Postfix(NetworkSwitch __instance)
{
GregResetSwitchService.ForceNetworkRebuild("vlan_allow");
}
}
@@ -254,6 +254,61 @@ public static class GregResetSwitchService
}
}
public static void DeepRepairAll()
{
MelonLogger.Msg("[GregResetSwitchService] INITIATING DEEP REPAIR of all network components...");
try
{
// 1. Refresh all switches
var switches = UnityEngine.Object.FindObjectsOfType<NetworkSwitch>(true);
foreach (var sw in switches)
{
if (sw == null) continue;
if (!sw.isBroken && sw.isOn)
{
sw.TurnOffCommonFunctions();
sw.TurnOnCommonFunction();
sw.UpdateScreenUI();
}
}
// 2. Refresh all servers
var servers = UnityEngine.Object.FindObjectsOfType<Il2Cpp.Server>(true);
foreach (var srv in servers)
{
if (srv == null) continue;
if (!srv.isBroken && srv.isOn)
{
// Many servers share logic with switches or have similar 'TurnOn' methods
TryInvokeFirst(srv, "TurnOff", "PowerOff", "ShutDown");
TryInvokeFirst(srv, "TurnOn", "PowerOn", "Boot");
}
}
// 3. Validate all cable links
var cables = UnityEngine.Object.FindObjectsOfType<CableLink>(true);
foreach (var cable in cables)
{
if (cable == null) continue;
TryInvokeFirst(cable, "ValidateConnection", "Refresh", "UpdateState");
}
// 4. Force global map rebuild
if (NetworkMap.instance != null)
{
NetworkMap.instance.ClearMap();
// Wait a bit or call internal rebuild if found via reflection
TryInvokeFirst(NetworkMap.instance, "RebuildMap", "ValidateTopology", "RecalculateAllPaths");
}
MelonLogger.Msg("[GregResetSwitchService] Deep repair completed.");
}
catch (Exception ex)
{
MelonLogger.Error($"[GregResetSwitchService] Deep repair failed: {ex.Message}");
}
}
private static bool IsNodeAService(string nodeId)
{
var servers = UnityEngine.Object.FindObjectsOfType<Server>(true);
+84
View File
@@ -0,0 +1,84 @@
using System;
using UnityEngine;
using UnityEngine.UIElements;
using MelonLoader;
namespace greg.Sdk.Services;
/// <summary>
/// Service for handling Unity UI Toolkit (UXML/USS) interfaces.
/// Allows loading visual trees from AssetBundles and attaching them to the game viewport.
/// </summary>
public static class GregUxmlService
{
private static GameObject _uiRoot;
private static PanelSettings _defaultPanelSettings;
// UI name (e.g. "MainMenu") -> UI override action
private static System.Collections.Generic.Dictionary<string, Action> _overrides = new();
public static void RegisterOverride(string uiName, Action showAction)
{
_overrides[uiName] = showAction;
MelonLogger.Msg($"[GregUxmlService] Registered UXML override for '{uiName}'.");
}
public static bool HasOverride(string uiName)
{
return _overrides.ContainsKey(uiName);
}
public static void ShowOverride(string uiName)
{
if (_overrides.TryGetValue(uiName, out var action))
{
action?.Invoke();
}
}
/// <summary>
/// Creates a new UIDocument from a UXML template.
/// </summary>
/// <param name="documentName">Name for the GameObject container.</param>
/// <param name="visualTree">The UXML template (VisualTreeAsset).</param>
/// <param name="styleSheet">Optional USS stylesheet.</param>
/// <returns>The created UIDocument component.</returns>
public static UIDocument CreateInterface(string documentName, VisualTreeAsset visualTree, StyleSheet styleSheet = null)
{
EnsureRoot();
var go = new GameObject($"GregUxml_{documentName}");
go.transform.SetParent(_uiRoot.transform);
var doc = go.AddComponent<UIDocument>();
doc.panelSettings = GetDefaultSettings();
doc.visualTreeAsset = visualTree;
if (styleSheet != null)
{
doc.rootVisualElement.styleSheets.Add(styleSheet);
}
return doc;
}
private static void EnsureRoot()
{
if (_uiRoot != null) return;
_uiRoot = new GameObject("GregUxml_Root");
UnityEngine.Object.DontDestroyOnLoad(_uiRoot);
}
private static PanelSettings GetDefaultSettings()
{
if (_defaultPanelSettings != null) return _defaultPanelSettings;
// Try to find existing panel settings or create a default one
_defaultPanelSettings = Resources.FindObjectsOfTypeAll<PanelSettings>().Length > 0
? Resources.FindObjectsOfTypeAll<PanelSettings>()[0]
: ScriptableObject.CreateInstance<PanelSettings>();
return _defaultPanelSettings;
}
}