Added : Module / Events / Patches Structure and System

This commit is contained in:
2026-03-06 23:37:32 +01:00
parent af40d24900
commit 61c519be05
5 changed files with 483 additions and 2 deletions

View File

@@ -1,16 +1,98 @@
using BepInEx;
using System;
using System.Collections.Generic;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using JordanMod.Events;
using JordanMod.Modules;
namespace JordanMod;
[BepInAutoPlugin]
public partial class Plugin : BaseUnityPlugin
{
public static Plugin Instance { get; private set; } = null!;
internal static ManualLogSource Log { get; private set; } = null!;
private static Harmony? _harmony;
private ModuleManager? _moduleManager;
private static readonly List<Type> _globalPatches = [];
private void Awake()
{
Instance = this;
ConfigHandler.Initialize(Config);
Log = Logger;
Log.LogInfo($"Plugin {Name} is loaded!");
Debug.Log("JordanMod is starting...");
SetupEvents();
SetupModules();
SetupPatches();
}
protected void SetupEvents()
{
GlobalEventListener.Initialize();
}
protected void SetupModules()
{
_moduleManager = new ModuleManager();
_moduleManager.Initialize();
}
protected void SetupPatches()
{
_harmony ??= new Harmony(Info.Metadata.GUID);
Type[] patches = [.. _globalPatches];
if (_moduleManager != null)
{
foreach (Module module in _moduleManager.GetAllModules())
{
patches = [.. patches, .. module.GetPatches()];
}
}
foreach (var patchType in patches)
{
try
{
_harmony.PatchAll(patchType);
}
catch (Exception e)
{
Log.LogError($"Failed to patch {patchType.Name}: {e}");
}
}
}
protected void RemovePatches()
{
if (_harmony == null) return;
try
{
_harmony.UnpatchSelf();
_harmony = null;
}
catch (Exception e)
{
Log.LogError($"Failed to remove patches: {e}");
}
}
private void Update()
{
_moduleManager?.Update();
}
private void FixedUpdate()
{
_moduleManager?.FixedUpdate();
}
private void OnDestroy()
{
RemovePatches();
_moduleManager?.Destroy();
}
}

View File

@@ -0,0 +1,10 @@
namespace JordanMod.Events;
public class GlobalEventListener
{
public static void Initialize()
{
}
}

View File

