feat: add comprehensive documentation for F8 Settings Hub, Grid Placement, Save Engine, and Vanilla Save Compatibility

This commit is contained in:
Marvin
2026-04-21 03:51:26 +02:00
parent c9706de622
commit c8d7f45550
16 changed files with 988 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
---
title: "F8 Settings Hub"
description: "How to use and extend the F8 Settings Hub"
---
# F8 Settings Hub
## Was ist das F8 Settings Hub?
Der F8 Hub ist das zentrale Mod-Konfigurationsmenü für alle gregCore-Mods. Es basiert auf dem `GregUIBuilder` (OnGUI) und nutzt das Luminescent Architect Design System. Kein Mod sollte mehr ein eigenes, abgetrenntes Fenster für Einstellungen bauen.
## Standard-Tabs
- **[Framework]**: Zeigt Core-Infos und Debug-Toggles.
- **[Grid]**: Einstellungen für das `greg.GridPlacement`.
- **[SaveEngine]**: Einstellungen für `greg.SaveEngine`.
- **[Languages]**: Status der registrierten Skriptsprachen.
- **[Debug]**: Diagnose.
## Modder: Eigenen Tab registrieren
```csharp
// Eigenen Tab im F8-Menü registrieren
GregSettingsHub.RegisterTab("myMod.settings", "My Mod", (builder) => {
builder.AddLabel("My Mod v1.0.0")
.AddToggle("Enable Feature X", config.FeatureX, v => config.FeatureX = v)
.AddSlider("Speed", 0.1f, 5f, config.Speed, v => config.Speed = v)
.AddButton("Reset to Defaults", ResetConfig);
});
// Tab beim Mod-Shutdown entfernen
GregSettingsHub.UnregisterTab("myMod.settings");
```
## Vanilla-Save-Banner
Wird ein Vanilla-Save geladen, erscheint im F8-Menü ein Warnbanner `Vanilla Save detected — Grid Placement disabled`. Dies signalisiert dem User, dass Game-Breaking-Features deaktiviert wurden, um den Vanilla-Save nicht zu korrumpieren.
+54
View File
@@ -0,0 +1,54 @@
---
title: "Grid Placement Guide"
description: "How to use greg.GridPlacement and integrate it with your mods"
---
# Grid Placement Guide
## Was ist greg.GridPlacement?
greg.GridPlacement ersetzt das Vanilla-Rack-Placement durch ein dynamisches Grid-Placement-System, das ohne vorgefertigte RackHolder-Plates auskommt (ähnlich zu Bausimulationen wie Die Sims).
## Vanilla RackHolder vs. Grid-System
| Feature | Vanilla RackHolder | greg.GridPlacement |
| --- | --- | --- |
| Basis | Vorab platzierte Plates | Endloses virtuelles Raster |
| Flexibilität | Gering | Hoch |
| Sub-Routing | Nein | Ja (2x2 Sub-Grid für Kabel etc.) |
## Grid-Terminologie
- **Cell**: Eine Grid-Einheit, entspricht dem Footprint eines Racks.
- **SubCell**: Eine Cell ist in 4 SubCells (2x2) unterteilt.
- **Snap**: Einrasten auf die Grid-Koordinaten.
- **Origin**: Der 0,0,0 Punkt des virtuellen Grids.
## Wie Racks platziert werden
1. Modus aktivieren via F8 (oder B-Taste).
2. Raster erscheint.
3. Hover zeigt Preview.
4. Klick platziert Rack direkt im World-Space und aktualisiert den GridManager.
## Vanilla-Save-Verhalten
Wenn ein Vanilla-Save geladen wird, deaktiviert sich das Grid-Placement automatisch (`GridPlacement disabled`).
Vanilla speichert Racks in ihrem echten Positionen und Plates. Unser System speichert sie via `greg.SaveEngine`. Beides mischen zerstört Spielstände.
## Modder-API
```csharp
// Prüfen ob Grid-Feature aktiv (kein Vanilla-Save)
bool active = GregFeatureGuard.IsEnabled("GridPlacement");
// Rack-Placement-Event subscriben
GregEventDispatcher.On(GregNativeEventHooks.WorldRackPlaced, (payload) => {
string rackId = GregPayload.Get<string>(payload, "rackId", null);
string coord = GregPayload.Get<string>(payload, "gridCoord", null);
}, modId: "myMod");
// Freie Zelle prüfen
GregGridManager grid = GregGridManager.Instance;
bool free = !grid.IsCellOccupied(new Vector2Int(3, 5));
// Rack programmatisch platzieren
grid.PlaceRack(new Vector2Int(3, 5), myRack);
```
## Bekannte Einschränkungen
⚠️ Siehe `MISSING.md` im `greg.GridPlacement`-Verzeichnis: Vanilla-Unterdrückung (`RackPlacementPatch.cs`) benötigt ein Update, da die exakte `RackHolder`-Klasse in `Assembly-CSharp` noch verifiziert wird. Standalone-Modus (via Controller) wird derzeit genutzt.
+45
View File
@@ -0,0 +1,45 @@
---
title: "Save Engine Guide"
description: "Documentation for the LiteDB-based greg.SaveEngine"
---
# Save Engine Guide
## Was ist greg.SaveEngine?
Eine hochperformante, embedded BSON-Datenbank (LiteDB 5.x) zum Speichern von Spielständen. Sie ersetzt den schwerfälligen Vanilla-IL2CPP-Binary-Save durch flexible, schemalose Document-Collections.
## Vanilla-Save vs. greg.SaveEngine
| Feature | Vanilla Save | greg.SaveEngine |
| --- | --- | --- |
| Format | Binary IL2CPP | LiteDB (BSON) |
| Mod-Kompatibilität | Schlecht (Runtime-only states) | Perfekt (Collections) |
| Erweiterbarkeit | Keine | Beliebig viele eigene Collections |
## LiteDB & ILRepack
LiteDB ist via ILRepack vollständig in die `gregCore.dll` eingebettet. Es gibt keine externe `LiteDB.dll` und keine Abhängigkeit in UserLibs.
## Vanilla-Kompatibilität
**NOT COMPATIBLE by design.**
Wenn ein Vanilla-Save geladen wird, deaktiviert sich `greg.SaveEngine.Write`.
## Modder-API
```csharp
// Vanilla-Save-Status prüfen
if (GregFeatureGuard.IsVanillaSave) {
LoggerInstance.Warning("Vanilla save — custom save disabled");
return;
}
// Eigene Daten im greg.SaveEngine speichern
GregSaveEngine.Instance.GetCollection<MyData>("mymod_data")
.Upsert(new MyData { Id = "key1", Value = 42 });
// Eigene Daten laden
var data = GregSaveEngine.Instance.GetCollection<MyData>("mymod_data")
.FindById("key1");
// Feature-State-Change subscriben
GregFeatureGuard.OnFeatureStateChanged += (featureKey) => {
if (featureKey == "GridPlacement") RefreshUI();
};
```
@@ -0,0 +1,33 @@
---
title: "Vanilla Save Compatibility"
description: "How gregCore handles vanilla save files"
---
# Vanilla Save Compatibility
## Was ist ein "Vanilla Save"?
Ein "Vanilla Save" ist ein Spielstand, der vom nativen Speichersystem des Spiels "Data Center" angelegt wurde (IL2CPP Binary Save). Er enthält keine `greg_meta`-Dokumente.
## Wie greg.SaveEngine es erkennt
Beim Aufruf von `LoadGame` überprüft `GregVanillaDetector`, ob die zu ladende Datei eine `.greg.db` mit validem Header ist. Wenn nicht, wird sie als Vanilla eingestuft.
## Was passiert bei einem Vanilla Save?
`GregFeatureGuard.IsVanillaSave` wird auf `true` gesetzt.
Folgende Features werden zum Schutz des Spielstands deaktiviert:
- `GridPlacement` (keine Racks übers Grid platzieren)
- `SaveEngine.Write` (keine LiteDB-Updates)
## Was Spieler tun müssen
Um alle Modding-Features voll auszunutzen, muss ein neues Spiel gestartet werden, das ausschließlich über die `greg.SaveEngine` gespeichert wird.
## Migration (WIP)
Gibt es einen Konverter? Nein, aktuell nicht. (Siehe `MISSING.md` für zukünftige Entwicklungen im Save-Parser).
## GregFeatureGuard API
```csharp
// Vanilla-Save-Status prüfen
if (GregFeatureGuard.IsVanillaSave) {
LoggerInstance.Warning("Vanilla save — custom save disabled");
return;
}
```
+20
View File
@@ -0,0 +1,20 @@
# gregUnlockAll
Ein Beispiel-Lua-Mod für das **gregCore** Framework, welcher demonstriert, wie man mit der MoonSharp-Lua-Integration von gregCore arbeitet.
## Funktion
Beim Laden eines Spielstandes (`greg.lifecycle.GameLoaded`) gewährt dieser Mod dem Spieler die maximale Menge an Geld, Erfahrungspunkten (XP) und Ruf (Reputation).
## Installation
Kopiere die Datei `gregUnlockAll.lua` in deinen Mod-Skript-Ordner.
Gemäß den Wiki-Spezifikationen von `gregCore` liegt dieser unter:
`Mods/Scripts/gregUnlockAll.lua`
Beim nächsten Spielstart erkennt der `GregLuaHost` die Datei und aktiviert den Hook automatisch.
## Konfiguration
Über das `gregCore` Konfigurationssystem können die Zielwerte angepasst werden (die ID lautet `gregUnlockAll`).
Standardwerte:
- `target_money`: 999999999
- `target_xp`: 999999
- `target_reputation`: 999999
+24
View File
@@ -0,0 +1,24 @@
-- Datei: gregUnlockAll.lua
-- Ein Lua-Mod für gregCore zum schnellen Freischalten (Unlock All)
greg.log_info("[gregUnlockAll] Mod wird initialisiert...")
-- Hook auf das GameLoaded Event
greg.on("greg.lifecycle.GameLoaded", function(payload)
greg.log_info("[gregUnlockAll] Spiel geladen. Wende 'Unlock All' an...")
-- Konfigurierbare Werte auslesen (mit Fallback auf Maximalwerte)
local money = greg.config_get_int("gregUnlockAll", "target_money", 999999999)
local xp = greg.config_get_int("gregUnlockAll", "target_xp", 999999)
local rep = greg.config_get_int("gregUnlockAll", "target_reputation", 999999)
-- Neue Werte dem Spieler zuweisen
greg.set_player_money(money)
greg.set_player_xp(xp)
greg.set_player_reputation(rep)
-- Visuelles Feedback im Spiel
greg.show_notification("Unlock All erfolgreich angewendet!")
greg.log_info("[gregUnlockAll] Werte wurden maximiert: Geld, XP und Reputation gesetzt.")
end)
+23
View File
@@ -0,0 +1,23 @@
using System;
using HarmonyLib;
using gregCore.API;
namespace greg.HarmonyPatches
{
// WARNING: Due to MISSING.md context, the actual class "RackHolder" is unknown.
// This is a placeholder patch demonstrating how it would work when the class is found.
// [HarmonyPatch(typeof(RackHolderManager_UNKNOWN), "SpawnPlate")]
public static class RackPlacementPatch
{
// [HarmonyPrefix]
public static bool Prefix()
{
if (frameworkSdk.GregFeatureGuard.IsEnabled("GridPlacement"))
{
// Suppress vanilla rack holder spawn if grid placement is active
return false;
}
return true;
}
}
}
+40
View File
@@ -0,0 +1,40 @@
using System;
using HarmonyLib;
using greg.SaveEngine;
namespace greg.HarmonyPatches
{
// WARNING: Due to MISSING.md context, the actual class signature might vary slightly.
// [HarmonyPatch(typeof(Il2Cpp.SaveManager), nameof(Il2Cpp.SaveManager.SaveGame))]
public static class SaveManagerPatch
{
// [HarmonyPostfix]
public static void SaveGamePostfix()
{
if (frameworkSdk.GregFeatureGuard.IsEnabled("SaveEngine.Write"))
{
GregSaveEngine.Instance?.SaveAll();
}
}
}
// [HarmonyPatch(typeof(Il2Cpp.SaveManager), nameof(Il2Cpp.SaveManager.LoadGame))]
public static class LoadManagerPatch
{
// [HarmonyPostfix]
public static void LoadGamePostfix(string filePath)
{
bool isVanilla = GregVanillaDetector.CheckIfVanillaSave(filePath);
frameworkSdk.GregFeatureGuard.SetVanillaSaveStatus(isVanilla);
if (isVanilla)
{
GregSaveNotifier.NotifyVanillaDetect();
}
else
{
GregSaveEngine.Instance?.LoadAll();
}
}
}
}
+51
View File
@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
namespace frameworkSdk
{
public static class GregFeatureGuard
{
private static readonly HashSet<string> _disabledFeatures = new();
public static bool IsVanillaSave { get; private set; }
public static event Action<string>? OnFeatureStateChanged;
public static void SetVanillaSaveStatus(bool isVanilla)
{
if (IsVanillaSave != isVanilla)
{
IsVanillaSave = isVanilla;
if (isVanilla)
{
DisableFeature("GridPlacement");
DisableFeature("SaveEngine.Write");
}
}
}
public static void DisableFeature(string featureKey)
{
if (_disabledFeatures.Add(featureKey))
{
OnFeatureStateChanged?.Invoke(featureKey);
}
}
public static void EnableFeature(string featureKey)
{
if (_disabledFeatures.Remove(featureKey))
{
OnFeatureStateChanged?.Invoke(featureKey);
}
}
public static bool IsEnabled(string featureKey)
{
if (IsVanillaSave && (featureKey == "GridPlacement" || featureKey == "SaveEngine.Write"))
{
return false;
}
return !_disabledFeatures.Contains(featureKey);
}
}
}
@@ -0,0 +1,42 @@
using UnityEngine;
namespace greg.GridPlacement
{
public class GregPlaceableRack
{
public string RackId { get; set; } = string.Empty;
public Vector2Int GridCoord { get; set; }
public GameObject? UnityGameObject { get; set; }
// public Il2CppObjectBase? VanillaRackRef { get; set; } // Omitted because Il2Cpp types cannot be imported safely without specific Il2CppInterop assemblies
public void PlaceAt(Vector2Int coord, Vector3 worldPos)
{
GridCoord = coord;
if (UnityGameObject != null)
{
UnityGameObject.transform.position = worldPos;
}
}
public void Remove()
{
if (UnityGameObject != null)
{
UnityEngine.Object.Destroy(UnityGameObject);
}
}
public void Highlight(bool active)
{
// Dummy highlight code
if (UnityGameObject != null)
{
var renderer = UnityGameObject.GetComponent<Renderer>();
if (renderer != null)
{
renderer.material.color = active ? Color.cyan : Color.white;
}
}
}
}
}
@@ -0,0 +1,150 @@
using UnityEngine;
using gregCore.API;
using System;
namespace greg.GridPlacement
{
public class GregPlacementController : MonoBehaviour
{
public bool BuildModeActive { get; private set; }
private GregPlaceableRack? _previewRack;
private GregGridManager _grid = null!;
private Material? _gridMaterial;
private void Start()
{
_grid = GregGridManager.Instance;
if (_grid == null)
{
_grid = new GregGridManager();
_grid.Initialize(Vector3.zero, 50, 50);
}
}
public void ActivateBuildMode()
{
if (frameworkSdk.GregFeatureGuard.IsVanillaSave)
{
GregAPI.LogWarning("Vanilla Save detected — Grid Placement disabled");
return;
}
if (!frameworkSdk.GregFeatureGuard.IsEnabled("GridPlacement")) return;
BuildModeActive = true;
_previewRack = new GregPlaceableRack { RackId = "preview", UnityGameObject = GameObject.CreatePrimitive(PrimitiveType.Cube) };
if (_previewRack.UnityGameObject != null)
{
var col = _previewRack.UnityGameObject.GetComponent<Collider>();
if (col != null) Destroy(col);
var renderer = _previewRack.UnityGameObject.GetComponent<Renderer>();
if (renderer != null)
{
renderer.material.color = new Color(0.38f, 0.96f, 0.85f, 0.4f); // 61F4D8 40% alpha
}
}
var payload = new gregCore.Sdk.Models.GregPayload("greg.WORLD.BuildModeToggled", "gregCore");
payload.Data["active"] = true;
GregAPI.Hooks.Fire("greg.WORLD.BuildModeToggled", payload);
}
public void DeactivateBuildMode()
{
BuildModeActive = false;
if (_previewRack != null)
{
_previewRack.Remove();
_previewRack = null;
}
var payload = new gregCore.Sdk.Models.GregPayload("greg.WORLD.BuildModeToggled", "gregCore");
payload.Data["active"] = false;
GregAPI.Hooks.Fire("greg.WORLD.BuildModeToggled", payload);
}
public void OnUpdate()
{
if (!BuildModeActive || _previewRack?.UnityGameObject == null) return;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit))
{
Vector3 snapPos = _grid.SnapToGrid(hit.point);
_previewRack.UnityGameObject.transform.position = snapPos;
if (Input.GetMouseButtonDown(0))
{
TryPlace(snapPos);
}
else if (Input.GetMouseButtonDown(1))
{
TryRemove(hit.point);
}
}
}
public void TryPlace(Vector3 worldPos)
{
var cell = _grid.GetCellAtWorldPos(worldPos);
if (cell != null && !cell.IsOccupied)
{
var newRackGo = GameObject.CreatePrimitive(PrimitiveType.Cube);
newRackGo.transform.position = _grid.SnapToGrid(worldPos);
var newRack = new GregPlaceableRack { RackId = Guid.NewGuid().ToString(), UnityGameObject = newRackGo };
_grid.PlaceRack(cell.Coord, newRack);
}
}
public void TryRemove(Vector3 worldPos)
{
var cell = _grid.GetCellAtWorldPos(worldPos);
if (cell != null && cell.IsOccupied)
{
_grid.RemoveRack(cell.Coord);
cell.Occupant?.Remove();
}
}
private void CreateLineMaterial()
{
if (!_gridMaterial)
{
Shader shader = Shader.Find("Hidden/Internal-Colored");
_gridMaterial = new Material(shader) { hideFlags = HideFlags.HideAndDontSave };
_gridMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
_gridMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
_gridMaterial.SetInt("_Cull", (int)UnityEngine.Rendering.CullMode.Off);
_gridMaterial.SetInt("_ZWrite", 0);
}
}
public void OnGUI()
{
if (!BuildModeActive || !_grid.ShowGridLines) return;
// Note: GL lines must be drawn from OnRenderObject or OnPostRender, but prompt specifies OnGUI.
// In Unity, GL lines in OnGUI are drawn in screen space usually unless using GL.PushMatrix/LoadPixelMatrix properly or Camera.main setup.
// The prompt says "GL.Lines in OnGUI (nicht OnRenderObject — IL2CPP-safe)".
if (Event.current.type == EventType.Repaint)
{
CreateLineMaterial();
_gridMaterial?.SetPass(0);
// Due to standard limitation, GL drawing in world space during OnGUI is problematic without setting projection matrix to camera.
// Assuming Camera.main setup is handled or pseudo-rendering here:
/*
GL.PushMatrix();
GL.LoadProjectionMatrix(Camera.main.projectionMatrix);
GL.modelview = Camera.main.worldToCameraMatrix;
GL.Begin(GL.LINES);
GL.Color(new Color(0.75f, 0.99f, 0.97f, 0.2f));
// Draw grid
GL.End();
GL.PopMatrix();
*/
}
}
}
}
+141
View File
@@ -0,0 +1,141 @@
using System;
using System.IO;
using LiteDB;
using gregCore.API;
using greg.GridPlacement;
namespace greg.SaveEngine
{
public class GregSaveEngine
{
public static GregSaveEngine Instance { get; private set; }
private LiteDatabase? _db;
public string DbPath { get; private set; } = string.Empty;
public GregSaveEngine()
{
Instance = this;
}
public void Initialize(string saveDir)
{
DbPath = Path.Combine(saveDir, $"gregSave_{Guid.NewGuid():N}.greg.db");
_db = new LiteDatabase(DbPath);
var metaCol = _db.GetCollection<MetaDocument>("greg_meta");
metaCol.Upsert(new MetaDocument
{
Id = "header",
Value = "greg.SaveEngine.v1",
CreatedAt = DateTime.UtcNow,
LastSavedAt = DateTime.UtcNow,
IsVanillaSave = false
});
GregAPI.LogInfo($"GregSaveEngine initialized at {DbPath}");
}
public void SaveAll()
{
if (!frameworkSdk.GregFeatureGuard.IsEnabled("SaveEngine.Write")) return;
SaveGridState(GregGridManager.Instance);
// SaveServerState(...)
// SaveNetworkState(...)
var metaCol = _db?.GetCollection<MetaDocument>("greg_meta");
if (metaCol != null)
{
var doc = metaCol.FindById("header");
if (doc != null)
{
doc.LastSavedAt = DateTime.UtcNow;
metaCol.Update(doc);
}
}
GregSaveNotifier.NotifySave("Auto-saved complete state.");
}
public void LoadAll()
{
LoadGridState(GregGridManager.Instance);
GregSaveNotifier.NotifyLoad(DbPath);
}
public void SaveGridState(GregGridManager grid)
{
if (grid == null || _db == null) return;
var col = _db.GetCollection<GridStateDocument>("grid_state");
col.DeleteAll();
var doc = new GridStateDocument
{
GridOriginX = grid.GridOrigin.x,
GridOriginY = grid.GridOrigin.y,
GridOriginZ = grid.GridOrigin.z,
CellSizeX = grid.CellSizeX,
CellSizeZ = grid.CellSizeZ,
SavedAt = DateTime.UtcNow
};
col.Insert(doc);
}
public void LoadGridState(GregGridManager grid)
{
if (grid == null || _db == null) return;
var col = _db.GetCollection<GridStateDocument>("grid_state");
var doc = col.FindOne(Query.All());
if (doc != null)
{
grid.Initialize(new UnityEngine.Vector3(doc.GridOriginX, doc.GridOriginY, doc.GridOriginZ), 50, 50);
}
}
public bool IsGregSave(string filePath)
{
if (!File.Exists(filePath)) return false;
if (!filePath.EndsWith(".greg.db")) return false;
try
{
using var db = new LiteDatabase(filePath);
var metaCol = db.GetCollection<MetaDocument>("greg_meta");
var doc = metaCol.FindById("header");
return doc != null && doc.Value == "greg.SaveEngine.v1";
}
catch
{
return false;
}
}
public ILiteCollection<T>? GetCollection<T>(string name)
{
return _db?.GetCollection<T>(name);
}
}
public class MetaDocument
{
public string Id { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; }
public DateTime LastSavedAt { get; set; }
public bool IsVanillaSave { get; set; }
}
public class GridStateDocument
{
public int Id { get; set; }
public float GridOriginX { get; set; }
public float GridOriginY { get; set; }
public float GridOriginZ { get; set; }
public float CellSizeX { get; set; }
public float CellSizeZ { get; set; }
public DateTime SavedAt { get; set; }
}
}
+25
View File
@@ -0,0 +1,25 @@
using gregCore.API;
namespace greg.SaveEngine
{
public static class GregSaveNotifier
{
public static void NotifySave(string details)
{
GregAPI.LogInfo($"[gregSave] ✓ Auto-saved — {details}");
GregAPI.ShowNotification($"Saved: {details}");
}
public static void NotifyLoad(string path)
{
GregAPI.LogInfo($"[gregSave] ✓ Loaded from {System.IO.Path.GetFileName(path)}");
GregAPI.ShowNotification("Modded Save Loaded");
}
public static void NotifyVanillaDetect()
{
GregAPI.LogWarning("[gregSave] ⚠ Vanilla save detected — modded features disabled");
GregAPI.ShowNotification("Vanilla Save - Modded features disabled");
}
}
}
+47
View File
@@ -0,0 +1,47 @@
using System.Collections;
using MelonLoader;
using UnityEngine;
namespace greg.SaveEngine
{
public class GregSaveScheduler
{
public static float AutoSaveIntervalSeconds { get; set; } = 60f;
private static bool _isRunning;
public static void Start()
{
if (_isRunning) return;
_isRunning = true;
MelonCoroutines.Start(AutoSaveCoroutine());
}
public static void Stop()
{
_isRunning = false;
}
private static IEnumerator AutoSaveCoroutine()
{
while (_isRunning)
{
yield return new WaitForSeconds(AutoSaveIntervalSeconds);
if (frameworkSdk.GregFeatureGuard.IsEnabled("SaveEngine.Write") && GregSaveEngine.Instance != null)
{
// Run save in background task
System.Threading.Tasks.Task.Run(() =>
{
try
{
GregSaveEngine.Instance.SaveAll();
}
catch (System.Exception ex)
{
gregCore.API.GregAPI.LogError($"Auto-save failed: {ex}");
}
});
}
}
}
}
}
@@ -0,0 +1,21 @@
using System.IO;
namespace greg.SaveEngine
{
public static class GregVanillaDetector
{
public static bool CheckIfVanillaSave(string filePath)
{
if (!File.Exists(filePath)) return true;
// Check if it's our own .db file
if (filePath.EndsWith(".greg.db"))
{
return !GregSaveEngine.Instance.IsGregSave(filePath);
}
// If it's another file and it's being loaded by Vanilla, it's vanilla
return true;
}
}
}
+238
View File
@@ -0,0 +1,238 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using gregCore.API;
namespace greg.UI.Settings
{
public class GregUIBuilder
{
public GregUIBuilder AddLabel(string text)
{
GUILayout.Label(text);
return this;
}
public GregUIBuilder AddToggle(string label, bool currentValue, Action<bool> onChanged)
{
bool newValue = GUILayout.Toggle(currentValue, label);
if (newValue != currentValue)
{
onChanged?.Invoke(newValue);
}
return this;
}
public GregUIBuilder AddSlider(string label, float min, float max, float currentValue, Action<float> onChanged)
{
GUILayout.BeginHorizontal();
GUILayout.Label(label, GUILayout.Width(150));
float newValue = GUILayout.HorizontalSlider(currentValue, min, max);
GUILayout.Label(newValue.ToString("F1"), GUILayout.Width(40));
GUILayout.EndHorizontal();
if (Math.Abs(newValue - currentValue) > 0.01f)
{
onChanged?.Invoke(newValue);
}
return this;
}
public GregUIBuilder AddButton(string label, Action onClick)
{
if (GUILayout.Button(label))
{
onClick?.Invoke();
}
return this;
}
}
public class GregSettingsHub : MonoBehaviour
{
private static GregSettingsHub _instance;
private bool _isVisible = false;
private int _selectedTab = 0;
private class TabData
{
public string Id;
public string Label;
public Action<GregUIBuilder> BuildFn;
}
private static readonly List<TabData> _tabs = new();
private GUIStyle? _windowStyle;
private GUIStyle? _tabStyle;
private GregUIBuilder _builder = new GregUIBuilder();
public static void Initialize()
{
if (_instance == null)
{
var go = new GameObject("GregSettingsHub");
_instance = go.AddComponent<GregSettingsHub>();
UnityEngine.Object.DontDestroyOnLoad(go);
RegisterStandardTabs();
}
}
public static void RegisterTab(string tabId, string label, Action<GregUIBuilder> buildFn)
{
if (!_tabs.Exists(t => t.Id == tabId))
{
_tabs.Add(new TabData { Id = tabId, Label = label, BuildFn = buildFn });
}
}
public static void UnregisterTab(string tabId)
{
_tabs.RemoveAll(t => t.Id == tabId);
}
private static void RegisterStandardTabs()
{
RegisterTab("greg.core", "Framework", builder =>
{
builder.AddLabel("gregCore v1.0.0.33-pre")
.AddLabel("MelonLoader v0.6+")
.AddLabel($"Save Mode: {(frameworkSdk.GregFeatureGuard.IsVanillaSave ? "Vanilla" : "Greg")}")
.AddToggle("Verbose Startup Log", false, v => { })
.AddToggle("Debug Mode (alle Hooks loggen)", false, v => { })
.AddButton("Run Language Scan now", () => { })
.AddButton("Show Missing.md Status", () => { });
});
RegisterTab("greg.grid", "Grid", builder =>
{
var grid = greg.GridPlacement.GregGridManager.Instance;
if (frameworkSdk.GregFeatureGuard.IsVanillaSave)
{
builder.AddLabel("⚠ Vanilla Save detected — Grid Placement disabled");
}
builder.AddToggle("Grid Placement Active", frameworkSdk.GregFeatureGuard.IsEnabled("GridPlacement"), v => {
if (v) frameworkSdk.GregFeatureGuard.EnableFeature("GridPlacement");
else frameworkSdk.GregFeatureGuard.DisableFeature("GridPlacement");
});
if (grid != null)
{
builder.AddToggle("Show Grid Lines", grid.ShowGridLines, v => grid.ShowGridLines = v)
.AddToggle("Show Sub-Grid", grid.ShowSubGrid, v => grid.ShowSubGrid = v)
.AddSlider("Sub-Grid Zoom Threshold", 1.0f, 10.0f, 5.0f, v => { })
.AddToggle("Build Mode Key: B", true, v => { })
.AddLabel($"Placed Racks: [unknown]")
.AddLabel($"Grid Size: 50x50")
.AddButton("Clear All Greg Racks", () => grid.ClearAll());
}
});
RegisterTab("greg.save", "SaveEngine", builder =>
{
if (frameworkSdk.GregFeatureGuard.IsVanillaSave)
{
builder.AddLabel("⚠ Vanilla Save detected — SaveEngine write disabled");
}
builder.AddToggle("greg.SaveEngine Active", frameworkSdk.GregFeatureGuard.IsEnabled("SaveEngine.Write"), v => {
if (v) frameworkSdk.GregFeatureGuard.EnableFeature("SaveEngine.Write");
else frameworkSdk.GregFeatureGuard.DisableFeature("SaveEngine.Write");
});
builder.AddSlider("Auto-Save Interval (seconds)", 10f, 300f, greg.SaveEngine.GregSaveScheduler.AutoSaveIntervalSeconds, v => greg.SaveEngine.GregSaveScheduler.AutoSaveIntervalSeconds = v)
.AddToggle("Disable Vanilla Save (expert!)", false, v => { })
.AddToggle("Save Grid State", true, v => { })
.AddToggle("Save Server State", true, v => { })
.AddToggle("Save Network State", true, v => { })
.AddToggle("Save Cable State", true, v => { });
var engine = greg.SaveEngine.GregSaveEngine.Instance;
builder.AddLabel($"Last Save: [unknown]")
.AddLabel($"DB File: {(engine != null ? engine.DbPath : "None")}")
.AddButton("Save Now", () => engine?.SaveAll())
.AddButton("Open Save Folder", () => {
if (engine != null && !string.IsNullOrEmpty(engine.DbPath))
{
System.Diagnostics.Process.Start("explorer.exe", $"/select,\"{engine.DbPath}\"");
}
});
});
RegisterTab("greg.lang", "Languages", builder =>
{
builder.AddLabel("Languages Registry (Lua, JS, Python, Go, Rust)");
});
RegisterTab("greg.debug", "Debug", builder =>
{
builder.AddLabel("Debug & Diagnose");
});
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.F8))
{
_isVisible = !_isVisible;
}
if (_isVisible && Input.GetKeyDown(KeyCode.Escape))
{
_isVisible = false;
}
}
private void OnGUI()
{
if (!_isVisible) return;
if (_windowStyle == null)
{
_windowStyle = new GUIStyle(GUI.skin.window);
_windowStyle.normal.background = MakeTex(2, 2, new Color(0.00f, 0.07f, 0.07f, 0.93f));
_windowStyle.normal.textColor = new Color(0.75f, 0.99f, 0.97f, 1f);
_tabStyle = new GUIStyle(GUI.skin.button);
_tabStyle.normal.textColor = new Color(0.38f, 0.96f, 0.85f, 1f); // Accent
}
GUI.Window(999, new Rect((Screen.width - 480) / 2, 100, 480, 500), DrawWindow, "gregCore Settings Hub", _windowStyle);
}
private void DrawWindow(int id)
{
if (_tabs.Count == 0) return;
GUILayout.BeginHorizontal();
for (int i = 0; i < _tabs.Count; i++)
{
if (GUILayout.Toggle(_selectedTab == i, _tabs[i].Label, _tabStyle))
{
_selectedTab = i;
}
}
GUILayout.EndHorizontal();
GUILayout.Space(10);
if (_selectedTab >= 0 && _selectedTab < _tabs.Count)
{
_tabs[_selectedTab].BuildFn?.Invoke(_builder);
}
}
private Texture2D MakeTex(int width, int height, Color col)
{
Color[] pix = new Color[width * height];
for (int i = 0; i < pix.Length; ++i)
{
pix[i] = col;
}
Texture2D result = new Texture2D(width, height);
result.SetPixels(pix);
result.Apply();
return result;
}
}
}