diff --git a/GamecraftModdingAPI/Blocks/BlockIdentifiers.cs b/GamecraftModdingAPI/Blocks/BlockIdentifiers.cs index 3c433e4..bd0d8a6 100644 --- a/GamecraftModdingAPI/Blocks/BlockIdentifiers.cs +++ b/GamecraftModdingAPI/Blocks/BlockIdentifiers.cs @@ -33,6 +33,8 @@ namespace GamecraftModdingAPI.Blocks public static ExclusiveGroup SPAWN_POINTS_DISABLED { get { return CommonExclusiveGroups.SPAWN_POINTS_DISABLED_GROUP; } } + public static ExclusiveGroup OUTPUT_SIGNAL_CHANNELS { get { return CommonExclusiveGroups.CHANNEL_OUTPUT_SIGNAL_GROUPS; } } + /// /// The ID of the most recently placed block /// diff --git a/GamecraftModdingAPI/Blocks/MovementEngine.cs b/GamecraftModdingAPI/Blocks/MovementEngine.cs index f51002b..f501513 100644 --- a/GamecraftModdingAPI/Blocks/MovementEngine.cs +++ b/GamecraftModdingAPI/Blocks/MovementEngine.cs @@ -81,7 +81,7 @@ namespace GamecraftModdingAPI.Blocks public bool IsBuildMode() { - return this.entitiesDB.QueryUniqueEntity(SimulationModeStateExclusiveGroups.GAME_STATE_GROUP).simulationMode == SimulationMode.Build; + return SimModeUtil.IsBuildMode(this.entitiesDB); } } } diff --git a/GamecraftModdingAPI/Blocks/Rotation.cs b/GamecraftModdingAPI/Blocks/Rotation.cs index 4cf1a00..5f13663 100644 --- a/GamecraftModdingAPI/Blocks/Rotation.cs +++ b/GamecraftModdingAPI/Blocks/Rotation.cs @@ -21,7 +21,7 @@ namespace GamecraftModdingAPI.Blocks /// The block's id /// The rotation amount around the x,y,z-planes /// - public static bool MoveBlock(uint id, float3 vector) + public static bool RotateBlock(uint id, float3 vector) { if (rotationEngine.IsInGame && rotationEngine.IsBuildMode()) { @@ -37,11 +37,11 @@ namespace GamecraftModdingAPI.Blocks /// The starting block's id /// The rotation around the x,y,z-planes /// - public static bool MoveRotateBlocks(uint id, float3 vector) + public static bool RotateConnectedBlocks(uint id, float3 vector) { if (rotationEngine.IsInGame && rotationEngine.IsBuildMode()) { - rotationEngine.MoveConnectedBlocks(id, vector); + rotationEngine.RotateConnectedBlocks(id, vector); return true; } return false; diff --git a/GamecraftModdingAPI/Blocks/RotationEngine.cs b/GamecraftModdingAPI/Blocks/RotationEngine.cs index 20a56df..079d08f 100644 --- a/GamecraftModdingAPI/Blocks/RotationEngine.cs +++ b/GamecraftModdingAPI/Blocks/RotationEngine.cs @@ -73,14 +73,14 @@ namespace GamecraftModdingAPI.Blocks } - public float3 MoveConnectedBlocks(uint blockID, Vector3 vector) + public float3 RotateConnectedBlocks(uint blockID, Vector3 vector) { throw new NotImplementedException(); } public bool IsBuildMode() { - return this.entitiesDB.QueryUniqueEntity(SimulationModeStateExclusiveGroups.GAME_STATE_GROUP).simulationMode == SimulationMode.Build; + return SimModeUtil.IsBuildMode(this.entitiesDB); } } } diff --git a/GamecraftModdingAPI/Blocks/SignalEngine.cs b/GamecraftModdingAPI/Blocks/SignalEngine.cs new file mode 100644 index 0000000..b16a10b --- /dev/null +++ b/GamecraftModdingAPI/Blocks/SignalEngine.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using RobocraftX; +using RobocraftX.Blocks; +using RobocraftX.Blocks.Ghost; +using RobocraftX.Common; +using RobocraftX.Multiplayer; +using RobocraftX.SimulationModeState; +using RobocraftX.UECS; +using Unity.Entities; +using Svelto.Context; +using Svelto.ECS; +using Svelto.ECS.EntityStructs; +using Unity.Transforms; +using Unity.Mathematics; +using UnityEngine; + +using GamecraftModdingAPI.Utility; + +namespace GamecraftModdingAPI.Blocks +{ + /// + /// Engine which executes block movement actions + /// + public class SignalEngine : IApiEngine + { + public string Name { get; } = "GamecraftModdingAPISignalGameEngine"; + + public IEntitiesDB entitiesDB { set; private get; } + + public bool IsInGame = false; + + private Stack cubesStack = new Stack(); + private bool stackInUse = false; + + private Gamecraft.DataStructures.FasterList cubesList = new Gamecraft.DataStructures.FasterList(); + private bool listInUse = false; + + public void Dispose() + { + IsInGame = false; + } + + public void Ready() + { + IsInGame = true; + } + + // implementations for Signal static class + + public bool SetSignal(uint blockID, uint channel, float signal, out EGID clusterID) + { + clusterID = GetClusterEGID(blockID, channel); + return SetSignal(clusterID, signal); + } + + public bool SetSignal(EGID clusterID, float signal) + { + if (entitiesDB.Exists(clusterID)) + { + entitiesDB.QueryEntity(clusterID).outputSignal = signal; + return true; + } + return false; + } + + public float AddSignal(uint blockID, uint channel, float signal, out EGID clusterID, bool clamp = true) + { + clusterID = GetClusterEGID(blockID, channel); + return AddSignal(clusterID, signal, clamp); + } + + public float AddSignal(EGID clusterID, float signal, bool clamp=true) + { + if (entitiesDB.Exists(clusterID)) + { + ref ChannelOutputSignalDataStruct chanOutSig = ref entitiesDB.QueryEntity(clusterID); + chanOutSig.outputSignal += signal; + if (clamp) + { + if (chanOutSig.outputSignal > Signals.POSITIVE_HIGH) + { + chanOutSig.outputSignal = Signals.POSITIVE_HIGH; + } + else if (chanOutSig.outputSignal < Signals.NEGATIVE_HIGH) + { + chanOutSig.outputSignal = Signals.NEGATIVE_HIGH; + } + return chanOutSig.outputSignal; + } + } + return signal; + } + + public float GetSignal(uint blockID, uint channel, out EGID clusterID) + { + clusterID = GetClusterEGID(blockID, channel); + return GetSignal(clusterID); + } + + public float GetSignal(EGID clusterID) + { + if (entitiesDB.Exists(clusterID)) + { + return entitiesDB.QueryEntity(clusterID).outputSignal; + } + return 0f; + } + + public EGID GetClusterEGID(uint blockID, uint channel) + { + uint[] connectedCubeIDs = GetConductivelyConnectedBlocks(blockID); + uint index; + ElectricityEntityStruct[] structs; + Logging.CommandLog($"Found {connectedCubeIDs.Length} connected cubes"); + for (int i = 0; i < connectedCubeIDs.Length; i++) + { + if (entitiesDB.TryQueryEntitiesAndIndex(new EGID(connectedCubeIDs[i], CommonExclusiveGroups.OWNED_BLOCKS_GROUP), out index, out structs) + || entitiesDB.TryQueryEntitiesAndIndex(new EGID(connectedCubeIDs[i], CommonExclusiveGroups.FUNCTIONAL_BLOCK_PART_GROUP), out index, out structs)) + { + ref ConductiveClusterID clusterId = ref entitiesDB.QueryEntity(structs[index].ID).clusterId; + uint operatingChannel = entitiesDB.QueryEntity(structs[index].ID).operatingChannel; + Logging.CommandLog($"Channel {operatingChannel} found"); + EGID eGID = new EGID(channel, BlockIdentifiers.OUTPUT_SIGNAL_CHANNELS + clusterId.ID); + if (clusterId.initialized && clusterId.isConductive && entitiesDB.Exists(eGID)) + { + return eGID; + } + } + } + // failsafe; not 100% reliable + foreach (ref ConductiveClusterIdStruct clusterIdStruct in entitiesDB.QueryEntities(CommonExclusiveGroups.FUNCTIONAL_CUBES_IN_BOTH_SIM_AND_BUILD)) + { + EGID eGID = new EGID(channel, BlockIdentifiers.OUTPUT_SIGNAL_CHANNELS + clusterIdStruct.clusterId.ID); + if (clusterIdStruct.clusterId.initialized && clusterIdStruct.clusterId.isConductive && entitiesDB.Exists(eGID)) + { + return eGID; + } + } + return default; + } + + private uint[] GetConductivelyConnectedBlocks(uint blockID) + { + if (!(stackInUse || listInUse)) + { + stackInUse = true; + listInUse = true; + cubesStack.Clear(); + cubesList.FastClear(); + ConnectedCubesUtility.TreeTraversal.GetConnectedCubes(entitiesDB, blockID, cubesStack, cubesList, (in GridConnectionsEntityStruct g) => { return false; }); + uint[] res = cubesList.ToArray(); + stackInUse = false; + listInUse = false; + return res; + } + Stack cubeStack = new Stack(); + Gamecraft.DataStructures.FasterList cubeList = new Gamecraft.DataStructures.FasterList(); + ConnectedCubesUtility.TreeTraversal.GetConnectedCubes(entitiesDB, blockID, cubesStack, cubesList, (in GridConnectionsEntityStruct g) => { return false; }); + return cubeList.ToArray(); + } + + public bool IsSimulationMode() + { + return SimModeUtil.IsSimulationMode(this.entitiesDB); + } + } +} diff --git a/GamecraftModdingAPI/Blocks/Signals.cs b/GamecraftModdingAPI/Blocks/Signals.cs new file mode 100644 index 0000000..58c2e7c --- /dev/null +++ b/GamecraftModdingAPI/Blocks/Signals.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Svelto.ECS; + +using GamecraftModdingAPI.Utility; + +namespace GamecraftModdingAPI.Blocks +{ + /// + /// [EXPERIMENTAL] Common block signal operations + /// + public static class Signals + { + // Signal constants + public static readonly float HIGH = 1.0f; + public static readonly float POSITIVE_HIGH = HIGH; + public static readonly float NEGATIVE_HIGH = -1.0f; + public static readonly float LOW = 0.0f; + + private static SignalEngine signalEngine = new SignalEngine(); + + /// + /// Set a channel to a value in the block's conductive block cluster + /// + /// The block's id + /// The channel (1 to 99) + /// The signal value (-1 to 1; not enforced) + public static void SetSignalConnectedBlocks(uint id, uint channel, float signal) + { + if (signalEngine.IsInGame && signalEngine.IsSimulationMode()) + { + signalEngine.SetSignal(id, channel, signal, out EGID _); + } + } + + /// + /// Set a conductive cluster channel to a value + /// + /// The channel cluster's id + /// The signal value (-1 to 1; not enforced) + public static void SetSignalCluster(EGID clusterID, float signal) + { + if (signalEngine.IsInGame && signalEngine.IsSimulationMode()) + { + signalEngine.SetSignal(clusterID, signal); + } + } + + /// + /// Add a value to a channel signal in the block's conductive block cluster + /// + /// The block's id + /// The channel (1 to 99) + /// The signal value to add + /// Whether to clamp the resulting signal value between -1 and 1 + public static void AddSignalConnectedBlocks(uint id, uint channel, float signal, bool clamp = true) + { + if (signalEngine.IsInGame && signalEngine.IsSimulationMode()) + { + signalEngine.AddSignal(id, channel, signal, out EGID _, clamp); + } + } + + /// + /// Add a value to a conductive cluster channel + /// + /// The channel cluster's id + /// The signal value to add + /// Whether to clamp the resulting signal value between -1 and 1 + public static void AddSignalCluster(EGID clusterID, float signal, bool clamp = true) + { + if (signalEngine.IsInGame && signalEngine.IsSimulationMode()) + { + signalEngine.AddSignal(clusterID, signal, clamp); + } + } + + /// + /// Get a channel's signal value from the block's conductive block cluster + /// + /// The block's id + /// The channel (1 to 99) + /// The signal value + public static float GetSignalConnectedBlocks(uint id, uint channel) + { + if (signalEngine.IsInGame && signalEngine.IsSimulationMode()) + { + return signalEngine.GetSignal(id, channel, out EGID _); + } + return 0f; + } + + /// + /// Get a conductive cluster channel's signal value + /// + /// The channel cluster's id + /// The signal value + public static float GetSignalCluster(EGID clusterID) + { + if (signalEngine.IsInGame && signalEngine.IsSimulationMode()) + { + return signalEngine.GetSignal(clusterID); + } + return 0f; + } + + public static EGID GetClusterID(uint id, uint channel) + { + return signalEngine.GetClusterEGID(id, channel); + } + + + public static void Init() + { + GameEngineManager.AddGameEngine(signalEngine); + } + } +} diff --git a/GamecraftModdingAPI/Commands/ICustomCommandEngine.cs b/GamecraftModdingAPI/Commands/ICustomCommandEngine.cs index 0b1d305..b971390 100644 --- a/GamecraftModdingAPI/Commands/ICustomCommandEngine.cs +++ b/GamecraftModdingAPI/Commands/ICustomCommandEngine.cs @@ -13,7 +13,7 @@ namespace GamecraftModdingAPI.Commands /// /// Engine interface to handle command operations /// - public interface ICustomCommandEngine :IApiEngine + public interface ICustomCommandEngine : IApiEngine { /// /// The command's description, shown in command help messages diff --git a/GamecraftModdingAPI/Events/SimpleEventHandlerEngine.cs b/GamecraftModdingAPI/Events/SimpleEventHandlerEngine.cs index 267a603..2afffa6 100644 --- a/GamecraftModdingAPI/Events/SimpleEventHandlerEngine.cs +++ b/GamecraftModdingAPI/Events/SimpleEventHandlerEngine.cs @@ -60,7 +60,8 @@ namespace GamecraftModdingAPI.Events /// The operation to do when the event is destroyed (if applicable) /// The type of event to handle /// The name of the engine - public SimpleEventHandlerEngine(Action activated, Action removed, object type, string name) + /// A useless parameter to use to avoid Python overload resolution errors + public SimpleEventHandlerEngine(Action activated, Action removed, object type, string name, bool simple = true) : this((IEntitiesDB _) => { activated.Invoke(); }, (IEntitiesDB _) => { removed.Invoke(); }, type, name) { } /// diff --git a/GamecraftModdingAPI/Main.cs b/GamecraftModdingAPI/Main.cs index 8c56df2..40b82b7 100644 --- a/GamecraftModdingAPI/Main.cs +++ b/GamecraftModdingAPI/Main.cs @@ -9,6 +9,7 @@ using Harmony; using GamecraftModdingAPI.Utility; using GamecraftModdingAPI.Events; +using GamecraftModdingAPI.Tasks; namespace GamecraftModdingAPI { @@ -52,6 +53,7 @@ namespace GamecraftModdingAPI Logging.MetaDebugLog($"Initializing Blocks"); Blocks.Movement.Init(); Blocks.Rotation.Init(); + Blocks.Signals.Init(); Logging.MetaLog($"{currentAssembly.GetName().Name} v{currentAssembly.GetName().Version} initialized"); } @@ -62,8 +64,15 @@ namespace GamecraftModdingAPI /// public static void Shutdown() { + if (!IsInitialized) + { + Logging.LogWarning("GamecraftModdingAPI.Main.Shutdown() called but API is not initialized!"); + return; + } + Scheduler.Dispose(); var currentAssembly = Assembly.GetExecutingAssembly(); harmony.UnpatchAll(currentAssembly.GetName().Name); + harmony = null; Logging.MetaLog($"{currentAssembly.GetName().Name} v{currentAssembly.GetName().Version} shutdown"); } } diff --git a/GamecraftModdingAPI/Tasks/ISchedulable.cs b/GamecraftModdingAPI/Tasks/ISchedulable.cs new file mode 100644 index 0000000..0b04a3e --- /dev/null +++ b/GamecraftModdingAPI/Tasks/ISchedulable.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Svelto.Tasks; + +namespace GamecraftModdingAPI.Tasks +{ + /// + /// Interface for asynchronous tasks + /// + public interface ISchedulable + { + /// + /// Asynchronous task runner + /// + /// A yield-ed Svelto.Tasks-compatible object + IEnumerator Run(); + } +} diff --git a/GamecraftModdingAPI/Tasks/Once.cs b/GamecraftModdingAPI/Tasks/Once.cs new file mode 100644 index 0000000..a3dc20f --- /dev/null +++ b/GamecraftModdingAPI/Tasks/Once.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Svelto.Tasks; +using Svelto.Tasks.Enumerators; + +namespace GamecraftModdingAPI.Tasks +{ + /// + /// An asynchronous task to be performed once + /// + public class Once : ISchedulable + { + private Action task; + + private float delay; + public IEnumerator Run() + { + yield return new WaitForSecondsEnumerator(delay).Continue(); + task(); + yield return Yield.It; + } + + /// + /// Construct a single-run task + /// + /// The task to run once + /// The delay (in seconds) before the task is run + public Once(Action task, float after = 0.0f) + { + this.task = task; + this.delay = after; + } + } +} diff --git a/GamecraftModdingAPI/Tasks/Repeatable.cs b/GamecraftModdingAPI/Tasks/Repeatable.cs new file mode 100644 index 0000000..60d3026 --- /dev/null +++ b/GamecraftModdingAPI/Tasks/Repeatable.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Svelto.Tasks; +using Svelto.Tasks.Enumerators; + +namespace GamecraftModdingAPI.Tasks +{ + /// + /// An asynchronous repeating task + /// + public class Repeatable : ISchedulable + { + /// + /// Determines if the task should continue to repeat + /// + /// Whether the task should run again (true) or end (false) + public delegate bool ShouldContinue(); + + private ShouldContinue shouldContinue; + + private Action task; + + private float delay; + + public IEnumerator Run() + { + while (shouldContinue()) + { + task(); + yield return new WaitForSecondsEnumerator(delay).Continue(); + } + yield return Yield.It; + } + + /// + /// Construct a repeating task + /// + /// The task to repeat + /// The check to determine if the task should run again + /// The time to wait between repeats (in seconds) + public Repeatable(Action task, ShouldContinue shouldContinue, float delay = 0.0f) + { + this.task = task; + this.shouldContinue = shouldContinue; + this.delay = delay; + } + + /// + /// Construct a repeating task + /// + /// The task to repeat + /// The amount of times to repeat + /// The time to wait between repeats (in seconds) + public Repeatable(Action task, int count, float delay = 0.0f) + { + this.task = task; + this.shouldContinue = () => { return count-- != 0; }; + this.delay = delay; + } + } +} diff --git a/GamecraftModdingAPI/Tasks/Scheduler.cs b/GamecraftModdingAPI/Tasks/Scheduler.cs new file mode 100644 index 0000000..523329c --- /dev/null +++ b/GamecraftModdingAPI/Tasks/Scheduler.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Svelto.Tasks.Lean; +using Svelto.Tasks.ExtraLean; + +namespace GamecraftModdingAPI.Tasks +{ + public static class Scheduler + { + public static Svelto.Tasks.Lean.Unity.UpdateMonoRunner leanRunnerUI + { + get + { + return RobocraftX.Schedulers.Lean.UIScheduler; + } + } + + public static Svelto.Tasks.ExtraLean.Unity.UpdateMonoRunner extraLeanRunnerUI + { + get + { + return RobocraftX.Schedulers.ExtraLean.UIScheduler; + } + } + + public static readonly Svelto.Tasks.ExtraLean.Unity.UpdateMonoRunner extraLeanRunner = new Svelto.Tasks.ExtraLean.Unity.UpdateMonoRunner("GamecraftModdingAPIExtraLean"); + + public static readonly Svelto.Tasks.Lean.Unity.UpdateMonoRunner leanRunner = new Svelto.Tasks.Lean.Unity.UpdateMonoRunner("GamecraftModdingAPILean"); + + public static void Schedule(ISchedulable toRun, bool extraLean = false) + { + if (extraLean) + { + toRun.Run().RunOn(extraLeanRunner); + } + else + { + toRun.Run().RunOn(leanRunner); + } + + } + + public static void Dispose() + { + leanRunner.Stop(); + extraLeanRunner.Stop(); + leanRunner.Dispose(); + extraLeanRunner.Dispose(); + } + } +} diff --git a/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs b/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs index c8e4dea..3cbb99d 100644 --- a/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs +++ b/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs @@ -2,6 +2,11 @@ using System.Reflection; using Harmony; +// test +using Svelto.ECS; +using RobocraftX.Blocks; +using RobocraftX.Common; +using RobocraftX.SimulationModeState; using GamecraftModdingAPI.Commands; using GamecraftModdingAPI.Events; @@ -94,6 +99,27 @@ namespace GamecraftModdingAPI.Tests public void OnLevelWasLoaded(int level) { } - public void OnUpdate() { } + public void OnUpdate() + { + /* + if (db != null && signalStrength != 0.0f && db.QueryUniqueEntity(SimulationModeStateExclusiveGroups.GAME_STATE_GROUP).simulationMode == SimulationMode.Simulation) + { + //GamecraftModdingAPI.Utility.Logging.MetaDebugLog("Simulation frame update"); + foreach (ref ElectricityEntityStruct powah in db.QueryEntities(CommonExclusiveGroups.FUNCTIONAL_CUBES_IN_BOTH_SIM_AND_BUILD)) + { + ref ConductiveClusterID clusterId = ref db.QueryEntity(powah.ID).clusterId; + GamecraftModdingAPI.Utility.Logging.MetaDebugLog($"ID {powah.ID.entityID} unmodded values: Power* {powah.powerMultiplier} Conductivity* {powah.conductivityMultiplier} Output* {powah.outputSignalMultiplier}"); + uint operatingChannel = db.QueryEntity(powah.ID).operatingChannel; + if (operatingChannel != 0) + { + EGID eGID = new EGID(operatingChannel, CommonExclusiveGroups.CHANNEL_OUTPUT_SIGNAL_GROUPS + clusterId.ID); + if (db.Exists(eGID)) + { + db.QueryEntity(eGID).outputSignal = signalStrength; + } + } + } + }*/ + } } } diff --git a/GamecraftModdingAPI/Utility/Logging.cs b/GamecraftModdingAPI/Utility/Logging.cs index c7ac989..28cc6fc 100644 --- a/GamecraftModdingAPI/Utility/Logging.cs +++ b/GamecraftModdingAPI/Utility/Logging.cs @@ -167,7 +167,7 @@ namespace GamecraftModdingAPI.Utility public static void MetaLog(object obj) { var method = (new StackTrace()).GetFrame(1).GetMethod(); - Log($"[{DateTime.Now.ToString()}][{method.DeclaringType.Name}.{method.Name}]{obj.ToString()}"); + Log($"[{DateTime.Now.ToString()}][{method.DeclaringType.FullName}.{method.Name}]{obj.ToString()}"); } // CLI logging