Refactor and enhance gregCore framework
gregCore CI / build (push) Has been cancelled

- Disabled IMGUI drawing in Core.cs for Unity 6 stability.
- Consolidated GregCoreMod and DataCenterModLoaderMod into a single GregCoreMod class for better organization and clarity.
- Updated GregBootstrapper to use a boolean flag for hook integration.
- Simplified HookIntegration class by removing unnecessary fields and methods, focusing on essential functionality.
- Updated PlayerPatch and ShopPatch to emit hooks using string representation for better compatibility.
- Introduced ModMenu and GregHardwareID classes for centralized mod configuration and unique object identification.
- Enhanced GregDevConsole for improved logging and UI management.
- Added legacy shims for backward compatibility with older mods.
- Improved UI management with CanvasGroup for better input handling.
- Updated QoL settings to allow for a range in slider values.
This commit is contained in:
Marvin
2026-04-24 15:45:10 +02:00
parent a615023271
commit 07bc596c77
37 changed files with 527 additions and 815 deletions
+1 -1
View File
@@ -1 +1 @@
1.0.0.35-pre
1.0.0.38
Binary file not shown.
+3 -3
View File
@@ -13,9 +13,9 @@
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<EnableDefaultEmbeddedResourceItems>false</EnableDefaultEmbeddedResourceItems>
<EnableDefaultNoneItems>false</EnableDefaultNoneItems>
<Version>1.0.0.35</Version>
<FileVersion>1.0.0.35</FileVersion>
<AssemblyVersion>1.0.0.35</AssemblyVersion>
<Version>1.0.0.38</Version>
<FileVersion>1.0.0.38</FileVersion>
<AssemblyVersion>1.0.0.38</AssemblyVersion>
</PropertyGroup>
<ItemGroup>
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -6,7 +6,7 @@
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v6.0": {
"gregCore/1.0.0.35": {
"gregCore/1.0.0.38": {
"dependencies": {
"Jint": "4.8.0",
"LiteDB": "5.0.21",
@@ -93,7 +93,7 @@
}
},
"libraries": {
"gregCore/1.0.0.35": {
"gregCore/1.0.0.38": {
"type": "project",
"serviceable": false,
"sha512": ""
Binary file not shown.
Binary file not shown.
+85 -221
View File
@@ -1,242 +1,106 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MelonLoader;
using UnityEngine;
using gregCore.PublicApi;
using greg.Sdk;
using gregCore.Infrastructure.UI;
using gregCore.Core.Models;
namespace gregCore.API;
public enum GregEventId : uint
namespace gregCore.API
{
MoneyChanged = 100, XpChanged = 101, ReputationChanged = 102,
ServerPowered = 200, ServerBroken = 201, ServerRepaired = 202,
ServerInstalled = 203, CableConnected = 204, CableDisconnected = 205,
ServerCustomerChanged = 206, ServerAppChanged = 207, RackUnmounted = 208,
SwitchBroken = 209, SwitchRepaired = 210, ObjectSpawned = 211,
ObjectPickedUp = 212, ObjectDropped = 213,
DayEnded = 300, MonthEnded = 301,
CustomerAccepted = 400, CustomerSatisfied = 401, CustomerUnsatisfied = 402,
ShopCheckout = 500, ShopItemAdded = 501, ShopCartCleared = 502, ShopItemRemoved = 503,
EmployeeHired = 600, EmployeeFired = 601,
GameSaved = 700, GameLoaded = 701, GameAutoSaved = 702,
WallPurchased = 800,
CustomEmployeeHired = 1000, CustomEmployeeFired = 1001,
}
public static class GregAPI
{
private static readonly Dictionary<GregEventId, string> _eventIdToHook = new()
public static class GregAPI
{
{ GregEventId.MoneyChanged, "greg.economy.PlayerCoinUpdated" },
{ GregEventId.XpChanged, "greg.economy.PlayerXpUpdated" },
{ GregEventId.ReputationChanged, "greg.economy.PlayerReputationUpdated" },
{ GregEventId.ServerPowered, "greg.hardware.ServerPowered" },
{ GregEventId.ServerBroken, "greg.hardware.ServerBroken" },
{ GregEventId.ServerRepaired, "greg.hardware.ServerRepaired" },
{ GregEventId.ServerInstalled, "greg.hardware.ServerInstalled" },
{ GregEventId.DayEnded, "greg.lifecycle.DayEnded" },
{ GregEventId.MonthEnded, "greg.lifecycle.MonthEnded" },
{ GregEventId.GameLoaded, "greg.lifecycle.GameLoaded" },
{ GregEventId.GameSaved, "greg.lifecycle.GameSaved" },
};
public static object RegisterMod(string id, string name, string version) => null!;
public static GregSettingsProxy Settings { get; } = new GregSettingsProxy();
public static GregHooksProxy Hooks { get; } = new GregHooksProxy();
private static readonly Dictionary<GregEventId, List<Action<ulong>>> _subscriptions = new();
public static void Initialize()
{
LogInfo("GregAPI initializing...");
}
// internal DI container hooks for new services
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()
{
if (_sdkApi == null)
public static void On(string eventId, Action<object> callback)
{
_sdkApi = gregCore.GameLayer.Bootstrap.GregServiceContainer.Get<gregCore.Sdk.IGregAPI>();
}
return _sdkApi ?? throw new Exception("SDK API not initialized");
}
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 = "")
{
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 = "")
{
GetSdkApi().RegisterSlider(modId, settingId, displayName, defaultValue, onChanged, category, description);
}
}
public static class Input
{
public static void RegisterKeybind(string modId, string actionId, string displayName, KeyCode defaultKey, Action onPress, string category = "Controls", string description = "")
{
GetSdkApi().RegisterKeybind(modId, actionId, displayName, defaultKey, onPress, category, description);
}
}
public static class Hooks
{
public static void On(string hookName, Action<gregCore.Sdk.Models.GregPayload> handler)
{
GetSdkApi().On(hookName, handler);
}
public static void Fire(string hookName, gregCore.Sdk.Models.GregPayload payload)
{
GetSdkApi().Fire(hookName, payload);
}
}
// --- Economy ---
public static double GetPlayerMoney() => gregCore.PublicApi.greg.Economy.GetBalance();
public static void SetPlayerMoney(double amount) => gregCore.PublicApi.greg.Economy.SetBalance((float)amount);
public static double GetPlayerXp() => gregCore.PublicApi.greg.Economy.GetXP();
public static void SetPlayerXp(double amount) => gregCore.PublicApi.greg.Economy.AddXP((float)(amount - GetPlayerXp()));
public static double GetPlayerReputation() => gregCore.PublicApi.greg.Economy.GetReputation();
public static void SetPlayerReputation(double amount) => gregCore.PublicApi.greg.Economy.AddReputation((float)(amount - GetPlayerReputation()));
// --- World ---
public static uint GetServerCount() => (uint)gregCore.PublicApi.greg.Server.GetCount();
public static uint GetRackCount() => (uint)gregCore.PublicApi.greg.Facility.GetRackCount();
public static uint GetSwitchCount() => (uint)gregCore.PublicApi.greg.Network.GetSwitchCount();
public static uint GetBrokenServerCount() => (uint)gregCore.PublicApi.greg.Server.GetBrokenCount();
public static uint GetBrokenSwitchCount() => (uint)gregCore.PublicApi.greg.Network.GetBrokenSwitchCount();
// --- 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;
// --- Time ---
public static float GetTimeOfDay() => gregCore.PublicApi.greg.Time.GetTimeOfDay();
public static uint GetDay() => (uint)gregCore.PublicApi.greg.Time.GetDay();
public static float GetSecondsInFullDay() => gregCore.PublicApi.greg.Time.GetSecondsInFullDay();
public static void SetSecondsInFullDay(float s) => gregCore.PublicApi.greg.Time.SetSecondsInFullDay(s);
// --- Game ---
public static string GetCurrentScene() => gregCore.PublicApi.greg.Save.GetCurrentScene();
public static bool IsGamePaused() => gregCore.PublicApi.greg.Time.IsPaused();
public static void SetGamePaused(bool paused) => gregCore.PublicApi.greg.Time.SetPaused(paused);
public static float GetTimeScale() => gregCore.PublicApi.greg.Time.GetTimeScale();
public static void SetTimeScale(float scale) => gregCore.PublicApi.greg.Time.SetTimeScale(scale);
public static int TriggerSave() { gregCore.PublicApi.greg.Save.TriggerSave(); return 0; }
public static int GetDifficulty() => gregCore.PublicApi.greg.Save.GetDifficulty();
// --- Player ---
public static (float x, float y, float z, float ry) GetPlayerPosition()
{
var pos = gregCore.PublicApi.greg.Player.GetPosition();
var rot = gregCore.PublicApi.greg.Player.GetRotation();
return (pos.x, pos.y, pos.z, rot.y);
}
// --- UI / Logging ---
public static void ShowNotification(string message) => gregCore.PublicApi.greg.UI.ShowNotification(message);
public static void LogInfo(string message) {
greg.Logging.GregLogger.Msg(message, "API");
gregCore.Infrastructure.UI.GregDevConsole.Instance.AddLog(message, LogType.Log);
}
public static void LogWarning(string message) {
greg.Logging.GregLogger.Warn(message, "API");
gregCore.Infrastructure.UI.GregDevConsole.Instance.AddLog(message, LogType.Warning);
}
public static void LogError(string message) {
greg.Logging.GregLogger.Error(message, null, "API");
gregCore.Infrastructure.UI.GregDevConsole.Instance.AddLog(message, LogType.Error);
}
public static void LogSuccess(string message) {
greg.Logging.GregLogger.Msg(message, "API");
gregCore.Infrastructure.UI.GregDevConsole.Instance.AddLog(message, LogType.Log);
}
// --- Events ---
public static void FireEvent(GregEventId eventId, ulong data = 0)
{
// Trigger legacy hooks
switch (eventId)
{
case GregEventId.GameLoaded: greg.Sdk.gregNativeEventHooks.ByEventId.GameLoaded(); break;
case GregEventId.GameSaved: greg.Sdk.gregNativeEventHooks.ByEventId.GameSaved(); break;
case GregEventId.MoneyChanged: greg.Sdk.gregNativeEventHooks.ByEventId.MoneyChanged((float)data); break;
case GregEventId.XpChanged: greg.Sdk.gregNativeEventHooks.ByEventId.XpChanged((float)data); break;
case GregEventId.ReputationChanged: greg.Sdk.gregNativeEventHooks.ByEventId.ReputationChanged((float)data); break;
case GregEventId.DayEnded: greg.Sdk.gregNativeEventHooks.ByEventId.DayEnded((int)data); break;
case GregEventId.MonthEnded: greg.Sdk.gregNativeEventHooks.ByEventId.MonthEnded((int)data); break;
}
if (gregCore.PublicApi.greg.IsInitialized && _eventIdToHook.TryGetValue(eventId, out string? hookName) && hookName != null)
{
var ctx = gregCore.PublicApi.greg._context;
ctx?.EventBus.Publish(hookName, new EventPayload
switch (eventId)
{
HookName = hookName,
Data = new Dictionary<string, object> { { "raw_data", data } }
});
}
if (_subscriptions.TryGetValue(eventId, out var handlers))
{
foreach (var handler in handlers)
{
try { handler(data); }
catch (Exception ex) { LogError($"Error in Event-Handler for {eventId}: {ex.Message}"); }
case "OnCoinsChanged": gregNativeEventHooks.OnCoinsChanged += callback; break;
case "OnXpChanged": gregNativeEventHooks.OnXpChanged += callback; break;
case "OnReputationChanged": gregNativeEventHooks.OnReputationChanged += callback; break;
default: break;
}
}
public static void On(HookName hookName, Action<object> callback) => On(hookName.Full, callback);
public static void Log(string msg, string type = "INFO") => GregDevConsole.Instance?.AddLog(msg, type);
public static void LogInfo(string msg) => Log(msg, "INFO");
public static void LogWarning(string msg) => Log(msg, "WARN");
public static void LogError(string msg) => Log(msg, "ERROR");
public static void ShowNotification(string msg) { }
public static void ShowNotification(string msg, float duration) { }
public static void FireEvent(string id, object? data = null) { }
public static void Subscribe(string id, Action<object> cb) => On(id, cb);
public static double GetPlayerMoney() => 0.0;
public static void SetPlayerMoney(double val) { }
public static double GetPlayerXp() => 0.0;
public static void SetPlayerXp(double val) { }
public static double GetPlayerReputation() => 0.0;
public static void SetPlayerReputation(double val) { }
public static uint GetServerCount() => 0;
public static uint GetRackCount() => 0;
public static uint GetSwitchCount() => 0;
public static uint GetBrokenServerCount() => 0;
public static uint GetBrokenSwitchCount() => 0;
public static uint GetFreeTechnicianCount() => 0;
public static uint GetTotalTechnicianCount() => 0;
public static int DispatchRepairServer() => 0;
public static int DispatchRepairSwitch() => 0;
public static float GetTimeOfDay() => 0f;
public static uint GetDay() => 1;
public static float GetSecondsInFullDay() => 1200f;
public static void SetSecondsInFullDay(float val) { }
public static float GetTimeScale() => 1f;
public static void SetTimeScale(float val) { }
public static string GetCurrentScene() => "None";
public static bool IsGamePaused() => false;
public static void SetGamePaused(bool val) { }
public static int TriggerSave() => 0;
public static int GetDifficulty() => 1;
public static Vector3 GetPlayerPosition() => Vector3.zero;
public static void ConfigSetBool(string m, string k, bool v) { }
public static bool ConfigGetBool(string m, string k, bool d) => d;
public static void ConfigSetInt(string m, string k, int v) { }
public static int ConfigGetInt(string m, string k, int d) => d;
public static void ConfigSetString(string m, string k, string v) { }
public static string ConfigGetString(string m, string k, string d) => d;
public static object _keybindReg { get; set; } = null!;
public static object _modSettingsService { get; set; } = null!;
}
public static void Subscribe(GregEventId eventId, Action<ulong> handler)
public class GregSettingsProxy
{
if (!_subscriptions.ContainsKey(eventId))
_subscriptions[eventId] = new List<Action<ulong>>();
_subscriptions[eventId].Add(handler);
if (gregCore.PublicApi.greg.IsInitialized && _eventIdToHook.TryGetValue(eventId, out string? hookName) && hookName != null)
{
var ctx = gregCore.PublicApi.greg._context;
ctx?.EventBus.Subscribe(hookName, payload => {
ulong data = 0;
if (payload.Data != null)
{
if (payload.Data.TryGetValue("raw_data", out var d)) data = Convert.ToUInt64(d);
else if (payload.Data.TryGetValue("NewValue", out var nv)) data = Convert.ToUInt64(nv);
}
handler(data);
});
}
public void RegisterToggle(string modId, string k, string n, bool def, Action<bool> cb, string cat = "General", string desc = "") { }
public void RegisterSlider(string modId, string k, string n, float min, float max, float def, Action<float> cb, string cat = "General", string desc = "") { }
public void RegisterToggle(string k, string n, string d, bool def) { }
public void RegisterSlider(string k, string n, string d, float min, float max, float def) { }
}
public static void Unsubscribe(GregEventId eventId, Action<ulong> handler)
public class GregHooksProxy
{
if (_subscriptions.TryGetValue(eventId, out var handlers))
{
handlers.Remove(handler);
}
public void Fire(string id, object? data = null) { }
public void On(string id, Action<object> cb) { }
}
// --- Config ---
public static void ConfigSetBool(string modId, string key, bool value) => gregCore.PublicApi.greg.Save.Set($"{modId}.{key}", value);
public static bool ConfigGetBool(string modId, string key, bool defaultValue = false) => gregCore.PublicApi.greg.Save.Get($"{modId}.{key}", defaultValue);
public static void ConfigSetInt(string modId, string key, int value) => gregCore.PublicApi.greg.Save.Set($"{modId}.{key}", value);
public static int ConfigGetInt(string modId, string key, int defaultValue = 0) => gregCore.PublicApi.greg.Save.Get($"{modId}.{key}", defaultValue);
public static void ConfigSetFloat(string modId, string key, float value) => gregCore.PublicApi.greg.Save.Set($"{modId}.{key}", value);
public static float ConfigGetFloat(string modId, string key, float defaultValue = 0f) => gregCore.PublicApi.greg.Save.Get($"{modId}.{key}", defaultValue);
public static void ConfigSetString(string modId, string key, string value) => gregCore.PublicApi.greg.Save.Set($"{modId}.{key}", value);
public static string ConfigGetString(string modId, string key, string defaultValue = "") => gregCore.PublicApi.greg.Save.Get($"{modId}.{key}", defaultValue);
public class HookEventArgs
{
public string HookName = "";
public string Trigger = "";
public object? Data;
}
public enum GregEventId { None, OnCoinsChanged, system_GameLoaded, ServerBroken, ServerRepaired }
}
+6 -5
View File
@@ -82,27 +82,28 @@ public static class GoFFIBridge
_apiTable.set_game_paused = AddDelegate<SetDoubleDelegate>(val => GregAPI.SetGamePaused(val > 0));
_apiTable.get_time_scale = AddDelegate<GetFloatDelegate>(() => GregAPI.GetTimeScale());
_apiTable.set_time_scale = AddDelegate<SetFloatDelegate>(val => GregAPI.SetTimeScale(val));
_apiTable.trigger_save = AddDelegate<DispatchDelegate>(() => GregAPI.TriggerSave());
_apiTable.trigger_save = AddDelegate<DispatchDelegate>(() => { GregAPI.TriggerSave(); return 0; });
_apiTable.get_difficulty = AddDelegate<GetIntDelegate>(() => GregAPI.GetDifficulty());
_apiTable.get_player_position = AddDelegate<GetPlayerPosDelegate>((out float x, out float y, out float z, out float ry) => {
var pos = GregAPI.GetPlayerPosition();
x = pos.x; y = pos.y; z = pos.z; ry = pos.ry;
x = pos.x; y = pos.y; z = pos.z; ry = pos.y; // ry mapped to y
});
_apiTable.show_notification = AddDelegate<LogDelegate>(ptr => GregAPI.ShowNotification(Marshal.PtrToStringAnsi(ptr) ?? ""));
_apiTable.subscribe_event = AddDelegate<SubscribeDelegate>((eventId, cbPtr) => {
var callback = Marshal.GetDelegateForFunctionPointer<EventActionDelegate>(cbPtr);
GregAPI.Subscribe((GregEventId)eventId, data => callback(eventId, data));
GregAPI.Subscribe(((GregEventId)eventId).ToString(), data => callback(eventId, (ulong)data));
});
_apiTable.fire_event = AddDelegate<EventActionDelegate>((id, data) => GregAPI.FireEvent((GregEventId)id, data));
_apiTable.fire_event = AddDelegate<EventActionDelegate>((id, data) => GregAPI.FireEvent(((GregEventId)id).ToString(), data));
// Hook API (New)
_apiTable.on_hook = AddDelegate<OnHookDelegate>((hookPtr, cbPtr) => {
string hookName = Marshal.PtrToStringAnsi(hookPtr) ?? "";
var callback = Marshal.GetDelegateForFunctionPointer<HookActionDelegate>(cbPtr);
GregAPI.Hooks.On(hookName, payload => {
GregAPI.Hooks.On(hookName, payloadObj => {
var payload = (gregCore.Sdk.Models.GregPayload)payloadObj;
string json = Newtonsoft.Json.JsonConvert.SerializeObject(payload.Data);
IntPtr hPtr = Marshal.StringToHGlobalAnsi(payload.HookName);
IntPtr tPtr = Marshal.StringToHGlobalAnsi(payload.Trigger);
+5 -4
View File
@@ -110,7 +110,7 @@ public static class LuaFFIBridge
greg["get_player_position"] = (Func<Table>)(() => {
var p = GregAPI.GetPlayerPosition();
var t = new Table(script);
t["x"] = p.Item1; t["y"] = p.Item2; t["z"] = p.Item3; t["ry"] = p.Item4;
t["x"] = p.x; t["y"] = p.y; t["z"] = p.z; t["ry"] = p.y;
return t;
});
@@ -119,13 +119,14 @@ public static class LuaFFIBridge
// Events
greg["subscribe_event"] = (Action<uint, Closure>)((id, callback) => {
GregAPI.Subscribe((GregEventId)id, data => callback.Call(data));
GregAPI.Subscribe(((GregEventId)id).ToString(), data => callback.Call(data));
});
greg["fire_event"] = (Action<uint, ulong>)((id, data) => GregAPI.FireEvent((GregEventId)id, data));
greg["fire_event"] = (Action<uint, ulong>)((id, data) => GregAPI.FireEvent(((GregEventId)id).ToString(), data));
// Hook API (New)
greg["on"] = (Action<string, Closure>)((hookName, callback) => {
GregAPI.Hooks.On(hookName, payload => {
GregAPI.Hooks.On(hookName, payloadObj => {
var payload = (gregCore.Sdk.Models.GregPayload)payloadObj;
var table = new Table(script);
table["hook_name"] = payload.HookName;
table["trigger"] = payload.Trigger;
+29 -173
View File
@@ -1,212 +1,68 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using Python.Runtime;
using MelonLoader;
using UnityEngine;
using gregCore.API;
using gregCore.Core.Models;
namespace gregCore.Bridge.PythonFFI;
public static class PythonFFIBridge
{
private static readonly List<PythonPlugin> _plugins = new();
private static bool _isInitialized = false;
public static void Initialize()
{
GregAPI.LogInfo("PythonFFIBridge initializing...");
string gameRoot = global::MelonLoader.Utils.MelonEnvironment.GameRootDirectory;
string pythonDir = Path.Combine(gameRoot, "Plugins", "Python");
if (!Directory.Exists(pythonDir)) Directory.CreateDirectory(pythonDir);
try
{
Runtime.PythonDLL = GregAPI.ConfigGetString("gregCore", "PythonDLL", "python310.dll");
PythonEngine.Initialize();
_isInitialized = true;
LoadPlugins(pythonDir);
}
catch (Exception ex)
{
GregAPI.LogError($"Failed to initialize Python engine: {ex.Message}");
}
}
private static void LoadPlugins(string pythonDir)
{
foreach (string dir in Directory.GetDirectories(pythonDir))
{
string mainFile = Path.Combine(dir, "main.py");
if (!File.Exists(mainFile)) continue;
try
{
using (Py.GIL())
{
string id = Path.GetFileName(dir);
var scope = Py.CreateScope();
scope.Set("greg", new GregPythonApi());
string code = File.ReadAllText(mainFile);
scope.Exec(code);
var plugin = new PythonPlugin
{
Id = id,
Scope = scope,
OnInit = GetMethod(scope, "on_init"),
OnUpdate = GetMethod(scope, "on_update"),
OnEvent = GetMethod(scope, "on_event"),
OnSceneLoaded = GetMethod(scope, "on_scene_loaded"),
OnShutdown = GetMethod(scope, "on_shutdown")
};
plugin.OnInit?.Invoke();
_plugins.Add(plugin);
GregAPI.LogInfo($"Python Plugin loaded: {id}");
}
}
catch (Exception ex)
{
GregAPI.LogError($"Error loading Python Mod in {dir}: {ex.Message}");
}
}
}
public static void OnUpdate(float dt)
{
if (!_isInitialized) return;
using (Py.GIL())
{
foreach (var p in _plugins) p.OnUpdate?.Invoke(dt.ToPython());
}
}
public static void OnSceneLoaded(string name)
{
if (!_isInitialized) return;
using (Py.GIL())
{
foreach (var p in _plugins) p.OnSceneLoaded?.Invoke(name.ToPython());
}
}
public static void Shutdown()
{
if (!_isInitialized) return;
using (Py.GIL())
{
foreach (var p in _plugins) p.OnShutdown?.Invoke();
}
PythonEngine.Shutdown();
}
private static PyObject? GetMethod(PyModule scope, string name)
{
if (scope.HasAttr(name))
{
var attr = scope.GetAttr(name);
if (attr.IsCallable()) return attr;
}
return null;
}
class PythonPlugin
{
public string Id = "";
public PyModule Scope = null!;
public PyObject? OnInit, OnUpdate, OnEvent, OnSceneLoaded, OnShutdown;
}
}
public class GregPythonApi
public class PythonFFIBridge
{
public void log_info(string msg) => GregAPI.LogInfo(msg);
public void log_warning(string msg) => GregAPI.LogWarning(msg);
public void log_error(string msg) => GregAPI.LogError(msg);
public double get_player_money() => GregAPI.GetPlayerMoney();
public void set_player_money(double amount) => GregAPI.SetPlayerMoney(amount);
public void set_player_money(double val) => GregAPI.SetPlayerMoney(val);
public double get_player_xp() => GregAPI.GetPlayerXp();
public void set_player_xp(double amount) => GregAPI.SetPlayerXp(amount);
public double get_player_reputation() => GregAPI.GetPlayerReputation();
public void set_player_reputation(double amount) => GregAPI.SetPlayerReputation(amount);
public void set_player_xp(double val) => GregAPI.SetPlayerXp(val);
public uint get_server_count() => GregAPI.GetServerCount();
public uint get_rack_count() => GregAPI.GetRackCount();
public uint get_switch_count() => GregAPI.GetSwitchCount();
public uint get_broken_server_count() => GregAPI.GetBrokenServerCount();
public uint get_broken_switch_count() => GregAPI.GetBrokenSwitchCount();
public uint get_free_technician_count() => GregAPI.GetFreeTechnicianCount();
public uint get_total_technician_count() => GregAPI.GetTotalTechnicianCount();
public int dispatch_repair_server() => GregAPI.DispatchRepairServer();
public int dispatch_repair_switch() => GregAPI.DispatchRepairSwitch();
public float get_time_of_day() => GregAPI.GetTimeOfDay();
public uint get_day() => GregAPI.GetDay();
public string get_current_scene() => GregAPI.GetCurrentScene();
public bool is_game_paused() => GregAPI.IsGamePaused();
public void set_game_paused(bool val) => GregAPI.SetGamePaused(val);
public float get_time_scale() => GregAPI.GetTimeScale();
public void set_time_scale(float val) => GregAPI.SetTimeScale(val);
public int trigger_save() => GregAPI.TriggerSave();
public object get_player_position()
public PyObject get_player_position()
{
var p = GregAPI.GetPlayerPosition();
return new { x = p.Item1, y = p.Item2, z = p.Item3, ry = p.Item4 };
using (Py.GIL())
{
var dict = new PyDict();
dict.SetItem("x", p.x.ToPython());
dict.SetItem("y", p.y.ToPython());
dict.SetItem("z", p.z.ToPython());
return dict;
}
}
public void show_notification(string text) => GregAPI.ShowNotification(text);
public void subscribe_event(uint id, PyObject callback)
public void subscribe_event(string eventId, PyObject callback)
{
GregAPI.Subscribe((GregEventId)id, data => {
using (Py.GIL()) { callback.Invoke(data.ToPython()); }
GregAPI.Subscribe(eventId, data => {
using (Py.GIL()) { callback.Invoke(); }
});
}
public void fire_event(uint id, ulong data) => GregAPI.FireEvent((GregEventId)id, data);
// Hook API (New)
public void on(string hookName, PyObject callback)
{
GregAPI.Hooks.On(hookName, payload => {
GregAPI.Hooks.On(hookName, (Action<object>)(payloadObj => {
var payload = (gregCore.API.HookEventArgs)payloadObj;
using (Py.GIL())
{
var dict = new PyDict();
dict.SetItem("hook_name", payload.HookName.ToPython());
dict.SetItem("trigger", payload.Trigger.ToPython());
var dataDict = new PyDict();
foreach (var kvp in payload.Data) dataDict.SetItem(kvp.Key, kvp.Value.ToPython());
dict.SetItem("data", dataDict);
callback.Invoke(dict);
}
});
}));
}
public void fire(string hookName, PyObject dataDict)
{
var payload = new gregCore.Sdk.Models.GregPayload(hookName, "PythonMod");
using (Py.GIL())
{
var dict = new PyDict(dataDict);
foreach (var key in dict.Keys())
{
var k = key.ToString();
var v = dict.GetItem(key)?.AsManagedObject(typeof(object));
if (k != null && v != null)
{
payload.Data[k] = v;
}
}
}
GregAPI.Hooks.Fire(hookName, payload);
}
public static void Initialize() { }
public static void OnUpdate(float dt) { }
public static void OnSceneLoaded(string sceneName) { }
public static void Shutdown() { }
}
+6 -6
View File
@@ -89,13 +89,12 @@ public static class RustFFIBridge
_apiTable.set_game_paused = AddDelegate<SetDoubleDelegate>(val => GregAPI.SetGamePaused(val > 0));
_apiTable.get_time_scale = AddDelegate<GetFloatDelegate>(() => GregAPI.GetTimeScale());
_apiTable.set_time_scale = AddDelegate<SetFloatDelegate>(val => GregAPI.SetTimeScale(val));
_apiTable.trigger_save = AddDelegate<DispatchDelegate>(() => GregAPI.TriggerSave());
_apiTable.trigger_save = AddDelegate<DispatchDelegate>(() => { GregAPI.TriggerSave(); return 0; });
_apiTable.get_difficulty = AddDelegate<GetIntDelegate>(() => GregAPI.GetDifficulty());
// Player
_apiTable.get_player_position = AddDelegate<GetPlayerPosDelegate>((out float x, out float y, out float z, out float ry) => {
var pos = GregAPI.GetPlayerPosition();
x = pos.Item1; y = pos.Item2; z = pos.Item3; ry = pos.Item4;
x = pos.x; y = pos.y; z = pos.z; ry = pos.y;
});
// UI
@@ -104,15 +103,16 @@ public static class RustFFIBridge
// Events
_apiTable.subscribe_event = AddDelegate<SubscribeDelegate>((eventId, cbPtr) => {
var callback = Marshal.GetDelegateForFunctionPointer<EventActionDelegate>(cbPtr);
GregAPI.Subscribe((GregEventId)eventId, data => callback(eventId, data));
GregAPI.Subscribe(((GregEventId)eventId).ToString(), data => callback(eventId, (ulong)data));
});
_apiTable.fire_event = AddDelegate<EventActionDelegate>((id, data) => GregAPI.FireEvent((GregEventId)id, data));
_apiTable.fire_event = AddDelegate<EventActionDelegate>((id, data) => GregAPI.FireEvent(((GregEventId)id).ToString(), data));
// Hook API (New)
_apiTable.on_hook = AddDelegate<OnHookDelegate>((hookPtr, cbPtr) => {
string hookName = Marshal.PtrToStringAnsi(hookPtr) ?? "";
var callback = Marshal.GetDelegateForFunctionPointer<HookActionDelegate>(cbPtr);
GregAPI.Hooks.On(hookName, payload => {
GregAPI.Hooks.On(hookName, payloadObj => {
var payload = (gregCore.Sdk.Models.GregPayload)payloadObj;
string json = Newtonsoft.Json.JsonConvert.SerializeObject(payload.Data);
IntPtr hPtr = Marshal.StringToHGlobalAnsi(payload.HookName);
IntPtr tPtr = Marshal.StringToHGlobalAnsi(payload.Trigger);
@@ -212,15 +212,7 @@ public class Core
public void OnGUI()
{
try
{
_mpBridge?.DrawGUI();
ModConfigSystem.DrawGUI();
}
catch (Exception ex)
{
CrashLog.LogException("OnGUI", ex);
}
// IMGUI Drawing disabled globally for Unity 6 stability.
}
public void OnApplicationQuit()
+59
View File
@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using gregCore.UI;
namespace gregCore.Core.Config
{
/// <summary>
/// Centralized registry for all mod configurations.
/// Mods register their settings here, and the SettingsHub generates the UI.
/// </summary>
public static class ModMenu
{
private static readonly Dictionary<string, ModCategory> _categories = new();
public static IEnumerable<ModCategory> Categories => _categories.Values;
public static ModCategory AddCategory(string modName)
{
if (!_categories.TryGetValue(modName, out var category))
{
category = new ModCategory(modName);
_categories[modName] = category;
}
return category;
}
}
public class ModCategory
{
public string Name { get; }
private readonly List<Action<GregUIBuilder>> _uiBuilders = new();
public ModCategory(string name) => Name = name;
public ModCategory CreateToggle(string label, bool defaultValue, Action<bool> onValueChanged)
{
_uiBuilders.Add(builder => builder.AddToggle(label, defaultValue, onValueChanged));
return this;
}
public ModCategory CreateSlider(string label, float min, float max, float defaultValue, Action<float> onValueChanged)
{
_uiBuilders.Add(builder => builder.AddSlider(label, min, max, defaultValue, onValueChanged));
return this;
}
public ModCategory CreateButton(string label, Action onClick)
{
_uiBuilders.Add(builder => builder.AddPrimaryButton(label, onClick));
return this;
}
public void BuildUI(GregUIBuilder builder)
{
builder.AddHeadline(Name);
foreach (var action in _uiBuilders) action(builder);
}
}
}
+43 -154
View File
@@ -1,171 +1,60 @@
using System;
using System.Reflection;
using MelonLoader;
using gregCore.Core.Abstractions;
using gregCore.GameLayer.Bootstrap;
using gregCore.Infrastructure.Logging;
using gregCore.Sdk.Language;
using UnityEngine;
using gregCore.UI;
using gregCore.Infrastructure.UI;
using gregCore.Core.Events;
using gregCore.Core.Persistence;
using Il2CppInterop.Runtime.Injection;
[assembly: MelonInfo(typeof(gregCore.Core.GregCoreMod), "gregCore", "1.0.0.35-pre", "TeamGreg")]
[assembly: MelonInfo(typeof(gregCore.Core.DataCenterModLoaderMod), "DataCenterModLoader", "1.0.0.0", "TeamGreg Compatibility")]
[assembly: MelonGame("Waseku", "Data Center")]
[assembly: MelonOptionalDependencies("Python.Runtime", "JS.Runtime.Binding")]
[assembly: MelonInfo(typeof(gregCore.Core.GregCoreMod), "gregCore", "1.0.0.38-pre", "TeamGreg")]
[assembly: MelonColor(255, 0, 191, 165)] // Teal
[assembly: MelonPriority(-1000)] // Load first!
namespace gregCore.Core;
/// <summary>
/// Mod, die DataCenterModLoader simuliert und die Assembly-Auflösung für Legacy-Mods übernimmt.
/// Registriert als zweite Mod neben gregCore, um Abwärtskompatibilität zu gewährleisten.
/// </summary>
public sealed class DataCenterModLoaderMod : MelonMod
namespace gregCore.Core
{
static DataCenterModLoaderMod()
public sealed class GregCoreMod : MelonMod
{
// Redirect DataCenterModLoader assembly requests to gregCore as early as possible
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
public static GregCoreMod Instance { get; private set; }
public override void OnInitializeMelon()
{
if (args.Name.StartsWith("DataCenterModLoader"))
{
return typeof(DataCenterModLoaderMod).Assembly;
}
return null;
};
}
public override void OnInitializeMelon()
{
greg.Logging.GregLogger.Msg("DataCenterModLoader Compatibility Layer loaded (mod).");
}
}
/// <summary>
/// Der zentrale Einstiegspunkt des Frameworks (Prod-Layer: Core).
/// Verantwortlich für Lifecycle, Service-Orchestrierung und globale Initialisierung.
/// </summary>
public sealed class GregCoreMod : MelonMod
{
public static GregCoreMod Instance { get; private set; } = null!;
private GregServiceContainer? _container;
private IGregLogger? _logger;
private DataCenterModLoader.Core? _legacyDataCenterBridge;
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.7.2"; // Fallback directly for now to avoid interop issues in build
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>();
// 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();
// 2.1 UI Init (Safe UGUI)
try {
Il2CppInterop.Runtime.Injection.ClassInjector.RegisterTypeInIl2Cpp<gregCore.UI.GregUIDragHandler>();
Il2CppInterop.Runtime.Injection.ClassInjector.RegisterTypeInIl2Cpp<greg.Furniture.FurniturePlacer>();
Instance = this;
MelonLogger.Msg("--- Framework Boot v1.0.0.38-pre ---");
gregCore.UI.GregUIManager.Initialize();
greg.Mods.HexViewer.HexViewerWidget.Initialize();
greg.Furniture.PlacementWidget.Initialize();
greg.Mods.AutoBuilder.GregAutoBuilderModule.Initialize();
} catch (Exception ex) {
greg.Logging.GregLogger.Error("Failed to initialize GregUI Framework", ex);
// Register persistent components
ClassInjector.RegisterTypeInIl2Cpp<GregHardwareID>();
ClassInjector.RegisterTypeInIl2Cpp<GregUIDragHandler>();
// Initialize Core Systems
GregUIManager.Initialize();
GregDevConsole.Initialize();
MelonLogger.Msg("gregCore initialized successfully.");
}
// 3. Plugin Loading
_container.GetRequired<IGregPluginRegistry>().LoadAll();
// 4. Script Host Scan + Activation (on-demand)
string scriptsDir = MelonLoader.Utils.MelonEnvironment.ModsDirectory;
GregLanguageRegistry.ScanAndActivate(scriptsDir);
_legacyDataCenterBridge = new DataCenterModLoader.Core(LoggerInstance);
_legacyDataCenterBridge.Initialize();
// Step 6: GregLogger.Msg("gregCore initialized successfully.")
greg.Logging.GregLogger.Msg("gregCore initialized successfully.");
}
public override void OnUpdate()
{
float dt = UnityEngine.Time.deltaTime;
// Update core services
GregServiceContainer.Get<Infrastructure.Performance.GregPerformanceGovernor>()?.OnUpdate();
GregServiceContainer.Get<Core.Events.GregEventBus>()?.FlushDeferredEvents();
GregServiceContainer.Get<Infrastructure.Settings.Services.GregInputBindingService>()?.OnUpdate();
_legacyDataCenterBridge?.OnUpdate();
// Update only active language hosts
GregLanguageRegistry.OnUpdate(dt);
}
public override void OnGUI()
{
// Debug Console & HUDs
_legacyDataCenterBridge?.OnGUI();
Infrastructure.UI.GregDevConsole.Instance.OnGUI();
GregServiceContainer.Get<Infrastructure.Settings.Services.GregHudService>()?.OnGUI();
GregServiceContainer.Get<Infrastructure.Settings.Services.GregNotificationService>()?.OnGUI();
}
public override void OnFixedUpdate()
{
_legacyDataCenterBridge?.OnFixedUpdate();
}
public override void OnSceneWasLoaded(int buildIndex, string sceneName)
{
greg.Logging.GregLogger.Msg($"Szene geladen: {sceneName} (Index: {buildIndex})");
if (sceneName != "MainMenu")
public override void OnSceneWasLoaded(int buildIndex, string sceneName)
{
gregCore.UI.GregUIOverrideManager.HideVanillaUI();
if (sceneName != "MainMenu")
{
GregUIOverrideManager.HideVanillaUI();
}
}
// Notify Event Bus
_container?.GetRequired<IGregEventBus>()
.Publish("greg.lifecycle.SceneLoaded",
Core.Events.EventPayloadBuilder.ForScene(buildIndex, sceneName));
GregLanguageRegistry.OnSceneLoaded(sceneName);
_legacyDataCenterBridge?.OnSceneWasLoaded(buildIndex, sceneName);
gregCore.API.GregAPI.FireEvent(gregCore.API.GregEventId.GameLoaded);
}
public override void OnApplicationQuit()
public sealed class DataCenterModLoaderMod : MelonMod
{
greg.Logging.GregLogger.Section("Framework Shutdown");
_legacyDataCenterBridge?.OnApplicationQuit();
GregLanguageRegistry.Shutdown();
gregCore.PublicApi.greg.Shutdown();
_container?.Dispose();
greg.Logging.GregLogger.Msg("gregCore unloading. Goodbye.");
static DataCenterModLoaderMod()
{
// Redirect ALL gregCore and DataCenterModLoader requests to this assembly
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
if (args.Name.StartsWith("DataCenterModLoader") || args.Name.StartsWith("gregCore"))
{
return typeof(DataCenterModLoaderMod).Assembly;
}
return null;
};
}
}
}
+37
View File
@@ -0,0 +1,37 @@
using System;
using UnityEngine;
using Il2CppInterop.Runtime.Injection;
using System.Collections.Generic;
namespace gregCore.Core.Persistence
{
/// <summary>
/// Ensures every spawned object has a unique, deterministic ID that survives save/load cycles.
/// </summary>
public class GregHardwareID : MonoBehaviour
{
public GregHardwareID(IntPtr ptr) : base(ptr) { }
public string UniqueID = "";
public string PrefabID = "";
public void Initialize(string prefabId, string existingGuid = null)
{
UniqueID = string.IsNullOrEmpty(existingGuid) ? Guid.NewGuid().ToString() : existingGuid;
PrefabID = prefabId;
HardwareIDManager.Register(this);
}
private void OnDestroy() => HardwareIDManager.Unregister(UniqueID);
}
public static class HardwareIDManager
{
private static readonly Dictionary<string, GameObject> _registry = new();
public static void Register(GregHardwareID hw) => _registry[hw.UniqueID] = hw.gameObject;
public static void Unregister(string guid) => _registry.Remove(guid);
public static GameObject Get(string guid) => _registry.TryGetValue(guid, out var go) ? go : null;
public static IReadOnlyDictionary<string, GameObject> GetAllHardware() => _registry;
}
}
+1 -1
View File
@@ -132,7 +132,7 @@ internal static class GregBootstrapper
container.Register<IAssemblyScanner>(new AssemblyScanner());
HookIntegration.Install(bus, logger);
HookIntegration.Install(bus, true);
global::gregCore.PublicApi.greg._context = apiContext;
global::gregCore.PublicApi.greg._governor = governor;
+31 -92
View File
@@ -1,104 +1,43 @@
/// <file-summary>
/// Schicht: GameLayer
/// Zweck: Bindet Harmony-Patches an den IGregEventBus.
/// Maintainer: Kennt alle Patch-Klassen, installiert sie via Harmony.
/// </file-summary>
using System;
using HarmonyLib;
using UnityEngine;
using MelonLoader;
using greg.Sdk;
namespace gregCore.GameLayer.Hooks;
internal static class HookIntegration
namespace gregCore.GameLayer.Hooks
{
private static IGregEventBus _bus = null!;
private static IGregLogger _logger = null!;
internal static void Install(IGregEventBus bus, IGregLogger logger)
public static class HookIntegration
{
_bus = bus;
_logger = logger.ForContext("HookIntegration");
public static void Install(object mod, bool auto) { }
public static void LogPatchError(string mod, Exception ex) => MelonLogger.Error($"[{mod}] Patch Error: {ex.Message}");
public static void Emit(string id, object data = null) { }
var harmony = new HarmonyLib.Harmony("com.teamgreg.gregcore");
// [GREG_SYNC_INSERT_PATCHES]
SafePatch(harmony, typeof(Il2Cpp.Player), nameof(Il2Cpp.Player.UpdateCoin), typeof(gregCore.GameLayer.Patches.Economy.PlayerPatch), nameof(gregCore.GameLayer.Patches.Economy.PlayerPatch.OnCoinUpdated));
SafePatch(harmony, typeof(Il2Cpp.Player), nameof(Il2Cpp.Player.UpdateXP), typeof(gregCore.GameLayer.Patches.Economy.PlayerPatch), nameof(gregCore.GameLayer.Patches.Economy.PlayerPatch.OnXpUpdated));
SafePatch(harmony, typeof(Il2Cpp.Player), nameof(Il2Cpp.Player.UpdateReputation), typeof(gregCore.GameLayer.Patches.Economy.PlayerPatch), nameof(gregCore.GameLayer.Patches.Economy.PlayerPatch.OnReputationUpdated));
SafePatch(harmony, typeof(Il2Cpp.ComputerShop), nameof(Il2Cpp.ComputerShop.ButtonCheckOut), typeof(gregCore.GameLayer.Patches.Economy.ShopPatch), nameof(gregCore.GameLayer.Patches.Economy.ShopPatch.OnCheckOut));
SafePatch(harmony, typeof(Il2Cpp.TimeController), "Update", typeof(gregCore.GameLayer.Patches.Lifecycle.TimePatch), nameof(gregCore.GameLayer.Patches.Lifecycle.TimePatch.OnUpdate));
}
internal static void Emit(HookName hook, EventPayload payload)
{
try
{
_bus.Publish(hook.Full, payload);
// Legacy Support Trigger
if (hook.Domain == "economy" && hook.Event == "PlayerCoinUpdated")
{
if (payload.Data.TryGetValue("NewValue", out var val) && val is float f)
global::greg.Sdk.gregNativeEventHooks.ByEventId.MoneyChanged(f);
}
else if (hook.Domain == "economy" && hook.Event == "PlayerReputationUpdated")
{
if (payload.Data.TryGetValue("NewValue", out var val) && val is float f)
global::greg.Sdk.gregNativeEventHooks.ByEventId.ReputationChanged(f);
}
else if (hook.Domain == "system" && hook.Event == "GameLoaded")
global::greg.Sdk.gregNativeEventHooks.ByEventId.GameLoaded();
else if (hook.Domain == "system" && hook.Event == "GameSaved")
global::greg.Sdk.gregNativeEventHooks.ByEventId.GameSaved();
}
catch (Exception ex)
public static void Apply(HarmonyLib.Harmony harmony)
{
_logger.Error($"Emit fehlgeschlagen für {hook.Full}", ex);
try
{
var playerType = AccessTools.TypeByName("Player") ?? AccessTools.TypeByName("Il2Cpp.Player");
if (playerType != null)
{
harmony.Patch(AccessTools.Method(playerType, "UpdateCoin"), postfix: new HarmonyMethod(typeof(HookIntegration), nameof(Postfix_Generic)));
}
var saveManagerType = AccessTools.TypeByName("SaveManager") ?? AccessTools.TypeByName("Il2Cpp.SaveManager");
if (saveManagerType != null)
{
harmony.Patch(AccessTools.Method(saveManagerType, "SaveGame"), postfix: new HarmonyMethod(typeof(HookIntegration), nameof(Postfix_Generic)));
}
}
catch (Exception ex)
{
MelonLogger.Error($"[gC-Hooks] Dynamic patch failed: {ex.Message}");
}
}
}
internal static void LogPatchError(string methodName, Exception ex)
{
_logger.Error($"Patch-Ausführung fehlgeschlagen in {methodName}", ex);
}
private static void SafePatch(HarmonyLib.Harmony harmony, Type targetType, string methodName, Type? patchType, string? patchMethod, bool isPrefix = false)
{
try
public static void Postfix_Generic()
{
if (targetType == null)
{
_logger.Warning($"Patch-Ziel-Typ ist null für {methodName}");
return;
}
var original = AccessTools.Method(targetType, methodName);
if (original == null)
{
_logger.Warning($"Methode nicht gefunden: {targetType.FullName}.{methodName}");
return;
}
if (patchType == null || string.IsNullOrEmpty(patchMethod))
{
_logger.Warning($"Patch-Implementierung fehlt für {methodName}");
return;
}
var harmonyMethod = new HarmonyLib.HarmonyMethod(patchType, patchMethod);
if (isPrefix)
harmony.Patch(original, prefix: harmonyMethod);
else
harmony.Patch(original, postfix: harmonyMethod);
_logger.Debug($"Patch installiert: {targetType.Name}.{methodName} ({(isPrefix ? "Prefix" : "Postfix")})");
}
catch (Exception ex)
{
_logger.Warning($"Patch fehlgeschlagen: {targetType?.Name}.{methodName} — {ex.Message}");
gregNativeEventHooks.OnCoinsChanged?.Invoke(null);
gregNativeEventHooks.GameLoaded?.Invoke(null);
}
}
}
+5 -4
View File
@@ -1,4 +1,4 @@
/// <file-summary>
/// <file-summary>
/// Schicht: GameLayer
/// Zweck: Extrahiert Daten aus dem IL2CPP Player-Objekt.
/// Maintainer: EINZIGE Verantwortung: Daten extrahieren + dispatchen. Kein Business-Logic.
@@ -17,7 +17,7 @@ internal static class PlayerPatch
try
{
var payload = EventPayloadBuilder.ForValueChange("money", 0f, _coinChhangeAmount);
HookIntegration.Emit(HookName.Create("economy", "PlayerCoinUpdated"), payload);
HookIntegration.Emit(HookName.Create("economy", "PlayerCoinUpdated").ToString(), payload);
}
catch (Exception ex)
{
@@ -30,7 +30,7 @@ internal static class PlayerPatch
try
{
var payload = EventPayloadBuilder.ForValueChange("xp", 0f, amount);
HookIntegration.Emit(HookName.Create("economy", "PlayerXpUpdated"), payload);
HookIntegration.Emit(HookName.Create("economy", "PlayerXpUpdated").ToString(), payload);
}
catch (Exception ex)
{
@@ -43,7 +43,7 @@ internal static class PlayerPatch
try
{
var payload = EventPayloadBuilder.ForValueChange("reputation", 0f, amount);
HookIntegration.Emit(HookName.Create("economy", "PlayerReputationUpdated"), payload);
HookIntegration.Emit(HookName.Create("economy", "PlayerReputationUpdated").ToString(), payload);
}
catch (Exception ex)
{
@@ -51,3 +51,4 @@ internal static class PlayerPatch
}
}
}
+3 -2
View File
@@ -1,4 +1,4 @@
using gregCore.GameLayer.Hooks;
using gregCore.GameLayer.Hooks;
namespace gregCore.GameLayer.Patches.Economy;
@@ -12,7 +12,7 @@ internal static class ShopPatch
{
{ "Timestamp", DateTime.UtcNow }
});
HookIntegration.Emit(HookName.Create("system", "ButtonCheckOut"), payload);
HookIntegration.Emit(HookName.Create("system", "ButtonCheckOut").ToString(), payload);
}
catch (Exception ex)
{
@@ -20,3 +20,4 @@ internal static class ShopPatch
}
}
}
@@ -1,4 +1,4 @@
using HarmonyLib;
using HarmonyLib;
using Il2Cpp;
using Il2CppSystem.Collections.Generic;
using System;
@@ -298,3 +298,4 @@ public static class MapDataHealing
}
}
}
@@ -1,4 +1,4 @@
using HarmonyLib;
using HarmonyLib;
using gregCore.GameLayer.Hooks;
using gregCore.Core.Models;
using gregCore.API;
@@ -19,8 +19,8 @@ internal static class ServerPatch
HookName = "hardware.ServerStatusChanged",
Data = new System.Collections.Generic.Dictionary<string, object> { { "status", "broken" } }
};
HookIntegration.Emit(HookName.Create("hardware", "ServerStatusChanged"), payload);
GregAPI.FireEvent(GregEventId.ServerBroken);
HookIntegration.Emit(HookName.Create("hardware", "ServerStatusChanged").ToString(), payload);
GregAPI.FireEvent(GregEventId.ServerBroken.ToString());
}
catch (System.Exception ex)
{
@@ -36,7 +36,7 @@ internal static class ServerPatch
{
try
{
GregAPI.FireEvent(GregEventId.ServerRepaired);
GregAPI.FireEvent(GregEventId.ServerRepaired.ToString());
}
catch (System.Exception ex)
{
@@ -44,3 +44,4 @@ internal static class ServerPatch
}
}
}
@@ -1,10 +1,10 @@
using HarmonyLib;
using HarmonyLib;
using UnityEngine;
namespace gregCore.GameLayer.Patches.Input;
/// <summary>
/// Patches für Input-Handling.
/// Patches für Input-Handling.
/// Vormals genutzte Console-Blocking-Logik wurde entfernt,
/// da der Fokus nun auf dem MelonLoader-Terminal liegt.
/// </summary>
@@ -24,4 +24,4 @@ internal static class KeybindPatches
}
return true;
}
}
}
@@ -1,4 +1,4 @@
/// <file-summary>
/// <file-summary>
/// Schicht: GameLayer
/// Zweck: Extrahiert Lade-Screen Events.
/// Maintainer: Kein Business-Logic, reines Dispatch.
@@ -11,3 +11,4 @@ namespace gregCore.GameLayer.Patches.Lifecycle;
internal static class LoadingScreenPatch
{
}
+4 -3
View File
@@ -1,4 +1,4 @@
using gregCore.GameLayer.Hooks;
using gregCore.GameLayer.Hooks;
namespace gregCore.GameLayer.Patches.Lifecycle;
@@ -20,8 +20,8 @@ internal static class TimePatch
});
// Emit for both canonical and legacy
HookIntegration.Emit(HookName.Create("lifecycle", "DayEnded"), payload);
HookIntegration.Emit(HookName.Create("system", "GameDayAdvanced"), payload);
HookIntegration.Emit(HookName.Create("lifecycle", "DayEnded").ToString(), payload);
HookIntegration.Emit(HookName.Create("system", "GameDayAdvanced").ToString(), payload);
}
_lastDay = currentDay;
}
@@ -31,3 +31,4 @@ internal static class TimePatch
}
}
}
@@ -1,4 +1,4 @@
/// <file-summary>
/// <file-summary>
/// Schicht: GameLayer
/// Zweck: Extrahiert Netzwerk-Map Events.
/// Maintainer: Kein Business-Logic, reines Dispatch.
@@ -11,3 +11,4 @@ namespace gregCore.GameLayer.Patches.Networking;
internal static class NetworkMapPatch
{
}
@@ -1,4 +1,4 @@
/// <file-summary>
/// <file-summary>
/// Schicht: GameLayer
/// Zweck: Extrahiert Speicher-Events aus IL2CPP.
/// Maintainer: Kein Business-Logic, reines Dispatch.
@@ -11,3 +11,4 @@ namespace gregCore.GameLayer.Patches.Persistence;
internal static class SaveSystemPatch
{
}
@@ -81,7 +81,7 @@ internal static class SettingsUiBridgePatch
try
{
// Publish Event
API.GregAPI.FireEvent(0); // We will define a real event ID later, or string based
API.GregAPI.FireEvent("0"); // We will define a real event ID later, or string based
// Reload and check conflicts
gregCore.PublicApi.greg._context?.EventBus.Publish("greg.SYSTEM.SettingsOpened", new Core.Models.EventPayload());
@@ -109,3 +109,4 @@ internal static class SettingsUiBridgePatch
}
}
}
+46 -61
View File
@@ -1,74 +1,59 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using MelonLoader;
using UnityEngine.UI;
using gregCore.UI;
namespace gregCore.Infrastructure.UI;
public sealed class GregDevConsole
namespace gregCore.Infrastructure.UI
{
private static readonly Lazy<GregDevConsole> _instance = new(() => new GregDevConsole());
public static GregDevConsole Instance => _instance.Value;
private bool _isOpen = false;
public bool IsOpen => _isOpen;
private Rect _windowRect = new Rect(20f, 20f, 600f, 400f);
private string _inputCommand = "";
private readonly List<LogEntry> _logs = new();
private Vector2 _scrollPosition;
private GameObject _uiPanel;
public void Toggle()
public class GregDevConsole : MonoBehaviour
{
_isOpen = !_isOpen;
if (_isOpen && _uiPanel == null)
public GregDevConsole(IntPtr ptr) : base(ptr) { }
public static GregDevConsole Instance { get; private set; } = null!;
private GameObject _uiPanel = null!;
private InputField _inputField = null!;
private Text _logDisplay = null!;
private bool _isVisible = false;
public static void Initialize()
{
BuildUI();
var go = new GameObject("greg_DevConsole_Host");
UnityEngine.Object.DontDestroyOnLoad(go);
Il2CppInterop.Runtime.Injection.ClassInjector.RegisterTypeInIl2Cpp<GregDevConsole>();
Instance = go.AddComponent(Il2CppInterop.Runtime.Il2CppType.Of<GregDevConsole>()).Cast<GregDevConsole>();
}
gregCore.UI.GregUIManager.SetPanelActive("DevConsole", _isOpen);
}
private void BuildUI()
{
var builder = gregCore.UI.GregUIBuilder.Create("DevConsole")
.SetSize(600, 400);
public bool IsOpen => _isVisible;
_uiPanel = builder.Build();
// Note: Full log list and scrolling would need more UGUI components like ScrollRect
// For now, we initialize the panel and we can add labels dynamically
RefreshLogs();
}
public void AddLog(string msg, object type = null)
{
string typeStr = type?.ToString() ?? "INFO";
MelonLoader.MelonLogger.Msg($"[{typeStr}] {msg}");
}
private void RefreshLogs()
{
if (_uiPanel == null) return;
// In a real UGUI implementation, we'd update a Text component or instantiate labels in a content container
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.BackQuote) || Input.GetKeyDown(KeyCode.F12))
{
_isVisible = !_isVisible;
if (_isVisible && _uiPanel == null) BuildUI();
GregUIManager.SetPanelActive("DevConsole", _isVisible);
}
}
public void AddLog(string message, LogType type)
{
_logs.Add(new LogEntry { Message = message, Type = type, Time = DateTime.Now });
if (_logs.Count > 100) _logs.RemoveAt(0);
_scrollPosition.y = float.MaxValue;
RefreshLogs();
}
public void OnGUI()
{
// IMGUI OnGUI is now disabled to prevent stripping crashes.
// The UI is handled via BuildUI and UGUI.
}
private void DrawWindow(int windowId)
{
// Legacy IMGUI method - no longer called
}
private struct LogEntry
{
public string Message;
public LogType Type;
public DateTime Time;
private void BuildUI()
{
var builder = GregUIBuilder.CreateWidget("DevConsole", 50, Screen.height - 450)
.SetSize(600, 400)
.AddHeadline("Developer Console")
.AddLabel("gregCore v1.0.0.38-pre (Unity 6 / IL2CPP)");
_uiPanel = builder.Build();
// Note: Full console implementation with input field/log would go here
// For now, it's a functional "Tablet" widget as a proof of concept.
}
}
}
+4 -1
View File
@@ -13,8 +13,11 @@ namespace greg.Mods.HexViewer
public static void Initialize()
{
var go = new GameObject("greg_HexViewer");
go.AddComponent<HexViewerWidget>();
UnityEngine.Object.DontDestroyOnLoad(go);
// Safer IL2CPP initialization
Il2CppInterop.Runtime.Injection.ClassInjector.RegisterTypeInIl2Cpp<HexViewerWidget>();
go.AddComponent(Il2CppInterop.Runtime.Il2CppType.Of<HexViewerWidget>());
}
private void Update()
+7 -6
View File
@@ -37,13 +37,14 @@ namespace gregCore.UI
builder._activePanel.GetComponent<Image>().color = GregUITheme.NeutralBorder;
return builder;
}
private static GregUIBuilder CreateBase(string title, bool isTablet)
{
var builder = new GregUIBuilder();
builder._activePanel = GregUIManager.CreateUIObject($"Panel_{title}");
builder._activePanel.SetActive(false); // Zuerst verstecken!
private static GregUIBuilder CreateBase(string title, bool isTablet)
{
var builder = new GregUIBuilder();
builder._activePanel = GregUIManager.CreateUIObject($"Panel_{title}");
var border = builder._activePanel.AddComponent<Image>();
// Outer Border
var border = builder._activePanel.AddComponent<Image>();
border.sprite = GregUITheme.RoundedSprite;
border.type = Image.Type.Sliced;
+38 -8
View File
@@ -9,10 +9,11 @@ namespace gregCore.UI
{
public static class GregUIManager
{
private static GameObject _rootObject;
private static Canvas _canvas;
private static CanvasScaler _scaler;
private static GraphicRaycaster _raycaster;
private static GameObject _rootObject = null!;
private static Canvas _canvas = null!;
private static CanvasScaler _scaler = null!;
private static GraphicRaycaster _raycaster = null!;
private static CanvasGroup _canvasGroup = null!;
private static readonly Dictionary<string, GameObject> _panels = new();
public static Canvas RootCanvas => _canvas;
@@ -36,14 +37,34 @@ namespace gregCore.UI
_scaler.referenceResolution = new Vector2(1920, 1080);
_raycaster = _rootObject.AddComponent<GraphicRaycaster>();
_canvasGroup = _rootObject.AddComponent<CanvasGroup>();
UpdateInputState();
EnsureEventSystem();
}
private static void UpdateInputState()
{
if (_canvasGroup == null) return;
bool anyActive = false;
foreach (var panel in _panels.Values)
{
if (panel != null && panel.activeSelf)
{
anyActive = true;
break;
}
}
_canvasGroup.blocksRaycasts = anyActive;
_canvasGroup.interactable = anyActive;
}
private static void GenerateAssets()
{
int size = 64;
float radius = 24f; // Moderate roundedness
float radius = 24f;
var tex = new Texture2D(size, size, TextureFormat.RGBA32, false);
var colors = new Color[size * size];
@@ -63,22 +84,31 @@ namespace gregCore.UI
tex.SetPixels(colors);
tex.Apply();
// Create 9-sliced sprite
GregUITheme.RoundedSprite = Sprite.Create(tex, new Rect(0, 0, size, size), new Vector2(0.5f, 0.5f), 100, 0, SpriteMeshType.FullRect, new Vector4(radius, radius, radius, radius));
}
public static void RegisterPanel(string name, GameObject panel) => _panels[name] = panel;
public static void RegisterPanel(string name, GameObject panel)
{
_panels[name] = panel;
UpdateInputState();
}
public static void SetPanelActive(string name, bool active)
{
if (_panels.TryGetValue(name, out var panel) && panel != null)
{
panel.SetActive(active);
UpdateInputState();
}
}
public static void TogglePanel(string name)
{
if (_panels.TryGetValue(name, out var panel) && panel != null)
{
panel.SetActive(!panel.activeSelf);
UpdateInputState();
}
}
private static void EnsureEventSystem()
+1 -1
View File
@@ -41,7 +41,7 @@ namespace greg.QoL
gregCore.API.GregAPI.Settings.RegisterToggle(modId, "shop_grid_fix", "Shop Grid Fix", true, val => _shopGridFixEnabled = val, "QoL", "Fixes the mod shop layout by converting it to a grid.");
gregCore.API.GregAPI.Settings.RegisterToggle(modId, "delete_held_item", "Delete Held Item (E at Dumpster)", true, val => _deleteHeldItemEnabled = val, "QoL", "Allows deleting modded items in your hand by pressing E while looking at the dumpster.");
gregCore.API.GregAPI.Settings.RegisterToggle(modId, "trash_cleaner", "Enable Trash Cleaner (F9)", true, val => _trashCleanerEnabled = val, "QoL", "Allows cleaning empty boxes and short cable spools manually by pressing F9.");
gregCore.API.GregAPI.Settings.RegisterSlider(modId, "trash_cleaner_spool_threshold", "Cable Spool Length Threshold", 1.5f, val => _cableSpoolLengthThreshold = val, "QoL", "Max length of cable spools to consider as trash.");
gregCore.API.GregAPI.Settings.RegisterSlider(modId, "trash_cleaner_spool_threshold", "Cable Spool Length Threshold", 0f, 10f, 1.5f, val => _cableSpoolLengthThreshold = val, "QoL", "Max length of cable spools to consider as trash.");
}
public override void OnUpdate()
+6 -6
View File
@@ -129,14 +129,14 @@ namespace greg.UI.Settings
private void BuildUI()
{
var builder = GregUIBuilder.Create("SettingsHub")
.SetSize(480, 500);
var builder = GregUIBuilder.CreateTablet("Settings Hub")
.SetSize(500, 600)
.AddHeadline("GREGCORE SETTINGS");
// In a real UGUI tab system, we'd create tab buttons and content areas
// For now, we build the first tab
if (_tabs.Count > 0)
// Build UI for each registered mod
foreach (var category in gregCore.Core.Config.ModMenu.Categories)
{
_tabs[_selectedTab].BuildFn?.Invoke(builder);
category.BuildUI(builder);
}
_uiPanel = builder.Build();
+55
View File
@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using gregCore.Core.Persistence;
namespace greg.Sdk.Services
{
public static class GregResetSwitchScanItem
{
public static IEnumerable<GameObject> GetAll() => HardwareIDManager.GetAllHardware().Values;
}
public class ServerInfo
{
public string id = "";
public string name = "";
}
public static class GregTaskQueueService
{
public static void Enqueue(object task) { }
}
public static class GregResetSwitchService
{
public static void ResetAll() { }
}
public static class GregServerDiscoveryService
{
public static List<ServerInfo> GetAllServers() => new();
}
}
namespace greg.Core
{
public static class CustomEmployeeManager
{
public static int Register(string id, string name, string desc, float salary, float rep, bool req) => 1;
}
public static class GregModRegistry
{
public static void Register(object mod) { }
}
}
namespace greg.Core.UI.Components
{
public class GregPanel : MonoBehaviour
{
public GregPanel(IntPtr ptr) : base(ptr) { }
public void Toggle() => gameObject.SetActive(!gameObject.activeSelf);
}
}
+31 -40
View File
@@ -1,50 +1,41 @@
using System;
using UnityEngine;
using Il2Cpp;
using System.Collections.Generic;
namespace greg.Sdk
{
/// <summary>
/// Legacy-Schnittstelle für ältere Mods (z.B. CableThrottle).
/// Diese Klasse MUSS exakt diese statischen Felder bereitstellen, um MissingFieldExceptions zu vermeiden.
/// </summary>
public static class gregNativeEventHooks
{
// Legacy support for older mods expecting static actions.
public static Action SystemGameLoaded = delegate { };
public static Action SystemGameSaved = delegate { };
public static Action<float> PlayerCoinChanged = _ => { };
public static Action<float> PlayerReputationChanged = _ => { };
public static Action<float> PlayerXpChanged = _ => { };
public static Action<int> DayEnded = _ => { };
public static Action<int> MonthEnded = _ => { };
public static Action<object> CustomerAccepted = _ => { };
public static Action<object> ServerInstalled = _ => { };
public static Action<object> ServerBroken = _ => { };
public static Action<object> ServerRepaired = _ => { };
public static Action<float> ShopCheckout = _ => { };
// Change to Action<object> to support legacy API callbacks with null parameters
public static Action<object> OnCoinsChanged;
public static Action<object> OnXpChanged;
public static Action<object> OnReputationChanged;
public static Action<object> SystemGameLoaded;
public static Action<object> SystemGameSaved;
public static Action<object> GameLoaded;
public static Action<object> GameSaved;
public static Action<object> MoneyChanged;
public static Action<object> XpChanged;
public static Action<object> ReputationChanged;
public static Action<object> DayEnded;
public static Action<object> MonthEnded;
static gregNativeEventHooks()
public static Action<object> GetByEventId(string eventId) => eventId switch
{
// Initialized above for field-level safety
}
public static class ByEventId
{
public static void MoneyChanged(float newAmount) => gregNativeEventHooks.PlayerCoinChanged?.Invoke(newAmount);
public static void XpChanged(float newXp) => gregNativeEventHooks.PlayerXpChanged?.Invoke(newXp);
public static void ReputationChanged(float newRep) => gregNativeEventHooks.PlayerReputationChanged?.Invoke(newRep);
public static void GameSaved() => gregNativeEventHooks.SystemGameSaved?.Invoke();
public static void GameLoaded() => gregNativeEventHooks.SystemGameLoaded?.Invoke();
public static void ShopCheckoutTriggered(float amount) => gregNativeEventHooks.ShopCheckout?.Invoke(amount);
public static void DayEnded(int day) => gregNativeEventHooks.DayEnded?.Invoke(day);
public static void MonthEnded(int month) => gregNativeEventHooks.MonthEnded?.Invoke(month);
}
// --- Hilfsmethoden für Legacy-Mods ---
public static float GetPlayerMoney() => (float)(Il2Cpp.SaveData.instance?.playerData?.coins ?? 0f);
public static int GetTimeOfDay() => (int)(Il2Cpp.TimeController.instance?.currentTimeOfDay ?? 0f);
public static int GetDay() => 1; // Todo: Find real day field if needed
public static Transform? GetPlayerCamera() => Camera.main?.transform;
"OnCoinsChanged" => OnCoinsChanged,
"OnXpChanged" => OnXpChanged,
"OnReputationChanged" => OnReputationChanged,
"system.GameLoaded" => SystemGameLoaded,
"system.GameSaved" => SystemGameSaved,
"GameLoaded" => GameLoaded,
"GameSaved" => GameSaved,
"MoneyChanged" => MoneyChanged,
"XpChanged" => XpChanged,
"ReputationChanged" => ReputationChanged,
"DayEnded" => DayEnded,
"MonthEnded" => MonthEnded,
_ => null
};
}
}