diff --git a/GamecraftModdingAPI/Blocks/Tweakable.cs b/GamecraftModdingAPI/Blocks/Tweakable.cs
new file mode 100644
index 0000000..920bbb6
--- /dev/null
+++ b/GamecraftModdingAPI/Blocks/Tweakable.cs
@@ -0,0 +1,102 @@
+using System;
+
+using Svelto.ECS;
+
+namespace GamecraftModdingAPI.Blocks
+{
+ ///
+ /// Common tweakable stats operations.
+ /// The functionality of this class works best in build mode.
+ ///
+ public static class Tweakable
+ {
+ private static TweakableEngine tweakableEngine = new TweakableEngine();
+
+ ///
+ /// Get the tweakable stat's value using a dynamic variable type.
+ /// This is similar to GetStat but without strong type enforcement.
+ /// This should be used in dynamically-typed languages like Python.
+ ///
+ /// The stat's value.
+ /// The block's id.
+ /// The stat's enumerated id.
+ public static dynamic GetStatD(uint blockID, TweakableStat stat)
+ {
+ return tweakableEngine.GetStatDynamic(blockID, stat);
+ }
+
+ ///
+ /// Get the tweakable stat's value.
+ /// If T is not the same type as the stat, an InvalidCastException will be thrown.
+ ///
+ /// The stat's value.
+ /// The block's id.
+ /// The stat's enumerated id.
+ /// The stat's type.
+ public static T GetStat(uint blockID, TweakableStat stat)
+ {
+ return tweakableEngine.GetStatAny(blockID, stat);
+ }
+
+ ///
+ /// Set the tweakable stat's value using dynamically-typed variables.
+ /// This is similar to SetStat but without strong type enforcement.
+ /// This should be used in dynamically-typed languages like Python.
+ ///
+ /// The stat's new value.
+ /// The block's id.
+ /// The stat's enumerated id.
+ /// The stat's new value.
+ public static dynamic SetStatD(uint blockID, TweakableStat stat, dynamic value)
+ {
+ return tweakableEngine.SetStatDynamic(blockID, stat, value);
+ }
+
+ ///
+ /// Set the tweakable stat's value.
+ /// If T is not the stat's actual type, an InvalidCastException will be thrown.
+ ///
+ /// The stat's new value.
+ /// The block's id.
+ /// The stat's enumerated id.
+ /// The stat's new value.
+ /// The stat's type.
+ public static T SetStat(uint blockID, TweakableStat stat, T value)
+ {
+ return tweakableEngine.SetStatAny(blockID, stat, value);
+ }
+
+ ///
+ /// Add another value to the tweakable stat's value using dynamically-typed variables.
+ /// This is similar to AddStat but without strong type enforcement.
+ /// This should be used in dynamically-typed languages like Python.
+ ///
+ /// The stat's new value.
+ /// The block's id.
+ /// The stat's enumerated id.
+ /// The value to be added to the stat.
+ public static dynamic AddStatD(uint blockID, TweakableStat stat, dynamic value)
+ {
+ return tweakableEngine.AddStatDynamic(blockID, stat, value);
+ }
+
+ ///
+ /// Add another value to the tweakable stat's value.
+ /// If T is not the stat's actual type, an InvalidCastException will be thrown.
+ ///
+ /// The stat's new value.
+ /// The block's id.
+ /// The stat's enumerated id.
+ /// The value to be added to the stat.
+ /// The stat's type.
+ public static T AddStat(uint blockID, TweakableStat stat, T value)
+ {
+ return tweakableEngine.AddStatAny(blockID, stat, value);
+ }
+
+ public static void Init()
+ {
+ GamecraftModdingAPI.Utility.GameEngineManager.AddGameEngine(tweakableEngine);
+ }
+ }
+}
diff --git a/GamecraftModdingAPI/Blocks/TweakableEngine.cs b/GamecraftModdingAPI/Blocks/TweakableEngine.cs
new file mode 100644
index 0000000..6d23f85
--- /dev/null
+++ b/GamecraftModdingAPI/Blocks/TweakableEngine.cs
@@ -0,0 +1,452 @@
+using System;
+using System.Reflection;
+
+using RobocraftX.Blocks;
+using Gamecraft.Wires;
+
+using GamecraftModdingAPI.Utility;
+using Svelto.ECS;
+
+namespace GamecraftModdingAPI.Blocks
+{
+ public class TweakableEngine : IApiEngine
+ {
+ public string Name { get; } = "GamecraftModdingAPITweakableGameEngine";
+
+ public IEntitiesDB entitiesDB { set; private get; }
+
+ public bool IsInGame = false;
+
+ public void Dispose()
+ {
+ IsInGame = false;
+ }
+
+ public void Ready()
+ {
+ IsInGame = true;
+ }
+
+ // Implementations for Tweakable static class
+
+ public T GetStatAny(EGID blockID, TweakableStat stat)
+ {
+ switch (stat)
+ {
+ case TweakableStat.TopSpeed:
+ if (entitiesDB.Exists(blockID))
+ {
+ return (T)(object)entitiesDB.QueryEntity(blockID).maxVelocity;
+ }
+ break;
+ case TweakableStat.Torque:
+ if (entitiesDB.Exists(blockID))
+ {
+ return (T)(object)entitiesDB.QueryEntity(blockID).maxForce;
+ }
+ break;
+ case TweakableStat.MaxExtension:
+ if (entitiesDB.Exists(blockID))
+ {
+ return (T)(object)entitiesDB.QueryEntity(blockID).maxDeviation;
+ }
+ break;
+ case TweakableStat.MinAngle:
+ if (entitiesDB.Exists(blockID))
+ {
+ return (T)(object)entitiesDB.QueryEntity(blockID).minDeviation;
+ }
+ break;
+ case TweakableStat.MaxAngle:
+ if (entitiesDB.Exists(blockID))
+ {
+ return (T)(object)entitiesDB.QueryEntity(blockID).maxDeviation;
+ }
+ break;
+ case TweakableStat.Reverse:
+ if (entitiesDB.Exists(blockID))
+ {
+ return (T)(object)entitiesDB.QueryEntity(blockID).reverse;
+ }
+ else if (entitiesDB.Exists(blockID))
+ {
+ return (T)(object)entitiesDB.QueryEntity(blockID).reverse;
+ }
+ break;
+ case TweakableStat.StartValue:
+ if (entitiesDB.Exists(blockID))
+ {
+ return (T)(object)entitiesDB.QueryEntity(blockID).startValue;
+ }
+ break;
+ }
+ return default(T);
+ }
+
+ public T GetStatAny(uint blockID, TweakableStat stat)
+ {
+ return GetStatAny(new EGID(blockID, BlockIdentifiers.OWNED_BLOCKS), stat);
+ }
+
+ public dynamic GetStatDynamic(EGID blockID, TweakableStat stat)
+ {
+ switch (stat)
+ {
+ case TweakableStat.TopSpeed:
+ if (entitiesDB.Exists(blockID))
+ {
+ return entitiesDB.QueryEntity(blockID).maxVelocity;
+ }
+ break;
+ case TweakableStat.Torque:
+ if (entitiesDB.Exists(blockID))
+ {
+ return entitiesDB.QueryEntity(blockID).maxForce;
+ }
+ break;
+ case TweakableStat.MaxExtension:
+ if (entitiesDB.Exists(blockID))
+ {
+ return entitiesDB.QueryEntity(blockID).maxDeviation;
+ }
+ break;
+ case TweakableStat.MinAngle:
+ if (entitiesDB.Exists(blockID))
+ {
+ return entitiesDB.QueryEntity(blockID).minDeviation;
+ }
+ break;
+ case TweakableStat.MaxAngle:
+ if (entitiesDB.Exists(blockID))
+ {
+ return entitiesDB.QueryEntity(blockID).maxDeviation;
+ }
+ break;
+ case TweakableStat.Reverse:
+ if (entitiesDB.Exists(blockID))
+ {
+ return entitiesDB.QueryEntity(blockID).reverse;
+ }
+ else if (entitiesDB.Exists(blockID))
+ {
+ return entitiesDB.QueryEntity(blockID).reverse;
+ }
+ break;
+ case TweakableStat.StartValue:
+ if (entitiesDB.Exists(blockID))
+ {
+ return entitiesDB.QueryEntity(blockID).startValue;
+ }
+ break;
+ }
+ return null;
+ }
+
+ public dynamic GetStatDynamic(uint blockID, TweakableStat stat)
+ {
+ return GetStatDynamic(new EGID(blockID, BlockIdentifiers.OWNED_BLOCKS), stat);
+ }
+
+ public T SetStatAny(EGID blockID, TweakableStat stat, T value)
+ {
+ switch (stat)
+ {
+ case TweakableStat.TopSpeed:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref MotorReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.maxVelocity = (float)(object)value;
+ return (T)(object)refStruct.maxVelocity;
+ }
+ break;
+ case TweakableStat.Torque:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref MotorReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.maxForce = (float)(object)value;
+ return (T)(object)refStruct.maxForce;
+ }
+ break;
+ case TweakableStat.MaxExtension:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref PistonReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.maxDeviation = (float)(object)value;
+ return (T)(object)refStruct.maxDeviation;
+ }
+ break;
+ case TweakableStat.MinAngle:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref ServoReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.minDeviation = (float)(object)value;
+ return (T)(object)refStruct.minDeviation;
+ }
+ break;
+ case TweakableStat.MaxAngle:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref ServoReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.maxDeviation = (float)(object)value;
+ return (T)(object)refStruct.maxDeviation;
+ }
+ break;
+ case TweakableStat.Reverse:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref MotorReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.reverse = (bool)(object)value;
+ return (T)(object)refStruct.reverse;
+ }
+ else if (entitiesDB.Exists(blockID))
+ {
+ ref ServoReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.reverse = (bool)(object)value;
+ return (T)(object)refStruct.reverse;
+ }
+ break;
+ case TweakableStat.StartValue:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref SignalGeneratorEntityStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.startValue = (float)(object)value;
+ return (T)(object)refStruct.startValue;
+ }
+ break;
+ }
+ return default(T);
+ }
+
+ public T SetStatAny(uint blockID, TweakableStat stat, T value)
+ {
+ return SetStatAny(new EGID(blockID, BlockIdentifiers.OWNED_BLOCKS), stat, value);
+ }
+
+ public dynamic SetStatDynamic(EGID blockID, TweakableStat stat, dynamic value)
+ {
+ switch (stat)
+ {
+ case TweakableStat.TopSpeed:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref MotorReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.maxVelocity = value;
+ return refStruct.maxVelocity;
+ }
+ break;
+ case TweakableStat.Torque:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref MotorReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.maxForce = value;
+ return refStruct.maxForce;
+ }
+ break;
+ case TweakableStat.MaxExtension:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref PistonReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.maxDeviation = value;
+ return refStruct.maxDeviation;
+ }
+ break;
+ case TweakableStat.MinAngle:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref ServoReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.minDeviation = value;
+ return refStruct.minDeviation;
+ }
+ break;
+ case TweakableStat.MaxAngle:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref ServoReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.maxDeviation = value;
+ return refStruct.maxDeviation;
+ }
+ break;
+ case TweakableStat.Reverse:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref MotorReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.reverse = value;
+ return refStruct.reverse;
+ }
+ else if (entitiesDB.Exists(blockID))
+ {
+ ref ServoReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.reverse = value;
+ return refStruct.reverse;
+ }
+ break;
+ case TweakableStat.StartValue:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref SignalGeneratorEntityStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.startValue = value;
+ return refStruct.startValue;
+ }
+ break;
+ }
+ return null;
+ }
+
+ public dynamic SetStatDynamic(uint blockID, TweakableStat stat, dynamic value)
+ {
+ return SetStatDynamic(new EGID(blockID, BlockIdentifiers.OWNED_BLOCKS), stat, value);
+ }
+
+ public T AddStatAny(EGID blockID, TweakableStat stat, T value)
+ {
+ switch (stat)
+ {
+ case TweakableStat.TopSpeed:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref MotorReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.maxVelocity += (float)(object)value;
+ return (T)(object)refStruct.maxVelocity;
+ }
+ break;
+ case TweakableStat.Torque:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref MotorReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.maxForce += (float)(object)value;
+ return (T)(object)refStruct.maxForce;
+ }
+ break;
+ case TweakableStat.MaxExtension:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref PistonReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.maxDeviation += (float)(object)value;
+ return (T)(object)refStruct.maxDeviation;
+ }
+ break;
+ case TweakableStat.MinAngle:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref ServoReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.minDeviation += (float)(object)value;
+ return (T)(object)refStruct.minDeviation;
+ }
+ break;
+ case TweakableStat.MaxAngle:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref ServoReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.maxDeviation += (float)(object)value;
+ return (T)(object)refStruct.maxDeviation;
+ }
+ break;
+ case TweakableStat.Reverse:
+ // '+' is associated with logical OR in some fields, so it technically isn't invalid to "add" booleans
+ if (entitiesDB.Exists(blockID))
+ {
+ ref MotorReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.reverse = refStruct.reverse || (bool)(object)value;
+ return (T)(object)refStruct.reverse;
+ }
+ else if (entitiesDB.Exists(blockID))
+ {
+ ref ServoReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.reverse = refStruct.reverse || (bool)(object)value;
+ return (T)(object)refStruct.reverse;
+ }
+ break;
+ case TweakableStat.StartValue:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref SignalGeneratorEntityStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.startValue += (float)(object)value;
+ return (T)(object)refStruct.startValue;
+ }
+ break;
+ }
+ return default(T);
+ }
+
+ public T AddStatAny(uint blockID, TweakableStat stat, T value)
+ {
+ return AddStatAny(new EGID(blockID, BlockIdentifiers.OWNED_BLOCKS), stat, value);
+ }
+
+ public dynamic AddStatDynamic(EGID blockID, TweakableStat stat, dynamic value)
+ {
+ switch (stat)
+ {
+ case TweakableStat.TopSpeed:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref MotorReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.maxVelocity += value;
+ return refStruct.maxVelocity;
+ }
+ break;
+ case TweakableStat.Torque:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref MotorReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.maxForce += value;
+ return refStruct.maxForce;
+ }
+ break;
+ case TweakableStat.MaxExtension:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref PistonReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.maxDeviation += value;
+ return refStruct.maxDeviation;
+ }
+ break;
+ case TweakableStat.MinAngle:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref ServoReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.minDeviation += value;
+ return refStruct.minDeviation;
+ }
+ break;
+ case TweakableStat.MaxAngle:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref ServoReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.maxDeviation += value;
+ return refStruct.maxDeviation;
+ }
+ break;
+ case TweakableStat.Reverse:
+ // '+' is associated with logical OR in some fields, so it technically isn't invalid to "add" booleans
+ if (entitiesDB.Exists(blockID))
+ {
+ ref MotorReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.reverse = refStruct.reverse || value;
+ return refStruct.reverse;
+ }
+ else if (entitiesDB.Exists(blockID))
+ {
+ ref ServoReadOnlyStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.reverse = refStruct.reverse || value;
+ return refStruct.reverse;
+ }
+ break;
+ case TweakableStat.StartValue:
+ if (entitiesDB.Exists(blockID))
+ {
+ ref SignalGeneratorEntityStruct refStruct = ref entitiesDB.QueryEntity(blockID);
+ refStruct.startValue += value;
+ return refStruct.startValue;
+ }
+ break;
+ }
+ return null;
+ }
+
+ public dynamic AddStatDynamic(uint blockID, TweakableStat stat, dynamic value)
+ {
+ return AddStatDynamic(new EGID(blockID, BlockIdentifiers.OWNED_BLOCKS), stat, value);
+ }
+ }
+}
diff --git a/GamecraftModdingAPI/Blocks/TweakableStat.cs b/GamecraftModdingAPI/Blocks/TweakableStat.cs
new file mode 100644
index 0000000..18f5bad
--- /dev/null
+++ b/GamecraftModdingAPI/Blocks/TweakableStat.cs
@@ -0,0 +1,14 @@
+using System;
+namespace GamecraftModdingAPI.Blocks
+{
+ public enum TweakableStat
+ {
+ TopSpeed, // MotorReadOnlyStruct
+ Torque, // MotorReadOnlyStruct
+ MaxExtension, // PistonReadOnlyStruct
+ MinAngle, // ServoReadOnlyStruct
+ MaxAngle, // ServoReadOnlyStruct
+ Reverse, // MotorReadOnlyStruct or ServoReadOnlyStruct
+ StartValue, // SignalGeneratorEntityStruct
+ }
+}
diff --git a/GamecraftModdingAPI/GamecraftModdingAPI.csproj b/GamecraftModdingAPI/GamecraftModdingAPI.csproj
index a96974d..f7a0abf 100644
--- a/GamecraftModdingAPI/GamecraftModdingAPI.csproj
+++ b/GamecraftModdingAPI/GamecraftModdingAPI.csproj
@@ -3,12 +3,16 @@
net472
true
- 0.1.3.0
+ 0.1.4.0
Exmods
GNU General Public Licence 3+
https://git.exmods.org/modtainers/GamecraftModdingAPI
en-CA
+
+
+
+
@@ -543,6 +547,7 @@
..\ref\Gamecraft_Data\Managed\VisualProfiler.dll
+
diff --git a/GamecraftModdingAPI/Main.cs b/GamecraftModdingAPI/Main.cs
index 2c51872..5294c75 100644
--- a/GamecraftModdingAPI/Main.cs
+++ b/GamecraftModdingAPI/Main.cs
@@ -64,6 +64,7 @@ namespace GamecraftModdingAPI
Blocks.Rotation.Init();
Blocks.Signals.Init();
Blocks.Placement.Init();
+ Blocks.Tweakable.Init();
Logging.MetaLog($"{currentAssembly.GetName().Name} v{currentAssembly.GetName().Version} initialized");
}
diff --git a/doxygen.conf b/doxygen.conf
index d163852..1d0f155 100644
--- a/doxygen.conf
+++ b/doxygen.conf
@@ -38,7 +38,7 @@ PROJECT_NAME = "GamecraftModdingAPI"
# could be handy for archiving the generated documentation or if some version
# control system is used.
-PROJECT_NUMBER = "v0.1.3.0"
+PROJECT_NUMBER = "v0.1.4.0"
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a