feat: add comprehensive documentation for F8 Settings Hub, Grid Placement, Save Engine, and Vanilla Save Compatibility
This commit is contained in:
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
```
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user