diff --git a/GamecraftModdingAPI/Commands/CommandPatch.cs b/GamecraftModdingAPI/Commands/CommandPatch.cs new file mode 100644 index 0000000..3d26d1a --- /dev/null +++ b/GamecraftModdingAPI/Commands/CommandPatch.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Reflection; +using Harmony; +using Svelto.Context; +using Svelto.ECS; +using RobocraftX; +using RobocraftX.Multiplayer; +using Unity.Entities; + +using GamecraftModdingAPI.Utility; + +namespace GamecraftModdingAPI.Commands +{ + [HarmonyPatch] + class CommandPatch + { + public static void Prefix(UnityContext contextHolder, EnginesRoot enginesRoot, World physicsWorld, Action reloadGame, MultiplayerInitParameters multiplayerParameters) + { + Logging.Log("Command Line was loaded"); + // When a game is loaded, register the command engines + // TODO + } + + public static MethodBase TargetMethod(HarmonyInstance instance) + { + var func = (Action, EnginesRoot, World, Action, MultiplayerInitParameters>)RobocraftX.GUI.CommandLine.CommandLineCompositionRoot.Compose>; + return func.Method; + } + } +} diff --git a/GamecraftModdingAPI/Commands/ICustomCommandEngine.cs b/GamecraftModdingAPI/Commands/ICustomCommandEngine.cs new file mode 100644 index 0000000..34b5682 --- /dev/null +++ b/GamecraftModdingAPI/Commands/ICustomCommandEngine.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Svelto.ECS; + +namespace GamecraftModdingAPI.Commands +{ + interface ICustomCommandEngine : IEngine, IQueryingEntitiesEngine + { + string Name { get; } + + string Description { get; } + + void ExecuteCommand(); + } +} diff --git a/GamecraftModdingAPI/Events/EventType.cs b/GamecraftModdingAPI/Events/EventType.cs new file mode 100644 index 0000000..55676e0 --- /dev/null +++ b/GamecraftModdingAPI/Events/EventType.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GamecraftModdingAPI.Events +{ + public enum EventType + { + ApplicationInitialized, + MenuActivated, + MenuDestroyed, + MenuSwitchedTo, + GameActivated, + GameDestroyed, + GameReloaded, + GameSwitchedTo + } +} diff --git a/GamecraftModdingAPI/Events/GameInitPatch.cs b/GamecraftModdingAPI/Events/GameInitPatch.cs new file mode 100644 index 0000000..2b627fa --- /dev/null +++ b/GamecraftModdingAPI/Events/GameInitPatch.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Harmony; +using RobocraftX; +using Svelto.ECS; + +using GamecraftModdingAPI.Utility; + +namespace GamecraftModdingAPI.Events +{ + [HarmonyPatch(typeof(FullGameCompositionRoot), "ActivateMenu")] + class GameInitPatch + { + + private static bool firstLoad = true; + public static void Postfix(ref EnginesRoot ____frontEndEnginesRoot) + { + // A new EnginesRoot is always created when ActivateMenu is called + // so all event emitters and handlers must be re-registered. + Manager.RegisterEngines(____frontEndEnginesRoot); + if (firstLoad) + { + firstLoad = false; + Logging.Log("Dispatching App Init event"); + Manager.GetEventEmitter("GamecraftModdingAPIApplicationInitializedEventEmitter").Emit(); + } + Logging.Log("Dispatching Menu Activated event"); + Manager.GetEventEmitter("GamecraftModdingAPIMenuActivatedEventEmitter").Emit(); + } + } +} diff --git a/GamecraftModdingAPI/Events/IEventEmitterEngine.cs b/GamecraftModdingAPI/Events/IEventEmitterEngine.cs new file mode 100644 index 0000000..6372705 --- /dev/null +++ b/GamecraftModdingAPI/Events/IEventEmitterEngine.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Svelto.ECS; + +namespace GamecraftModdingAPI.Events +{ + public interface IEventEmitterEngine : IEngine, IQueryingEntitiesEngine + { + string Name { get; } + object type { get; } + + IEntityFactory Factory { set; } + + void Emit(); + } +} diff --git a/GamecraftModdingAPI/Events/IEventHandlerEngine.cs b/GamecraftModdingAPI/Events/IEventHandlerEngine.cs new file mode 100644 index 0000000..8501325 --- /dev/null +++ b/GamecraftModdingAPI/Events/IEventHandlerEngine.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Svelto.ECS; +using Svelto.ECS.Internal; + +namespace GamecraftModdingAPI.Events +{ + public interface IEventHandlerEngine : IEngine, IQueryingEntitiesEngine, IReactOnAddAndRemove, IReactOnAddAndRemove + { + string Name { get; } + } +} diff --git a/GamecraftModdingAPI/Events/Manager.cs b/GamecraftModdingAPI/Events/Manager.cs new file mode 100644 index 0000000..5521434 --- /dev/null +++ b/GamecraftModdingAPI/Events/Manager.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Svelto.ECS; + +namespace GamecraftModdingAPI.Events +{ + /// + /// Keeps track of event handlers and emitters. + /// This class can be used to add, remove and get event handlers and emitters. + /// + public static class Manager + { + private static Dictionary _eventEmitters = new Dictionary(); + + private static Dictionary _eventHandlers = new Dictionary(); + + // event handler management + + public static void AddEventHandler(IEventHandlerEngine engine) + { + _eventHandlers[engine.Name] = engine; + } + + public static bool ExistsEventHandler(string name) + { + return _eventHandlers.ContainsKey(name); + } + + public static bool ExistsEventHandler(IEventHandlerEngine engine) + { + return ExistsEventHandler(engine.Name); + } + + public static IEventHandlerEngine GetEventHandler(string name) + { + return _eventHandlers[name]; + } + + public static Dictionary GetEventHandlers() + { + return _eventHandlers; + } + + public static void RemoveEventHandler(string name) + { + _eventHandlers.Remove(name); + } + + // event emitter management + + public static void AddEventEmitter(IEventEmitterEngine engine) + { + _eventEmitters[engine.Name] = engine; + } + + public static bool ExistsEventEmitter(string name) + { + return _eventEmitters.ContainsKey(name); + } + + public static bool ExistsEventEmitter(IEventEmitterEngine engine) + { + return ExistsEventEmitter(engine.Name); + } + + public static IEventEmitterEngine GetEventEmitter(string name) + { + return _eventEmitters[name]; + } + + public static Dictionary GetEventEmitters() + { + return _eventEmitters; + } + + public static void RemoveEventEmitter(string name) + { + _eventEmitters.Remove(name); + } + + public static void RegisterEngines(EnginesRoot enginesRoot) + { + // Register handlers before emitters so no events are missed + var entityFactory = enginesRoot.GenerateEntityFactory(); + foreach (var key in _eventHandlers.Keys) + { + enginesRoot.AddEngine(_eventHandlers[key]); + } + foreach (var key in _eventEmitters.Keys) + { + _eventEmitters[key].Factory = entityFactory; + enginesRoot.AddEngine(_eventEmitters[key]); + } + } + + } +} diff --git a/GamecraftModdingAPI/Events/ModEventEntityDescriptor.cs b/GamecraftModdingAPI/Events/ModEventEntityDescriptor.cs new file mode 100644 index 0000000..a1dae44 --- /dev/null +++ b/GamecraftModdingAPI/Events/ModEventEntityDescriptor.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Svelto.ECS; + +namespace GamecraftModdingAPI.Events +{ + public class ModEventEntityDescriptor : GenericEntityDescriptor + { + } +} diff --git a/GamecraftModdingAPI/Events/ModEventEntityStruct.cs b/GamecraftModdingAPI/Events/ModEventEntityStruct.cs new file mode 100644 index 0000000..6e4e970 --- /dev/null +++ b/GamecraftModdingAPI/Events/ModEventEntityStruct.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Svelto.ECS; +using Svelto.ECS.Hybrid; + +namespace GamecraftModdingAPI.Events +{ + public struct ModEventEntityStruct : IEntityStruct + { + public object type; + + public EGID ID { get; set; } + } +} diff --git a/GamecraftModdingAPI/Events/SimpleEventEmitterEngine.cs b/GamecraftModdingAPI/Events/SimpleEventEmitterEngine.cs new file mode 100644 index 0000000..f31f21c --- /dev/null +++ b/GamecraftModdingAPI/Events/SimpleEventEmitterEngine.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Svelto.ECS; +using GamecraftModdingAPI.Utility; + +namespace GamecraftModdingAPI.Events +{ + class SimpleEventEmitterEngine : IEventEmitterEngine + { + public string Name { get; set; } + public object type { get; set; } + + public IEntityFactory Factory { private get; set; } + + public IEntitiesDB entitiesDB { set; private get; } + + public void Ready() { } + + public void Emit() + { + Factory.BuildEntity(ApiExclusiveGroups.eventID++, ApiExclusiveGroups.eventsExclusiveGroup) + .Init(new ModEventEntityStruct + { + type = type + }); + } + + public SimpleEventEmitterEngine(EventType type, string name) + { + this.type = type; + this.Name = name; + } + + public SimpleEventEmitterEngine(object type, string name) + { + this.type = type; + this.Name = name; + } + } +} diff --git a/GamecraftModdingAPI/Events/SimpleEventHandlerEngine.cs b/GamecraftModdingAPI/Events/SimpleEventHandlerEngine.cs new file mode 100644 index 0000000..229c527 --- /dev/null +++ b/GamecraftModdingAPI/Events/SimpleEventHandlerEngine.cs @@ -0,0 +1,40 @@ +using Svelto.ECS; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GamecraftModdingAPI.Events +{ + class SimpleEventHandlerEngine : IEventHandlerEngine + { + public object type { get; set; } + public string Name { get; set; } + + private readonly Action onEvent; + + public IEntitiesDB entitiesDB { set; private get; } + + public void Add(ref ModEventEntityStruct entityView, EGID egid) + { + if (entityView.type.Equals(this.type)) + { + onEvent.Invoke(entitiesDB); + } + } + + public void Ready() { } + + public void Remove(ref ModEventEntityStruct entityView, EGID egid) { } + + public SimpleEventHandlerEngine(Action handleEvent, object type, string name) : this((IEntitiesDB db) => { handleEvent.Invoke(); }, type, name) { } + + public SimpleEventHandlerEngine(Action handleEvent, object type, string name) + { + this.type = type; + this.Name = name; + this.onEvent = handleEvent; + } + } +} diff --git a/GamecraftModdingAPI/GamecraftModdingAPI.csproj b/GamecraftModdingAPI/GamecraftModdingAPI.csproj index d19bc37..0eba782 100644 --- a/GamecraftModdingAPI/GamecraftModdingAPI.csproj +++ b/GamecraftModdingAPI/GamecraftModdingAPI.csproj @@ -3,6 +3,11 @@ net48 true + 0.1.0.0 + Exmods + GNU General Public Licence 3+ + https://git.exmods.org/modtainers/GamecraftModdingAPI + en-CA @@ -531,6 +536,19 @@ ..\ref\Gamecraft_Data\Managed\VisualProfiler.dll + + + True + True + Settings.settings + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + diff --git a/GamecraftModdingAPI/Main.cs b/GamecraftModdingAPI/Main.cs new file mode 100644 index 0000000..d0ae328 --- /dev/null +++ b/GamecraftModdingAPI/Main.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Harmony; +using System.Reflection; +using GamecraftModdingAPI.Utility; +using GamecraftModdingAPI.Events; + +namespace GamecraftModdingAPI +{ + static class Main + { + private static HarmonyInstance harmony; + public static void Init() + { + var currentAssembly = Assembly.GetExecutingAssembly(); + if (harmony == null) + { + harmony = HarmonyInstance.Create(currentAssembly.GetName().Name); + harmony.PatchAll(currentAssembly); + } + // create default event objects + Manager.AddEventHandler(new SimpleEventHandlerEngine(() => { Logging.Log("App Inited event!"); }, + EventType.ApplicationInitialized, "appinit API debug")); + Manager.AddEventEmitter(new SimpleEventEmitterEngine(EventType.ApplicationInitialized, "GamecraftModdingAPIApplicationInitializedEventEmitter")); + Manager.AddEventEmitter(new SimpleEventEmitterEngine(EventType.MenuActivated, "GamecraftModdingAPIMenuActivatedEventEmitter")); + Logging.Log($"{currentAssembly.GetName().Name} {currentAssembly.GetName().Version} start & patch complete"); + } + + public static void Shutdown() + { + var currentAssembly = Assembly.GetExecutingAssembly(); + harmony.UnpatchAll(currentAssembly.GetName().Name); + Logging.Log($"{currentAssembly.GetName().Name} {currentAssembly.GetName().Version} shutdown & unpatch complete"); + } + } +} diff --git a/GamecraftModdingAPI/Properties/Settings.Designer.cs b/GamecraftModdingAPI/Properties/Settings.Designer.cs new file mode 100644 index 0000000..95bae2c --- /dev/null +++ b/GamecraftModdingAPI/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace GamecraftModdingAPI.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.4.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/GamecraftModdingAPI/Properties/Settings.settings b/GamecraftModdingAPI/Properties/Settings.settings new file mode 100644 index 0000000..049245f --- /dev/null +++ b/GamecraftModdingAPI/Properties/Settings.settings @@ -0,0 +1,6 @@ + + + + + + diff --git a/GamecraftModdingAPI/TestPatch.cs b/GamecraftModdingAPI/TestPatch.cs deleted file mode 100644 index c7703d0..0000000 --- a/GamecraftModdingAPI/TestPatch.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Reflection; -using Harmony; -using UnityEngine; - -namespace TestMod -{ - [HarmonyPatch] - class TestPatch - { - static void Prefix() - { - Debug.Log("Test Patch Prefix"); - } - - [HarmonyTargetMethod] - static MethodBase HTargetMethod(HarmonyInstance instance) - { - throw new NotImplementedException(); - } - } -} diff --git a/GamecraftModdingAPI/GamecraftModdingAPIPlugin.cs b/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs similarity index 63% rename from GamecraftModdingAPI/GamecraftModdingAPIPlugin.cs rename to GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs index 05db235..95541e5 100644 --- a/GamecraftModdingAPI/GamecraftModdingAPIPlugin.cs +++ b/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs @@ -1,13 +1,12 @@ using System; -using IllusionPlugin; using UnityEngine; using Harmony; using System.Reflection; -namespace GamecraftModdingAPI +namespace GamecraftModdingAPI.Tests { // unused by design - public class GamecraftModdingAPIPlugin //: IllusionPlugin.IEnhancedPlugin + public class GamecraftModdingAPIPluginTest //: IllusionPlugin.IEnhancedPlugin { public static HarmonyInstance harmony { get; protected set; } @@ -21,18 +20,12 @@ namespace GamecraftModdingAPI public void OnApplicationQuit() { - harmony.UnpatchAll(HarmonyID); - Debug.Log(Name + " shutdown complete"); + GamecraftModdingAPI.Main.Shutdown(); } public void OnApplicationStart() { - if (harmony == null) - { - harmony = HarmonyInstance.Create(HarmonyID); - harmony.PatchAll(Assembly.GetExecutingAssembly()); - } - Debug.Log(Name + " start & patch complete"); + GamecraftModdingAPI.Main.Init(); } public void OnFixedUpdate() { } diff --git a/GamecraftModdingAPI/Utility/ApiExclusiveGroups.cs b/GamecraftModdingAPI/Utility/ApiExclusiveGroups.cs new file mode 100644 index 0000000..b8ca08a --- /dev/null +++ b/GamecraftModdingAPI/Utility/ApiExclusiveGroups.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Svelto.ECS; + +namespace GamecraftModdingAPI.Utility +{ + static class ApiExclusiveGroups + { + public static readonly ExclusiveGroup eventsExclusiveGroup = new ExclusiveGroup(); + + public static uint eventID; + } +} diff --git a/GamecraftModdingAPI/Utility/Logging.cs b/GamecraftModdingAPI/Utility/Logging.cs new file mode 100644 index 0000000..8d71224 --- /dev/null +++ b/GamecraftModdingAPI/Utility/Logging.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GamecraftModdingAPI.Utility +{ + /// + /// Utility class to access Gamecraft's built-in logging capabilities. + /// The log is saved to %APPDATA%\..\LocalLow\FreeJam\Gamecraft\Player.Log + /// + static class Logging + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Log(string msg) + { + Svelto.Console.Log(msg); + } + + /// + /// Write a regular message to Gamecraft's log + /// + /// The object to log + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Log(object obj) + { + Svelto.Console.Log(obj.ToString()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LogDebug(string msg) + { + Svelto.Console.LogDebug(msg); + } + + /// + /// Write a debug message to Gamecraft's log + /// + /// The object to log + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LogDebug(object obj) + { + Svelto.Console.LogDebug(obj.ToString()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LogDebug(string msg, T extraDebug) + { + Svelto.Console.LogDebug(msg, extraDebug); + } + + /// + /// Write a debug message and object to Gamecraft's log + /// The reason this method exists in Svelto.Console is beyond my understanding + /// + /// The type of the extra debug object + /// The object to log + /// The extra object to log + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LogDebug(object obj, T extraDebug) + { + Svelto.Console.LogDebug(obj.ToString(), extraDebug); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LogError(string msg, Dictionary extraData = null) + { + Svelto.Console.LogError(msg, extraData); + } + + /// + /// Write an error message to Gamecraft's log + /// + /// The object to log + /// The extra data to pass to the ILogger + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LogError(object obj, Dictionary extraData = null) + { + Svelto.Console.LogError(obj.ToString(), extraData); + } + + /// + /// Write an exception to Gamecraft's log + /// + /// The exception to log + /// The extra data to pass to the ILogger. + /// This is automatically populated with "OuterException#" and "OuterStacktrace#" entries + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LogException(Exception e, Dictionary extraData = null) + { + Svelto.Console.LogException(e, extraData); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LogException(string msg, Exception e, Dictionary extraData = null) + { + Svelto.Console.LogException(msg, e, extraData); + } + + /// + /// Write an exception message to Gamecraft's log + /// + /// The object to log + /// The exception to log + /// The extra data to pass to the ILogger. + /// This is implemented similar to LogException(Exception e, Dictionary extraData)'s extraData + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LogException(object obj, Exception e, Dictionary extraData = null) + { + Svelto.Console.LogException(obj.ToString(), e, extraData); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LogWarning(string msg) + { + Svelto.Console.LogWarning(msg); + } + + /// + /// Write a warning message to Gamecraft's log + /// + /// The object to log + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LogWarning(object obj) + { + Svelto.Console.LogWarning(obj.ToString()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SystemLog(string msg) + { + Svelto.Console.SystemLog(msg); + } + + /// + /// Write a message to stdout (usually the terminal which is running, like Command Prompt or PowerShell) + /// + /// The object to log + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SystemLog(object obj) + { + Svelto.Console.SystemLog(obj.ToString()); + } + } +}