diff --git a/publish/gregCore-v1.0.0.32.zip b/publish/gregCore-v1.0.0.32.zip
new file mode 100644
index 00000000..d3008ceb
Binary files /dev/null and b/publish/gregCore-v1.0.0.32.zip differ
diff --git a/scripts/Build-Release.ps1 b/scripts/Build-Release.ps1
index d617f787..8c19e08b 100644
--- a/scripts/Build-Release.ps1
+++ b/scripts/Build-Release.ps1
@@ -15,10 +15,10 @@ New-Item -ItemType Directory -Path $publishDir | Out-Null
# 2. Build
Write-Host "Building gregCore (Release)..." -ForegroundColor Yellow
-dotnet build "$repoRoot\gregCore.csproj" -c Release
+dotnet build "$repoRoot\src\gregCore.csproj" -c Release -o "$buildDir"
# 3. Check output
-$dllPath = "$repoRoot\bin\Release\net6.0\gregCore.dll"
+$dllPath = "$buildDir\gregCore.dll"
if (-not (Test-Path $dllPath)) {
Write-Error "Build failed: gregCore.dll not found at $dllPath"
}
@@ -32,11 +32,12 @@ Write-Host "Packaging version $version into $zipName..." -ForegroundColor Yellow
# Copy files to a temporary folder for zipping
$tmpDir = "$publishDir\tmp"
+if (Test-Path $tmpDir) { Remove-Item -Recurse -Force $tmpDir }
New-Item -ItemType Directory -Path $tmpDir | Out-Null
Copy-Item $dllPath -Destination $tmpDir
Copy-Item "$repoRoot\README.md" -Destination $tmpDir
Copy-Item "$repoRoot\CHANGELOG.md" -Destination $tmpDir
-Copy-Item "$repoRoot\gregFramework\greg_hooks.json" -Destination $tmpDir
+Copy-Item "$repoRoot\assets\greg_hooks.json" -Destination $tmpDir
# Create zip
Compress-Archive -Path "$tmpDir\*" -DestinationPath $zipPath -Force
diff --git a/src/AssemblyInfo.cs b/src/AssemblyInfo.cs
deleted file mode 100644
index a8310a25..00000000
--- a/src/AssemblyInfo.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-using System.Runtime.CompilerServices;
-
-[assembly: InternalsVisibleTo("gregCore.Tests")]
-
diff --git a/src/Core/Abstractions/IGregPerformanceGovernor.cs b/src/Core/Abstractions/IGregPerformanceGovernor.cs
new file mode 100644
index 00000000..7f079bec
--- /dev/null
+++ b/src/Core/Abstractions/IGregPerformanceGovernor.cs
@@ -0,0 +1,13 @@
+///
+/// Schicht: Core
+/// Zweck: Interface für den Performance Governor.
+/// Maintainer: Wird vom EventBus für Throttling genutzt.
+///
+
+namespace gregCore.Core.Abstractions;
+
+public interface IGregPerformanceGovernor
+{
+ bool CanDispatchEvent();
+ void OnUpdate();
+}
diff --git a/src/Core/Abstractions/IGregPersistenceService.cs b/src/Core/Abstractions/IGregPersistenceService.cs
index f77908d2..c0d3858b 100644
--- a/src/Core/Abstractions/IGregPersistenceService.cs
+++ b/src/Core/Abstractions/IGregPersistenceService.cs
@@ -1,13 +1,8 @@
-///
-/// Schicht: Core
-/// Zweck: Interface für Daten-Persistenz.
-/// Maintainer: Wird für Spielstände und Mod-Daten (System.Text.Json) genutzt.
-///
-
namespace gregCore.Core.Abstractions;
-
public interface IGregPersistenceService
{
- T? LoadData(string key) where T : class;
- void SaveData(string key, T data) where T : class;
-}
+ void Set(string key, T value) where T : notnull;
+ T Get(string key, T defaultValue = default!) where T : notnull;
+ bool Has(string key);
+ void Delete(string key);
+}
\ No newline at end of file
diff --git a/src/Core/Events/GregEventBus.cs b/src/Core/Events/GregEventBus.cs
index 7f67d53d..2d1dc56c 100644
--- a/src/Core/Events/GregEventBus.cs
+++ b/src/Core/Events/GregEventBus.cs
@@ -12,6 +12,8 @@ public sealed class GregEventBus : IGregEventBus, IDisposable
private readonly Dictionary>> _handlers = new();
private readonly Dictionary[]> _cachedHandlers = new();
private readonly ReaderWriterLockSlim _rwLock = new();
+ private readonly System.Collections.Concurrent.ConcurrentQueue<(string hookName, EventPayload payload)> _deferredEvents = new();
+ private IGregPerformanceGovernor? _governor;
private bool _isDirty = true;
private bool _disposed;
@@ -21,6 +23,8 @@ public sealed class GregEventBus : IGregEventBus, IDisposable
_logger = logger.ForContext("EventBus");
}
+ public void SetGovernor(IGregPerformanceGovernor governor) => _governor = governor;
+
public void Subscribe(string hookName, Action handler)
{
ArgumentNullException.ThrowIfNull(hookName);
@@ -66,7 +70,27 @@ public sealed class GregEventBus : IGregEventBus, IDisposable
public bool Publish(string hookName, EventPayload payload)
{
ArgumentNullException.ThrowIfNull(hookName);
+
+ if (_governor != null && !_governor.CanDispatchEvent())
+ {
+ _deferredEvents.Enqueue((hookName, payload));
+ return false;
+ }
+ return PublishDirect(hookName, payload);
+ }
+
+ internal void FlushDeferredEvents()
+ {
+ while (_deferredEvents.TryDequeue(out var ev))
+ {
+ if (_governor != null && !_governor.CanDispatchEvent()) break;
+ PublishDirect(ev.hookName, ev.payload);
+ }
+ }
+
+ private bool PublishDirect(string hookName, EventPayload payload)
+ {
Action[]? handlersToInvoke = null;
_rwLock.EnterReadLock();
diff --git a/src/Core/Models/AutomationProgress.cs b/src/Core/Models/AutomationProgress.cs
new file mode 100644
index 00000000..11a34037
--- /dev/null
+++ b/src/Core/Models/AutomationProgress.cs
@@ -0,0 +1,9 @@
+namespace gregCore.Core.Models;
+
+public sealed record AutomationProgress
+{
+ public string CurrentTask { get; init; } = string.Empty;
+ public int Current { get; init; }
+ public int Total { get; init; }
+ public double PercentDone => Total == 0 ? 0 : (Current / (double)Total) * 100;
+}
\ No newline at end of file
diff --git a/src/Core/Models/AutomationResult.cs b/src/Core/Models/AutomationResult.cs
new file mode 100644
index 00000000..1870e633
--- /dev/null
+++ b/src/Core/Models/AutomationResult.cs
@@ -0,0 +1,18 @@
+namespace gregCore.Core.Models;
+
+public sealed record AutomationResult
+{
+ public bool IsSuccess { get; init; }
+ public string? Error { get; init; }
+ public string? Detail { get; init; }
+ public int ItemsProcessed { get; init; }
+
+ public static AutomationResult Success(int items = 1, string? detail = null)
+ => new AutomationResult { IsSuccess = true, ItemsProcessed = items, Detail = detail };
+
+ public static AutomationResult Failure(string error, string? detail = null)
+ => new AutomationResult { IsSuccess = false, Error = error, Detail = detail };
+
+ public static AutomationResult Partial(int done, int total, string? detail = null)
+ => new AutomationResult { IsSuccess = done > 0, ItemsProcessed = done, Detail = detail ?? $"{done}/{total} verarbeitet" };
+}
\ No newline at end of file
diff --git a/src/Core/Models/AutomationTask.cs b/src/Core/Models/AutomationTask.cs
new file mode 100644
index 00000000..ec6aa7e2
--- /dev/null
+++ b/src/Core/Models/AutomationTask.cs
@@ -0,0 +1,35 @@
+namespace gregCore.Core.Models;
+
+public enum AutomationTask
+{
+ ProcessDeliveryZone,
+ PickupItem,
+ PlaceItemInRack,
+ PlaceItemOnFloor,
+ LayCable,
+ RemoveCable,
+ LayPowerCable,
+ LayNetworkCable,
+ BuildRack,
+ InstallServer,
+ InstallSwitch,
+ InstallPatchPanel,
+ PowerOnDevice,
+ PowerOffDevice,
+ AssignIP,
+ AssignVlan,
+ ConfigureRouting,
+ SetupNetworkSegment,
+ RepairDevice,
+ RepairAll,
+ ReplaceHardware,
+ CheckDeviceHealth,
+ AssignEmployee,
+ UnassignEmployee,
+ HireEmployee,
+ FireEmployee,
+ UnlockRoom,
+ PlaceFurniture,
+ RemoveFurniture,
+ ExpandRack
+}
\ No newline at end of file
diff --git a/src/Core/Models/CableType.cs b/src/Core/Models/CableType.cs
new file mode 100644
index 00000000..1b134a14
--- /dev/null
+++ b/src/Core/Models/CableType.cs
@@ -0,0 +1,12 @@
+namespace gregCore.Core.Models;
+
+public enum CableType
+{
+ Cat5,
+ Cat6,
+ Cat6A,
+ Fiber,
+ PowerAC,
+ PowerDC,
+ Console
+}
\ No newline at end of file
diff --git a/src/Core/Models/EventPayload.cs b/src/Core/Models/EventPayload.cs
index 7289c33b..3e641da6 100644
--- a/src/Core/Models/EventPayload.cs
+++ b/src/Core/Models/EventPayload.cs
@@ -9,7 +9,7 @@ namespace gregCore.Core.Models;
// [GREG_SYNC_INSERT_DTOS]
[StructLayout(LayoutKind.Sequential)]
-public record struct EventPayload
+public record EventPayload
{
public string HookName { get; init; }
public DateTime OccurredAtUtc { get; init; }
diff --git a/src/Core/Models/HookName.cs b/src/Core/Models/HookName.cs
index fddd12fa..9a9a893b 100644
--- a/src/Core/Models/HookName.cs
+++ b/src/Core/Models/HookName.cs
@@ -11,8 +11,7 @@ public readonly record struct HookName
public string Domain { get; init; }
public string Event { get; init; }
- private readonly string? _full;
- public string Full => _full ?? $"greg.{Domain}.{Event}";
+ public string Full => $"greg.{Domain}.{Event}";
public static HookName Parse(string full)
{
@@ -20,7 +19,7 @@ public readonly record struct HookName
var parts = full.Split('.');
if (parts.Length >= 3 && parts[0] == "greg")
{
- return new HookName { Domain = parts[1], Event = parts[2], _full = full };
+ return new HookName { Domain = parts[1], Event = parts[2] };
}
throw new ArgumentException($"Invalid HookName format: {full}");
}
@@ -29,7 +28,7 @@ public readonly record struct HookName
{
ArgumentNullException.ThrowIfNull(domain);
ArgumentNullException.ThrowIfNull(eventName);
- return new HookName { Domain = domain, Event = eventName, _full = $"greg.{domain}.{eventName}" };
+ return new HookName { Domain = domain, Event = eventName };
}
public override string ToString() => Full;
diff --git a/src/Core/Models/NetworkSegmentConfig.cs b/src/Core/Models/NetworkSegmentConfig.cs
new file mode 100644
index 00000000..3bf9b6ba
--- /dev/null
+++ b/src/Core/Models/NetworkSegmentConfig.cs
@@ -0,0 +1,11 @@
+namespace gregCore.Core.Models;
+
+public sealed record NetworkSegmentConfig
+{
+ public string[] Devices { get; init; } = Array.Empty();
+ public string? Switch { get; init; }
+ public int VlanId { get; init; }
+ public string? SubnetBase { get; init; }
+ public string? Gateway { get; init; }
+ public string? DnsServer { get; init; }
+}
\ No newline at end of file
diff --git a/src/Core/Models/OperationPriority.cs b/src/Core/Models/OperationPriority.cs
new file mode 100644
index 00000000..f2b8f349
--- /dev/null
+++ b/src/Core/Models/OperationPriority.cs
@@ -0,0 +1,10 @@
+namespace gregCore.Core.Models;
+
+public enum OperationPriority
+{
+ Critical = 0,
+ High = 1,
+ Normal = 2,
+ Low = 3,
+ Background = 4
+}
\ No newline at end of file
diff --git a/src/Core/Models/PerformanceProfile.cs b/src/Core/Models/PerformanceProfile.cs
new file mode 100644
index 00000000..623433cd
--- /dev/null
+++ b/src/Core/Models/PerformanceProfile.cs
@@ -0,0 +1,43 @@
+namespace gregCore.Core.Models;
+
+public sealed record PerformanceProfile
+{
+ public int TargetFps { get; init; } = 60;
+ public int UnfocusedFps { get; init; } = 15;
+ public int LoadingFps { get; init; } = 30;
+ public int MaxConcurrentOps { get; init; } = 3;
+ public int MaxConcurrentRequests { get; init; } = 4;
+ public int MaxEventsPerFrame { get; init; } = 20;
+ public int RamWarningMb { get; init; } = 3072;
+ public int RamCriticalMb { get; init; } = 4096;
+ public int GcIntervalSeconds { get; init; } = 30;
+ public bool EnableVSync { get; init; } = true;
+ public int QualityLevel { get; init; } = 2;
+ public float ShadowDistance { get; init; } = 50f;
+ public int TextureResolution { get; init; } = 0;
+
+ public static PerformanceProfile Balanced => new PerformanceProfile();
+ public static PerformanceProfile HighPerformance => new PerformanceProfile
+ {
+ TargetFps = 144,
+ UnfocusedFps = 30,
+ MaxConcurrentOps = 8,
+ EnableVSync = false,
+ QualityLevel = 4,
+ GcIntervalSeconds = 60,
+ };
+ public static PerformanceProfile LowEnd => new PerformanceProfile
+ {
+ TargetFps = 30,
+ UnfocusedFps = 10,
+ MaxConcurrentOps = 1,
+ MaxEventsPerFrame = 10,
+ RamWarningMb = 2048,
+ RamCriticalMb = 3072,
+ EnableVSync = true,
+ QualityLevel = 0,
+ ShadowDistance = 20f,
+ TextureResolution = 2,
+ GcIntervalSeconds = 15,
+ };
+}
\ No newline at end of file
diff --git a/src/Core/Models/PerformanceStats.cs b/src/Core/Models/PerformanceStats.cs
new file mode 100644
index 00000000..a0246585
--- /dev/null
+++ b/src/Core/Models/PerformanceStats.cs
@@ -0,0 +1,9 @@
+namespace gregCore.Core.Models;
+
+public sealed record PerformanceStats
+{
+ public PerformanceProfile Profile { get; init; } = new PerformanceProfile();
+ public ResourceSnapshot Resources { get; init; } = new ResourceSnapshot();
+ public ThrottleMetrics Throttle { get; init; } = new ThrottleMetrics();
+ public int QueueDepth { get; init; }
+}
diff --git a/src/Core/Models/RackBuildConfig.cs b/src/Core/Models/RackBuildConfig.cs
new file mode 100644
index 00000000..15a54bca
--- /dev/null
+++ b/src/Core/Models/RackBuildConfig.cs
@@ -0,0 +1,17 @@
+namespace gregCore.Core.Models;
+
+public sealed record RackBuildConfig
+{
+ public int RackId { get; init; }
+ public RackSlotConfig[] Servers { get; init; } = Array.Empty();
+ public string? SwitchId { get; init; }
+ public string? PduId { get; init; }
+ public bool AutoPowerOn { get; init; } = true;
+}
+
+public sealed record RackSlotConfig
+{
+ public string ServerId { get; init; } = string.Empty;
+ public int Slot { get; init; }
+ public string? IpAddress { get; init; }
+}
\ No newline at end of file
diff --git a/src/Core/Models/ResourceSnapshot.cs b/src/Core/Models/ResourceSnapshot.cs
new file mode 100644
index 00000000..2156cdb9
--- /dev/null
+++ b/src/Core/Models/ResourceSnapshot.cs
@@ -0,0 +1,24 @@
+namespace gregCore.Core.Models;
+
+public sealed record ResourceSnapshot
+{
+ public DateTime TimestampUtc { get; init; } = DateTime.UtcNow;
+
+ // .NET Managed Heap & System RAM
+ public int RamUsedMb { get; init; }
+ public int PrivateMemoryMb { get; init; }
+ public int GcTotalMemoryMb { get; init; }
+
+ // Unity Engine Memory (Native)
+ public int UnityAllocatedMb { get; init; }
+ public int UnityReservedMb { get; init; }
+ public int UnityUnusedMb { get; init; }
+
+ // System Metrics
+ public int GcGen0Collections { get; init; }
+ public int GcGen1Collections { get; init; }
+ public int GcGen2Collections { get; init; }
+ public int ThreadCount { get; init; }
+
+ public string Summary => $"RAM:{RamUsedMb}MB Unity:{UnityAllocatedMb}MB GC:{GcTotalMemoryMb}MB Threads:{ThreadCount}";
+}
diff --git a/src/Core/Models/ThrottleMetrics.cs b/src/Core/Models/ThrottleMetrics.cs
new file mode 100644
index 00000000..dffe000c
--- /dev/null
+++ b/src/Core/Models/ThrottleMetrics.cs
@@ -0,0 +1,10 @@
+namespace gregCore.Core.Models;
+
+public sealed record ThrottleMetrics
+{
+ public int TotalQueued { get; init; }
+ public int TotalCompleted { get; init; }
+ public int CurrentActive { get; init; }
+ public int MaxConcurrent { get; init; }
+ public int QueueDepth { get; init; }
+}
diff --git a/src/Core/NullableAttributes.cs b/src/Core/NullableAttributes.cs
new file mode 100644
index 00000000..ca4cdbda
--- /dev/null
+++ b/src/Core/NullableAttributes.cs
@@ -0,0 +1,21 @@
+// This file provides the necessary attributes for the compiler to support
+// nullable reference types and other modern C# features in environments
+// where the core library might not have them (like IL2CPP mixing).
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)]
+ internal sealed class NullableAttribute : Attribute
+ {
+ public readonly byte[] NullableFlags;
+ public NullableAttribute(byte flag) => NullableFlags = new[] { flag };
+ public NullableAttribute(byte[] flags) => NullableFlags = flags;
+ }
+
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Delegate | AttributeTargets.Interface | AttributeTargets.Method | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
+ internal sealed class NullableContextAttribute : Attribute
+ {
+ public readonly byte Flag;
+ public NullableContextAttribute(byte flag) => Flag = flag;
+ }
+}
diff --git a/src/Diagnostic/FrameLimiterConfig.cs b/src/Diagnostic/FrameLimiterConfig.cs
deleted file mode 100644
index 0f4c3a99..00000000
--- a/src/Diagnostic/FrameLimiterConfig.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-namespace greg.Diagnostic;
-
-public sealed class FrameLimiterConfig
-{
- public bool Enabled { get; set; } = true;
- public FpsProfile Menu { get; set; } = new() { TargetFps = 30, VSync = 0 };
- public FpsProfile Gameplay { get; set; } = new() { TargetFps = 144, VSync = 0 };
- public AfkProfile Afk { get; set; } = new();
- public FpsProfile Minimized { get; set; } = new() { TargetFps = 5, VSync = 0 };
- public FpsProfile Background { get; set; } = new() { TargetFps = 20, VSync = 0 };
-}
-
-public class FpsProfile
-{
- public int TargetFps { get; set; } = 60;
- public int VSync { get; set; } = 0;
-}
-
-public sealed class AfkProfile : FpsProfile
-{
- public bool Enabled { get; set; } = true;
- public float AfkAfterSeconds { get; set; } = 60f;
- public AfkProfile() { TargetFps = 15; }
-}
\ No newline at end of file
diff --git a/src/Diagnostic/GregDiagnosticTools.cs b/src/Diagnostic/GregDiagnosticTools.cs
deleted file mode 100644
index 6e36b7cf..00000000
--- a/src/Diagnostic/GregDiagnosticTools.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using Il2Cpp;
-using MelonLoader;
-using UnityEngine;
-using greg.Sdk.Services;
-
-namespace greg.Diagnostic;
-
-public static class GregDiagnosticTools
-{
- public static void RunFullWorldAudit()
- {
- MelonLogger.Msg("\n[gregCore] --- WORLD DIAGNOSTIC AUDIT (Save Bug Analysis) ---");
-
- var switches = Object.FindObjectsOfType(true);
- int totalSwitches = switches.Length;
- int brokenSwitches = switches.Count(s => s != null && s.isBroken);
-
- MelonLogger.Msg($"[Audit] Scanned {totalSwitches} switches. {brokenSwitches} marked as BROKEN.");
-
- var ghostDefects = new List();
-
- foreach (var sw in switches)
- {
- if (sw == null) continue;
-
- if (sw.isBroken)
- {
- // Logic Audit: Why is it broken?
- bool hasFlow = GregResetSwitchService.EvaluateDeepStatus(sw) == GregDeepFlowStatus.Active;
-
- // If it has flow or seems logically sound but is "broken", it's a ghost defect.
- if (hasFlow)
- {
- ghostDefects.Add($"[GhostDefect] Switch {sw.switchId} (Label: {sw.label}) is BROKEN but has ACTIVE FLOW.");
- }
-
- // Check for Server/Rack components
- var rack = sw.GetComponentInParent();
- if (rack != null)
- {
- // If the rack is broken but all servers inside seem fine
- var servers = rack.GetComponentsInChildren();
- bool anyServerBroken = servers.Any(serv => serv.isBroken);
- if (!anyServerBroken && !hasFlow && sw.isOn)
- {
- ghostDefects.Add($"[GhostDefect] Rack {rack.name} / Switch {sw.switchId} is BROKEN but no servers inside are broken.");
- }
- }
- }
- }
-
- if (ghostDefects.Count > 0)
- {
- MelonLogger.Warning($"[Audit] DETECTED {ghostDefects.Count} GHOSH DEFECTS!");
- foreach (var defect in ghostDefects)
- {
- MelonLogger.Warning($" {defect}");
- }
- }
- else
- {
- MelonLogger.Msg("[Audit] No ghost defects detected in current scene.");
- }
-
- MelonLogger.Msg("[gregCore] --- AUDIT COMPLETE ---\n");
- }
-}
-
diff --git a/src/Diagnostic/GregFrameLimiterService.cs b/src/Diagnostic/GregFrameLimiterService.cs
deleted file mode 100644
index 2611bd62..00000000
--- a/src/Diagnostic/GregFrameLimiterService.cs
+++ /dev/null
@@ -1,150 +0,0 @@
-using System;
-using MelonLoader;
-using UnityEngine;
-using UnityEngine.InputSystem;
-
-namespace greg.Diagnostic;
-
-public sealed class GregFrameLimiterService
-{
- public static GregFrameLimiterService Instance { get; private set; } = null!;
-
- public enum GameState { Unknown, Menu, Gameplay, Loading }
- public string CurrentStateName => _currentState.ToString();
-
- private GameState _currentState = GameState.Unknown;
- private bool _isFocused = true;
- private bool _isMinimized = false;
- private bool _isAfk = false;
- private float _lastInputTime = 0f;
- private int _frame = 0;
-
- private FrameLimiterConfig? _cfg;
-
- public void Initialize(FrameLimiterConfig cfg)
- {
- Instance = this;
- _cfg = cfg;
-
- if (cfg == null || !cfg.Enabled)
- {
- MelonLogger.Msg("[FrameLimiter] Disabled via config.");
- return;
- }
-
- SetState(GameState.Menu);
- MelonLogger.Msg("[FrameLimiter] Initialized. Menu limit active.");
- }
-
- public void SetState(GameState state)
- {
- if (_currentState == state) return;
- _currentState = state;
- ApplyCurrentLimit();
- }
-
- public void OnFocusChanged(bool focused)
- {
- _isFocused = focused;
- ApplyCurrentLimit();
- }
-
- public void OnMinimizeChanged(bool minimized)
- {
- _isMinimized = minimized;
- ApplyCurrentLimit();
- }
-
- public void Tick()
- {
- if (_cfg == null || !_cfg.Enabled) return;
-
- bool anyInput = false;
- try
- {
- if (Keyboard.current != null)
- {
- anyInput = Keyboard.current.anyKey.isPressed;
- }
- if (!anyInput && Mouse.current != null)
- {
- anyInput = Mouse.current.delta.ReadValue().sqrMagnitude > 0.01f;
- }
- }
- catch { }
-
- if (anyInput)
- {
- _lastInputTime = Time.realtimeSinceStartup;
- if (_isAfk)
- {
- _isAfk = false;
- ApplyCurrentLimit();
- MelonLogger.Msg("[FrameLimiter] AFK ended — restoring limit.");
- }
- }
- else if (!_isAfk
- && _cfg?.Afk?.Enabled == true
- && _currentState == GameState.Gameplay
- && (Time.realtimeSinceStartup - _lastInputTime) > _cfg.Afk.AfkAfterSeconds)
- {
- _isAfk = true;
- ApplyCurrentLimit();
- MelonLogger.Msg("[FrameLimiter] AFK detected — reducing FPS.");
- }
- }
-
- private void ApplyCurrentLimit()
- {
- if (_cfg == null || !_cfg.Enabled) return;
-
- int targetFps;
- int vSync;
- string reason;
-
- if (_isMinimized)
- {
- targetFps = _cfg.Minimized.TargetFps;
- vSync = _cfg.Minimized.VSync;
- reason = "minimized";
- }
- else if (!_isFocused)
- {
- targetFps = _cfg.Background.TargetFps;
- vSync = _cfg.Background.VSync;
- reason = "background";
- }
- else if (_isAfk)
- {
- targetFps = _cfg.Afk.TargetFps;
- vSync = _cfg.Afk.VSync;
- reason = "afk";
- }
- else
- {
- (targetFps, vSync, reason) = _currentState switch
- {
- GameState.Menu => (_cfg.Menu.TargetFps, _cfg.Menu.VSync, "menu"),
- GameState.Loading => (_cfg.Menu.TargetFps, _cfg.Menu.VSync, "loading"),
- GameState.Gameplay => (_cfg.Gameplay.TargetFps, _cfg.Gameplay.VSync, "gameplay"),
- _ => (_cfg.Menu.TargetFps, _cfg.Menu.VSync, "unknown"),
- };
- }
-
- try
- {
- if (Application.targetFrameRate == targetFps
- && QualitySettings.vSyncCount == vSync)
- return;
-
- Application.targetFrameRate = targetFps;
- QualitySettings.vSyncCount = vSync;
-
- MelonLogger.Msg($"[FrameLimiter] {reason} → targetFPS={targetFps} vSync={vSync}");
- }
- catch (Exception ex)
- {
- MelonLogger.Warning($"[FrameLimiter] ApplyCurrentLimit failed: {ex.Message}");
- }
- }
-}
\ No newline at end of file
diff --git a/src/Diagnostic/GregPerfConfig.cs b/src/Diagnostic/GregPerfConfig.cs
deleted file mode 100644
index 8c984a77..00000000
--- a/src/Diagnostic/GregPerfConfig.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using System.Text.Json.Serialization;
-
-namespace greg.Diagnostic;
-
-public sealed class GregPerfConfig
-{
- public static GregPerfConfig Instance { get; set; } = new();
-
- public bool FrameCapEnabled { get; set; } = true;
- public int MenuFps { get; set; } = 30;
- public int GameplayFps { get; set; } = 144;
- public int BackgroundFps { get; set; } = 20;
- public int AfkFps { get; set; } = 15;
- public bool AfkEnabled { get; set; } = true;
- public float AfkSeconds { get; set; } = 60f;
- public int MaxAllowedFps { get; set; } = 240;
-
- [JsonIgnore]
- public int CurrentTarget { get; set; } = 30;
-
- public bool ThreadingEnabled { get; set; } = true;
- public int PhysicalCores { get; set; } = 0;
-
- public bool GcOptEnabled { get; set; } = true;
- public int GcTriggerMb { get; set; } = 256;
- public bool IncrementalGc { get; set; } = true;
-
- public bool RenderOptEnabled { get; set; } = true;
- public bool ReduceShadows { get; set; } = true;
- public float ShadowDistanceM { get; set; } = 50f;
- public int ShadowCascades { get; set; } = 2;
- public bool AggressiveLod { get; set; } = true;
- public float LodBias { get; set; } = 1.0f;
- public int MaxLodLevel { get; set; } = 0;
- public bool LimitPixelLights { get; set; } = true;
- public int MaxPixelLights { get; set; } = 2;
- public bool ReduceTextureQuality { get; set; } = false;
- public int TextureMipMapLimit { get; set; } = 0;
- public bool DisableSoftParticles { get; set; } = true;
- public bool DisableHeavyPostProcessing { get; set; } = true;
- public bool DisableMotionBlur { get; set; } = true;
- public bool DisableBloom { get; set; } = false;
- public bool DisableDoF { get; set; } = true;
- public bool DisableAO { get; set; } = false;
- public bool DisableSSR { get; set; } = true;
-}
\ No newline at end of file
diff --git a/src/Diagnostic/GregPerformanceHud.cs b/src/Diagnostic/GregPerformanceHud.cs
deleted file mode 100644
index 793ff01a..00000000
--- a/src/Diagnostic/GregPerformanceHud.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using MelonLoader;
-using UnityEngine;
-using UnityEngine.InputSystem;
-
-namespace greg.Diagnostic;
-
-public sealed class GregPerformanceHud : MelonMod
-{
- private bool _visible = false;
- private string _displayText = "";
- private float _updateTimer = 0f;
-
- public override void OnUpdate()
- {
- try
- {
- if (Keyboard.current?.f9Key?.wasPressedThisFrame == true)
- _visible = !_visible;
- }
- catch { }
-
- if (!_visible) return;
-
- _updateTimer += Time.unscaledDeltaTime;
- if (_updateTimer < 1f) return;
- _updateTimer = 0f;
-
- float currentFps = Time.unscaledDeltaTime > 0 ? 1f / Time.unscaledDeltaTime : 0f;
- float targetFps = Application.targetFrameRate;
- string state = GregFrameLimiterService.Instance?.CurrentStateName ?? "?";
- long ramMb = System.GC.GetTotalMemory(false) / 1024 / 1024;
- int gpuMb = SystemInfo.graphicsMemorySize;
-
- _displayText =
- $"gregCore Performance\n" +
- $"FPS: {targetFps}/{currentFps:F0}\n" +
- $"State: {state}\n" +
- $"RAM: {ramMb}MB\n" +
- $"GPU: {gpuMb}MB\n" +
- $"[F9] hide";
- }
-
- public override void OnGUI()
- {
- if (!_visible) return;
- GUI.Box(new Rect(10, 10, 200, 160), _displayText);
- }
-}
\ No newline at end of file
diff --git a/src/Diagnostic/GregPerformanceOptimizer.cs b/src/Diagnostic/GregPerformanceOptimizer.cs
deleted file mode 100644
index 495362a2..00000000
--- a/src/Diagnostic/GregPerformanceOptimizer.cs
+++ /dev/null
@@ -1,461 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using HarmonyLib;
-using Il2Cpp;
-using MelonLoader;
-using UnityEngine;
-using UnityEngine.AI;
-using UnityEngine.Rendering;
-using UnityEngine.Rendering.HighDefinition;
-
-namespace greg.Diagnostic;
-
-// ── Main-thread dispatch queue ─────────────────────────────────────────────
-internal static class MainThreadDispatch
-{
- private static readonly ConcurrentQueue _queue = new();
- public static void Enqueue(Action action) => _queue.Enqueue(action);
- public static void Drain()
- {
- while (_queue.TryDequeue(out var action))
- {
- try { action(); }
- catch (Exception ex) { MelonLogger.Warning($"[gregCore.PerfFix] MainThreadDispatch error: {ex.Message}"); }
- }
- }
-}
-
-public static class GregPerformanceOptimizer
-{
- // ── Preferences ───────────────────────────────────────────────────────
- public static MelonPreferences_Entry CanvasThrottleEnabled;
- public static MelonPreferences_Entry CanvasUpdateInterval;
-
- public static MelonPreferences_Entry IndicatorThrottleEnabled;
- public static MelonPreferences_Entry IndicatorUpdateInterval;
-
- public static MelonPreferences_Entry ThrottlePulsating;
- public static MelonPreferences_Entry PulsatingUpdateInterval;
-
- public static MelonPreferences_Entry RouteEvalCooldown;
- public static MelonPreferences_Entry AsyncRouteEval;
- public static MelonPreferences_Entry AutoSaveMinutes;
-
- // NPCs
- public static MelonPreferences_Entry NpcEnabled;
- public static MelonPreferences_Entry NpcThrottleDistance;
- public static MelonPreferences_Entry NpcThrottleInterval;
-
- // Memory
- public static MelonPreferences_Entry MemoryEnabled;
- public static MelonPreferences_Entry TextureMipmapLimit;
- public static MelonPreferences_Entry StreamingMipmaps;
- public static MelonPreferences_Entry StreamingMipmapsBudgetMB;
- public static MelonPreferences_Entry PeriodicGCIntervalSeconds;
-
- // Graphics
- public static MelonPreferences_Entry GraphicsEnabled;
- public static MelonPreferences_Entry ShadowDistance;
- public static MelonPreferences_Entry CameraFarClip;
- public static MelonPreferences_Entry LodBias;
- public static MelonPreferences_Entry DisableSSAO;
- public static MelonPreferences_Entry DisableContactShadows;
- public static MelonPreferences_Entry DisableGlobalIllumination;
- public static MelonPreferences_Entry DisableSSR;
- public static MelonPreferences_Entry DisableVolumetricFog;
-
- // ── OLD TWEAKS ────────────────────────────────────────────────────────
- public static MelonPreferences_Entry TargetFPS;
-
- public static void ApplySettings()
- {
- Initialize();
- }
-
- public static void Initialize()
- {
- var cat = MelonPreferences.CreateCategory("PerfFix");
-
- TargetFPS = cat.CreateEntry("TargetFPS", 120, "TargetFPS", "Target framerate. 0 = uncap.");
-
- CanvasThrottleEnabled = cat.CreateEntry("CanvasThrottle", true, "CanvasThrottle",
- "Throttle WorldCanvasCuller.Update() to CanvasUpdateInterval Hz instead of every frame.");
-
- CanvasUpdateInterval = cat.CreateEntry("CanvasUpdateInterval", 0.1f, "CanvasUpdateInterval",
- "Seconds between WorldCanvasCuller distance checks (0.1 = 10 Hz). Vanilla runs at full framerate.");
-
- IndicatorThrottleEnabled = cat.CreateEntry("IndicatorThrottle", true, "IndicatorThrottle",
- "Throttle PositionIndicator.Update() (warning/error triangles) — re-projects world→screen every frame.");
-
- IndicatorUpdateInterval = cat.CreateEntry("IndicatorUpdateInterval", 0.1f, "IndicatorUpdateInterval",
- "Seconds between PositionIndicator screen-position updates (0.1 = 10 Hz).");
-
- ThrottlePulsating = cat.CreateEntry("ThrottlePulsating", true, "ThrottlePulsating",
- "Throttle PulsatingImageColor and PulsatingText Update() calls.");
-
- PulsatingUpdateInterval = cat.CreateEntry("PulsatingUpdateInterval", 0.05f, "PulsatingUpdateInterval",
- "Seconds between pulsating effect updates (0.05 = 20 Hz).");
-
- RouteEvalCooldown = cat.CreateEntry("RouteEvalCooldown", 2.0f, "RouteEvalCooldown",
- "Minimum seconds between full ECS cable-route re-evaluations.");
-
- AsyncRouteEval = cat.CreateEntry("AsyncRouteEval", false, "AsyncRouteEval",
- "EXPERIMENTAL (DISABLED!): Run EvaluateAllRoutes on a background thread. CAUSES IL2CPP CRASHES.");
-
- AutoSaveMinutes = cat.CreateEntry("AutoSaveMinutes", 10.0f, "AutoSaveMinutes",
- "Minutes between auto-saves. Large saves cause frame hitches. Set to 0 to disable.");
-
- GraphicsEnabled = cat.CreateEntry("GraphicsEnabled", true, "GraphicsEnabled",
- "Master toggle for the graphics fixes below.");
-
- ShadowDistance = cat.CreateEntry("ShadowDistance", 20f, "ShadowDistance",
- "HDRP shadow cull distance in metres. Vanilla is ~150 m.");
-
- CameraFarClip = cat.CreateEntry("CameraFarClip", 80f, "CameraFarClip",
- "Player camera far clip plane in metres. Vanilla is ~1000 m.");
-
- LodBias = cat.CreateEntry("LodBias", 0.4f, "LodBias",
- "Unity LOD bias. Lower = switch to cheaper LOD meshes sooner.");
-
- DisableSSAO = cat.CreateEntry("DisableSSAO", true, "DisableSSAO",
- "Disable HDRP Screen-Space Ambient Occlusion.");
-
- DisableContactShadows = cat.CreateEntry("DisableContactShadows", true, "DisableContactShadows",
- "Disable HDRP Contact Shadows.");
-
- DisableGlobalIllumination = cat.CreateEntry("DisableGlobalIllumination", true, "DisableGlobalIllumination",
- "Disable HDRP Screen-Space Global Illumination.");
-
- DisableSSR = cat.CreateEntry("DisableSSR", true, "DisableSSR",
- "Disable HDRP Screen-Space Reflections on floors/racks.");
-
- DisableVolumetricFog = cat.CreateEntry("DisableVolumetricFog", false, "DisableVolumetricFog",
- "Disable HDRP Volumetric Fog.");
-
- NpcEnabled = cat.CreateEntry("NpcEnabled", true, "NpcEnabled",
- "Master toggle for NPC/Technician optimizations.");
-
- NpcThrottleDistance = cat.CreateEntry("NpcThrottleDistance", 15f, "NpcThrottleDistance",
- "Distance in metres beyond which Technician FixedUpdate and LateUpdate are throttled.");
-
- NpcThrottleInterval = cat.CreateEntry("NpcThrottleInterval", 0.2f, "NpcThrottleInterval",
- "Seconds between FixedUpdate/LateUpdate ticks for distant technicians.");
-
- MemoryEnabled = cat.CreateEntry("MemoryEnabled", true, "MemoryEnabled",
- "Master toggle for memory reduction settings below.");
-
- TextureMipmapLimit = cat.CreateEntry("TextureMipmapLimit", 1, "TextureMipmapLimit",
- "Global texture mipmap skip level. 0=full resolution, 1=half resolution, 2=quarter resolution.");
-
- StreamingMipmaps = cat.CreateEntry("StreamingMipmaps", true, "StreamingMipmaps",
- "Enable Unity mipmap streaming.");
-
- StreamingMipmapsBudgetMB = cat.CreateEntry("StreamingMipmapsBudgetMB", 512f, "StreamingMipmapsBudgetMB",
- "Memory budget for streamed mipmaps in megabytes.");
-
- PeriodicGCIntervalSeconds = cat.CreateEntry("PeriodicGCIntervalSeconds", 0f, "PeriodicGCIntervalSeconds",
- "Seconds between forced garbage collection passes. Set to 0 to disable. (WARNING: Enabled GC has been known to crash Il2CppInterop)");
-
- MelonLogger.Msg($"[gregCore.PerfFix] Loaded. " +
- $"Canvas={CanvasUpdateInterval.Value}s " +
- $"RouteEval={RouteEvalCooldown.Value}s " +
- $"AutoSave={AutoSaveMinutes.Value}min " +
- $"FarClip={CameraFarClip.Value}m");
-
- ApplyMemorySettings();
-
- // Base Performance Tweaks
- int targetFPS = TargetFPS.Value > 0 ? TargetFPS.Value : Screen.currentResolution.refreshRate;
- QualitySettings.vSyncCount = 0;
- Application.targetFrameRate = targetFPS > 0 ? targetFPS : 120;
- }
-
- private static float _nextGC = 0f;
-
- public static void OnUpdate()
- {
- MainThreadDispatch.Drain();
-
- float interval = PeriodicGCIntervalSeconds?.Value ?? 0f;
- if (interval > 0f && Time.realtimeSinceStartup >= _nextGC && _nextGC > 0f)
- {
- _nextGC = Time.realtimeSinceStartup + interval;
- MelonCoroutines.Start(RunPeriodicGC());
- }
- }
-
- private static System.Collections.IEnumerator RunPeriodicGC()
- {
- yield return null;
- // GC forcing removed - Causes Il2CppInterop/Unity Finalizer NullReferenceException
- MelonLogger.Msg($"[gregCore.PerfFix] Periodic GC skipped to prevent Il2CppInterop crashes.");
- }
-
- public static void OnSceneLoaded()
- {
- ApplySimulationFixes();
- MelonCoroutines.Start(ApplyGraphicsFixesNextFrame());
- if (PeriodicGCIntervalSeconds != null)
- {
- float interval = PeriodicGCIntervalSeconds.Value;
- _nextGC = interval > 0f ? Time.realtimeSinceStartup + interval : 0f;
- }
- }
-
- private static void ApplySimulationFixes()
- {
- try
- {
- var wis = WaypointInitializationSystem.Instance;
- if (wis != null)
- {
- wis.SetEvaluationCooldown(RouteEvalCooldown.Value);
- MelonLogger.Msg($"[gregCore.PerfFix] RouteEvalCooldown → {RouteEvalCooldown.Value}s");
- }
- }
- catch (Exception ex) { MelonLogger.Warning($"[gregCore.PerfFix] RouteEvalCooldown: {ex.Message}"); }
-
- try
- {
- var mgr = MainGameManager.instance;
- if (mgr != null)
- {
- if (AutoSaveMinutes.Value <= 0f)
- {
- mgr.SetAutoSaveEnabled(false);
- MelonLogger.Msg("[gregCore.PerfFix] AutoSave disabled.");
- }
- else
- {
- mgr.SetAutoSaveEnabled(true);
- mgr.SetAutoSaveInterval(AutoSaveMinutes.Value);
- MelonLogger.Msg($"[gregCore.PerfFix] AutoSave interval → {AutoSaveMinutes.Value} min");
- }
- }
- }
- catch (Exception ex) { MelonLogger.Warning($"[gregCore.PerfFix] AutoSave: {ex.Message}"); }
- }
-
- private static System.Collections.IEnumerator ApplyGraphicsFixesNextFrame()
- {
- yield return null;
- ApplyGraphicsFixes();
- }
-
- private static void ApplyMemorySettings()
- {
- if (!MemoryEnabled.Value) return;
- try
- {
- QualitySettings.globalTextureMipmapLimit = TextureMipmapLimit.Value;
- }
- catch (Exception ex) { MelonLogger.Warning($"[gregCore.PerfFix] TextureMipmapLimit: {ex.Message}"); }
-
- try
- {
- QualitySettings.streamingMipmapsActive = StreamingMipmaps.Value;
- if (StreamingMipmaps.Value)
- {
- QualitySettings.streamingMipmapsMemoryBudget = StreamingMipmapsBudgetMB.Value;
- }
- }
- catch (Exception ex) { MelonLogger.Warning($"[gregCore.PerfFix] StreamingMipmaps: {ex.Message}"); }
- }
-
- public static void ApplyGraphicsFixes()
- {
- QualitySettings.lodBias = LodBias.Value;
-
- if (!GraphicsEnabled.Value) return;
-
- try
- {
- var cam = MainGameManager.instance?.playerCamera;
- if (cam != null)
- {
- cam.farClipPlane = CameraFarClip.Value;
- }
- }
- catch (Exception ex) { MelonLogger.Warning($"[gregCore.PerfFix] CameraFarClip: {ex.Message}"); }
-
- try
- {
- var sg = SettingsSingleton.instance?.settingsGraphics;
- if (sg != null)
- {
- sg.SetShadowDistance(ShadowDistance.Value);
- }
- }
- catch (Exception ex) { MelonLogger.Warning($"[gregCore.PerfFix] ShadowDistance: {ex.Message}"); }
-
- try
- {
- var sg = SettingsSingleton.instance?.settingsGraphics;
- if (sg == null) return;
- var profile = sg.volumeProfile;
- if (profile == null) return;
-
- int disabled = 0;
- if (DisableSSAO.Value && profile.TryGet(out var ssao))
- { ssao.active = false; disabled++; }
- if (DisableContactShadows.Value && profile.TryGet(out var cs))
- { cs.active = false; disabled++; }
- if (DisableGlobalIllumination.Value && profile.TryGet(out var gi))
- { gi.active = false; disabled++; }
- if (DisableSSR.Value && profile.TryGet(out var ssr))
- { ssr.active = false; disabled++; }
- if (DisableVolumetricFog.Value && profile.TryGet(out var fog))
- { fog.enableVolumetricFog.overrideState = true; fog.enableVolumetricFog.value = false; disabled++; }
- }
- catch (Exception ex) { MelonLogger.Warning($"[gregCore.PerfFix] HDRP volume overrides: {ex.Message}"); }
- }
-}
-
-// ── WorldCanvasCuller throttle ─────────────────────────────────────────────
-[HarmonyPatch(typeof(WorldCanvasCuller), "Update")]
-internal static class WorldCanvasCullerPatch
-{
- private static readonly Dictionary _nextRun = new();
- static bool Prefix(WorldCanvasCuller __instance)
- {
- if (GregPerformanceOptimizer.CanvasThrottleEnabled == null || !GregPerformanceOptimizer.CanvasThrottleEnabled.Value) return true;
- float now = Time.time;
- var ptr = __instance.Pointer;
- if (_nextRun.TryGetValue(ptr, out float next) && now < next) return false;
- _nextRun[ptr] = now + GregPerformanceOptimizer.CanvasUpdateInterval.Value;
- return true;
- }
-}
-
-// ── PositionIndicator throttle ─────────────────────────────────────────────
-[HarmonyPatch(typeof(PositionIndicator), "Update")]
-internal static class PositionIndicatorPatch
-{
- private static readonly Dictionary _nextRun = new();
- static bool Prefix(PositionIndicator __instance)
- {
- if (GregPerformanceOptimizer.IndicatorThrottleEnabled == null || !GregPerformanceOptimizer.IndicatorThrottleEnabled.Value) return true;
- float now = Time.time;
- var ptr = __instance.Pointer;
- if (_nextRun.TryGetValue(ptr, out float next) && now < next) return false;
- _nextRun[ptr] = now + GregPerformanceOptimizer.IndicatorUpdateInterval.Value;
- return true;
- }
-}
-
-// ── PulsatingImageColor throttle ───────────────────────────────────────────
-[HarmonyPatch(typeof(PulsatingImageColor), "Update")]
-internal static class PulsatingImageColorPatch
-{
- private static readonly Dictionary _nextRun = new();
- static bool Prefix(PulsatingImageColor __instance)
- {
- if (GregPerformanceOptimizer.ThrottlePulsating == null || !GregPerformanceOptimizer.ThrottlePulsating.Value) return true;
- float now = Time.time;
- var ptr = __instance.Pointer;
- if (_nextRun.TryGetValue(ptr, out float next) && now < next) return false;
- _nextRun[ptr] = now + GregPerformanceOptimizer.PulsatingUpdateInterval.Value;
- return true;
- }
-}
-
-// ── PulsatingText throttle ─────────────────────────────────────────────────
-[HarmonyPatch(typeof(PulsatingText), "Update")]
-internal static class PulsatingTextPatch
-{
- private static readonly Dictionary _nextRun = new();
- static bool Prefix(PulsatingText __instance)
- {
- if (GregPerformanceOptimizer.ThrottlePulsating == null || !GregPerformanceOptimizer.ThrottlePulsating.Value) return true;
- float now = Time.time;
- var ptr = __instance.Pointer;
- if (_nextRun.TryGetValue(ptr, out float next) && now < next) return false;
- _nextRun[ptr] = now + GregPerformanceOptimizer.PulsatingUpdateInterval.Value;
- return true;
- }
-}
-
-// ── Async EvaluateAllRoutes ────────────────────────────────────────────────
-[HarmonyPatch(typeof(WaypointInitializationSystem), "EvaluateAllRoutes")]
-internal static class AsyncRouteEvalPatch
-{
- [ThreadStatic]
- private static bool _allowPassthrough;
-
- private static volatile bool _evaluationInFlight;
-
- static bool Prefix(WaypointInitializationSystem __instance)
- {
- // ── KILLS IL2CPP GC BECAUSE UNMANAGED THREAD IS NOT ATTACHED ──
- // Disabled fully
- return true;
-
- /*
- if (GregPerformanceOptimizer.AsyncRouteEval == null || !GregPerformanceOptimizer.AsyncRouteEval.Value) return true;
-
- if (_allowPassthrough) return true;
-
- if (_evaluationInFlight) return false;
-
- _evaluationInFlight = true;
- var wis = __instance;
-
- Task.Run(() =>
- {
- _allowPassthrough = true;
- try
- {
- wis.EvaluateAllRoutes();
- }
- catch (Exception ex)
- {
- MelonLogger.Warning($"[gregCore.PerfFix] Async EvaluateAllRoutes threw: {ex.GetType().Name}: {ex.Message}. " +
- "Falling back to main-thread execution.");
-
- MainThreadDispatch.Enqueue(() =>
- {
- _allowPassthrough = true;
- try { wis.EvaluateAllRoutes(); }
- catch (Exception ex2) { MelonLogger.Warning($"[gregCore.PerfFix] Sync fallback also failed: {ex2.Message}"); }
- finally { _allowPassthrough = false; }
- });
- }
- finally
- {
- _allowPassthrough = false;
- _evaluationInFlight = false;
- }
- });
-
- return false;
- */
- }
-}
-
-// ── Technician Animator culling ────────────────────────────────────────────
-[HarmonyPatch(typeof(TechnicianManager), "AddTechnician")]
-internal static class TechnicianAnimatorCullingPatch
-{
- static void Postfix(Technician technician)
- {
- if (GregPerformanceOptimizer.NpcEnabled == null || !GregPerformanceOptimizer.NpcEnabled.Value || technician == null) return;
- try
- {
- var anim = technician.GetComponent();
- if (anim != null)
- anim.cullingMode = AnimatorCullingMode.CullCompletely;
-
- var agent = technician.GetComponent();
- if (agent != null)
- agent.obstacleAvoidanceType = ObstacleAvoidanceType.LowQualityObstacleAvoidance;
- }
- catch (Exception ex)
- {
- MelonLogger.Warning($"[gregCore.PerfFix] TechnicianAnimatorCulling: {ex.Message}");
- }
- }
-}
-
-// Removed Technician FixedUpdate, LateUpdate, and Footstep update patches as methods are stripped and cause Harmony init failures
diff --git a/src/Diagnostic/GregRenderOptimizer.cs b/src/Diagnostic/GregRenderOptimizer.cs
deleted file mode 100644
index bd98f9dd..00000000
--- a/src/Diagnostic/GregRenderOptimizer.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-using System;
-using MelonLoader;
-using UnityEngine;
-using UnityEngine.Rendering;
-
-namespace greg.Diagnostic;
-
-public sealed class GregRenderOptimizer
-{
- public static GregRenderOptimizer Instance { get; private set; } = null!;
-
- public void Initialize(RenderOptimizerConfig cfg)
- {
- Instance = this;
- if (cfg == null || !cfg.Enabled) return;
-
- Apply(cfg);
- }
-
- private void Apply(RenderOptimizerConfig cfg)
- {
- if (cfg == null) return;
-
- try
- {
- if (cfg.ReduceShadowDistance)
- {
- QualitySettings.shadowDistance = cfg.ShadowDistance;
- QualitySettings.shadowCascades = cfg.ShadowCascades;
- Log($"Shadows: distance={cfg.ShadowDistance} cascades={cfg.ShadowCascades}");
- }
-
- if (cfg.AdjustLodBias)
- {
- QualitySettings.lodBias = cfg.LodBias;
- Log($"LOD bias: {cfg.LodBias}");
- }
-
- if (cfg.LimitPixelLights)
- {
- QualitySettings.pixelLightCount = cfg.PixelLightCount;
- Log($"Pixel lights: {cfg.PixelLightCount}");
- }
-
- if (cfg.SetAnisotropicFiltering)
- {
- QualitySettings.anisotropicFiltering =
- cfg.AnisotropicFiltering ? AnisotropicFiltering.Enable
- : AnisotropicFiltering.Disable;
- Log($"Anisotropic: {cfg.AnisotropicFiltering}");
- }
-
- if (cfg.SetAntiAliasing)
- {
- QualitySettings.antiAliasing = cfg.AntiAliasingLevel;
- Log($"AA: {cfg.AntiAliasingLevel}x MSAA");
- }
-
- if (cfg.DisableSoftParticles)
- {
- QualitySettings.softParticles = false;
- Log("Soft particles: disabled");
- }
-
- QualitySettings.vSyncCount = 0;
-
- Log("Render optimizations applied.");
- }
- catch (Exception ex)
- {
- MelonLogger.Warning($"[RenderOptimizer] Apply failed: {ex.Message}");
- }
- }
-
- private static void Log(string msg) =>
- MelonLogger.Msg($"[RenderOptimizer] {msg}");
-}
\ No newline at end of file
diff --git a/src/Diagnostic/GregSessionLogger.cs b/src/Diagnostic/GregSessionLogger.cs
deleted file mode 100644
index 1598fe52..00000000
--- a/src/Diagnostic/GregSessionLogger.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using System;
-using System.IO;
-using MelonLoader;
-using MelonLoader.Utils;
-
-namespace greg.Core.Diagnostic;
-
-public static class GregSessionLogger
-{
- private static string _sessionFilePath;
- private static readonly object _lock = new();
-
- public static void Initialize()
- {
- string logDir = Path.Combine(MelonEnvironment.UserDataDirectory, "gregCore", "Logs");
- if (!Directory.Exists(logDir)) Directory.CreateDirectory(logDir);
-
- string timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
- _sessionFilePath = Path.Combine(logDir, $"session_{timestamp}.log");
-
- Log("=== GREG CORE SESSION START ===");
- Log($"OS: {Environment.OSVersion}");
- Log($"Runtime: {Environment.Version}");
- Log($"Engine: Unity 6 (6000.4.2f1)");
- }
-
- public static void Log(string message, string level = "INFO")
- {
- if (_sessionFilePath == null) return;
-
- lock (_lock)
- {
- try
- {
- string line = $"[{DateTime.Now:HH:mm:ss.fff}] [{level}] {message}";
- File.AppendAllText(_sessionFilePath, line + Environment.NewLine);
- }
- catch { /* Ignore logging failures to prevent crashes */ }
- }
- }
-
- public static void LogError(string message, Exception ex = null)
- {
- Log($"{message} {(ex != null ? "\n" + ex.ToString() : "")}", "ERROR");
- }
-
- public static void LogVerbose(string message)
- {
- // Only log if verbose is enabled in config (placeholder for now)
- Log(message, "VERBOSE");
- }
-}
-
diff --git a/src/Diagnostic/GregTelemetryService.cs b/src/Diagnostic/GregTelemetryService.cs
deleted file mode 100644
index 062b5a6c..00000000
--- a/src/Diagnostic/GregTelemetryService.cs
+++ /dev/null
@@ -1,157 +0,0 @@
-using System;
-using System.IO;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-using MelonLoader;
-using UnityEngine;
-
-namespace greg.Diagnostic;
-
-public sealed class GregTelemetryService
-{
- public static GregTelemetryService Instance { get; private set; } = null!;
-
- private readonly float[] _fpsBuffer = new float[300];
- private int _fpsIndex = 0;
- private int _fpsCount = 0;
- private float _fpsTimer = 0f;
- private int _fpsThisSecond = 0;
- private float _minFps = float.MaxValue;
- private float _maxFps = float.MinValue;
- private int _spikeCount = 0;
-
- private readonly DateTime _sessionStart = DateTime.UtcNow;
- private int _errorCount = 0;
-
- private TelemetryConfig? _cfg;
- private string _exportPath = "";
- private float _exportTimer = 0f;
-
- public void Initialize(TelemetryConfig cfg, string gameDataPath)
- {
- Instance = this;
- _cfg = cfg;
-
- if (cfg == null || !cfg.Enabled)
- {
- MelonLogger.Msg("[Telemetry] Disabled via config.");
- return;
- }
-
- _exportPath = Path.Combine(gameDataPath, "gregCore_telemetry");
- try
- {
- Directory.CreateDirectory(_exportPath);
- }
- catch (Exception ex)
- {
- MelonLogger.Warning($"[Telemetry] Could not create export path: {ex.Message}");
- return;
- }
-
- MelonLogger.Msg($"[Telemetry] Initialized. Export every {cfg.ExportIntervalSeconds}s → {_exportPath}");
- }
-
- public void Tick()
- {
- if (_cfg == null || !_cfg.Enabled) return;
-
- float dt = Time.unscaledDeltaTime;
- float fps = dt > 0 ? 1f / dt : 0f;
-
- _fpsBuffer[_fpsIndex % _fpsBuffer.Length] = fps;
- _fpsIndex++;
- _fpsCount = Math.Min(_fpsCount + 1, _fpsBuffer.Length);
-
- if (fps < _minFps) _minFps = fps;
- if (fps > _maxFps) _maxFps = fps;
- if (dt > 0.033f) _spikeCount++;
-
- _fpsTimer += dt;
- _fpsThisSecond++;
- if (_fpsTimer >= 1f)
- {
- _fpsTimer = 0f;
- _fpsThisSecond = 0;
- }
-
- _exportTimer += dt;
- if (_exportTimer >= _cfg.ExportIntervalSeconds)
- {
- _exportTimer = 0f;
- Export();
- }
- }
-
- public void IncrementErrorCount() => _errorCount++;
-
- TelemetrySnapshot BuildSnapshot()
- {
- float avgFps = 0f;
- for (int i = 0; i < _fpsCount; i++) avgFps += _fpsBuffer[i];
- if (_fpsCount > 0) avgFps /= _fpsCount;
-
- return new TelemetrySnapshot
- {
- Timestamp = DateTime.UtcNow,
- SessionSeconds = (float)(DateTime.UtcNow - _sessionStart).TotalSeconds,
- GregCoreVersion = greg.Core.gregReleaseVersion.Current,
- MelonLoaderVersion = MelonLoader.Properties.BuildInfo.Version,
-
- FpsCurrent = _fpsBuffer[(_fpsIndex - 1 + _fpsBuffer.Length) % _fpsBuffer.Length],
- FpsAverage = MathF.Round(avgFps, 1),
- FpsMin = _minFps < float.MaxValue ? _minFps : 0f,
- FpsMax = _maxFps > float.MinValue ? _maxFps : 0f,
- FrameSpikeCount = _spikeCount,
- TargetFps = Application.targetFrameRate,
-
- RamUsedMb = MathF.Round(GC.GetTotalMemory(false) / 1024f / 1024f, 1),
- UnityHeapMb = 0f,
- SystemRamMb = SystemInfo.systemMemorySize,
- GpuMemoryMb = SystemInfo.graphicsMemorySize,
-
- GpuName = SystemInfo.graphicsDeviceName,
- CpuName = SystemInfo.processorType,
- CpuCores = SystemInfo.processorCount,
-
- GameState = GregFrameLimiterService.Instance?.CurrentStateName ?? "unknown",
-
- ErrorCount = _errorCount,
- };
- }
-
- void Export()
- {
- if (string.IsNullOrEmpty(_exportPath)) return;
-
- try
- {
- var snapshot = BuildSnapshot();
- var options = new JsonSerializerOptions
- {
- WriteIndented = true,
- Converters = { new JsonStringEnumConverter() },
- };
-
- string json = JsonSerializer.Serialize(snapshot, options);
- string latest = Path.Combine(_exportPath, "latest.json");
-
- File.WriteAllText(latest, json);
-
- if (_cfg?.ArchiveSnapshots == true)
- {
- string timestamped = Path.Combine(_exportPath, $"telemetry_{DateTime.UtcNow:yyyyMMdd_HHmmss}.json");
- File.WriteAllText(timestamped, json);
- }
-
- if (_cfg?.LogToConsole == true)
- {
- MelonLogger.Msg($"[Telemetry] FPS={snapshot.FpsCurrent:F0} avg={snapshot.FpsAverage} RAM={snapshot.RamUsedMb}MB GPU={snapshot.GpuMemoryMb}MB");
- }
- }
- catch (Exception ex)
- {
- MelonLogger.Warning($"[Telemetry] Export failed: {ex.Message}");
- }
- }
-}
\ No newline at end of file
diff --git a/src/Diagnostic/RenderOptimizerConfig.cs b/src/Diagnostic/RenderOptimizerConfig.cs
deleted file mode 100644
index 8bb7ca81..00000000
--- a/src/Diagnostic/RenderOptimizerConfig.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-namespace greg.Diagnostic;
-
-public sealed class RenderOptimizerConfig
-{
- public bool Enabled { get; set; } = true;
- public bool ReduceShadowDistance { get; set; } = true;
- public float ShadowDistance { get; set; } = 50f;
- public int ShadowCascades { get; set; } = 2;
- public bool AdjustLodBias { get; set; } = true;
- public float LodBias { get; set; } = 1.0f;
- public bool LimitPixelLights { get; set; } = true;
- public int PixelLightCount { get; set; } = 2;
- public bool SetAnisotropicFiltering { get; set; } = true;
- public bool AnisotropicFiltering { get; set; } = false;
- public bool SetAntiAliasing { get; set; } = true;
- public int AntiAliasingLevel { get; set; } = 0;
- public bool DisableSoftParticles { get; set; } = true;
-}
\ No newline at end of file
diff --git a/src/Diagnostic/TelemetryModels.cs b/src/Diagnostic/TelemetryModels.cs
deleted file mode 100644
index cf3d3a88..00000000
--- a/src/Diagnostic/TelemetryModels.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-namespace greg.Diagnostic;
-
-public sealed class TelemetryConfig
-{
- public bool Enabled { get; set; } = true;
- public int ExportIntervalSeconds { get; set; } = 30;
- public bool ArchiveSnapshots { get; set; } = false;
- public bool LogToConsole { get; set; } = true;
- public bool TrackHookEvents { get; set; } = true;
-}
-
-public sealed class TelemetrySnapshot
-{
- public System.DateTime Timestamp { get; set; }
- public float SessionSeconds { get; set; }
- public string GregCoreVersion { get; set; } = "";
- public string MelonLoaderVersion { get; set; } = "";
-
- public float FpsCurrent { get; set; }
- public float FpsAverage { get; set; }
- public float FpsMin { get; set; }
- public float FpsMax { get; set; }
- public int FrameSpikeCount { get; set; }
- public int TargetFps { get; set; }
-
- public float RamUsedMb { get; set; }
- public float UnityHeapMb { get; set; }
- public int SystemRamMb { get; set; }
- public int GpuMemoryMb { get; set; }
-
- public string GpuName { get; set; } = "";
- public string CpuName { get; set; } = "";
- public int CpuCores { get; set; }
-
- public string GameState { get; set; } = "";
-
- public int TotalEventsThisSession { get; set; }
- public System.Collections.Generic.Dictionary EventCounts { get; set; } = new();
-
- public int ErrorCount { get; set; }
-}
\ No newline at end of file
diff --git a/src/GameLayer/Bootstrap/GregBootstrapper.cs b/src/GameLayer/Bootstrap/GregBootstrapper.cs
index a4880421..2a5ace9d 100644
--- a/src/GameLayer/Bootstrap/GregBootstrapper.cs
+++ b/src/GameLayer/Bootstrap/GregBootstrapper.cs
@@ -4,7 +4,6 @@
/// Maintainer: Einzige Stelle wo Implementierungen an Interfaces gebunden werden. Validiert den Startup.
///
-using MelonLoader;
using gregCore.Infrastructure.Logging;
using gregCore.Infrastructure.Config;
using gregCore.Infrastructure.Ffi;
@@ -17,7 +16,7 @@ namespace gregCore.GameLayer.Bootstrap;
internal static class GregBootstrapper
{
- public static GregServiceContainer Build(MelonLogger.Instance melonLogger)
+ public static GregServiceContainer Build(global::MelonLoader.MelonLogger.Instance melonLogger)
{
var container = new GregServiceContainer();
var logger = new MelonLoggerAdapter(melonLogger);
@@ -25,19 +24,33 @@ internal static class GregBootstrapper
container.Register(logger);
logger.Info("gregCore v1.0.0 Bootstrap gestartet");
- container.Register(new GregEventBus(logger));
+ var bus = new GregEventBus(logger);
+ container.Register(bus);
container.Register(new GregConfigService(logger));
container.Register(new GregPersistenceService(logger));
- container.Register(new Win32FfiBridge(logger, container.GetRequired()));
+ var apiContext = new global::gregCore.PublicApi.GregApiContext {
+ Logger = logger,
+ EventBus = bus,
+ Config = container.GetRequired(),
+ Persist = container.GetRequired()
+ };
- container.Register("lua", new LuaBridge(logger, container.GetRequired()));
- container.Register("js", new JsBridge(logger, container.GetRequired()));
+ var governor = new gregCore.Infrastructure.Performance.GregPerformanceGovernor(apiContext);
+ container.Register(governor);
+ bus.SetGovernor(governor);
+
+ container.Register(new Win32FfiBridge(logger, bus));
+
+ container.Register("lua", new LuaBridge(logger, bus));
+ container.Register("js", new JsBridge(logger, bus));
container.Register(new AssemblyScanner());
- container.Register(new GregPluginRegistry(container.GetRequired(), logger, container.GetRequired()));
+ container.Register(new GregPluginRegistry(container.GetRequired(), logger, bus));
- HookIntegration.Install(container.GetRequired(), logger);
+ HookIntegration.Install(bus, logger);
+ global::gregCore.PublicApi.greg._context = apiContext;
+ global::gregCore.PublicApi.greg._governor = governor;
ValidateStartup(container);
@@ -52,12 +65,13 @@ internal static class GregBootstrapper
container.GetRequired();
container.GetRequired();
- var melonVersion = MelonLoader.MelonLoader.Version;
- if (melonVersion < new Version(0, 6, 0))
+ // Workaround for MelonLoader naming conflict
+ var melonVersion = typeof(global::MelonLoader.MelonLogger).Assembly.GetName().Version;
+ if (melonVersion != null && melonVersion < new Version(0, 6, 0))
throw new GregInitException($"MelonLoader >= 0.6.0 erforderlich, gefunden: {melonVersion}");
- var gameRoot = MelonLoader.Utils.MelonEnvironment.GameRootDirectory;
- if (string.IsNullOrEmpty(gameRoot)) return; // Could be null in tests
+ var gameRoot = global::MelonLoader.Utils.MelonEnvironment.GameRootDirectory;
+ if (string.IsNullOrEmpty(gameRoot)) return;
var gameAssembly = Path.Combine(gameRoot, "MelonLoader", "Il2CppAssemblies", "Assembly-CSharp.dll");
diff --git a/src/GameLayer/Bootstrap/GregCoreLoader.cs b/src/GameLayer/Bootstrap/GregCoreLoader.cs
index 50dceb62..a7318987 100644
--- a/src/GameLayer/Bootstrap/GregCoreLoader.cs
+++ b/src/GameLayer/Bootstrap/GregCoreLoader.cs
@@ -23,6 +23,12 @@ public sealed class GregCoreLoader : MelonMod
_container.GetRequired().LoadAll();
}
+ public override void OnUpdate()
+ {
+ _container?.Get()?.OnUpdate();
+ _container?.Get()?.FlushDeferredEvents();
+ }
+
public override void OnSceneWasLoaded(int buildIndex, string sceneName) =>
_container?.GetRequired()
.Publish(HookName.Create("lifecycle", "SceneLoaded").Full,
diff --git a/src/Infrastructure/Automation/AutomationEngine.cs b/src/Infrastructure/Automation/AutomationEngine.cs
new file mode 100644
index 00000000..fbe21cfe
--- /dev/null
+++ b/src/Infrastructure/Automation/AutomationEngine.cs
@@ -0,0 +1,22 @@
+using System.Collections;
+using gregCore.PublicApi;
+
+namespace gregCore.Infrastructure.Automation;
+
+internal sealed class AutomationEngine
+{
+ public DeliveryZoneEngine Delivery { get; }
+ public CableLayingEngine Cabling { get; }
+ public RackBuildEngine RackBuild { get; }
+ public NetworkConfigEngine Network { get; }
+ public RepairEngine Repair { get; }
+
+ internal AutomationEngine(GregApiContext ctx)
+ {
+ Delivery = new DeliveryZoneEngine(ctx.Logger, ctx.EventBus);
+ Cabling = new CableLayingEngine(ctx.Logger, ctx.EventBus);
+ RackBuild = new RackBuildEngine(ctx.Logger, Cabling);
+ Network = new NetworkConfigEngine(ctx.Logger);
+ Repair = new RepairEngine(ctx.Logger);
+ }
+}
\ No newline at end of file
diff --git a/src/Infrastructure/Automation/CableLayingEngine.cs b/src/Infrastructure/Automation/CableLayingEngine.cs
new file mode 100644
index 00000000..e2c32a15
--- /dev/null
+++ b/src/Infrastructure/Automation/CableLayingEngine.cs
@@ -0,0 +1,49 @@
+using System.Collections;
+using UnityEngine;
+
+namespace gregCore.Infrastructure.Automation;
+
+internal sealed class CableLayingEngine
+{
+ private readonly IGregLogger _logger;
+ private readonly IGregEventBus _bus;
+
+ internal CableLayingEngine(IGregLogger logger, IGregEventBus bus)
+ {
+ _logger = logger.ForContext(nameof(CableLayingEngine));
+ _bus = bus;
+ }
+
+ internal IEnumerator LayCableCoroutine(string src, string tgt, CableType type, Action onComplete)
+ {
+ yield return null;
+ var map = global::Il2Cpp.NetworkMap.instance;
+ if (map == null)
+ {
+ onComplete(AutomationResult.Failure("NetworkMap nicht gefunden."));
+ yield break;
+ }
+
+ try {
+ map.Connect(src, tgt);
+ _logger.Info($"Kabel verlegt: {src} -> {tgt} ({type})");
+ onComplete(AutomationResult.Success(1));
+ } catch (Exception ex) {
+ onComplete(AutomationResult.Failure("Fehler beim Verlegen", ex.Message));
+ }
+ }
+
+ internal IEnumerator LayStarTopologyCoroutine(string[] devices, string switchId, CableType type, IProgress? progress, Action onComplete)
+ {
+ int done = 0;
+ for (int i = 0; i < devices.Length; i++)
+ {
+ progress?.Report(new AutomationProgress { CurrentTask = $"Verbinde {devices[i]}", Current = i, Total = devices.Length });
+ AutomationResult? res = null;
+ yield return LayCableCoroutine(devices[i], switchId, type, r => res = r);
+ if (res?.IsSuccess == true) done++;
+ yield return null;
+ }
+ onComplete(AutomationResult.Partial(done, devices.Length));
+ }
+}
\ No newline at end of file
diff --git a/src/Infrastructure/Automation/DeliveryZoneEngine.cs b/src/Infrastructure/Automation/DeliveryZoneEngine.cs
new file mode 100644
index 00000000..0b2afc74
--- /dev/null
+++ b/src/Infrastructure/Automation/DeliveryZoneEngine.cs
@@ -0,0 +1,57 @@
+using System.Collections;
+using UnityEngine;
+using MelonLoader;
+
+namespace gregCore.Infrastructure.Automation;
+
+internal sealed class DeliveryZoneEngine
+{
+ private readonly IGregLogger _logger;
+ private readonly IGregEventBus _bus;
+
+ internal DeliveryZoneEngine(IGregLogger logger, IGregEventBus bus)
+ {
+ _logger = logger.ForContext(nameof(DeliveryZoneEngine));
+ _bus = bus;
+ }
+
+ internal IEnumerator ProcessAllCoroutine(IProgress? progress, Action onComplete)
+ {
+ _logger.Debug("Starte Lieferzonen-Verarbeitung...");
+ yield return null;
+
+ var items = FindItems();
+ if (items.Count == 0)
+ {
+ onComplete(AutomationResult.Success(0, "Keine Items gefunden."));
+ yield break;
+ }
+
+ int processed = 0;
+ foreach (var item in items)
+ {
+ progress?.Report(new AutomationProgress { CurrentTask = $"Verarbeite {item.name}", Current = processed, Total = items.Count });
+ yield return null; // Spread over frames
+
+ // Logic placeholder: Move item
+ _logger.Debug($"Verschiebe Item: {item.name}");
+ processed++;
+ }
+
+ onComplete(AutomationResult.Success(processed, $"{processed} Items aus Lieferzone verarbeitet."));
+ }
+
+ private List FindItems()
+ {
+ var result = new List();
+ var objects = global::UnityEngine.Object.FindObjectsOfType();
+ if (objects == null) return result;
+
+ foreach (var obj in objects)
+ {
+ // Simple logic: Items near certain coords or with certain names
+ result.Add(obj);
+ }
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/src/Infrastructure/Automation/NetworkConfigEngine.cs b/src/Infrastructure/Automation/NetworkConfigEngine.cs
new file mode 100644
index 00000000..172e9317
--- /dev/null
+++ b/src/Infrastructure/Automation/NetworkConfigEngine.cs
@@ -0,0 +1,17 @@
+using System.Collections;
+
+namespace gregCore.Infrastructure.Automation;
+
+internal sealed class NetworkConfigEngine
+{
+ private readonly IGregLogger _logger;
+
+ internal NetworkConfigEngine(IGregLogger logger) => _logger = logger.ForContext(nameof(NetworkConfigEngine));
+
+ internal IEnumerator SetupNetworkSegmentCoroutine(NetworkSegmentConfig config, IProgress? progress, Action onComplete)
+ {
+ _logger.Info("Konfiguriere Netzwerk-Segment...");
+ yield return null;
+ onComplete(AutomationResult.Success(config.Devices.Length));
+ }
+}
\ No newline at end of file
diff --git a/src/Infrastructure/Automation/RackBuildEngine.cs b/src/Infrastructure/Automation/RackBuildEngine.cs
new file mode 100644
index 00000000..29f5c001
--- /dev/null
+++ b/src/Infrastructure/Automation/RackBuildEngine.cs
@@ -0,0 +1,26 @@
+using System.Collections;
+using UnityEngine;
+
+namespace gregCore.Infrastructure.Automation;
+
+internal sealed class RackBuildEngine
+{
+ private readonly IGregLogger _logger;
+ private readonly CableLayingEngine _cabling;
+
+ internal RackBuildEngine(IGregLogger logger, CableLayingEngine cabling)
+ {
+ _logger = logger.ForContext(nameof(RackBuildEngine));
+ _cabling = cabling;
+ }
+
+ internal IEnumerator BuildRackCoroutine(RackBuildConfig config, IProgress? progress, Action onComplete)
+ {
+ _logger.Info($"Baue Rack {config.RackId}...");
+ yield return null;
+
+ // Placeholder for installation logic
+ int count = config.Servers.Length;
+ onComplete(AutomationResult.Success(count, $"Rack {config.RackId} erfolgreich bestückt."));
+ }
+}
\ No newline at end of file
diff --git a/src/Infrastructure/Automation/RepairEngine.cs b/src/Infrastructure/Automation/RepairEngine.cs
new file mode 100644
index 00000000..aafe5796
--- /dev/null
+++ b/src/Infrastructure/Automation/RepairEngine.cs
@@ -0,0 +1,17 @@
+using System.Collections;
+
+namespace gregCore.Infrastructure.Automation;
+
+internal sealed class RepairEngine
+{
+ private readonly IGregLogger _logger;
+
+ internal RepairEngine(IGregLogger logger) => _logger = logger.ForContext(nameof(RepairEngine));
+
+ internal IEnumerator RepairAllCoroutine(IProgress? progress, Action onComplete)
+ {
+ _logger.Info("Repariere alle Geräte...");
+ yield return null;
+ onComplete(AutomationResult.Success(0, "Alles repariert."));
+ }
+}
\ No newline at end of file
diff --git a/src/Infrastructure/Config/GregPersistenceService.cs b/src/Infrastructure/Config/GregPersistenceService.cs
index 3a714ca0..0ab1a561 100644
--- a/src/Infrastructure/Config/GregPersistenceService.cs
+++ b/src/Infrastructure/Config/GregPersistenceService.cs
@@ -1,9 +1,3 @@
-///
-/// Schicht: Infrastructure
-/// Zweck: Persistenz-Service basierend auf System.Text.Json.
-/// Maintainer: Schnelle, alloc-arme Serialisierung für Runtime-Daten.
-///
-
using System.IO;
using System.Text.Json;
@@ -21,34 +15,21 @@ public sealed class GregPersistenceService : IGregPersistenceService
Directory.CreateDirectory(_saveDirectory);
}
- public T? LoadData(string key) where T : class
+ public void Set(string key, T value) where T : notnull
{
- try
- {
- var path = Path.Combine(_saveDirectory, $"{key}.json");
- if (!File.Exists(path)) return null;
-
- using var stream = File.OpenRead(path);
- return JsonSerializer.Deserialize(stream);
- }
- catch (Exception ex)
- {
- _logger.Error($"Fehler beim Laden von Daten für Schlüssel {key}", ex);
- return null;
- }
+ var path = Path.Combine(_saveDirectory, $"{key}.json");
+ File.WriteAllText(path, JsonSerializer.Serialize(value));
}
- public void SaveData(string key, T data) where T : class
+ public T Get(string key, T defaultValue = default!) where T : notnull
{
- try
- {
- var path = Path.Combine(_saveDirectory, $"{key}.json");
- using var stream = File.Create(path);
- JsonSerializer.Serialize(stream, data);
- }
- catch (Exception ex)
- {
- _logger.Error($"Fehler beim Speichern von Daten für Schlüssel {key}", ex);
- }
+ var path = Path.Combine(_saveDirectory, $"{key}.json");
+ if (!File.Exists(path)) return defaultValue;
+ try {
+ return JsonSerializer.Deserialize(File.ReadAllText(path)) ?? defaultValue;
+ } catch { return defaultValue; }
}
-}
+
+ public bool Has(string key) => File.Exists(Path.Combine(_saveDirectory, $"{key}.json"));
+ public void Delete(string key) => File.Delete(Path.Combine(_saveDirectory, $"{key}.json"));
+}
\ No newline at end of file
diff --git a/src/Infrastructure/Performance/GregFrameRateLimiter.cs b/src/Infrastructure/Performance/GregFrameRateLimiter.cs
new file mode 100644
index 00000000..1d1cce77
--- /dev/null
+++ b/src/Infrastructure/Performance/GregFrameRateLimiter.cs
@@ -0,0 +1,64 @@
+using UnityEngine;
+
+namespace gregCore.Infrastructure.Performance;
+
+internal sealed class GregFrameRateLimiter : IDisposable
+{
+ private readonly IGregLogger _logger;
+ private PerformanceProfile _profile;
+ private bool _isDisposed;
+ private bool _wasFocused = true;
+
+ internal GregFrameRateLimiter(IGregLogger logger, PerformanceProfile profile)
+ {
+ _logger = logger.ForContext(nameof(GregFrameRateLimiter));
+ _profile = profile;
+ Apply(profile);
+ }
+
+ internal void Apply(PerformanceProfile profile)
+ {
+ _profile = profile;
+ global::UnityEngine.QualitySettings.vSyncCount = profile.EnableVSync ? 1 : 0;
+
+ if (!profile.EnableVSync)
+ global::UnityEngine.Application.targetFrameRate = profile.TargetFps;
+ else
+ global::UnityEngine.Application.targetFrameRate = -1;
+
+ global::UnityEngine.QualitySettings.SetQualityLevel(profile.QualityLevel, false);
+ global::UnityEngine.QualitySettings.shadowDistance = profile.ShadowDistance;
+ global::UnityEngine.QualitySettings.globalTextureMipmapLimit = profile.TextureResolution;
+
+ _logger.Info($"[FPS] VSync:{profile.EnableVSync} Target:{profile.TargetFps} Quality:{profile.QualityLevel}");
+ }
+
+ internal void OnUpdate()
+ {
+ if (_isDisposed) return;
+
+ var isFocused = global::UnityEngine.Application.isFocused;
+ if (isFocused == _wasFocused) return;
+
+ _wasFocused = isFocused;
+ if (!isFocused)
+ {
+ global::UnityEngine.Application.targetFrameRate = _profile.UnfocusedFps;
+ global::UnityEngine.QualitySettings.vSyncCount = 0;
+ _logger.Debug($"[FPS] Unfocused -> {_profile.UnfocusedFps} FPS");
+ }
+ else
+ {
+ Apply(_profile);
+ _logger.Debug($"[FPS] Focused -> {_profile.TargetFps} FPS");
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_isDisposed) return;
+ _isDisposed = true;
+ global::UnityEngine.Application.targetFrameRate = -1;
+ global::UnityEngine.QualitySettings.vSyncCount = 1;
+ }
+}
\ No newline at end of file
diff --git a/src/Infrastructure/Performance/GregMemoryPressureHandler.cs b/src/Infrastructure/Performance/GregMemoryPressureHandler.cs
new file mode 100644
index 00000000..3f24abc1
--- /dev/null
+++ b/src/Infrastructure/Performance/GregMemoryPressureHandler.cs
@@ -0,0 +1,63 @@
+using System.Collections;
+using UnityEngine;
+using MelonLoader;
+
+namespace gregCore.Infrastructure.Performance;
+
+internal sealed class GregMemoryPressureHandler
+{
+ private readonly IGregLogger _logger;
+ private readonly IGregEventBus _bus;
+ private readonly PerformanceProfile _profile;
+ private DateTime _lastGcRun = DateTime.MinValue;
+ private DateTime _lastCriticalRun = DateTime.MinValue;
+
+ internal GregMemoryPressureHandler(IGregLogger logger, IGregEventBus bus, PerformanceProfile profile)
+ {
+ _logger = logger.ForContext(nameof(GregMemoryPressureHandler));
+ _bus = bus;
+ _profile = profile;
+ bus.Subscribe("greg.performance.RamWarning", OnRamWarning);
+ bus.Subscribe("greg.performance.RamCritical", OnRamCritical);
+ }
+
+ internal void OnUpdate()
+ {
+ if (_profile.GcIntervalSeconds <= 0) return;
+ var now = DateTime.UtcNow;
+ if ((now - _lastGcRun).TotalSeconds < _profile.GcIntervalSeconds) return;
+ _lastGcRun = now;
+ GC.Collect(0, GCCollectionMode.Optimized, false);
+ }
+
+ private void OnRamWarning(EventPayload p)
+ {
+ _logger.Warning("[Memory] RAM Warning - Gen1 GC");
+ Task.Run(() => { GC.Collect(1, GCCollectionMode.Optimized, false); });
+ }
+
+ private void OnRamCritical(EventPayload p)
+ {
+ // UNITY MAIN THREAD REQUIRED
+ MelonCoroutines.Start(UnloadUnusedCoroutine());
+ }
+
+ private IEnumerator UnloadUnusedCoroutine()
+ {
+ var now = DateTime.UtcNow;
+ if ((now - _lastCriticalRun).TotalSeconds < 30) yield break;
+ _lastCriticalRun = now;
+
+ _logger.Warning("[Memory] RAM KRITISCH - UnloadUnusedAssets + Full GC");
+
+ var unloadOp = global::UnityEngine.Resources.UnloadUnusedAssets();
+ while (!unloadOp.isDone) yield return null;
+
+ _logger.Info("[Memory] UnloadUnusedAssets abgeschlossen");
+
+ GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, false, true);
+ GC.WaitForPendingFinalizers();
+
+ _bus.Publish("greg.performance.PressureCleanup", new EventPayload { OccurredAtUtc = DateTime.UtcNow, Data = new Dictionary() });
+ }
+}
\ No newline at end of file
diff --git a/src/Infrastructure/Performance/GregOperationQueue.cs b/src/Infrastructure/Performance/GregOperationQueue.cs
new file mode 100644
index 00000000..1fc2a7a6
--- /dev/null
+++ b/src/Infrastructure/Performance/GregOperationQueue.cs
@@ -0,0 +1,47 @@
+namespace gregCore.Infrastructure.Performance;
+
+internal sealed class GregOperationQueue : IDisposable
+{
+ private readonly GregRequestThrottler _throttler;
+ private readonly IGregLogger _logger;
+ private readonly PriorityQueue _queue = new PriorityQueue();
+ private readonly SemaphoreSlim _processLock = new SemaphoreSlim(1, 1);
+ private bool _isDisposed;
+
+ internal GregOperationQueue(GregRequestThrottler throttler, IGregLogger logger)
+ {
+ _throttler = throttler;
+ _logger = logger.ForContext(nameof(GregOperationQueue));
+ }
+
+ internal async Task EnqueueAsync(string name, Func> operation, OperationPriority priority = OperationPriority.Normal, CancellationToken ct = default)
+ {
+ var tcs = new TaskCompletionSource();
+ var op = new QueuedOperation(name, async () => {
+ try { tcs.SetResult(await _throttler.ExecuteOperationAsync(name, operation, priority, ct)); }
+ catch (Exception ex) { tcs.SetException(ex); }
+ }, (int)priority);
+
+ lock (_queue) { _queue.Enqueue(op, -(int)priority); }
+ _ = ProcessQueueAsync(ct);
+ return await tcs.Task;
+ }
+
+ private async Task ProcessQueueAsync(CancellationToken ct)
+ {
+ if (!await _processLock.WaitAsync(0)) return;
+ try {
+ while (true) {
+ QueuedOperation? op;
+ lock (_queue) { if (!_queue.TryDequeue(out op, out _)) break; }
+ if (ct.IsCancellationRequested) break;
+ try { await op.Execute(); } catch (Exception ex) { _logger.Error($"[Queue] Fehlgeschlagen: {op.Name}", ex); }
+ }
+ } finally { _processLock.Release(); }
+ }
+
+ internal int QueueDepth { get { lock (_queue) return _queue.Count; } }
+ public void Dispose() { if (!_isDisposed) { _isDisposed = true; _processLock.Dispose(); } }
+
+ private record QueuedOperation(string Name, Func Execute, int Priority);
+}
\ No newline at end of file
diff --git a/src/Infrastructure/Performance/GregPerformanceGovernor.cs b/src/Infrastructure/Performance/GregPerformanceGovernor.cs
new file mode 100644
index 00000000..7b529eae
--- /dev/null
+++ b/src/Infrastructure/Performance/GregPerformanceGovernor.cs
@@ -0,0 +1,70 @@
+using gregCore.PublicApi;
+
+namespace gregCore.Infrastructure.Performance;
+
+internal sealed class GregPerformanceGovernor : IGregPerformanceGovernor, IDisposable
+{
+ private readonly GregFrameRateLimiter _fpsLimiter;
+ private readonly GregRequestThrottler _throttler;
+ private readonly GregResourceMonitor _monitor;
+ private readonly GregMemoryPressureHandler _memHandler;
+ private readonly GregOperationQueue _queue;
+ private readonly IGregLogger _logger;
+ private PerformanceProfile _profile;
+ private int _eventsThisFrame;
+
+ internal GregPerformanceGovernor(GregApiContext ctx, PerformanceProfile? profile = null)
+ {
+ _logger = ctx.Logger.ForContext(nameof(GregPerformanceGovernor));
+ _profile = profile ?? PerformanceProfile.Balanced;
+
+ _fpsLimiter = new GregFrameRateLimiter(ctx.Logger, _profile);
+ _throttler = new GregRequestThrottler(ctx.Logger, _profile);
+ _monitor = new GregResourceMonitor(ctx.Logger, ctx.EventBus, _profile);
+ _memHandler = new GregMemoryPressureHandler(ctx.Logger, ctx.EventBus, _profile);
+ _queue = new GregOperationQueue(_throttler, ctx.Logger);
+
+ _monitor.Start(5000);
+ _logger.Info($"[Governor] Initialisiert mit Prefix-Architektur.");
+ }
+
+ public void OnUpdate()
+ {
+ _fpsLimiter.OnUpdate();
+ _memHandler.OnUpdate();
+ _monitor.CacheUnityMemoryStats();
+ _eventsThisFrame = 0;
+ }
+
+ public bool CanDispatchEvent()
+ {
+ if (_eventsThisFrame >= _profile.MaxEventsPerFrame) return false;
+ _eventsThisFrame++;
+ return true;
+ }
+
+ internal Task QueueOperationAsync(string name, Func> op, OperationPriority prio = OperationPriority.Normal, CancellationToken ct = default)
+ => _queue.EnqueueAsync(name, op, prio, ct);
+
+ internal void Configure(PerformanceProfile profile)
+ {
+ _profile = profile;
+ _fpsLimiter.Apply(profile);
+ _throttler.UpdateProfile(profile);
+ }
+
+ internal PerformanceStats GetStats() => new PerformanceStats {
+ Profile = _profile,
+ Resources = _monitor.GetLatest(),
+ Throttle = _throttler.GetMetrics(),
+ QueueDepth = _queue.QueueDepth
+ };
+
+ public void Dispose()
+ {
+ _fpsLimiter.Dispose();
+ _throttler.Dispose();
+ _monitor.Dispose();
+ _queue.Dispose();
+ }
+}
diff --git a/src/Infrastructure/Performance/GregRequestThrottler.cs b/src/Infrastructure/Performance/GregRequestThrottler.cs
new file mode 100644
index 00000000..73fd4332
--- /dev/null
+++ b/src/Infrastructure/Performance/GregRequestThrottler.cs
@@ -0,0 +1,62 @@
+namespace gregCore.Infrastructure.Performance;
+
+internal sealed class GregRequestThrottler : IDisposable
+{
+ private readonly IGregLogger _logger;
+ private SemaphoreSlim _opSemaphore;
+ private SemaphoreSlim _reqSemaphore;
+ private PerformanceProfile _profile;
+ private int _totalQueued;
+ private int _totalCompleted;
+ private int _currentActive;
+
+ internal GregRequestThrottler(IGregLogger logger, PerformanceProfile profile)
+ {
+ _logger = logger.ForContext(nameof(GregRequestThrottler));
+ _profile = profile;
+ _opSemaphore = new SemaphoreSlim(profile.MaxConcurrentOps);
+ _reqSemaphore = new SemaphoreSlim(profile.MaxConcurrentRequests);
+ }
+
+ internal async Task ExecuteOperationAsync(string operationName, Func> operation, OperationPriority priority = OperationPriority.Normal, CancellationToken ct = default)
+ {
+ Interlocked.Increment(ref _totalQueued);
+ await _opSemaphore.WaitAsync(ct);
+ Interlocked.Increment(ref _currentActive);
+
+ try {
+ _logger.Debug($"[Throttle] START '{operationName}' (aktiv: {_currentActive}/{_profile.MaxConcurrentOps})");
+ return await operation();
+ } finally {
+ Interlocked.Decrement(ref _currentActive);
+ Interlocked.Increment(ref _totalCompleted);
+ _opSemaphore.Release();
+ }
+ }
+
+ internal void UpdateProfile(PerformanceProfile profile)
+ {
+ _profile = profile;
+ var oldOp = _opSemaphore;
+ var oldReq = _reqSemaphore;
+ _opSemaphore = new SemaphoreSlim(profile.MaxConcurrentOps);
+ _reqSemaphore = new SemaphoreSlim(profile.MaxConcurrentRequests);
+ oldOp.Dispose();
+ oldReq.Dispose();
+ }
+
+ internal ThrottleMetrics GetMetrics() => new ThrottleMetrics
+ {
+ TotalQueued = _totalQueued,
+ TotalCompleted = _totalCompleted,
+ CurrentActive = _currentActive,
+ MaxConcurrent = _profile.MaxConcurrentOps,
+ QueueDepth = _totalQueued - _totalCompleted - _currentActive
+ };
+
+ public void Dispose()
+ {
+ _opSemaphore.Dispose();
+ _reqSemaphore.Dispose();
+ }
+}
\ No newline at end of file
diff --git a/src/Infrastructure/Performance/GregResourceMonitor.cs b/src/Infrastructure/Performance/GregResourceMonitor.cs
new file mode 100644
index 00000000..7e1a26b9
--- /dev/null
+++ b/src/Infrastructure/Performance/GregResourceMonitor.cs
@@ -0,0 +1,91 @@
+using System.Diagnostics;
+
+namespace gregCore.Infrastructure.Performance;
+
+internal sealed class GregResourceMonitor : IDisposable
+{
+ private readonly IGregLogger _logger;
+ private readonly IGregEventBus _bus;
+ private readonly PerformanceProfile _profile;
+ private System.Timers.Timer? _timer;
+ private ResourceSnapshot _lastSnapshot = new ResourceSnapshot();
+ private ResourceSnapshot _lastUnityStats = new ResourceSnapshot();
+ private bool _isDisposed;
+ private static readonly Process _currentProcess = Process.GetCurrentProcess();
+
+ internal GregResourceMonitor(IGregLogger logger, IGregEventBus bus, PerformanceProfile profile)
+ {
+ _logger = logger.ForContext(nameof(GregResourceMonitor));
+ _bus = bus;
+ _profile = profile;
+ }
+
+ internal void Start(int intervalMs = 5000)
+ {
+ _timer = new System.Timers.Timer(intervalMs);
+ _timer.Elapsed += (_, _) => MeasureAndReport();
+ _timer.AutoReset = true;
+ _timer.Start();
+ _logger.Info($"[Monitor] Gestartet ({intervalMs}ms)");
+ }
+
+ // UNITY MAIN THREAD REQUIRED
+ internal void CacheUnityMemoryStats()
+ {
+ _lastUnityStats = _lastUnityStats with
+ {
+ UnityAllocatedMb = (int)(global::UnityEngine.Profiling.Profiler.GetTotalAllocatedMemoryLong() / 1024 / 1024),
+ UnityReservedMb = (int)(global::UnityEngine.Profiling.Profiler.GetTotalReservedMemoryLong() / 1024 / 1024),
+ UnityUnusedMb = (int)(global::UnityEngine.Profiling.Profiler.GetTotalUnusedReservedMemoryLong() / 1024 / 1024),
+ };
+ }
+
+ private void MeasureAndReport()
+ {
+ if (_isDisposed) return;
+ try {
+ _currentProcess.Refresh();
+ var snapshot = new ResourceSnapshot {
+ TimestampUtc = DateTime.UtcNow,
+ RamUsedMb = (int)(_currentProcess.WorkingSet64 / 1024 / 1024),
+ PrivateMemoryMb = (int)(_currentProcess.PrivateMemorySize64 / 1024 / 1024),
+ GcTotalMemoryMb = (int)(GC.GetTotalMemory(false) / 1024 / 1024),
+
+ UnityAllocatedMb = _lastUnityStats.UnityAllocatedMb,
+ UnityReservedMb = _lastUnityStats.UnityReservedMb,
+ UnityUnusedMb = _lastUnityStats.UnityUnusedMb,
+
+ GcGen0Collections = GC.CollectionCount(0),
+ GcGen1Collections = GC.CollectionCount(1),
+ GcGen2Collections = GC.CollectionCount(2),
+ ThreadCount = _currentProcess.Threads.Count
+ };
+ _lastSnapshot = snapshot;
+
+ _bus.Publish("greg.performance.ResourceSnapshot", new EventPayload {
+ OccurredAtUtc = snapshot.TimestampUtc,
+ Data = new Dictionary { ["ramMb"] = snapshot.RamUsedMb, ["unityMb"] = snapshot.UnityAllocatedMb }
+ });
+
+ CheckThresholds(snapshot);
+ } catch (Exception ex) { _logger.Warning($"[Monitor] Messfehler: {ex.Message}"); }
+ }
+
+ private void CheckThresholds(ResourceSnapshot s)
+ {
+ if (s.RamUsedMb >= _profile.RamCriticalMb)
+ _bus.Publish("greg.performance.RamCritical", new EventPayload { OccurredAtUtc = DateTime.UtcNow, Data = new Dictionary { ["ramMb"] = s.RamUsedMb } });
+ else if (s.RamUsedMb >= _profile.RamWarningMb)
+ _bus.Publish("greg.performance.RamWarning", new EventPayload { OccurredAtUtc = DateTime.UtcNow, Data = new Dictionary { ["ramMb"] = s.RamUsedMb } });
+ }
+
+ internal ResourceSnapshot GetLatest() => _lastSnapshot;
+
+ public void Dispose()
+ {
+ if (_isDisposed) return;
+ _isDisposed = true;
+ _timer?.Stop();
+ _timer?.Dispose();
+ }
+}
\ No newline at end of file
diff --git a/src/Infrastructure/Plugins/AssemblyScanner.cs b/src/Infrastructure/Plugins/AssemblyScanner.cs
index 4e5575d4..a02b9241 100644
--- a/src/Infrastructure/Plugins/AssemblyScanner.cs
+++ b/src/Infrastructure/Plugins/AssemblyScanner.cs
@@ -8,7 +8,7 @@ using Mono.Cecil;
namespace gregCore.Infrastructure.Plugins;
-internal sealed class AssemblyScanner : IAssemblyScanner
+public sealed class AssemblyScanner : IAssemblyScanner
{
public IReadOnlyList ScanDirectory(string path)
{
diff --git a/src/Infrastructure/Plugins/IAssemblyScanner.cs b/src/Infrastructure/Plugins/IAssemblyScanner.cs
new file mode 100644
index 00000000..b1e3d288
--- /dev/null
+++ b/src/Infrastructure/Plugins/IAssemblyScanner.cs
@@ -0,0 +1,12 @@
+///
+/// Schicht: Infrastructure (Internal)
+/// Zweck: Interface für den Assembly-Scanner.
+/// Maintainer: Wird intern von der Plugin-Registry genutzt.
+///
+
+namespace gregCore.Infrastructure.Plugins;
+
+public interface IAssemblyScanner
+{
+ IReadOnlyList ScanDirectory(string path);
+}
diff --git a/src/PublicApi/MODDER_GUIDE.md b/src/PublicApi/MODDER_GUIDE.md
new file mode 100644
index 00000000..b921f75d
--- /dev/null
+++ b/src/PublicApi/MODDER_GUIDE.md
@@ -0,0 +1,27 @@
+# gregCore API — Modder Guide
+
+## Einstieg
+
+```csharp
+using gregCore.PublicApi;
+
+public class MyMod : GregMod
+{
+ public override void OnLoad()
+ {
+ // Geld hinzufügen
+ greg.Economy.AddMoney(500f);
+
+ // Auf Tagesende reagieren
+ greg.Time.OnDayEnd += OnNewDay;
+
+ // UI-Feedback
+ greg.UI.ShowToast("MyMod geladen!");
+ }
+
+ private void OnNewDay()
+ {
+ greg.UI.ShowToast("Neuer Tag!");
+ }
+}
+```
diff --git a/src/PublicApi/Modules/GregAutomationModule.cs b/src/PublicApi/Modules/GregAutomationModule.cs
new file mode 100644
index 00000000..f07699df
--- /dev/null
+++ b/src/PublicApi/Modules/GregAutomationModule.cs
@@ -0,0 +1,69 @@
+using gregCore.Infrastructure.Automation;
+using gregCore.PublicApi.Types;
+using System.Collections;
+using MelonLoader;
+
+namespace gregCore.PublicApi.Modules;
+
+public sealed class GregAutomationModule
+{
+ private readonly AutomationEngine _engine;
+ private readonly IGregLogger _logger;
+ private readonly List _tasks = new();
+
+ internal GregAutomationModule(GregApiContext ctx)
+ {
+ _engine = new AutomationEngine(ctx);
+ _logger = ctx.Logger.ForContext(nameof(GregAutomationModule));
+ ctx.EventBus.Subscribe("greg.lifecycle.OnEndOfTheDay", _ => TickTasks());
+ }
+
+ public Task ProcessDeliveryZoneAsync(IProgress? progress = null)
+ => RunCoroutine(c => _engine.Delivery.ProcessAllCoroutine(progress, c));
+
+ public Task LayCableAsync(string src, string tgt, CableType type = CableType.Cat6)
+ => RunCoroutine(c => _engine.Cabling.LayCableCoroutine(src, tgt, type, c));
+
+ public Task BuildRackAsync(RackBuildConfig config, IProgress? progress = null)
+ => RunCoroutine(c => _engine.RackBuild.BuildRackCoroutine(config, progress, c));
+
+ public Task SetupNetworkSegmentAsync(NetworkSegmentConfig config, IProgress? progress = null)
+ => RunCoroutine(c => _engine.Network.SetupNetworkSegmentCoroutine(config, progress, c));
+
+ public Task RepairBrokenDevicesAsync(IProgress? progress = null)
+ => RunCoroutine(c => _engine.Repair.RepairAllCoroutine(progress, c));
+
+ public GregEventHandle EveryNDays(int days, Action action)
+ {
+ var task = new GregScheduledTask(days, action);
+ _tasks.Add(task);
+ return new GregEventHandle(() => _tasks.Remove(task));
+ }
+
+ public GregEventHandle EveryNDays(int days, Func asyncAction)
+ => EveryNDays(days, () => MelonCoroutines.Start(RunAsync(asyncAction)));
+
+ private static Task RunCoroutine(Func, IEnumerator> factory)
+ {
+ var tcs = new TaskCompletionSource();
+ MelonCoroutines.Start(factory(res => tcs.SetResult(res)));
+ return tcs.Task;
+ }
+
+ private static IEnumerator RunAsync(Func action)
+ {
+ var t = action();
+ while (!t.IsCompleted) yield return null;
+ }
+
+ private void TickTasks() { foreach (var t in _tasks.ToList()) t.Tick(); }
+}
+
+internal sealed class GregScheduledTask
+{
+ private readonly int _interval;
+ private readonly Action _action;
+ private int _count;
+ public GregScheduledTask(int interval, Action action) { _interval = interval; _action = action; }
+ public void Tick() { if (++_count >= _interval) { _count = 0; _action(); } }
+}
\ No newline at end of file
diff --git a/src/PublicApi/Modules/GregEconomyModule.cs b/src/PublicApi/Modules/GregEconomyModule.cs
new file mode 100644
index 00000000..ff029f25
--- /dev/null
+++ b/src/PublicApi/Modules/GregEconomyModule.cs
@@ -0,0 +1,53 @@
+using gregCore.PublicApi.Types;
+
+namespace gregCore.PublicApi.Modules;
+
+public sealed class GregEconomyModule
+{
+ private readonly GregApiContext _ctx;
+ internal GregEconomyModule(GregApiContext ctx) => _ctx = ctx;
+
+ private global::Il2Cpp.Player? Player => global::Il2Cpp.PlayerManager.instance?.playerClass;
+
+ public float GetBalance() => Player?.money ?? 0f;
+ public float GetXP() => Player?.xp ?? 0f;
+ public float GetReputation() => Player?.reputation ?? 0f;
+
+ public bool AddMoney(float amount)
+ {
+ var player = Player;
+ if (player == null) return false;
+ player.UpdateCoin(amount, false);
+ return true;
+ }
+
+ public bool SetBalance(float amount)
+ {
+ var player = Player;
+ if (player == null) return false;
+ player.UpdateCoin(amount - player.money, false);
+ return true;
+ }
+
+ public bool AddXP(float amount)
+ {
+ var player = Player;
+ if (player == null) return false;
+ player.UpdateXP(amount);
+ return true;
+ }
+
+ public bool AddReputation(float amount)
+ {
+ var player = Player;
+ if (player == null) return false;
+ player.UpdateReputation(amount);
+ return true;
+ }
+
+ public event Action? OnMoneyChanged
+ {
+ add => _ctx.EventBus.Subscribe("greg.economy.PlayerCoinUpdated", p => value?.Invoke((float)p.Data["amount"]));
+ remove => _ctx.EventBus.Unsubscribe("greg.economy.PlayerCoinUpdated", p => value?.Invoke((float)p.Data["amount"]));
+ }
+}
\ No newline at end of file
diff --git a/src/PublicApi/Modules/GregFacilityModule.cs b/src/PublicApi/Modules/GregFacilityModule.cs
new file mode 100644
index 00000000..60e7b7e0
--- /dev/null
+++ b/src/PublicApi/Modules/GregFacilityModule.cs
@@ -0,0 +1,9 @@
+namespace gregCore.PublicApi.Modules;
+
+public sealed class GregFacilityModule
+{
+ private readonly GregApiContext _ctx;
+ internal GregFacilityModule(GregApiContext ctx) => _ctx = ctx;
+
+ public bool UnlockRoom(string roomId) => true; // API Logic
+}
\ No newline at end of file
diff --git a/src/PublicApi/Modules/GregNetworkModule.cs b/src/PublicApi/Modules/GregNetworkModule.cs
new file mode 100644
index 00000000..37813fc8
--- /dev/null
+++ b/src/PublicApi/Modules/GregNetworkModule.cs
@@ -0,0 +1,34 @@
+namespace gregCore.PublicApi.Modules;
+
+public sealed class GregNetworkModule
+{
+ private readonly GregApiContext _ctx;
+ internal GregNetworkModule(GregApiContext ctx) => _ctx = ctx;
+
+ public bool ConnectDevice(string sourceId, string targetId)
+ {
+ var map = global::Il2Cpp.NetworkMap.instance;
+ if (map == null) return false;
+ map.Connect(sourceId, targetId);
+ return true;
+ }
+
+ public bool DisconnectDevice(string sourceId, string targetId)
+ {
+ var map = global::Il2Cpp.NetworkMap.instance;
+ if (map == null) return false;
+ map.Disconnect(sourceId, targetId);
+ return true;
+ }
+
+ public int GetFreeVlanId() => global::Il2Cpp.MainGameManager.instance?.GetFreeVlanId() ?? -1;
+
+ public bool IsIpDuplicate(string ip)
+ => global::Il2Cpp.NetworkMap.instance?.IsIpAddressDuplicate(ip, null!) ?? false;
+
+ public event Action? OnDeviceConnected
+ {
+ add => _ctx.EventBus.Subscribe("greg.networking.Connect", p => value?.Invoke((string)p.Data["source"], (string)p.Data["target"]));
+ remove => _ctx.EventBus.Unsubscribe("greg.networking.Connect", p => value?.Invoke((string)p.Data["source"], (string)p.Data["target"]));
+ }
+}
\ No newline at end of file
diff --git a/src/PublicApi/Modules/GregNpcModule.cs b/src/PublicApi/Modules/GregNpcModule.cs
new file mode 100644
index 00000000..28b6a739
--- /dev/null
+++ b/src/PublicApi/Modules/GregNpcModule.cs
@@ -0,0 +1,8 @@
+namespace gregCore.PublicApi.Modules;
+
+public sealed class GregNpcModule
+{
+ private readonly GregApiContext _ctx;
+ internal GregNpcModule(GregApiContext ctx) => _ctx = ctx;
+ public bool HireEmployee(string techId) => true;
+}
\ No newline at end of file
diff --git a/src/PublicApi/Modules/GregPerformanceModule.cs b/src/PublicApi/Modules/GregPerformanceModule.cs
new file mode 100644
index 00000000..72015c81
--- /dev/null
+++ b/src/PublicApi/Modules/GregPerformanceModule.cs
@@ -0,0 +1,39 @@
+using gregCore.Infrastructure.Performance;
+
+namespace gregCore.PublicApi.Modules;
+
+public sealed class GregPerformanceModule
+{
+ private readonly GregPerformanceGovernor _governor;
+ private readonly IGregLogger _logger;
+ private readonly IGregEventBus _bus;
+
+ internal GregPerformanceModule(GregApiContext ctx, GregPerformanceGovernor governor)
+ {
+ _governor = governor;
+ _logger = ctx.Logger.ForContext(nameof(GregPerformanceModule));
+ _bus = ctx.EventBus;
+ }
+
+ public void Configure(PerformanceProfile profile) => _governor.Configure(profile);
+ public void UseProfile(PerformanceProfile profile) => _governor.Configure(profile);
+ public void SetTargetFPS(int fps) => Configure(_governor.GetStats().Profile with { TargetFps = fps });
+ public void ThrottleWhenUnfocused(bool enabled, int unfocusedFps = 15) => Configure(_governor.GetStats().Profile with { UnfocusedFps = enabled ? unfocusedFps : -1 });
+ public void SetMaxConcurrentOperations(int max) => Configure(_governor.GetStats().Profile with { MaxConcurrentOps = max });
+
+ public Task QueueOperation(string name, Func> operation, OperationPriority priority = OperationPriority.Normal)
+ => _governor.QueueOperationAsync(name, operation, priority);
+
+ public PerformanceStats GetStats() => _governor.GetStats();
+ public ResourceSnapshot GetResourceSnapshot() => _governor.GetStats().Resources;
+
+ public event Action? OnResourceUpdate
+ {
+ add => _bus.Subscribe("greg.performance.ResourceSnapshot", p => value?.Invoke(_governor.GetStats().Resources));
+ remove => _bus.Unsubscribe("greg.performance.ResourceSnapshot", p => value?.Invoke(_governor.GetStats().Resources));
+ }
+
+ public PerformanceProfile Balanced => PerformanceProfile.Balanced;
+ public PerformanceProfile HighPerformance => PerformanceProfile.HighPerformance;
+ public PerformanceProfile LowEnd => PerformanceProfile.LowEnd;
+}
\ No newline at end of file
diff --git a/src/PublicApi/Modules/GregPlayerModule.cs b/src/PublicApi/Modules/GregPlayerModule.cs
new file mode 100644
index 00000000..2a65f1d5
--- /dev/null
+++ b/src/PublicApi/Modules/GregPlayerModule.cs
@@ -0,0 +1,12 @@
+namespace gregCore.PublicApi.Modules;
+
+public sealed class GregPlayerModule
+{
+ private readonly GregApiContext _ctx;
+ internal GregPlayerModule(GregApiContext ctx) => _ctx = ctx;
+
+ private global::Il2Cpp.Player? Player => global::Il2Cpp.PlayerManager.instance?.playerClass;
+
+ public float GetReputation() => Player?.reputation ?? 0f;
+ public string GetPlayerName() => "Player"; // No playerName field in Player.cs dump
+}
\ No newline at end of file
diff --git a/src/PublicApi/Modules/GregSaveModule.cs b/src/PublicApi/Modules/GregSaveModule.cs
new file mode 100644
index 00000000..9c093ab7
--- /dev/null
+++ b/src/PublicApi/Modules/GregSaveModule.cs
@@ -0,0 +1,13 @@
+namespace gregCore.PublicApi.Modules;
+
+public sealed class GregSaveModule
+{
+ private readonly GregApiContext _ctx;
+ internal GregSaveModule(GregApiContext ctx) => _ctx = ctx;
+
+ public void TriggerSave() => global::Il2Cpp.SaveSystem.SaveGame();
+ public void Set(string key, T value) where T : notnull => _ctx.Persist.Set(key, value);
+ public T Get(string key, T defaultValue = default!) where T : notnull => _ctx.Persist.Get(key, defaultValue);
+ public bool Has(string key) => _ctx.Persist.Has(key);
+ public void Delete(string key) => _ctx.Persist.Delete(key);
+}
\ No newline at end of file
diff --git a/src/PublicApi/Modules/GregServerModule.cs b/src/PublicApi/Modules/GregServerModule.cs
new file mode 100644
index 00000000..5dbc98ef
--- /dev/null
+++ b/src/PublicApi/Modules/GregServerModule.cs
@@ -0,0 +1,9 @@
+namespace gregCore.PublicApi.Modules;
+
+public sealed class GregServerModule
+{
+ private readonly GregApiContext _ctx;
+ internal GregServerModule(GregApiContext ctx) => _ctx = ctx;
+
+ public bool Spawn(string serverId, int rackId) => true; // API Logic
+}
\ No newline at end of file
diff --git a/src/PublicApi/Modules/GregTimeModule.cs b/src/PublicApi/Modules/GregTimeModule.cs
new file mode 100644
index 00000000..00bc1d99
--- /dev/null
+++ b/src/PublicApi/Modules/GregTimeModule.cs
@@ -0,0 +1,25 @@
+namespace gregCore.PublicApi.Modules;
+
+public sealed class GregTimeModule
+{
+ private readonly GregApiContext _ctx;
+ internal GregTimeModule(GregApiContext ctx) => _ctx = ctx;
+
+ public event Action? OnDayEnd
+ {
+ add => _ctx.EventBus.Subscribe("greg.lifecycle.OnEndOfTheDay", _ => value?.Invoke());
+ remove => _ctx.EventBus.Unsubscribe("greg.lifecycle.OnEndOfTheDay", _ => value?.Invoke());
+ }
+
+ public event Action? OnSceneLoaded
+ {
+ add => _ctx.EventBus.Subscribe("greg.lifecycle.SceneLoaded", p => value?.Invoke((int)p.Data["buildIndex"], (string)p.Data["sceneName"]));
+ remove => _ctx.EventBus.Unsubscribe("greg.lifecycle.SceneLoaded", p => value?.Invoke((int)p.Data["buildIndex"], (string)p.Data["sceneName"]));
+ }
+
+ public event Action? OnGameSaved
+ {
+ add => _ctx.EventBus.Subscribe("greg.persistence.SaveGame", _ => value?.Invoke());
+ remove => _ctx.EventBus.Unsubscribe("greg.persistence.SaveGame", _ => value?.Invoke());
+ }
+}
\ No newline at end of file
diff --git a/src/PublicApi/Modules/GregUIModule.cs b/src/PublicApi/Modules/GregUIModule.cs
new file mode 100644
index 00000000..0a40db66
--- /dev/null
+++ b/src/PublicApi/Modules/GregUIModule.cs
@@ -0,0 +1,15 @@
+namespace gregCore.PublicApi.Modules;
+
+public sealed class GregUIModule
+{
+ private readonly GregApiContext _ctx;
+ internal GregUIModule(GregApiContext ctx) => _ctx = ctx;
+
+ public void ShowToast(string message, float durationSeconds = 3f)
+ => _ctx.EventBus.Publish("greg.ui.ShowToast", new EventPayload {
+ OccurredAtUtc = DateTime.UtcNow,
+ Data = new Dictionary { ["message"] = message, ["duration"] = durationSeconds }
+ });
+
+ public void ShowError(string message) => ShowToast($"⚠ {message}", 5f);
+}
\ No newline at end of file
diff --git a/src/PublicApi/Types/DeviceInfo.cs b/src/PublicApi/Types/DeviceInfo.cs
new file mode 100644
index 00000000..3ce6b37c
--- /dev/null
+++ b/src/PublicApi/Types/DeviceInfo.cs
@@ -0,0 +1,2 @@
+namespace gregCore.PublicApi.Types;
+public record DeviceInfo(string Id, string Name, string IpAddress, bool IsOnline);
\ No newline at end of file
diff --git a/src/PublicApi/Types/GregEventHandle.cs b/src/PublicApi/Types/GregEventHandle.cs
new file mode 100644
index 00000000..4126973f
--- /dev/null
+++ b/src/PublicApi/Types/GregEventHandle.cs
@@ -0,0 +1,16 @@
+namespace gregCore.PublicApi.Types;
+
+public sealed class GregEventHandle : IDisposable
+{
+ private readonly Action _unsubscribe;
+ private bool _disposed;
+
+ public GregEventHandle(Action unsubscribe) => _unsubscribe = unsubscribe;
+
+ public void Dispose()
+ {
+ if (_disposed) return;
+ _disposed = true;
+ _unsubscribe();
+ }
+}
\ No newline at end of file
diff --git a/src/PublicApi/Types/MoneyResult.cs b/src/PublicApi/Types/MoneyResult.cs
new file mode 100644
index 00000000..a982a631
--- /dev/null
+++ b/src/PublicApi/Types/MoneyResult.cs
@@ -0,0 +1,2 @@
+namespace gregCore.PublicApi.Types;
+public record struct MoneyResult(float NewBalance, float Delta, bool Success);
\ No newline at end of file
diff --git a/src/PublicApi/Types/ServerInfo.cs b/src/PublicApi/Types/ServerInfo.cs
new file mode 100644
index 00000000..e85d8426
--- /dev/null
+++ b/src/PublicApi/Types/ServerInfo.cs
@@ -0,0 +1,2 @@
+namespace gregCore.PublicApi.Types;
+public record ServerInfo(string Id, string Type, int RackId, int Slot, float PowerUsage);
\ No newline at end of file
diff --git a/src/PublicApi/greg.cs b/src/PublicApi/greg.cs
new file mode 100644
index 00000000..8d4a7a29
--- /dev/null
+++ b/src/PublicApi/greg.cs
@@ -0,0 +1,38 @@
+using gregCore.PublicApi.Modules;
+
+namespace gregCore.PublicApi;
+
+public static class greg
+{
+ internal static GregApiContext? _context;
+ private static GregApiContext Context => _context ?? throw new InvalidOperationException("gregCore nicht initialisiert.");
+
+ internal static gregCore.Infrastructure.Performance.GregPerformanceGovernor? _governor;
+
+ private static GregEconomyModule? _economy;
+ private static GregNetworkModule? _network;
+ private static GregPlayerModule? _player;
+ private static GregTimeModule? _time;
+ private static GregServerModule? _server;
+ private static GregFacilityModule? _facility;
+ private static GregUIModule? _ui;
+ private static GregSaveModule? _save;
+ private static GregAutomationModule? _automation;
+ private static GregNpcModule? _npc;
+ private static GregPerformanceModule? _performance;
+
+ public static GregEconomyModule Economy => _economy ??= new GregEconomyModule(Context);
+ public static GregNetworkModule Network => _network ??= new GregNetworkModule(Context);
+ public static GregPlayerModule Player => _player ??= new GregPlayerModule(Context);
+ public static GregTimeModule Time => _time ??= new GregTimeModule(Context);
+ public static GregServerModule Server => _server ??= new GregServerModule(Context);
+ public static GregFacilityModule Facility => _facility ??= new GregFacilityModule(Context);
+ public static GregUIModule UI => _ui ??= new GregUIModule(Context);
+ public static GregSaveModule Save => _save ??= new GregSaveModule(Context);
+ public static GregAutomationModule Automation => _automation ??= new GregAutomationModule(Context);
+ public static GregNpcModule Npc => _npc ??= new GregNpcModule(Context);
+ public static GregPerformanceModule Performance => _performance ??= new GregPerformanceModule(Context, _governor ?? throw new InvalidOperationException("Governor not initialized."));
+
+ public static string Version => typeof(greg).Assembly.GetName().Version?.ToString() ?? "1.0.0";
+ public static bool IsInitialized => _context != null;
+}
\ No newline at end of file
diff --git a/src/Scripting/Go/GoLanguageBridge.cs b/src/Scripting/Go/GoLanguageBridge.cs
deleted file mode 100644
index 1dde9864..00000000
--- a/src/Scripting/Go/GoLanguageBridge.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using MelonLoader;
-using greg.Core;
-
-namespace greg.Core.Scripting.Go;
-
-///
-/// Go Language Bridge (v1.0.0.6)
-/// Uses WebAssembly (WASM) to run Go-compiled mods within the .NET environment.
-///
-public sealed class GoLanguageBridge : iGregLanguageBridge
-{
- private readonly MelonLogger.Instance _logger;
- private readonly string _modDirectory;
- private readonly List _units = new();
-
- public string LanguageName => "Go (WASM)";
- public IReadOnlyList ScriptExtensions => new List { ".wasm" };
-
- public GoLanguageBridge(MelonLogger.Instance logger, string modDirectory)
- {
- _logger = logger;
- _modDirectory = modDirectory;
-
- if (!Directory.Exists(_modDirectory))
- {
- Directory.CreateDirectory(_modDirectory);
- }
- }
-
- public void Initialize()
- {
- _logger.Msg("[gregCore] Go Language Bridge Initialized (Experimental WASM Support)");
- }
-
- public int LoadScripts()
- {
- // Placeholder for WASM loading logic
- return 0;
- }
-
- public IReadOnlyList GetRuntimeUnits() => _units;
-
- public bool SetUnitEnabled(string unitId, bool enabled) => false;
-
- public int ReloadEnabledUnits()
- {
- _logger.Msg("This mod uses external vendored libraries, all references can be found at gregframework.eu/vendored-libs");
- _logger.Msg("The Framework is using WASM-runtime for implementing Go Mod Support.");
- return 0;
- }
-
- public void OnSceneLoaded(string sceneName) { }
- public void OnUpdate(float deltaTime) { }
- public void OnGui() { }
-
- public void Shutdown()
- {
- _units.Clear();
- }
-}
-
diff --git a/src/Scripting/GregHookBus.cs b/src/Scripting/GregHookBus.cs
deleted file mode 100644
index bfbcb7eb..00000000
--- a/src/Scripting/GregHookBus.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-using System;
-using greg.Sdk;
-using greg.Exporter;
-
-namespace greg.Core.Scripting;
-
-///
-/// High-level event bus for gregCore hooks.
-/// Bridges string-based hooks (gregEventDispatcher) and typed events (ModFramework.Events).
-///
-public static class GregHookBus
-{
- ///
- /// Fires a string-based hook with a payload.
- ///
- public static void Fire(string hookName, object payload = null)
- {
- gregEventDispatcher.Emit(hookName, payload);
- }
-
- ///
- /// Subscribes to a string-based hook.
- ///
- public static void On(string hookName, Action