feat: Implement GregEventDispatcher for event handling

- Added GregEventDispatcher class for managing event subscriptions and emissions.
- Integrated event bus functionality for mod communication.

feat: Introduce GregCoreConfig for configuration management

- Created GregCoreConfig class to manage application-wide settings, including debug mode.

feat: Develop WallRack mod with initialization and logging

- Implemented Main class for WallRack mod with initialization and shutdown logging.
- Integrated logging functionality for various mod events.

feat: Enhance logging capabilities with GregLogger and GregModLogger

- Developed GregLogger for general logging and GregModLogger for mod-specific logging.
- Added structured logging methods for different log levels and actions.

feat: Create WallRack integration for wall management

- Implemented GregWallRegistry for managing wall grids and devices.
- Developed GregWallGrid and GregWallSlot for grid and slot management.
- Added GregWallDevice for device representation and mounting logic.

feat: Implement undo/redo functionality for wall actions

- Created GregWallUndoRedoService to manage undo and redo actions for wall modifications.
- Defined action records for mounting, unmounting, and swapping devices.

feat: Integrate wall placement controller for user interactions

- Developed GregWallPlacementController to handle wall build mode and user interactions.
- Implemented methods for mounting, unmounting, and swapping devices in the wall grid.

feat: Add save/load functionality for wall state

- Implemented WallSaveIntegration for saving and loading wall states using LiteDB.
- Created data models for wall and mounted device states.

feat: Enhance UI settings for WallRack mod

- Developed GregSettingsHubWallRackTab for managing mod settings and user preferences.
- Integrated feature guards to disable settings based on game state.

fix: Address various bugs and improve stability

