feat: add Cohousing service and patch for artificial demand and external capacity
This commit is contained in:
Binary file not shown.
+13
-1
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
@@ -22,7 +22,10 @@ public static class UIRouterHooks
|
||||
|
||||
if (to == UIMode.MainMenu)
|
||||
{
|
||||
GregMainMenuReplacement.Instance?.Show();
|
||||
if (GregMainMenuReplacement.Instance != null)
|
||||
{
|
||||
GregMainMenuReplacement.Instance.Show();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user