@@ -0,0 +1,338 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using ExitGames.Client.Photon;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;
namespace JordanMod.Modules;
public class ModuleManager
{
private readonly Dictionary<string, Module> _modules = [];
private event Action? OnInitialize;
private event Action? OnUpdate;
private event Action? OnFixedUpdate;
private event Action? OnDestroy;
public void Initialize()
{
AddNetworkEventListener();
LoadModules();
Plugin.Log.LogInfo($"ModuleManager initialized with {_modules.Count} modules");
}
public List<Module> GetAllModules()
{
return [.. _modules.Values];
}
private void AddNetworkEventListener()
{
GameObject listenerObject = new("PhotonNetworkEventListener");
UnityEngine.Object.DontDestroyOnLoad(listenerObject);
listenerObject.AddComponent<PhotonNetworkEventListener>();
}
private void LoadModules()
{
// Auto-discover all types with the [Module] attribute
var moduleTypes = Assembly.GetExecutingAssembly()
.GetTypes()
.Where(t => t.IsClass && !t.IsAbstract && t.IsSubclassOf(typeof(Module)))
.Select(t => new { Type = t, Attribute = t.GetCustomAttribute<ModuleAttribute>() })
.Where(x => x.Attribute != null)
.ToList();
Plugin.Log.LogInfo($"Discovered {moduleTypes.Count} module(s) with [Module] attribute");
foreach (var moduleInfo in moduleTypes)
{
try
{
Module module = (Module)Activator.CreateInstance(moduleInfo.Type)!;
if (!moduleInfo.Attribute!.Enabled)
continue;
_modules[module.ModuleName] = module;
if (HasOverriddenMethod(moduleInfo.Type, nameof(Module.Initialize)))
OnInitialize += module.Initialize;
if (HasOverriddenMethod(moduleInfo.Type, nameof(Module.Update)))
OnUpdate += module.Update;
if (HasOverriddenMethod(moduleInfo.Type, nameof(Module.FixedUpdate)))
OnFixedUpdate += module.FixedUpdate;
if (HasOverriddenMethod(moduleInfo.Type, nameof(Module.Destroy)))
OnDestroy += module.Destroy;
Plugin.Log.LogInfo($"Loaded module: {module.ModuleName}");
}
catch (Exception e)
{
Plugin.Log.LogError($"Failed to load module {moduleInfo.Type.Name}: {e}");
}
}
OnInitialize?.Invoke();
}
private static bool HasOverriddenMethod(Type type, string methodName)
{
var method = type.GetMethod(methodName);
return method != null && method.DeclaringType != typeof(Module);
}
public void Update()
{
OnUpdate?.Invoke();
}
public void FixedUpdate()
{
OnFixedUpdate?.Invoke();
}
public void Destroy()
{
OnDestroy?.Invoke();
_modules.Clear();
OnInitialize = null;
OnUpdate = null;
OnFixedUpdate = null;
OnDestroy = null;
}
public Module? GetModule(string moduleName)
{
_modules.TryGetValue(moduleName, out Module? module);
return module;
}
}
public abstract class Module
{
public abstract string ModuleName { get; }
public virtual void Initialize()
{
Plugin.Log.LogInfo($"Module '{ModuleName}' initialized");
}
public virtual void Update()
{
}
public virtual void FixedUpdate()
{
}
public virtual Type[] GetPatches()
{
return [];
}
public virtual void Destroy()
{
Plugin.Log.LogInfo($"Module '{ModuleName}' destroyed");
}
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ModuleAttribute : Attribute
{
public bool Enabled { get; set; } = true;
}
class PhotonNetworkEventListener : MonoBehaviourPunCallbacks
{
public static PhotonNetworkEventListener? Instance { get; private set; }
public override void OnEnable()
{
if (Instance != null) return;
Instance = this;
base.OnEnable();
}
public event Action? Connected;
public event Action? LeftRoom;
public event Action<Photon.Realtime.Player>? MasterClientSwitched;
public event Action<short, string>? CreateRoomFailed;
public event Action<short, string>? JoinRoomFailed;
public event Action? CreatedRoom;
public event Action? JoinedLobby;
public event Action? LeftLobby;
public event Action<DisconnectCause>? Disconnected;
public event Action<RegionHandler>? RegionListReceived;
public event Action<List<RoomInfo>>? RoomListUpdate;
public event Action? JoinedRoom;
public event Action<Photon.Realtime.Player>? PlayerEnteredRoom;
public event Action<Photon.Realtime.Player>? PlayerLeftRoom;
public event Action<short, string>? JoinRandomFailed;
public event Action? ConnectedToMaster;
public event Action<ExitGames.Client.Photon.Hashtable>? RoomPropertiesUpdate;
public event Action<Photon.Realtime.Player, ExitGames.Client.Photon.Hashtable>? PlayerPropertiesUpdate;
public event Action<List<FriendInfo>>? FriendListUpdate;
public event Action<Dictionary<string, object>>? CustomAuthenticationResponse;
public event Action<string>? CustomAuthenticationFailed;
public event Action<OperationResponse>? WebRpcResponse;
public event Action<List<TypedLobbyInfo>>? LobbyStatisticsUpdate;
public event Action<ErrorInfo>? ErrorInfoEvent;
// Register methods
public void RegisterOnConnected(Action handler) => Connected += handler;
public void RegisterOnLeftRoom(Action handler) => LeftRoom += handler;
public void RegisterOnMasterClientSwitched(Action<Photon.Realtime.Player> handler) => MasterClientSwitched += handler;
public void RegisterOnCreateRoomFailed(Action<short, string> handler) => CreateRoomFailed += handler;
public void RegisterOnJoinRoomFailed(Action<short, string> handler) => JoinRoomFailed += handler;
public void RegisterOnCreatedRoom(Action handler) => CreatedRoom += handler;
public void RegisterOnJoinedLobby(Action handler) => JoinedLobby += handler;
public void RegisterOnLeftLobby(Action handler) => LeftLobby += handler;
public void RegisterOnDisconnected(Action<DisconnectCause> handler) => Disconnected += handler;
public void RegisterOnRegionListReceived(Action<RegionHandler> handler) => RegionListReceived += handler;
public void RegisterOnRoomListUpdate(Action<List<RoomInfo>> handler) => RoomListUpdate += handler;
public void RegisterOnJoinedRoom(Action handler) => JoinedRoom += handler;
public void RegisterOnPlayerEnteredRoom(Action<Photon.Realtime.Player> handler) => PlayerEnteredRoom += handler;
public void RegisterOnPlayerLeftRoom(Action<Photon.Realtime.Player> handler) => PlayerLeftRoom += handler;
public void RegisterOnJoinRandomFailed(Action<short, string> handler) => JoinRandomFailed += handler;
public void RegisterOnConnectedToMaster(Action handler) => ConnectedToMaster += handler;
public void RegisterOnRoomPropertiesUpdate(Action<ExitGames.Client.Photon.Hashtable> handler) => RoomPropertiesUpdate += handler;
public void RegisterOnPlayerPropertiesUpdate(Action<Photon.Realtime.Player, ExitGames.Client.Photon.Hashtable> handler) => PlayerPropertiesUpdate += handler;
public void RegisterOnFriendListUpdate(Action<List<FriendInfo>> handler) => FriendListUpdate += handler;
public void RegisterOnCustomAuthenticationResponse(Action<Dictionary<string, object>> handler) => CustomAuthenticationResponse += handler;
public void RegisterOnCustomAuthenticationFailed(Action<string> handler) => CustomAuthenticationFailed += handler;
public void RegisterOnWebRpcResponse(Action<OperationResponse> handler) => WebRpcResponse += handler;
public void RegisterOnLobbyStatisticsUpdate(Action<List<TypedLobbyInfo>> handler) => LobbyStatisticsUpdate += handler;
public void RegisterOnErrorInfo(Action<ErrorInfo> handler) => ErrorInfoEvent += handler;
// Override and invoke actions
public override void OnConnected()
{
Connected?.Invoke();
}
public override void OnLeftRoom()
{
LeftRoom?.Invoke();
}
public override void OnMasterClientSwitched(Photon.Realtime.Player newMasterClient)
{
MasterClientSwitched?.Invoke(newMasterClient);
}
public override void OnCreateRoomFailed(short returnCode, string message)
{
CreateRoomFailed?.Invoke(returnCode, message);
}
public override void OnJoinRoomFailed(short returnCode, string message)
{
JoinRoomFailed?.Invoke(returnCode, message);
}
public override void OnCreatedRoom()
{
CreatedRoom?.Invoke();
}
public override void OnJoinedLobby()
{
JoinedLobby?.Invoke();
}
public override void OnLeftLobby()
{
LeftLobby?.Invoke();
}
public override void OnDisconnected(DisconnectCause cause)
{
Disconnected?.Invoke(cause);
}
public override void OnRegionListReceived(RegionHandler regionHandler)
{
RegionListReceived?.Invoke(regionHandler);
}
public override void OnRoomListUpdate(List<RoomInfo> roomList)
{
RoomListUpdate?.Invoke(roomList);
}
public override void OnJoinedRoom()
{
JoinedRoom?.Invoke();
}
public override void OnPlayerEnteredRoom(Photon.Realtime.Player newPlayer)
{
PlayerEnteredRoom?.Invoke(newPlayer);
}
public override void OnPlayerLeftRoom(Photon.Realtime.Player otherPlayer)
{
PlayerLeftRoom?.Invoke(otherPlayer);
}
public override void OnJoinRandomFailed(short returnCode, string message)
{
JoinRandomFailed?.Invoke(returnCode, message);
}
public override void OnConnectedToMaster()
{
ConnectedToMaster?.Invoke();
}
public override void OnRoomPropertiesUpdate(ExitGames.Client.Photon.Hashtable propertiesThatChanged)
{
RoomPropertiesUpdate?.Invoke(propertiesThatChanged);
}
public override void OnPlayerPropertiesUpdate(Photon.Realtime.Player targetPlayer, ExitGames.Client.Photon.Hashtable changedProps)
{
PlayerPropertiesUpdate?.Invoke(targetPlayer, changedProps);
}
public override void OnFriendListUpdate(List<FriendInfo> friendList)
{
FriendListUpdate?.Invoke(friendList);
}
public override void OnCustomAuthenticationResponse(Dictionary<string, object> data)
{
CustomAuthenticationResponse?.Invoke(data);
}
public override void OnCustomAuthenticationFailed(string debugMessage)
{
CustomAuthenticationFailed?.Invoke(debugMessage);
}
public override void OnWebRpcResponse(OperationResponse response)
{
WebRpcResponse?.Invoke(response);
}
public override void OnLobbyStatisticsUpdate(List<TypedLobbyInfo> lobbyStatistics)
{
LobbyStatisticsUpdate?.Invoke(lobbyStatistics);
}
public override void OnErrorInfo(ErrorInfo errorInfo)
{
ErrorInfoEvent?.Invoke(errorInfo);
}
}

View File

@@ -0,0 +1,34 @@
using System;
namespace JordanMod.Modules.Example;
[Module(Enabled = false)]
class ExampleModule : Module
{
public override string ModuleName => "Example Module";
public override Type[] GetPatches()
{
return [typeof(Patches.ExamplePatch)];
}
public override void Initialize()
{
base.Initialize();
}
public override void Update()
{
base.Update();
}
public override void FixedUpdate()
{
base.FixedUpdate();
}
public override void Destroy()
{
base.Destroy();
}
}

View File

@@ -0,0 +1,17 @@
using HarmonyLib;
namespace JordanMod.Patches;
public class ExamplePatch
{
[HarmonyPatch(typeof(Item), "Start")]
[HarmonyPostfix]
static void PostItemStartFix(Item __instance)
{
if (__instance.UIData.itemName.ToLower() == "passport")
{
__instance.UIData.canDrop = true;
__instance.UIData.canThrow = true;
}
}
}