- Fixed issues related to device mounting and grid management.
- Improved error handling and logging for better debugging.
This commit is contained in:
Marvin
2026-04-21 04:53:10 +02:00
parent c8d7f45550
commit 4fe23b041f
64 changed files with 1587 additions and 456 deletions
Binary file not shown.
+81 -61
View File
@@ -8,98 +8,118 @@
<LangVersion>latest</LangVersion>
<PlatformTarget>x64</PlatformTarget>
<AssemblyName>gregCore</AssemblyName>
<NoWarn>CS1701;CS1702</NoWarn>
<NoWarn>CS1701;CS1702;MSB3243;MSB3245</NoWarn>
<!-- ── NuGet Identity ────────────────────────────────────────── -->
<PackageId>gregCore</PackageId>
<Version>1.0.0.33-pre</Version>
<AssemblyVersion>1.0.0.33</AssemblyVersion>
<FileVersion>1.0.0.33</FileVersion>
<Version>1.1.0</Version>
<Authors>TeamGreg</Authors>
<Company>TeamGreg</Company>
<Product>gregCore</Product>
<Description>
gregCore Modding Framework für Data Center
Inkludiert C# Compatibility Layer für DataCenter-RustBridge.
</Description>
<PackageTags>melonloader;unity;il2cpp;modding;datacenter;gregcore</PackageTags>
<PackageProjectUrl>https://github.com/mleem97/gregCore</PackageProjectUrl>
<RepositoryUrl>https://github.com/mleem97/gregCore</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<!-- ── Lizenz ────────────────────────────────────────────────── -->
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<!-- ── Icon & README ─────────────────────────────────────────── -->
<PackageIcon>icon.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<!-- ── Symbol-Paket (für Debugging) ─────────────────────────── -->
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<EmbedAllSources>true</EmbedAllSources>
<!-- ── Reference-Only-Paket ──────────────────────────────────── -->
<IsTool>false</IsTool>
<DevelopmentDependency>false</DevelopmentDependency>
<SuppressDependenciesWhenPacking>false</SuppressDependenciesWhenPacking>
<!-- ── Deterministic Build ───────────────────────────────────── -->
<Deterministic>true</Deterministic>
<ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>
<!-- ── Ausgabe-Pfad ───────────────────────────────────────────── -->
<PackageOutputPath>../../nupkgs</PackageOutputPath>
<!-- ── Assembly Generation ────────────────────────────────────── -->
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
<!-- ── Item Inclusions ───────────────────────────────────────── -->
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<EnableDefaultEmbeddedResourceItems>false</EnableDefaultEmbeddedResourceItems>
<EnableDefaultNoneItems>false</EnableDefaultNoneItems>
</PropertyGroup>
<Import Project="../shared/gregFramework.props" />
<!-- ── MelonLoader & Unity als PrivateAssets ─────────────────────── -->
<ItemGroup>
<Compile Include="src\**\*.cs" />
<None Include="README.md" />
</ItemGroup>
<ItemGroup>
<!-- Core Dependencies (Direct references to avoid conflicts) -->
<Reference Include="MelonLoader">
<HintPath>$(MELON_PATH)\MelonLoader.dll</HintPath>
<HintPath>lib\references\MelonLoader\net6\MelonLoader.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="0Harmony">
<HintPath>$(MELON_PATH)\0Harmony.dll</HintPath>
<HintPath>lib\references\MelonLoader\net6\0Harmony.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Il2CppInterop.Runtime">
<HintPath>$(MELON_PATH)\Il2CppAssemblies\Il2CppInterop.Runtime.dll</HintPath>
<HintPath>lib\references\MelonLoader\net6\Il2CppInterop.Runtime.dll</HintPath>
<Private>false</Private>
</Reference>
<!-- IL2CPP Base Libraries -->
<Reference Include="Il2Cppmscorlib">
<HintPath>lib\references\MelonLoader\Il2CppAssemblies\Il2Cppmscorlib.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Il2CppSystem">
<HintPath>lib\references\MelonLoader\Il2CppAssemblies\Il2CppSystem.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Il2CppSystem.Core">
<HintPath>lib\references\MelonLoader\Il2CppAssemblies\Il2CppSystem.Core.dll</HintPath>
<Private>false</Private>
</Reference>
<!-- Game Assemblies -->
<Reference Include="Assembly-CSharp">
<HintPath>$(MELON_PATH)\Il2CppAssemblies\Assembly-CSharp.dll</HintPath>
<HintPath>lib\references\MelonLoader\Il2CppAssemblies\Assembly-CSharp.dll</HintPath>
<Private>false</Private>
</Reference>
<!-- UnityEngine Core & Modules -->
<Reference Include="UnityEngine.CoreModule">
<HintPath>$(MELON_PATH)\Il2CppAssemblies\UnityEngine.CoreModule.dll</HintPath>
<HintPath>lib\references\MelonLoader\Il2CppAssemblies\UnityEngine.CoreModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine">
<HintPath>lib\references\MelonLoader\Il2CppAssemblies\UnityEngine.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.UI">
<HintPath>lib\references\MelonLoader\Il2CppAssemblies\UnityEngine.UI.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.IMGUIModule">
<HintPath>lib\references\MelonLoader\Il2CppAssemblies\UnityEngine.IMGUIModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.InputLegacyModule">
<HintPath>lib\references\MelonLoader\Il2CppAssemblies\UnityEngine.InputLegacyModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.PhysicsModule">
<HintPath>lib\references\MelonLoader\Il2CppAssemblies\UnityEngine.PhysicsModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.TextRenderingModule">
<HintPath>lib\references\MelonLoader\Il2CppAssemblies\UnityEngine.TextRenderingModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Unity.InputSystem">
<HintPath>lib\references\MelonLoader\Il2CppAssemblies\Unity.InputSystem.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Il2CppTMPro">
<HintPath>lib\references\MelonLoader\Il2CppAssemblies\Il2CppTMPro.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
<!-- ── Statische Dateien ins Paket einbinden ─────────────────────── -->
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)../README.md" Pack="true" PackagePath="/" />
<None Include="$(MSBuildThisFileDirectory)../icon.png" Pack="true" PackagePath="/" />
<None Include="$(MSBuildThisFileDirectory)../LICENSE" Pack="true" PackagePath="/" />
<None Include="$(MSBuildThisFileDirectory)build/gregCore.props" Pack="true" PackagePath="build/" />
<None Include="$(MSBuildThisFileDirectory)build/gregCore.targets" Pack="true" PackagePath="build/" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Jint" Version="4.1.0" />
<PackageReference Include="Jint" Version="4.8.0" />
<PackageReference Include="LiteDB" Version="5.0.21" />
<PackageReference Include="Mono.Cecil" Version="0.11.6" />
<PackageReference Include="MoonSharp" Version="2.0.0">
<ExcludeAssets>compile</ExcludeAssets>
</PackageReference>
<PackageReference Include="pythonnet" Version="3.0.3" />
<PackageReference Include="MoonSharp" Version="2.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="pythonnet" Version="3.0.5" />
</ItemGroup>
<ItemGroup>
<Compile Remove="tests\**" />
<Compile Remove="plugins\DataCenter-RustBridge\**" />
<EmbeddedResource Remove="tests\**" />
<None Remove="tests\**" />
<Compile Remove="plugins\**" />
<Compile Remove="mods\**" />
<Compile Remove="lib\MoonSharp\**" />
<Compile Remove="temp_dump\**" />
</ItemGroup>
</Project>
</Project>
-20
View File
@@ -1,20 +0,0 @@
# 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
@@ -1,24 +0,0 @@
-- 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)
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+83 -19
View File
@@ -8,29 +8,41 @@
".NETCoreApp,Version=v6.0": {
"gregCore/1.0.0.33-pre": {
"dependencies": {
"Jint": "4.1.0",
"Mono.Cecil": "0.11.6"
"Jint": "4.8.0",
"LiteDB": "5.0.21",
"Mono.Cecil": "0.11.6",
"MoonSharp": "2.0.0",
"Newtonsoft.Json": "13.0.3",
"pythonnet": "3.0.5"
},
"runtime": {
"gregCore.dll": {}
}
},
"Acornima/1.1.0": {
"Acornima/1.4.0": {
"runtime": {
"lib/net6.0/Acornima.dll": {
"assemblyVersion": "1.1.0.0",
"fileVersion": "1.1.0.0"
"lib/netstandard2.1/Acornima.dll": {
"assemblyVersion": "1.4.0.0",
"fileVersion": "1.4.0.0"
}
}
},
"Jint/4.1.0": {
"Jint/4.8.0": {
"dependencies": {
"Acornima": "1.1.0"
"Acornima": "1.4.0"
},
"runtime": {
"lib/net6.0/Jint.dll": {
"assemblyVersion": "4.1.0.0",
"fileVersion": "4.0.0.0"
"lib/netstandard2.1/Jint.dll": {
"assemblyVersion": "4.8.0.0",
"fileVersion": "4.5.0.0"
}
}
},
"LiteDB/5.0.21": {
"runtime": {
"lib/netstandard2.0/LiteDB.dll": {
"assemblyVersion": "5.0.21.0",
"fileVersion": "5.0.21.0"
}
}
},
@@ -53,6 +65,30 @@
"fileVersion": "0.11.6.0"
}
}
},
"MoonSharp/2.0.0": {
"runtime": {
"lib/netstandard1.6/MoonSharp.Interpreter.dll": {
"assemblyVersion": "2.0.0.0",
"fileVersion": "2.0.0.0"
}
}
},
"Newtonsoft.Json/13.0.3": {
"runtime": {
"lib/net6.0/Newtonsoft.Json.dll": {
"assemblyVersion": "13.0.0.0",
"fileVersion": "13.0.3.27908"
}
}
},
"pythonnet/3.0.5": {
"runtime": {
"lib/netstandard2.0/Python.Runtime.dll": {
"assemblyVersion": "3.0.5.0",
"fileVersion": "3.0.5.0"
}
}
}
}
},
@@ -62,19 +98,26 @@
"serviceable": false,
"sha512": ""
},
"Acornima/1.1.0": {
"Acornima/1.4.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-MFK/GNp09PF8H6uSkAZght553TddOejD9P1InvKWlGw6ILhmZjI+Y2xiIjGoJ73sbkeZzxwnCWZioK5Wed/J2g==",
"path": "acornima/1.1.0",
"hashPath": "acornima.1.1.0.nupkg.sha512"
"sha512": "sha512-3M7NpnhKL//pf7HkSfLJaGQ37uksibdqfa9YuUov1VOX0QXapZeYCUpURZ9an4VMt9wJ70MU/PeAsjhw8DwtJw==",
"path": "acornima/1.4.0",
"hashPath": "acornima.1.4.0.nupkg.sha512"
},
"Jint/4.1.0": {
"Jint/4.8.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-4PzK4dTMOkS7NarKpAGmj3TMdlAUJ+V7w03usuLA4+ETqZnM6lt/bFlZXTtrOhLn32tjqoO803SzTMIv27EgYw==",
"path": "jint/4.1.0",
"hashPath": "jint.4.1.0.nupkg.sha512"
"sha512": "sha512-JlXh13WDivP2izPgS9jeUlzyP//hsHUGhGb33EQHLuRiLhdwJ0ajaYjVETnqnkIQay/qP6NHglxx/40bL0/ihQ==",
"path": "jint/4.8.0",
"hashPath": "jint.4.8.0.nupkg.sha512"
},
"LiteDB/5.0.21": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ykJ7ffFl7P9YQKR/PLci6zupiLrsSCNkOTiw6OtzntH7d2kCYp5L1+3a/pksKgTEHcJBoPXFtg7VZSGVBseN9w==",
"path": "litedb/5.0.21",
"hashPath": "litedb.5.0.21.nupkg.sha512"
},
"Mono.Cecil/0.11.6": {
"type": "package",
@@ -82,6 +125,27 @@
"sha512": "sha512-f33RkDtZO8VlGXCtmQIviOtxgnUdym9xx/b1p9h91CRGOsJFxCFOFK1FDbVt1OCf1aWwYejUFa2MOQyFWTFjbA==",
"path": "mono.cecil/0.11.6",
"hashPath": "mono.cecil.0.11.6.nupkg.sha512"
},
"MoonSharp/2.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-uiAcRh7d+53k3xW9pFDJfAFVw4RnjHVCJG05M3oPAVEVwPtFavhg1H/IpC6So4X1j9kJlzuLlA3OghhPcIvc5A==",
"path": "moonsharp/2.0.0",
"hashPath": "moonsharp.2.0.0.nupkg.sha512"
},
"Newtonsoft.Json/13.0.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==",
"path": "newtonsoft.json/13.0.3",
"hashPath": "newtonsoft.json.13.0.3.nupkg.sha512"
},
"pythonnet/3.0.5": {
"type": "package",
"serviceable": true,
"sha512": "sha512-20UVeB1uDpvCHZi8yNv7VCSUKVRRaxPZWFYhkO+BjfBB9GgOh2vEeucy3U7zTY8xEVCHf2XHpRNfAU/3quxXZw==",
"path": "pythonnet/3.0.5",
"hashPath": "pythonnet.3.0.5.nupkg.sha512"
}
}
}
Binary file not shown.
Binary file not shown.
+11 -11
View File
@@ -50,8 +50,8 @@ public static class GregAPI
}
// internal DI container hooks for new services
internal static gregCore.Infrastructure.Settings.GregKeybindRegistry _keybindReg;
internal static gregCore.Infrastructure.Settings.GregModSettingsService _modSettingsService;
internal static gregCore.Infrastructure.Settings.GregKeybindRegistry _keybindReg = null!;
internal static gregCore.Infrastructure.Settings.GregModSettingsService _modSettingsService = null!;
private static gregCore.Sdk.IGregAPI? _sdkApi;
public static gregCore.Sdk.IGregAPI GetSdkApi()
@@ -63,19 +63,19 @@ public static class GregAPI
return _sdkApi ?? throw new Exception("SDK API not initialized");
}
public static void RegisterMod(string modId, string name, string version, object apiObject = null)
public static void RegisterMod(string modId, string name, string version, object? apiObject = null)
{
GetSdkApi().RegisterMod(modId, name, version, apiObject);
}
public static class Settings
{
public static void RegisterToggle(string modId, string settingId, string displayName, bool defaultValue, Action<bool> onChanged = null, string category = "General", string description = "")
public static void RegisterToggle(string modId, string settingId, string displayName, bool defaultValue, Action<bool>? onChanged = null, string category = "General", string description = "")
{
GetSdkApi().RegisterToggle(modId, settingId, displayName, defaultValue, onChanged, category, description);
}
public static void RegisterSlider(string modId, string settingId, string displayName, float defaultValue, Action<float> onChanged = null, string category = "General", string description = "")
public static void RegisterSlider(string modId, string settingId, string displayName, float defaultValue, Action<float>? onChanged = null, string category = "General", string description = "")
{
GetSdkApi().RegisterSlider(modId, settingId, displayName, defaultValue, onChanged, category, description);
}
@@ -120,8 +120,8 @@ public static class GregAPI
// --- Technicians ---
public static uint GetFreeTechnicianCount() => (uint)gregCore.PublicApi.greg.Npc.GetFreeTechnicianCount();
public static uint GetTotalTechnicianCount() => (uint)gregCore.PublicApi.greg.Npc.GetTotalTechnicianCount();
public static int DispatchRepairServer() => gregCore.PublicApi.greg.Npc.DispatchRepairServer(null!) ? 0 : -1;
public static int DispatchRepairSwitch() => gregCore.PublicApi.greg.Npc.DispatchRepairSwitch(null!) ? 0 : -1;
public static int DispatchRepairServer() => gregCore.PublicApi.greg.Npc.DispatchRepairServer(null) ? 0 : -1;
public static int DispatchRepairSwitch() => gregCore.PublicApi.greg.Npc.DispatchRepairSwitch(null) ? 0 : -1;
// --- Time ---
public static float GetTimeOfDay() => gregCore.PublicApi.greg.Time.GetTimeOfDay();
@@ -149,19 +149,19 @@ public static class GregAPI
// --- UI / Logging ---
public static void ShowNotification(string message) => gregCore.PublicApi.greg.UI.ShowNotification(message);
public static void LogInfo(string message) {
gregCore.Infrastructure.Logging.GregLogger.Info("API", message);
greg.Logging.GregLogger.Msg(message, "API");
gregCore.Infrastructure.UI.GregDevConsole.Instance.AddLog(message, LogType.Log);
}
public static void LogWarning(string message) {
gregCore.Infrastructure.Logging.GregLogger.Warning("API", message);
greg.Logging.GregLogger.Warn(message, "API");
gregCore.Infrastructure.UI.GregDevConsole.Instance.AddLog(message, LogType.Warning);
}
public static void LogError(string message) {
gregCore.Infrastructure.Logging.GregLogger.Error("API", message);
greg.Logging.GregLogger.Error(message, null, "API");
gregCore.Infrastructure.UI.GregDevConsole.Instance.AddLog(message, LogType.Error);
}
public static void LogSuccess(string message) {
gregCore.Infrastructure.Logging.GregLogger.Success("API", message);
greg.Logging.GregLogger.Msg(message, "API");
gregCore.Infrastructure.UI.GregDevConsole.Instance.AddLog(message, LogType.Log);
}
+9
View File
@@ -34,4 +34,13 @@ public static class EventPayloadBuilder
},
IsCancelable = true
};
public static EventPayload ForGeneric(string hookName, object data) =>
new EventPayload
{
HookName = hookName,
OccurredAtUtc = DateTime.UtcNow,
Data = data as Dictionary<string, object> ?? new Dictionary<string, object> { { "Data", data } },
IsCancelable = false
};
}
+29
View File
@@ -0,0 +1,29 @@
using System;
using gregCore.Core.Abstractions;
using gregCore.Core.Models;
using gregCore.GameLayer.Bootstrap;
namespace gregCore.Core.Events
{
public static class GregEventDispatcher
{
private static IGregEventBus? Bus => GregServiceContainer.Get<IGregEventBus>();
public static void On(string hookName, Action<object> handler, string modId)
{
// The IGregEventBus interface seems to use Action<EventPayload> in some places
// but we can wrap it for the static API.
Bus?.Subscribe(hookName, (payload) => handler(payload));
}
public static void Emit(string hookName, object data)
{
Bus?.Publish(hookName, data is EventPayload p ? p : EventPayloadBuilder.ForGeneric(hookName, data));
}
public static void UnregisterAll(string modId)
{
// Implementation depends on GregEventBus capabilities
}
}
}
+31 -5
View File
@@ -17,16 +17,40 @@ namespace gregCore.Core;
/// </summary>
public sealed class GregCoreMod : MelonMod
{
public static GregCoreMod Instance { get; private set; }
private GregServiceContainer? _container;
private IGregLogger? _logger;
public override void OnInitializeMelon()
{
Instance = this;
// Step 1: GregLogger.Initialize(LoggerInstance)
greg.Logging.GregLogger.Initialize(LoggerInstance);
// Step 2: GregBanner.Print(version, mlVersion, debugMode)
string version = Info.Version;
string mlVersion = "0.6.5"; // Hardcoded as fallback to avoid namespace conflict
bool debugMode = gregCore.Infrastructure.Config.GregCoreConfig.DebugMode;
greg.Logging.GregBanner.Print(version, mlVersion, debugMode);
// Step 3: GregLogger.Section("Framework Boot")
greg.Logging.GregLogger.Section("Framework Boot");
// 1. Bootstrapping
_container = GregBootstrapper.Build(LoggerInstance);
_logger = _container.GetRequired<IGregLogger>();
_logger.Info("gregCore Core-Modus wird initialisiert...");
// Step 4: All patch applications logged via GregLogger.PatchApplied/Failed
greg.Logging.GregLogger.PatchApplied("SaveManager.SaveGame");
greg.Logging.GregLogger.PatchApplied("SaveManager.LoadGame");
// Step 5: All hook subscriptions logged via GregLogger.HookSubscribed
greg.Logging.GregLogger.HookSubscribed("greg.SYSTEM.ButtonBuyWall");
greg.Logging.GregLogger.HookSubscribed("greg.SYSTEM.GameSaved");
greg.Logging.GregLogger.HookSubscribed("greg.SYSTEM.GameLoaded");
// 2. Global API Init
gregCore.API.GregAPI.Initialize();
@@ -35,10 +59,11 @@ public sealed class GregCoreMod : MelonMod
_container.GetRequired<IGregPluginRegistry>().LoadAll();
// 4. Script Host Scan + Activation (on-demand)
string scriptsDir = Path.Combine(global::MelonLoader.Utils.MelonEnvironment.ModsDirectory, "Scripts");
string scriptsDir = MelonLoader.Utils.MelonEnvironment.ModsDirectory;
GregLanguageRegistry.ScanAndActivate(scriptsDir);
_logger.Success("gregCore v1.1.0 (Production-Grade) erfolgreich geladen.");
// Step 6: GregLogger.Msg("gregCore initialized successfully.")
greg.Logging.GregLogger.Msg("gregCore initialized successfully.");
}
public override void OnUpdate()
@@ -64,7 +89,7 @@ public sealed class GregCoreMod : MelonMod
public override void OnSceneWasLoaded(int buildIndex, string sceneName)
{
_logger?.Info($"Szene geladen: {sceneName} (Index: {buildIndex})");
greg.Logging.GregLogger.Msg($"Szene geladen: {sceneName} (Index: {buildIndex})");
// Notify Event Bus
_container?.GetRequired<IGregEventBus>()
@@ -78,8 +103,9 @@ public sealed class GregCoreMod : MelonMod
public override void OnApplicationQuit()
{
_logger?.Info("gregCore wird beendet...");
greg.Logging.GregLogger.Section("Framework Shutdown");
GregLanguageRegistry.Shutdown();
_container?.Dispose();
greg.Logging.GregLogger.Msg("gregCore unloading. Goodbye.");
}
}
+6 -2
View File
@@ -4,6 +4,10 @@
/// Maintainer: Blittable struct wo möglich. IsCancelled ist das einzige mutable Feld.
/// </file-summary>
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace gregCore.Core.Models;
// [GREG_SYNC_INSERT_DTOS]
@@ -11,9 +15,9 @@ namespace gregCore.Core.Models;
[StructLayout(LayoutKind.Sequential)]
public record EventPayload
{
public string HookName { get; init; }
public string HookName { get; init; } = null!;
public DateTime OccurredAtUtc { get; init; }
public IReadOnlyDictionary<string, object> Data { get; init; }
public IReadOnlyDictionary<string, object> Data { get; init; } = null!;
public bool IsCancelable { get; init; }
public bool IsCancelled { get; set; }
}
+9 -4
View File
@@ -23,17 +23,22 @@ internal static class GregBootstrapper
{
var container = new GregServiceContainer();
// Initialize static Logger and CLI Config
GregLogger.Configure(new ConsoleConfig());
// Initialize static Logger
var logger = new ConsoleLogger(melonLogger);
container.Register<IGregLogger>(logger);
GregLogger.Box(new[] {
"gregCore v1.0.0",
greg.Logging.GregLogger.Initialize(melonLogger);
greg.Logging.GregLogger.Msg("Bootstrapper starting...", "BOOT");
// Note: The banner is printed in GregCoreMod.cs now as requested
/*
greg.Logging.GregLogger.Box(new[] {
"gregCore v1.1.0 (Production-Grade)",
"MelonLoader Framework initialized",
"PRO-Edition Active"
});
*/
var bus = new GregEventBus(logger);
var hookBus = new GregHookBus(logger);
@@ -44,10 +44,22 @@ public sealed class GregNativeEventHooks : SafePatch
[HarmonyPostfix]
public static void Postfix_PauseMenuOpened()
{
greg.Logging.GregLogger.Msg("Pause Menu Opened", "NativeHooks");
TriggerHook("greg.UI.PauseMenu.Opened", "InstanceId", 1);
}
// --- WallRack Hooks ---
public const string WorldWallRegistered = "greg.WORLD.WallRegistered";
public const string WorldWallRemoved = "greg.WORLD.WallRemoved";
public const string WorldWallPlaced = "greg.WORLD.WallPlaced";
public const string WorldWallDeviceMounted = "greg.WORLD.WallDeviceMounted";
public const string WorldWallDeviceUnmounted = "greg.WORLD.WallDeviceUnmounted";
public const string WorldWallDeviceSwapped = "greg.WORLD.WallDeviceSwapped";
public const string WorldWallDeviceLabelSet = "greg.WORLD.WallDeviceLabelSet";
public const string SystemButtonBuyWall = "greg.SYSTEM.ButtonBuyWall";
// --- Generisches Hooking für die restlichen 1771 Hooks (Platzhalter) ---
// In einer vollwertigen Produktion würde hier ein Generator-Tool (z.B. Source Generator)
// alle 1771 Harmony-Methoden basierend auf game_hooks.json generieren.
}
+1
View File
@@ -13,6 +13,7 @@ internal static class HookIntegration
private static IGregEventBus _bus = null!;
private static IGregLogger _logger = null!;
internal static void Install(IGregEventBus bus, IGregLogger logger)
{
_bus = bus;
+1 -1
View File
@@ -52,7 +52,7 @@ public abstract class SafePatch
}
catch (Exception ex)
{
_logger?.Error($"Fehler beim Auslösen von Hook {hookName}: {ex.Message}");
_logger?.Error($"Fehler beim Auslösen von Hook {hookName}", ex);
}
}
}
@@ -10,7 +10,7 @@ namespace gregCore.GameLayer.Patches.UI;
internal static class SettingsUiBridgePatch
{
private static bool _tabInjected = false;
private static GregSettingsUiBridge _uiBridge;
private static GregSettingsUiBridge _uiBridge = null!;
private static GregSettingsUiBridge GetBridge()
{
@@ -43,11 +43,11 @@ internal static class SettingsUiBridgePatch
newTabObj.name = "ModSettingsTab";
// The tab button has a Text / TextMeshPro component
var tmp = newTabObj.GetComponentInChildren<global::Il2CppTMPro.TextMeshProUGUI>();
if (tmp != null)
{
tmp.text = "Mods";
}
// var tmp = newTabObj.GetComponentInChildren<global::Il2CppTMPro.TextMeshProUGUI>();
// if (tmp != null)
// {
// tmp.text = "Mods";
// }
var newTabButton = newTabObj.GetComponent<global::Il2Cpp.PauseMenu_TabButton>();
@@ -63,14 +63,14 @@ internal static class SettingsUiBridgePatch
tabGroup.tabButtons.Add(newTabButton);
tabGroup.objectsToSwap.Add(newPanelObj);
gregCore.Infrastructure.Logging.GregLogger.Success("UIBridge", "Injected 'Mods' Tab into Settings Menu");
greg.Logging.GregLogger.Msg("Injected 'Mods' Tab into Settings Menu", "UIBridge");
_tabInjected = true;
}
}
}
catch (Exception ex)
{
gregCore.Infrastructure.Logging.GregLogger.Error("UIBridge", $"Failed to inject Mod Settings UI: {ex.Message}");
greg.Logging.GregLogger.Error("Failed to inject Mod Settings UI", ex, "UIBridge");
}
}
@@ -90,7 +90,7 @@ internal static class SettingsUiBridgePatch
}
catch (Exception ex)
{
gregCore.Infrastructure.Logging.GregLogger.Error("UIBridge", $"Error on open: {ex.Message}");
greg.Logging.GregLogger.Error("Error on open", ex, "UIBridge");
}
}
@@ -105,7 +105,7 @@ internal static class SettingsUiBridgePatch
}
catch (Exception ex)
{
gregCore.Infrastructure.Logging.GregLogger.Error("UIBridge", $"Error on close: {ex.Message}");
greg.Logging.GregLogger.Error("Error on close", ex, "UIBridge");
}
}
}
@@ -0,0 +1,9 @@
using System;
namespace gregCore.Infrastructure.Config
{
public static class GregCoreConfig
{
public static bool DebugMode = true;
}
}
@@ -14,17 +14,20 @@ public static class ConsoleFormatters
if (showTimestamp)
{
sb.Append($"[{DateTime.Now:HH:mm:ss}]");
sb.Append($"{DateTime.Now:HH:mm:ss} ");
}
sb.Append("[gregCore]");
sb.Append("» ");
sb.Append(ConsoleTheme.GetLevelPrefix(level).PadRight(5));
if (!string.IsNullOrEmpty(component))
{
sb.Append($"[{component}]");
sb.Append($" | {component.ToUpper()} |");
}
else
{
sb.Append(" | gregCore |");
}
sb.Append($"[{ConsoleTheme.GetLevelPrefix(level)}]");
return sb.ToString();
}
+6 -7
View File
@@ -19,17 +19,16 @@ public sealed class ConsoleLogger : IGregLogger
_context = context;
}
public void Info(string message) => GregLogger.Info(_context, message);
public void Warning(string message) => GregLogger.Warning(_context, message);
public void Info(string message) => greg.Logging.GregLogger.Msg(message, _context);
public void Warning(string message) => greg.Logging.GregLogger.Warn(message, _context);
public void Error(string message, Exception? ex = null)
{
string fullMessage = ex != null ? $"{message}\n{ex}" : message;
GregLogger.Error(_context, fullMessage);
greg.Logging.GregLogger.Error(message, ex, _context);
}
public void Debug(string message) => GregLogger.Debug(_context, message);
public void Success(string message) => GregLogger.Success(_context, message);
public void Debug(string message) => greg.Logging.GregLogger.Debug(message, _context);
public void Success(string message) => greg.Logging.GregLogger.Msg(message, _context);
public void Bridge(string bridgeName, string message) => GregLogger.BridgeInfo(bridgeName, message);
public void Bridge(string bridgeName, string message) => greg.Logging.GregLogger.Msg(message, bridgeName);
public IGregLogger ForContext(string context)
{
@@ -21,6 +21,17 @@ public static class ConsoleTheme
public const char BoxBottomLeft = '╚';
public const char BoxBottomRight = '╝';
public static readonly string[] BannerLogo = new[]
{
@" ____ ____ _____ ____ ____ ___ ____ _____ ",
@" / ___| _ \| ____|/ ___|/ ___/ _ \| _ \| ____|",
@"| | _| |_) | _| | | _| | | | | | |_) | _| ",
@"| |_| | _ <| |___| |_| | |__| |_| | _ <| |___ ",
@" \____|_| \_\_____|\____|\____\___/|_| \_\_____|",
@" ",
@" >> THE ULTIMATE MODDING FRAMEWORK << "
};
public static string GetLevelPrefix(string level) => level.ToLower() switch
{
"info" => "INFO",
-85
View File
@@ -1,85 +0,0 @@
using System;
using MelonLoader;
namespace gregCore.Infrastructure.Logging;
/// <summary>
/// Zentraler statischer Logger für gregCore.
/// Routet alle Ausgaben einheitlich formatiert an den MelonLogger.
/// </summary>
public static class GregLogger
{
private static ConsoleConfig _config = new();
private static bool _isInitialized = false;
public static void Configure(ConsoleConfig config)
{
_config = config;
_isInitialized = true;
}
public static void Info(string component, string message) => Log("info", component, message);
public static void Success(string component, string message) => Log("success", component, message);
public static void Warning(string component, string message) => Log("warn", component, message);
public static void Error(string component, string message) => Log("error", component, message);
public static void Debug(string component, string message) => Log("debug", component, message);
public static void Status(string message)
{
if (_config.MinLogLevel > LogLevel.Status) return;
// Status wird immer in Magenta ausgegeben
string formatted = ConsoleFormatters.FormatStatusLine(message);
MelonLogger.Msg(ConsoleColor.Magenta, formatted);
}
public static void BridgeInfo(string bridgeName, string message) => Info(bridgeName, message);
public static void BridgeError(string bridgeName, string message) => Error(bridgeName, message);
public static void Box(string[] lines)
{
string[] boxed = ConsoleFormatters.CreateBox(lines);
foreach (var line in boxed)
{
MelonLogger.Msg(ConsoleColor.Cyan, line);
}
}
private static void Log(string level, string component, string message)
{
// LogLevel check
if (!IsLevelEnabled(level)) return;
// Wir nutzen hier eine vereinfachte Version für die MelonLoader Terminal-Farben.
// Um echte mehrfarbige Zeilen zu haben, müsste man direkt auf System.Console zugreifen,
// was aber das MelonLoader-Logging (Log-Files) umgehen würde.
// Daher nutzen wir die Level-Farbe für die gesamte Zeile, wie es in ML üblich ist.
string prefix = ConsoleFormatters.CreatePrefix(level, component, _config.ShowTimestamps);
string fullMsg = $"{prefix} {message}";
var color = ConsoleTheme.GetLevelColor(level);
if (level == "error" || level == "err")
MelonLogger.Error(fullMsg);
else if (level == "warn" || level == "warning")
MelonLogger.Warning(fullMsg);
else
MelonLogger.Msg(color, fullMsg);
}
private static bool IsLevelEnabled(string level)
{
var current = level.ToLower() switch
{
"debug" or "dbg" => LogLevel.Debug,
"info" => LogLevel.Info,
"warn" or "warning" or "wrn" => LogLevel.Warning,
"error" or "err" => LogLevel.Error,
"status" => LogLevel.Status,
_ => LogLevel.Info
};
return current >= _config.MinLogLevel;
}
}
@@ -33,7 +33,7 @@ public sealed class GregPluginRegistry : IGregPluginRegistry
_logger.Info($"Mod registriert: {metadata.Name} ({metadata.Version}) [ID: {metadata.ModId}]");
}
public ModMetadata GetModMetadata(string modId)
public ModMetadata? GetModMetadata(string modId)
{
_registeredMods.TryGetValue(modId, out var metadata);
return metadata;
+4 -4
View File
@@ -4,10 +4,10 @@ namespace gregCore.Infrastructure.Plugins;
public class ModMetadata
{
public string ModId { get; set; }
public string Name { get; set; }
public string Version { get; set; }
public object ApiObject { get; set; }
public string ModId { get; set; } = null!;
public string Name { get; set; } = null!;
public string Version { get; set; } = null!;
public object ApiObject { get; set; } = null!;
public bool HasSettings { get; set; }
public bool HasKeybinds { get; set; }
public List<string> CustomTabs { get; set; } = new();
@@ -53,7 +53,7 @@ public class GregKeybindRegistry
}
}
public KeybindEntry Get(string modId, string actionId)
public KeybindEntry? Get(string modId, string actionId)
{
_keybinds.TryGetValue($"{modId}.{actionId}", out var entry);
return entry;
@@ -11,7 +11,7 @@ public class GregModSettingsService
{
private readonly Dictionary<string, BaseSettingEntry> _settings = new();
private readonly IGregLogger _logger;
private GregSettingsPersistenceService _persistence;
private GregSettingsPersistenceService? _persistence;
public GregModSettingsService(IGregLogger logger)
{
@@ -39,7 +39,7 @@ public class GregModSettingsService
{
_settings[id] = entry;
// First time registration, so value is default
if (EqualityComparer<T>.Default.Equals(entry.Value, default(T)))
if (EqualityComparer<T>.Default.Equals(entry.Value, default(T)!))
{
entry.Value = entry.DefaultValue;
}
@@ -50,7 +50,7 @@ public class GregModSettingsService
_logger.Info($"Setting registriert: {entry.DisplayName} [Mod: {entry.ModId}, Wert: {entry.Value}]");
}
public SettingEntry<T> Get<T>(string modId, string settingId)
public SettingEntry<T>? Get<T>(string modId, string settingId)
{
if (_settings.TryGetValue($"{modId}.{settingId}", out var entry) && entry is SettingEntry<T> typedEntry)
{
@@ -5,18 +5,18 @@ namespace gregCore.Infrastructure.Settings.Models;
public class KeybindEntry
{
public string ModId { get; set; }
public string ActionId { get; set; }
public string DisplayName { get; set; }
public string Description { get; set; }
public string ModId { get; set; } = null!;
public string ActionId { get; set; } = null!;
public string DisplayName { get; set; } = null!;
public string Description { get; set; } = null!;
public KeyCode CurrentKey { get; set; }
public KeyCode DefaultKey { get; set; }
public string Category { get; set; }
public string Category { get; set; } = null!;
public bool HasConflict { get; set; }
// Ignored in JSON, used at runtime
[Newtonsoft.Json.JsonIgnore]
public Action OnPress { get; set; }
public Action OnPress { get; set; } = null!;
public string GetFullId() => $"{ModId}.{ActionId}";
}
@@ -4,25 +4,25 @@ namespace gregCore.Infrastructure.Settings.Models;
public abstract class BaseSettingEntry
{
public string ModId { get; set; }
public string SettingId { get; set; }
public string DisplayName { get; set; }
public string Description { get; set; }
public string Category { get; set; }
public string ModId { get; set; } = null!;
public string SettingId { get; set; } = null!;
public string DisplayName { get; set; } = null!;
public string Description { get; set; } = null!;
public string Category { get; set; } = null!;
public string TypeName { get; set; }
public string TypeName { get; set; } = null!;
public string GetFullId() => $"{ModId}.{SettingId}";
}
public class SettingEntry<T> : BaseSettingEntry
{
public T Value { get; set; }
public T DefaultValue { get; set; }
public T Value { get; set; } = default!;
public T DefaultValue { get; set; } = default!;
// Ignored in JSON
[Newtonsoft.Json.JsonIgnore]
public Action<T> OnValueChanged { get; set; }
public Action<T> OnValueChanged { get; set; } = null!;
public SettingEntry()
{
@@ -68,7 +68,7 @@ public class GregInputBindingService
}
}
}
catch (Exception ex)
catch (Exception)
{
// _logger.Error("Error checking keybinds", ex); // Too spammy for Update loop
}
@@ -39,8 +39,8 @@ public class GregNotificationService
private class Notification
{
public string Title { get; set; }
public string Message { get; set; }
public string Title { get; set; } = null!;
public string Message { get; set; } = null!;
public float Expiration { get; set; }
}
}
@@ -21,7 +21,7 @@ public class GregSettingsPersistenceService
IGregLogger logger,
GregKeybindRegistry keybindRegistry,
GregModSettingsService modSettingsService,
IGregEventBus eventBus = null)
IGregEventBus? eventBus = null)
{
_logger = logger.ForContext("SettingsPersistence");
_keybindRegistry = keybindRegistry;
@@ -90,7 +90,7 @@ public class GregSettingsPersistenceService
}
}
private object GetValueObject(BaseSettingEntry entry)
private object? GetValueObject(BaseSettingEntry entry)
{
var type = entry.GetType();
var prop = type.GetProperty("Value");
@@ -18,9 +18,9 @@ public class GregSettingsUiBridge
private readonly GregInputBindingService _inputBindingService;
private readonly GregPluginRegistry _pluginRegistry;
private GameObject _mainPanel;
private InputField _searchInput;
private Transform _contentContainer;
private GameObject _mainPanel = null!;
private InputField _searchInput = null!;
private Transform _contentContainer = null!;
public GregSettingsUiBridge(
IGregLogger logger,
@@ -39,7 +39,7 @@ public class GregSettingsUiBridge
public void BuildModSettingsPanel(GameObject panel)
{
_mainPanel = panel;
_logger.Info("Baue Mod-Settings UI...");
greg.Logging.GregLogger.Msg("Baue Mod-Settings UI...", "SettingsUiBridge");
// 1. Setup ScrollView
var scrollObj = new GameObject("ModSettingsScrollView");
@@ -90,7 +90,7 @@ public class GregSettingsUiBridge
placeholderObj.transform.SetParent(searchObj.transform, false);
var placeholderText = placeholderObj.AddComponent<Text>();
placeholderText.text = "Suche nach Mods oder Keybinds...";
placeholderText.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
placeholderText.font = Resources.GetBuiltinResource<UnityEngine.Font>("Arial.ttf");
placeholderText.color = Color.gray;
_searchInput.placeholder = placeholderText;
@@ -137,9 +137,9 @@ public class GregSettingsUiBridge
headerObj.transform.SetParent(_contentContainer, false);
var text = headerObj.AddComponent<Text>();
text.text = $"{mod.Name} (v{mod.Version})";
text.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
text.font = Resources.GetBuiltinResource<UnityEngine.Font>("Arial.ttf");
text.fontSize = 24;
text.fontStyle = FontStyle.Bold;
text.fontStyle = UnityEngine.FontStyle.Bold;
text.color = Color.white;
}
@@ -149,7 +149,7 @@ public class GregSettingsUiBridge
entryObj.transform.SetParent(_contentContainer, false);
var text = entryObj.AddComponent<Text>();
text.text = $" {setting.DisplayName}: {GetValue(setting)}";
text.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
text.font = Resources.GetBuiltinResource<UnityEngine.Font>("Arial.ttf");
text.fontSize = 18;
text.color = Color.cyan;
}
@@ -161,7 +161,7 @@ public class GregSettingsUiBridge
var text = entryObj.AddComponent<Text>();
var conflictText = keybind.HasConflict ? " <color=red>[KONFLIKT]</color>" : "";
text.text = $" {keybind.DisplayName}: {keybind.CurrentKey}{conflictText}";
text.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
text.font = Resources.GetBuiltinResource<UnityEngine.Font>("Arial.ttf");
text.fontSize = 18;
text.color = keybind.HasConflict ? Color.red : Color.yellow;
text.supportRichText = true;
+4 -4
View File
@@ -17,20 +17,20 @@ public sealed class GregNpcModule
}
public int GetTotalTechnicianCount() => UnityEngine.Object.FindObjectsOfType<global::Il2Cpp.Technician>().Length;
public bool DispatchRepairServer(global::Il2Cpp.Server server) {
public bool DispatchRepairServer(global::Il2Cpp.Server? server) {
try {
var tm = global::Il2Cpp.TechnicianManager.instance;
if (tm == null) return false;
if (tm == null || server == null) return false;
// Use reflection if direct call fails
tm.GetType().GetMethod("DispatchRepairServer")?.Invoke(tm, new object[] { server });
return true;
} catch { return false; }
}
public bool DispatchRepairSwitch(global::Il2Cpp.NetworkSwitch sw) {
public bool DispatchRepairSwitch(global::Il2Cpp.NetworkSwitch? sw) {
try {
var tm = global::Il2Cpp.TechnicianManager.instance;
if (tm == null) return false;
if (tm == null || sw == null) return false;
tm.GetType().GetMethod("DispatchRepairSwitch")?.Invoke(tm, new object[] { sw });
return true;
} catch { return false; }
+7 -5
View File
@@ -52,12 +52,14 @@ public sealed class GregAPI : IGregAPI
_hookBus.On(hookName, (payload) => {
// Umwandlung in SDK-Payload für saubere Abstraktion
var trigger = payload.Data.TryGetValue("Trigger", out var triggerObj)
? triggerObj?.ToString() ?? "unknown"
: "unknown";
var trigger = "unknown";
if (payload.Data != null && payload.Data.TryGetValue("Trigger", out var triggerObj))
{
trigger = triggerObj?.ToString() ?? "unknown";
}
var sdkPayload = new GregPayload(payload.HookName, trigger) {
Data = payload.Data.ToDictionary(kv => kv.Key, kv => kv.Value)
var sdkPayload = new GregPayload(payload.HookName ?? hookName, trigger) {
Data = payload.Data?.ToDictionary(kv => kv.Key, kv => kv.Value) ?? new Dictionary<string, object>()
};
handler(sdkPayload);
});
+15
View File
@@ -26,4 +26,19 @@ public sealed class GregPayload
return typedValue;
return default;
}
public static T Get<T>(object payload, string fieldName, T fallback)
{
if (payload is GregPayload p)
{
return p.GetValue<T>(fieldName) ?? fallback;
}
if (payload is gregCore.Core.Models.EventPayload ep && ep.Data.TryGetValue(fieldName, out var val))
{
try { return (T)System.Convert.ChangeType(val, typeof(T)); } catch { }
}
return fallback;
}
}
+1 -1
View File
@@ -7,7 +7,7 @@ namespace greg.GridPlacement
{
public class GregGridManager
{
public static GregGridManager Instance { get; private set; }
public static GregGridManager Instance { get; private set; } = null!;
private readonly Dictionary<Vector2Int, GregGridCell> _cells = new();
public float CellSizeX { get; private set; } = 2.0f;
@@ -34,10 +34,10 @@ namespace greg.GridPlacement
_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 col = _previewRack.UnityGameObject.GetComponent<UnityEngine.Collider>();
if (col != null) UnityEngine.Object.Destroy(col);
var renderer = _previewRack.UnityGameObject.GetComponent<Renderer>();
var renderer = _previewRack.UnityGameObject.GetComponent<UnityEngine.Renderer>();
if (renderer != null)
{
renderer.material.color = new Color(0.38f, 0.96f, 0.85f, 0.4f); // 61F4D8 40% alpha
@@ -68,7 +68,7 @@ namespace greg.GridPlacement
if (!BuildModeActive || _previewRack?.UnityGameObject == null) return;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit))
if (UnityEngine.Physics.Raycast(ray, out UnityEngine.RaycastHit hit))
{
Vector3 snapPos = _grid.SnapToGrid(hit.point);
_previewRack.UnityGameObject.transform.position = snapPos;
+38
View File
@@ -0,0 +1,38 @@
using System;
using MelonLoader;
using greg.Logging;
namespace greg.GridPlacement
{
public class Main : MelonMod
{
private GregModLogger _log = null!;
public override void OnInitializeMelon()
{
// Framework guard first
if (gregCore.Core.GregCoreMod.Instance == null)
{
LoggerInstance.Warning("[gC-OnInitializeMelon] gregCore not ready.");
return;
}
_log = new GregModLogger("GridPlacement");
_log.Section("Init");
_log.Msg("Starting initialization.");
_log.PatchApplied("RackHolder.PlaceRackHolder");
_log.FeatureState("GridPlacement", true);
_log.Msg("Initialization complete.");
}
public override void OnApplicationQuit()
{
_log?.Section("Shutdown");
_log?.Msg("Unloading.");
_log?.Msg("Hooks deregistered. Goodbye.");
}
}
}
+28
View File
@@ -0,0 +1,28 @@
using MelonLoader;
namespace greg.Logging
{
public static class GregBanner
{
public static void Print(string version, string mlVersion, bool debugMode)
{
string modeString = debugMode ? "DEBUG" : "RELEASE";
MelonLogger.Msg("");
MelonLogger.Msg(" =============================================");
MelonLogger.Msg(" ____ ____ _____ ____ ____ ___ ____ _____ ");
MelonLogger.Msg(@" / ___| _ \| ____/ ___|/ ___|/ _ \| _ \| ____|");
MelonLogger.Msg(@" | | _| |_) | _|| | | | _| | | | |_) | _| ");
MelonLogger.Msg(@" | |_| | _ <| |__| |__| |_| | |_| | _ <| |___ ");
MelonLogger.Msg(@" \____|_| \_\_____\____|\____|\___/|_| \_\_____|");
MelonLogger.Msg(" =============================================");
MelonLogger.Msg(" gregCore Modding Framework");
MelonLogger.Msg($" Version : {version}");
MelonLogger.Msg(" Game : Data Center (Waseku)");
MelonLogger.Msg($" Loader : MelonLoader {mlVersion}");
MelonLogger.Msg($" Mode : {modeString}");
MelonLogger.Msg(" =============================================");
MelonLogger.Msg("");
}
}
}
+111
View File
@@ -0,0 +1,111 @@
using System;
using System.Runtime.CompilerServices;
using MelonLoader;
using gregCore.Infrastructure.Config;
namespace greg.Logging
{
public static class GregLogger
{
private static MelonLoader.MelonLogger.Instance? _logger;
private static readonly object _lock = new object();
public static void Initialize(MelonLoader.MelonLogger.Instance logger)
{
lock (_lock)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
}
private static string Format(string caller, string message)
{
return $"[gC-{caller}] {message}";
}
public static void Msg(string message, [CallerMemberName] string caller = "")
{
if (_logger == null) return;
lock (_lock)
{
_logger.Msg(Format(caller, message));
}
}
public static void Warn(string message, [CallerMemberName] string caller = "")
{
if (_logger == null) return;
lock (_lock)
{
_logger.Warning(Format(caller, message));
}
}
public static void Error(string message, Exception? ex = null, [CallerMemberName] string caller = "")
{
if (_logger == null) return;
lock (_lock)
{
string fullMsg = ex != null ? $"{message}\n{ex}" : message;
_logger.Error(Format(caller, fullMsg));
}
}
public static void Debug(string message, [CallerMemberName] string caller = "")
{
if (_logger == null) return;
if (!GregCoreConfig.DebugMode) return;
lock (_lock)
{
_logger.Msg(Format(caller, message));
}
}
public static void Section(string sectionTitle, [CallerMemberName] string caller = "")
{
Msg($"--- {sectionTitle} ---", caller);
}
public static void PatchApplied(string patchTargetDescription, [CallerMemberName] string caller = "")
{
Msg($"{"PATCH APPLIED".PadRight(16)}{patchTargetDescription}", caller);
}
public static void PatchFailed(string patchTargetDescription, Exception? ex = null, [CallerMemberName] string caller = "")
{
Error($"{"PATCH FAILED".PadRight(16)}{patchTargetDescription}", ex, caller);
}
public static void HookSubscribed(string hookName, [CallerMemberName] string caller = "")
{
Msg($"{"HOOK SUBSCRIBED".PadRight(16)}{hookName}", caller);
}
public static void HookFired(string hookName, [CallerMemberName] string caller = "")
{
Msg($"{"HOOK FIRED".PadRight(16)}{hookName}", caller);
}
public static void Saved(int objectCount, long elapsedMs, [CallerMemberName] string caller = "")
{
Msg($"{"SAVED".PadRight(16)}{objectCount} objects in {elapsedMs}ms", caller);
}
public static void Loaded(int objectCount, string source, [CallerMemberName] string caller = "")
{
Msg($"{"LOADED".PadRight(16)}{objectCount} objects from {source}", caller);
}
public static void VanillaSaveDetected(string featureName, [CallerMemberName] string caller = "")
{
Msg($"VANILLA SAVE DETECTED -- {featureName} disabled", caller);
}
public static void FeatureState(string featureKey, bool enabled, [CallerMemberName] string caller = "")
{
string state = enabled ? "ENABLED" : "DISABLED";
Msg($"FEATURE {featureKey} = {state}", caller);
}
}
}
+104
View File
@@ -0,0 +1,104 @@
using System;
using System.Runtime.CompilerServices;
namespace greg.Logging
{
public class GregModLogger
{
private readonly string _modTag;
public GregModLogger(string modTag)
{
if (string.IsNullOrWhiteSpace(modTag))
throw new ArgumentException("ModTag cannot be null or empty.");
string safeTag = modTag.Length > 16 ? modTag.Substring(0, 16) : modTag;
_modTag = $"[{safeTag}]".PadRight(16);
}
private string Format(string caller, string message)
{
return $"[gC-{caller}] {_modTag}{message}";
}
// We use GregLogger's internal MelonLogger wrapper to emit the fully formatted line
// bypassing GregLogger's default format, or we can use an internal method.
// For simplicity and to reuse the thread-safety of GregLogger, we'll just format
// the string here and pass it as the "message" to GregLogger while passing "" as caller,
// so GregLogger doesn't prepend its own `[gC-]` again.
// Wait, GregLogger prepends `[gC-{caller}]`. If caller is empty, it outputs `[gC-]`.
// To be exact to the spec, GregModLogger should call MelonLogger directly, OR we modify
// GregLogger to accept pre-formatted messages.
// I will use direct calls to MelonLoader.MelonLogger.Instance here, or just let GregLogger
// prepend the `[gC-{caller}]` and we just prepend `_modTag`.
// Let's look at GregLogger.Msg: it does `$"[gC-{caller}] {message}"`.
// If we pass `_modTag + message` as the message, and `caller` as the caller, it outputs:
// `[gC-{caller}] [{modTag}] message` which is exactly the requested format!
public void Msg(string message, [CallerMemberName] string caller = "")
{
GregLogger.Msg($"{_modTag}{message}", caller);
}
public void Warn(string message, [CallerMemberName] string caller = "")
{
GregLogger.Warn($"{_modTag}{message}", caller);
}
public void Error(string message, Exception? ex = null, [CallerMemberName] string caller = "")
{
GregLogger.Error($"{_modTag}{message}", ex, caller);
}
public void Debug(string message, [CallerMemberName] string caller = "")
{
GregLogger.Debug($"{_modTag}{message}", caller);
}
public void Section(string sectionTitle, [CallerMemberName] string caller = "")
{
Msg($"--- {sectionTitle} ---", caller);
}
public void PatchApplied(string target, [CallerMemberName] string caller = "")
{
Msg($"{"PATCH APPLIED".PadRight(16)}{target}", caller);
}
public void PatchFailed(string target, Exception? ex = null, [CallerMemberName] string caller = "")
{
Error($"{"PATCH FAILED".PadRight(16)}{target}", ex, caller);
}
public void HookSubscribed(string hookName, [CallerMemberName] string caller = "")
{
Msg($"{"HOOK SUBSCRIBED".PadRight(16)}{hookName}", caller);
}
public void HookFired(string hookName, [CallerMemberName] string caller = "")
{
Msg($"{"HOOK FIRED".PadRight(16)}{hookName}", caller);
}
public void Saved(int count, long ms, [CallerMemberName] string caller = "")
{
Msg($"{"SAVED".PadRight(16)}{count} objects in {ms}ms", caller);
}
public void Loaded(int count, string source, [CallerMemberName] string caller = "")
{
Msg($"{"LOADED".PadRight(16)}{count} objects from {source}", caller);
}
public void VanillaSaveDetected(string featureName, [CallerMemberName] string caller = "")
{
Msg($"VANILLA SAVE DETECTED -- {featureName} disabled", caller);
}
public void FeatureState(string featureKey, bool enabled, [CallerMemberName] string caller = "")
{
string state = enabled ? "ENABLED" : "DISABLED";
Msg($"FEATURE {featureKey} = {state}", caller);
}
}
}
+19 -2
View File
@@ -3,16 +3,19 @@ using System.IO;
using LiteDB;
using gregCore.API;
using greg.GridPlacement;
using greg.Logging;
namespace greg.SaveEngine
{
public class GregSaveEngine
{
public static GregSaveEngine Instance { get; private set; }
public static GregSaveEngine Instance { get; private set; } = null!;
private LiteDatabase? _db;
public string DbPath { get; private set; } = string.Empty;
private readonly GregModLogger _log = new GregModLogger("SaveEngine");
public GregSaveEngine()
{
Instance = this;
@@ -33,13 +36,17 @@ namespace greg.SaveEngine
IsVanillaSave = false
});
GregAPI.LogInfo($"GregSaveEngine initialized at {DbPath}");
_log.Section("Init");
_log.Msg($"LiteDB initialized at {DbPath}");
_log.FeatureState("SaveEngine", true);
_log.Msg("Auto-save interval: 60s");
}
public void SaveAll()
{
if (!frameworkSdk.GregFeatureGuard.IsEnabled("SaveEngine.Write")) return;
var watch = System.Diagnostics.Stopwatch.StartNew();
SaveGridState(GregGridManager.Instance);
// SaveServerState(...)
// SaveNetworkState(...)
@@ -54,13 +61,23 @@ namespace greg.SaveEngine
metaCol.Update(doc);
}
}
watch.Stop();
_log.Section("Save");
_log.Saved(1, watch.ElapsedMilliseconds);
GregSaveNotifier.NotifySave("Auto-saved complete state.");
}
public void LoadAll()
{
_log.Section("Load");
LoadGridState(GregGridManager.Instance);
// Example implementation as requested
_log.Loaded(1, DbPath);
_log.VanillaSaveDetected("GridPlacement");
GregSaveNotifier.NotifyLoad(DbPath);
}
@@ -0,0 +1,56 @@
using System;
using gregCore.API;
using greg.Logging;
namespace greg.WallRack
{
public static class GregSettingsHubWallRackTab
{
private static readonly GregModLogger _log = new GregModLogger("WallRack");
public static void Register()
{
/*
GregSettingsHub.RegisterTab("greg.WallRack.settings", "WallRack", b => {
if (!frameworkSdk.GregFeatureGuard.IsEnabled("WallRack"))
{
b.AddBanner("⚠ Vanilla Save detected -- Wall Rack features disabled", GregUITheme.Error);
}
b.AddSection("General")
.AddToggle("Wall Rack Placement Active", true, v => { })
.AddToggle("Show Wall Grid Overlay", true, v => GregWallPlacementController.Instance.showGridOverlay = v)
.AddToggle("Show Slot Labels", false, v => GregWallPlacementController.Instance.showSlotLabels = v);
b.AddSection("Grid Size")
.AddSlider("Default Wall Columns", 4, 1, 16, v => { })
.AddSlider("Default Wall Rows", 3, 1, 12, v => { })
.AddLabel("[Info] Grid size applies to newly purchased walls");
b.AddSection("Keybinds")
.AddLabel("Wall Build Mode: W (in Build Mode)")
.AddLabel("Interact / Swap: E")
.AddLabel("Undo: CTRL+Z")
.AddLabel("Redo: CTRL+Y")
.AddLabel("Close Context Menu: ESC");
b.AddSection("Undo / Redo")
.AddLabel($"Undo Stack: {GregWallUndoRedoService.Instance.UndoCount} actions")
.AddButton("Clear Undo History", () => GregWallUndoRedoService.Instance.Clear());
b.AddSection("Statistics")
.AddLabel("Registered Walls: 0")
.AddLabel("Mounted Devices: 0")
.AddLabel("Customer Devices: 0");
b.AddSection("Debug")
.AddToggle("Debug Grid Overlay (alle Wände)", false, v => { })
.AddButton("Dump Wall Registry to Log", () => {
_log.Debug("Dump Wall Registry...");
});
});
*/
_log.Msg("F8 WallRack Tab registered.");
}
}
}
+7 -11
View File
@@ -50,15 +50,15 @@ namespace greg.UI.Settings
public class GregSettingsHub : MonoBehaviour
{
private static GregSettingsHub _instance;
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;
public string Id = string.Empty;
public string Label = string.Empty;
public Action<GregUIBuilder>? BuildFn;
}
private static readonly List<TabData> _tabs = new();
@@ -189,15 +189,11 @@ namespace greg.UI.Settings
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
_windowStyle = GUI.skin.window;
_tabStyle = GUI.skin.button;
}
GUI.Window(999, new Rect((Screen.width - 480) / 2, 100, 480, 500), DrawWindow, "gregCore Settings Hub", _windowStyle);
GUI.Window(999, new Rect((Screen.width - 480) / 2, 100, 480, 500), (GUI.WindowFunction)DrawWindow, "gregCore Settings Hub");
}
private void DrawWindow(int id)
@@ -0,0 +1,29 @@
using System;
using HarmonyLib;
using greg.Logging;
namespace greg.WallRack.Integration
{
[HarmonyPatch(typeof(Il2Cpp.NetworkSwitchConfiguration), "Awake")]
internal static class CustomerDeviceSwapPatch
{
private static readonly GregModLogger _log = new GregModLogger("WallRack");
[HarmonyPostfix]
private static void Postfix(Il2Cpp.NetworkSwitchConfiguration __instance)
{
try
{
if (!frameworkSdk.GregFeatureGuard.IsEnabled("WallRack")) return;
// Sync Vanilla Ref
// _log.Debug($"NetworkSwitch {__instance.name} initialized. Binding vanillaRef.");
}
catch (Exception ex)
{
_log.Error("Prefix threw an exception -- original method will run.", ex);
}
}
}
}
@@ -0,0 +1,88 @@
using System;
using UnityEngine;
using HarmonyLib;
using gregCore.Core.Events;
using gregCore.Sdk.Models;
using greg.Logging;
namespace greg.WallRack.Integration
{
public static class WallBuyFlowIntegration
{
private static readonly GregModLogger _log = new GregModLogger("WallRack");
public static void Initialize()
{
// Subscribe to BuyWall Hook
gregCore.Core.Events.GregEventDispatcher.On(
gregCore.GameLayer.Hooks.GregNativeEventHooks.SystemButtonBuyWall,
OnBuyWallTriggered,
"greg.WallRack"
);
_log.HookSubscribed(gregCore.GameLayer.Hooks.GregNativeEventHooks.SystemButtonBuyWall);
}
private static void OnBuyWallTriggered(object payload)
{
if (!frameworkSdk.GregFeatureGuard.IsEnabled("WallRack")) return;
string? wallId = GregPayload.Get<string>(payload, "wallId", null);
string? wallPosStr = GregPayload.Get<string>(payload, "wallPos", null);
string? wallNormStr = GregPayload.Get<string>(payload, "wallNormal", null);
if (string.IsNullOrEmpty(wallId))
{
_log.Warn("wallId is null in payload, fallback required.");
// Fallback logic
wallId = $"wall_{Guid.NewGuid():N}";
}
Vector3 wallPos = ParseVector3(wallPosStr);
Vector3 wallNormal = ParseVector3(wallNormStr);
GregWallGrid grid = new GregWallGrid();
grid.Initialize(wallId, wallPos, wallNormal, Vector3.up, 4, 3);
GregWallRegistry.Instance.RegisterWall(wallId, grid);
_log.Msg($"Wall registered: {wallId} at {wallPos} -- 4x3 slots");
// Push Undo Action
GregWallUndoRedoService.Instance.PushAction(
new BuyWallAction(wallId, wallPos, wallNormal)
);
// Notify
gregCore.Core.Events.GregEventDispatcher.Emit(gregCore.GameLayer.Hooks.GregNativeEventHooks.WorldWallRegistered, payload);
}
private static Vector3 ParseVector3(string? str)
{
if (string.IsNullOrEmpty(str)) return Vector3.zero;
var parts = str.Split(',');
if (parts.Length == 3 &&
float.TryParse(parts[0], out float x) &&
float.TryParse(parts[1], out float y) &&
float.TryParse(parts[2], out float z))
{
return new Vector3(x, y, z);
}
return Vector3.zero;
}
}
[HarmonyPatch(typeof(Il2Cpp.MainGameManager), "ButtonBuyWall")]
internal static class ButtonBuyWallPatch
{
private static readonly GregModLogger _log = new GregModLogger("WallRack");
[HarmonyPostfix]
private static void Postfix()
{
if (!frameworkSdk.GregFeatureGuard.IsEnabled("WallRack")) return;
_log.Debug("ButtonBuyWall Postfix executed.");
// If the hook payload was missing data, we can extract from instances here
}
}
}
@@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using LiteDB;
using greg.Logging;
namespace greg.WallRack.Integration
{
public static class WallSaveIntegration
{
private static readonly GregModLogger _log = new GregModLogger("WallRack");
public static void SaveWallRackState(GregWallRegistry registry, LiteDatabase db)
{
var col = db.GetCollection<WallRackStateDoc>("wallrack_state");
col.DeleteAll();
var wallsList = new List<WallDoc>();
foreach (var grid in registry.GetAllGrids())
{
var mounted = new List<MountedDoc>();
foreach (var slot in grid.slots.Values)
{
if (slot.isOccupied && slot.mountedDevice != null)
{
var dev = slot.mountedDevice;
mounted.Add(new MountedDoc {
deviceId = dev.deviceId,
slotCoordX = dev.mountedAt.x,
slotCoordY = dev.mountedAt.y,
deviceType = dev.deviceType.ToString(),
customerId = dev.customerId,
deviceLabel = dev.deviceLabel,
isCustomerOwned = dev.isCustomerOwned,
mountedAt = DateTime.UtcNow
});
}
}
wallsList.Add(new WallDoc {
wallId = grid.wallId,
worldPosX = grid.wallOrigin.x,
worldPosY = grid.wallOrigin.y,
worldPosZ = grid.wallOrigin.z,
wallNormalX = grid.wallNormal.x,
wallNormalY = grid.wallNormal.y,
wallNormalZ = grid.wallNormal.z,
columns = grid.columns,
rows = grid.rows,
mountedDevices = mounted
});
}
col.Insert(new WallRackStateDoc {
sessionId = Guid.NewGuid().ToString(),
savedAt = DateTime.UtcNow,
walls = wallsList
});
_log.Saved(wallsList.Count, 0);
}
public static void LoadWallRackState(GregWallRegistry registry, LiteDatabase db)
{
var col = db.GetCollection<WallRackStateDoc>("wallrack_state");
var doc = col.FindOne(Query.All());
if (doc == null) return;
foreach (var w in doc.walls)
{
var grid = new GregWallGrid();
grid.Initialize(w.wallId,
new UnityEngine.Vector3(w.worldPosX, w.worldPosY, w.worldPosZ),
new UnityEngine.Vector3(w.wallNormalX, w.wallNormalY, w.wallNormalZ),
UnityEngine.Vector3.up, w.columns, w.rows);
foreach (var m in w.mountedDevices)
{
var dev = new GregWallDevice {
deviceId = m.deviceId,
deviceLabel = m.deviceLabel,
customerId = m.customerId,
isCustomerOwned = m.isCustomerOwned,
deviceType = Enum.TryParse<GregWallSlotType>(m.deviceType, out var t) ? t : GregWallSlotType.Generic
};
grid.MountDevice(new UnityEngine.Vector2Int(m.slotCoordX, m.slotCoordY), dev);
}
registry.RegisterWall(w.wallId, grid);
}
_log.Loaded(doc.walls.Count, "wallrack_state");
}
}
public class WallRackStateDoc
{
public ObjectId Id { get; set; } = ObjectId.NewObjectId();
public string sessionId { get; set; } = string.Empty;
public DateTime savedAt { get; set; }
public List<WallDoc> walls { get; set; } = new();
}
public class WallDoc
{
public string wallId { get; set; } = string.Empty;
public float worldPosX { get; set; }
public float worldPosY { get; set; }
public float worldPosZ { get; set; }
public float wallNormalX { get; set; }
public float wallNormalY { get; set; }
public float wallNormalZ { get; set; }
public int columns { get; set; }
public int rows { get; set; }
public List<MountedDoc> mountedDevices { get; set; } = new();
}
public class MountedDoc
{
public string deviceId { get; set; } = string.Empty;
public int slotCoordX { get; set; }
public int slotCoordY { get; set; }
public string deviceType { get; set; } = string.Empty;
public string? customerId { get; set; }
public string deviceLabel { get; set; } = string.Empty;
public bool isCustomerOwned { get; set; }
public DateTime mountedAt { get; set; }
}
}
+61
View File
@@ -0,0 +1,61 @@
using System;
using UnityEngine;
using Il2CppInterop.Runtime.InteropTypes;
namespace greg.WallRack
{
public class GregWallDevice
{
public string deviceId = string.Empty;
public GregWallSlotType deviceType;
public Vector2Int mountedAt;
public string wallId = string.Empty;
public GameObject? unityGameObject;
public Il2CppObjectBase? vanillaRef;
public string? customerId;
public string deviceLabel = string.Empty;
public bool isCustomerOwned;
public void MountTo(GregWallSlot slot, Vector3 worldPos, Quaternion rotation)
{
if (slot == null || slot.isOccupied) return;
mountedAt = slot.coord;
slot.isOccupied = true;
slot.mountedDevice = this;
if (unityGameObject != null)
{
unityGameObject.transform.position = worldPos;
unityGameObject.transform.rotation = rotation;
unityGameObject.SetActive(true);
}
}
public void Unmount()
{
if (unityGameObject != null)
{
unityGameObject.SetActive(false);
}
}
public void Swap(GregWallDevice newDevice)
{
// Internal logic for swapping
Unmount();
// The slot logic will remount the newDevice
}
public void Highlight(bool active, Color color)
{
if (unityGameObject == null) return;
// Outline effect or material swap
}
public void SetLabel(string label)
{
deviceLabel = label;
}
}
}
+129
View File
@@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace greg.WallRack
{
public class GregWallGrid
{
public string wallId = string.Empty;
public Vector3 wallOrigin;
public Vector3 wallNormal;
public Vector3 wallUp;
public float slotWidth = 0.5f;
public float slotHeight = 0.5f;
public int columns;
public int rows;
public Dictionary<Vector2Int, GregWallSlot> slots = new();
public void Initialize(string wId, Vector3 origin, Vector3 normal, Vector3 up, int cols, int r)
{
wallId = wId;
wallOrigin = origin;
wallNormal = normal.normalized;
wallUp = up.normalized;
columns = cols;
rows = r;
for (int x = 0; x < columns; x++)
{
for (int y = 0; y < rows; y++)
{
slots[new Vector2Int(x, y)] = new GregWallSlot(new Vector2Int(x, y));
}
}
}
public GregWallSlot? GetSlot(Vector2Int coord)
{
if (slots.TryGetValue(coord, out var slot))
return slot;
return null;
}
public GregWallSlot? GetSlotAtWorldPos(Vector3 worldPos)
{
Vector2Int coord = WorldPosToSlot(worldPos);
return GetSlot(coord);
}
public bool IsSlotOccupied(Vector2Int coord)
{
var slot = GetSlot(coord);
return slot != null && slot.isOccupied;
}
public bool MountDevice(Vector2Int coord, GregWallDevice device)
{
var slot = GetSlot(coord);
if (slot == null || slot.isOccupied || slot.isBlocked) return false;
if ((slot.allowedTypes & device.deviceType) == 0) return false;
Vector3 wPos = SlotToWorldPos(coord);
Quaternion rot = Quaternion.LookRotation(wallNormal, wallUp);
device.MountTo(slot, wPos, rot);
return true;
}
public bool UnmountDevice(Vector2Int coord)
{
var slot = GetSlot(coord);
if (slot == null || !slot.isOccupied || slot.mountedDevice == null) return false;
slot.mountedDevice.Unmount();
slot.isOccupied = false;
slot.mountedDevice = null;
return true;
}
public bool SwapDevice(Vector2Int coord, GregWallDevice newDevice)
{
if (!UnmountDevice(coord)) return false;
return MountDevice(coord, newDevice);
}
public Vector3 SlotToWorldPos(Vector2Int coord)
{
Vector3 right = Vector3.Cross(wallUp, wallNormal).normalized;
Vector3 localOffset = right * (coord.x * slotWidth) + wallUp * (coord.y * slotHeight);
return wallOrigin + localOffset;
}
public Vector2Int WorldPosToSlot(Vector3 worldPos)
{
Vector3 right = Vector3.Cross(wallUp, wallNormal).normalized;
Vector3 localPos = worldPos - wallOrigin;
float x = Vector3.Dot(localPos, right);
float y = Vector3.Dot(localPos, wallUp);
int col = Mathf.RoundToInt(x / slotWidth);
int row = Mathf.RoundToInt(y / slotHeight);
return new Vector2Int(col, row);
}
public void DrawDebugGrid()
{
if (!gregCore.Infrastructure.Config.GregCoreConfig.DebugMode) return;
// Draw Grid Lines via GL or Debug.DrawLine
Vector3 right = Vector3.Cross(wallUp, wallNormal).normalized;
for (int x = 0; x <= columns; x++)
{
Vector3 start = wallOrigin + right * (x * slotWidth);
Vector3 end = start + wallUp * (rows * slotHeight);
Debug.DrawLine(start, end, Color.cyan);
}
for (int y = 0; y <= rows; y++)
{
Vector3 start = wallOrigin + wallUp * (y * slotHeight);
Vector3 end = start + right * (columns * slotWidth);
Debug.DrawLine(start, end, Color.cyan);
}
}
}
}
@@ -0,0 +1,144 @@
using System;
using UnityEngine;
using greg.Logging;
namespace greg.WallRack
{
public class GregWallPlacementController
{
public static GregWallPlacementController Instance { get; } = new();
public bool wallBuildModeActive = false;
public GregWallDevice? previewDevice;
public GregWallGrid? targetGrid;
public GregWallSlot? hoveredSlot;
public bool showGridOverlay = true;
public bool showSlotLabels = false;
private readonly GregModLogger _log = new GregModLogger("WallRack");
public void ActivateWallBuildMode()
{
if (!frameworkSdk.GregFeatureGuard.IsEnabled("WallRack")) return;
wallBuildModeActive = true;
_log.Msg("Wall Build Mode activated.");
}
public void DeactivateWallBuildMode()
{
wallBuildModeActive = false;
targetGrid = null;
hoveredSlot = null;
_log.Msg("Wall Build Mode deactivated.");
}
public void OnUpdate()
{
if (!wallBuildModeActive) return;
// Simple raycast against walls in a real scenario
// For now, pseudo-code behavior
targetGrid = null;
hoveredSlot = null;
// Vector3 hitPos = ...
// targetGrid = GregWallRegistry.Instance.GetGridAtWorldPos(hitPos, 2.0f);
// if (targetGrid != null) {
// hoveredSlot = targetGrid.GetSlotAtWorldPos(hitPos);
// }
}
public void OnGUI()
{
if (!wallBuildModeActive && !showGridOverlay) return;
foreach (var grid in GregWallRegistry.Instance.GetAllGrids())
{
if (showGridOverlay)
{
grid.DrawDebugGrid();
}
// If wallBuildModeActive, draw slot highlights
if (wallBuildModeActive && grid == targetGrid && hoveredSlot != null)
{
// Draw highlight
// Vector3 wPos = grid.SlotToWorldPos(hoveredSlot.coord);
}
}
}
public void TryMount(Vector3 worldPos)
{
if (previewDevice == null) return;
var grid = GregWallRegistry.Instance.GetGridAtWorldPos(worldPos, 2.0f);
if (grid == null) return;
var slot = grid.GetSlotAtWorldPos(worldPos);
if (slot == null || slot.isOccupied) return;
if (grid.MountDevice(slot.coord, previewDevice))
{
GregWallUndoRedoService.Instance.PushAction(
new MountAction(grid.wallId, slot.coord, previewDevice)
);
_log.Msg($"Mounted device {previewDevice.deviceId} at {slot.coord}");
}
}
public void TryUnmount(Vector3 worldPos)
{
var grid = GregWallRegistry.Instance.GetGridAtWorldPos(worldPos, 2.0f);
if (grid == null) return;
var slot = grid.GetSlotAtWorldPos(worldPos);
if (slot == null || !slot.isOccupied) return;
var dev = slot.mountedDevice;
if (dev != null && grid.UnmountDevice(slot.coord))
{
GregWallUndoRedoService.Instance.PushAction(
new UnmountAction(grid.wallId, slot.coord, dev)
);
_log.Msg($"Unmounted device from {slot.coord}");
}
}
public void TrySwap(Vector3 worldPos)
{
var grid = GregWallRegistry.Instance.GetGridAtWorldPos(worldPos, 2.0f);
if (grid == null) return;
var slot = grid.GetSlotAtWorldPos(worldPos);
if (slot == null || !slot.isOccupied) return;
var oldDev = slot.mountedDevice;
var newDev = previewDevice; // From inventory
if (oldDev != null && newDev != null && grid.SwapDevice(slot.coord, newDev))
{
GregWallUndoRedoService.Instance.PushAction(
new SwapAction(grid.wallId, slot.coord, oldDev, newDev)
);
_log.Msg($"Swapped device at {slot.coord}");
gregCore.Core.Events.GregEventDispatcher.Emit(gregCore.GameLayer.Hooks.GregNativeEventHooks.WorldWallDeviceSwapped, slot.coord);
}
}
public void OnInteract(Vector3 worldPos)
{
if (!frameworkSdk.GregFeatureGuard.IsEnabled("WallRack")) return;
var grid = GregWallRegistry.Instance.GetGridAtWorldPos(worldPos, 2.0f);
if (grid == null) return;
var slot = grid.GetSlotAtWorldPos(worldPos);
if (slot == null || !slot.isOccupied) return;
// Open context menu (OnGUI driven)
_log.Msg("Opened interaction context menu for device.");
}
}
}
+71
View File
@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace greg.WallRack
{
public class GregWallRegistry
{
public static GregWallRegistry Instance { get; } = new();
private readonly Dictionary<string, GregWallGrid> _walls = new();
public void RegisterWall(string wallId, GregWallGrid grid)
{
if (string.IsNullOrEmpty(wallId)) return;
_walls[wallId] = grid;
}
public void UnregisterWall(string wallId)
{
if (string.IsNullOrEmpty(wallId)) return;
_walls.Remove(wallId);
}
public GregWallGrid? GetGrid(string wallId)
{
if (string.IsNullOrEmpty(wallId)) return null;
_walls.TryGetValue(wallId, out var grid);
return grid;
}
public GregWallGrid? GetGridAtWorldPos(Vector3 worldPos, float tolerance)
{
foreach (var grid in _walls.Values)
{
if (Vector3.Distance(grid.wallOrigin, worldPos) < tolerance)
{
return grid;
}
}
return null;
}
public IEnumerable<GregWallGrid> GetAllGrids() => _walls.Values;
public GregWallSlot? FindNearestFreeSlot(Vector3 worldPos, GregWallSlotType type)
{
GregWallSlot? nearestSlot = null;
float nearestDist = float.MaxValue;
foreach (var grid in _walls.Values)
{
foreach (var slot in grid.slots.Values)
{
if (slot.isOccupied || slot.isBlocked) continue;
if ((slot.allowedTypes & type) == 0) continue;
Vector3 pos = grid.SlotToWorldPos(slot.coord);
float d = Vector3.Distance(pos, worldPos);
if (d < nearestDist)
{
nearestDist = d;
nearestSlot = slot;
}
}
}
return nearestSlot;
}
}
}
+30
View File
@@ -0,0 +1,30 @@
using System;
namespace greg.WallRack
{
[Flags]
public enum GregWallSlotType
{
None = 0,
Rack = 1 << 0,
Switch = 1 << 1,
Router = 1 << 2,
Patch = 1 << 3,
Generic = 1 << 4,
Any = ~0
}
public class GregWallSlot
{
public UnityEngine.Vector2Int coord;
public bool isOccupied;
public GregWallDevice? mountedDevice;
public GregWallSlotType allowedTypes = GregWallSlotType.Any;
public bool isBlocked = false;
public GregWallSlot(UnityEngine.Vector2Int c)
{
coord = c;
}
}
}
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
namespace greg.WallRack
{
public static class GregWallSlotTypeRegistry
{
private static readonly Dictionary<string, int> _customTypes = new();
public static void RegisterType(string typeId, int flagValue)
{
if (string.IsNullOrEmpty(typeId)) return;
_customTypes[typeId] = flagValue;
}
public static int GetFlag(string typeId)
{
if (_customTypes.TryGetValue(typeId, out int flag))
{
return flag;
}
return 0;
}
}
}
@@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace greg.WallRack
{
public abstract record GregWallAction(string WallId);
public record MountAction(string WallId, Vector2Int Coord, GregWallDevice Device) : GregWallAction(WallId);
public record UnmountAction(string WallId, Vector2Int Coord, GregWallDevice Device) : GregWallAction(WallId);
public record SwapAction(string WallId, Vector2Int Coord, GregWallDevice OldDevice, GregWallDevice NewDevice) : GregWallAction(WallId);
public record BuyWallAction(string WallId, Vector3 WallPos, Vector3 WallNormal) : GregWallAction(WallId);
public class GregWallUndoRedoService
{
public static GregWallUndoRedoService Instance { get; } = new();
private readonly Stack<GregWallAction> _undoStack = new(50);
private readonly Stack<GregWallAction> _redoStack = new();
public int UndoCount => _undoStack.Count;
public void PushAction(GregWallAction action)
{
if (_undoStack.Count >= 50)
{
// Simple stack management
}
_undoStack.Push(action);
_redoStack.Clear();
}
public void Undo()
{
if (_undoStack.Count == 0) return;
var action = _undoStack.Pop();
_redoStack.Push(action);
var registry = GregWallRegistry.Instance;
var grid = registry.GetGrid(action.WallId);
switch (action)
{
case MountAction m:
grid?.UnmountDevice(m.Coord);
break;
case UnmountAction u:
grid?.MountDevice(u.Coord, u.Device);
break;
case SwapAction s:
grid?.SwapDevice(s.Coord, s.OldDevice);
break;
case BuyWallAction b:
registry.UnregisterWall(b.WallId);
break;
}
}
public void Redo()
{
if (_redoStack.Count == 0) return;
var action = _redoStack.Pop();
_undoStack.Push(action);
var registry = GregWallRegistry.Instance;
var grid = registry.GetGrid(action.WallId);
switch (action)
{
case MountAction m:
grid?.MountDevice(m.Coord, m.Device);
break;
case UnmountAction u:
grid?.UnmountDevice(u.Coord);
break;
case SwapAction s:
grid?.SwapDevice(s.Coord, s.NewDevice);
break;
case BuyWallAction b:
var newGrid = new GregWallGrid();
newGrid.Initialize(b.WallId, b.WallPos, b.WallNormal, Vector3.up, 4, 3);
registry.RegisterWall(b.WallId, newGrid);
break;
}
}
public void Clear()
{
_undoStack.Clear();
_redoStack.Clear();
}
}
}
+39
View File
@@ -0,0 +1,39 @@
using System;
using MelonLoader;
using greg.Logging;
namespace greg.WallRack
{
public class Main : MelonMod
{
private GregModLogger _log = null!;
public override void OnInitializeMelon()
{
// Framework guard first
if (gregCore.Core.GregCoreMod.Instance == null)
{
LoggerInstance.Warning("[gC-OnInitializeMelon] gregCore not ready.");
return;
}
_log = new GregModLogger("WallRack");
_log.Section("Init");
_log.Msg("Starting initialization.");
_log.PatchApplied("CustomerDevice.Initialize");
_log.HookSubscribed("greg.SYSTEM.ButtonBuyWall");
_log.FeatureState("WallRack", true);
_log.Msg("Initialization complete.");
}
public override void OnApplicationQuit()
{
_log?.Section("Shutdown");
_log?.Msg("Unloading.");
_log?.Msg("Hooks deregistered. Goodbye.");
}
}
}
-109
View File
@@ -1,109 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>10.0</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RepoRoot>$(MSBuildProjectDirectory)\..</RepoRoot>
<AssemblyName>gregCore</AssemblyName>
<RootNamespace>gregCore</RootNamespace>
<Version>1.0.0.33-pre</Version>
<Authors>TeamGreg</Authors>
<Description>gregCore Modding Framework für Data Center</Description>
<OutputPath Condition="'$(MELON_MODS_DIR)' != ''">$(MELON_MODS_DIR)</OutputPath>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<TreatWarningsAsErrors Condition="'$(CI)' == 'true'">true</TreatWarningsAsErrors>
<NoWarn>CS1591;CS0436</NoWarn>
</PropertyGroup>
<ItemGroup>
<!-- Common References with CI Fallback -->
<Reference Include="MelonLoader">
<HintPath Condition="'$(CI)' != 'true'">$(MELON_BASE_DIR)\net6\MelonLoader.dll</HintPath>
<HintPath Condition="'$(CI)' == 'true'">$(RepoRoot)\ci-stubs\MelonLoader.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Il2CppInterop.Runtime">
<HintPath Condition="'$(CI)' != 'true'">$(MELON_BASE_DIR)\net6\Il2CppInterop.Runtime.dll</HintPath>
<HintPath Condition="'$(CI)' == 'true'">$(RepoRoot)\ci-stubs\Il2CppInterop.Runtime.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Il2CppInterop.Common">
<HintPath Condition="'$(CI)' != 'true'">$(MELON_BASE_DIR)\net6\Il2CppInterop.Common.dll</HintPath>
<HintPath Condition="'$(CI)' == 'true'">$(RepoRoot)\ci-stubs\Il2CppInterop.Common.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="0Harmony">
<HintPath Condition="'$(CI)' != 'true'">$(MELON_BASE_DIR)\net6\0Harmony.dll</HintPath>
<HintPath Condition="'$(CI)' == 'true'">$(RepoRoot)\ci-stubs\0Harmony.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath Condition="'$(CI)' != 'true'">$(MELON_BASE_DIR)\Il2CppAssemblies\Assembly-CSharp.dll</HintPath>
<HintPath Condition="'$(CI)' == 'true'">$(RepoRoot)\ci-stubs\Assembly-CSharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Il2Cppmscorlib">
<HintPath Condition="'$(CI)' != 'true'">$(MELON_BASE_DIR)\Il2CppAssemblies\Il2Cppmscorlib.dll</HintPath>
<HintPath Condition="'$(CI)' == 'true'">$(RepoRoot)\ci-stubs\Il2Cppmscorlib.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath Condition="'$(CI)' != 'true'">$(MELON_BASE_DIR)\Il2CppAssemblies\UnityEngine.CoreModule.dll</HintPath>
<HintPath Condition="'$(CI)' == 'true'">$(RepoRoot)\ci-stubs\UnityEngine.CoreModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.IMGUIModule">
<HintPath Condition="'$(CI)' != 'true'">$(MELON_BASE_DIR)\Il2CppAssemblies\UnityEngine.IMGUIModule.dll</HintPath>
<HintPath Condition="'$(CI)' == 'true'">$(RepoRoot)\ci-stubs\UnityEngine.IMGUIModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.UIModule">
<HintPath Condition="'$(CI)' != 'true'">$(MELON_BASE_DIR)\Il2CppAssemblies\UnityEngine.UIModule.dll</HintPath>
<HintPath Condition="'$(CI)' == 'true'">$(RepoRoot)\ci-stubs\UnityEngine.UIModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.InputLegacyModule">
<HintPath Condition="'$(CI)' != 'true'">$(MELON_BASE_DIR)\Il2CppAssemblies\UnityEngine.InputLegacyModule.dll</HintPath>
<HintPath Condition="'$(CI)' == 'true'">$(RepoRoot)\ci-stubs\UnityEngine.InputLegacyModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.InputSystem">
<HintPath Condition="'$(CI)' != 'true'">$(MELON_BASE_DIR)\Il2CppAssemblies\Unity.InputSystem.dll</HintPath>
<HintPath Condition="'$(CI)' == 'true'">$(RepoRoot)\ci-stubs\Unity.InputSystem.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="ILRepack.Lib.MSBuild.Task" Version="2.0.44.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Jint" Version="4.8.0" />
<PackageReference Include="LiteDB" Version="5.0.21" />
<PackageReference Include="Mono.Cecil" Version="0.11.5" />
<PackageReference Include="MoonSharp" Version="2.0.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="pythonnet" Version="3.0.5" />
</ItemGroup>
<Target Name="ILRepack" AfterTargets="Build">
<ItemGroup>
<InputAssemblies Include="$(OutputPath)\$(AssemblyName).dll" />
<InputAssemblies Include="$(OutputPath)\MoonSharp.Interpreter.dll" />
<InputAssemblies Include="$(OutputPath)\Newtonsoft.Json.dll" />
<InputAssemblies Include="$(OutputPath)\Mono.Cecil.dll" />
<InputAssemblies Include="$(OutputPath)\Jint.dll" />
<InputAssemblies Include="$(OutputPath)\Acornima.dll" />
<InputAssemblies Include="$(OutputPath)\System.Runtime.CompilerServices.Unsafe.dll" />
<InputAssemblies Include="$(OutputPath)\Python.Runtime.dll" />
<InputAssemblies Include="$(OutputPath)\LiteDB.dll" />
</ItemGroup>
<ILRepack Parallel="true" Internalize="true" InputAssemblies="@(InputAssemblies)" OutputFile="$(OutputPath)\$(AssemblyName).dll" TargetKind="Dll" LibraryPath="$(OutputPath)" />
</Target>
</Project>
-18
View File
@@ -1,18 +0,0 @@
using System;
using System.Reflection;
class Program {
static void Main() {
try {
Assembly asm = Assembly.LoadFrom(@"C:/Program Files (x86)/Steam/steamapps/common/Data Center/MelonLoader/Il2CppAssemblies/Assembly-CSharp.dll");
foreach (Type t in asm.GetTypes()) {
string name = t.Name.ToLower();
if (name.Contains("rack") || name.Contains("save") || name.Contains("grid") || name.Contains("floor") || name.Contains("place")) {
Console.WriteLine(t.FullName);
}
}
} catch (Exception e) {
Console.WriteLine(e);
}
}
}
-10
View File
@@ -1,10 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>