diff --git a/GamecraftModdingAPI/Block.cs b/GamecraftModdingAPI/Block.cs index 88fcc3d..11a7ddc 100644 --- a/GamecraftModdingAPI/Block.cs +++ b/GamecraftModdingAPI/Block.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; using Svelto.ECS; using Svelto.ECS.EntityStructs; @@ -11,179 +12,209 @@ using GamecraftModdingAPI.Utility; namespace GamecraftModdingAPI { - /// - /// A single (perhaps scaled) block. Properties may return default values if the block is removed and then setting them is ignored. - /// - public class Block - { - private static readonly PlacementEngine PlacementEngine = new PlacementEngine(); - private static readonly MovementEngine MovementEngine = new MovementEngine(); - private static readonly RotationEngine RotationEngine = new RotationEngine(); - private static readonly RemovalEngine RemovalEngine = new RemovalEngine(); - private static readonly BlockEngine BlockEngine = new BlockEngine(); + /// + /// A single (perhaps scaled) block. Properties may return default values if the block is removed and then setting them is ignored. + /// + public class Block + { + protected static readonly PlacementEngine PlacementEngine = new PlacementEngine(); + protected static readonly MovementEngine MovementEngine = new MovementEngine(); + protected static readonly RotationEngine RotationEngine = new RotationEngine(); + protected static readonly RemovalEngine RemovalEngine = new RemovalEngine(); + protected static readonly BlockEngine BlockEngine = new BlockEngine(); - /// - /// Place a new block at the given position. If scaled, position means the center of the block. The default block size is 0.2 in terms of position. - /// Place blocks next to each other to connect them. - /// The placed block will be a complete block with a placement grid and collision which will be saved along with the game. - /// - /// The block's type - /// The block's color - /// The block color's darkness (0-9) - 0 is default color - /// The block's position in the grid - default block size is 0.2 - /// The block's rotation in degrees - /// The block's uniform scale - default scale is 1 (with 0.2 width) - /// The block's non-uniform scale - 0 means is used - /// The player who placed the block - /// The placed block or null if failed - public static Block PlaceNew(BlockIDs block, float3 position, - float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0, - int uscale = 1, float3 scale = default, Player player = null) - { - if (PlacementEngine.IsInGame && GameState.IsBuildMode()) - { - try - { - return new Block(PlacementEngine.PlaceBlock(block, color, darkness, - position, uscale, scale, player, rotation)); - } - catch (Exception e) - { - Logging.MetaDebugLog(e); - } - } + /// + /// Place a new block at the given position. If scaled, position means the center of the block. The default block size is 0.2 in terms of position. + /// Place blocks next to each other to connect them. + /// The placed block will be a complete block with a placement grid and collision which will be saved along with the game. + /// + /// The block's type + /// The block's color + /// The block color's darkness (0-9) - 0 is default color + /// The block's position in the grid - default block size is 0.2 + /// The block's rotation in degrees + /// The block's uniform scale - default scale is 1 (with 0.2 width) + /// The block's non-uniform scale - 0 means is used + /// The player who placed the block + /// The placed block or null if failed + public static Block PlaceNew(BlockIDs block, float3 position, + float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0, + int uscale = 1, float3 scale = default, Player player = null) + { + if (PlacementEngine.IsInGame && GameState.IsBuildMode()) + { + try + { + return new Block(PlacementEngine.PlaceBlock(block, color, darkness, + position, uscale, scale, player, rotation)); + } + catch (Exception e) + { + Logging.MetaDebugLog(e); + } + } - return null; - } + return null; + } - /// - /// Returns the most recently placed block. - /// - /// The block object - public static Block GetLastPlacedBlock() - { - return new Block(BlockIdentifiers.LatestBlockID); - } + /// + /// Returns the most recently placed block. + /// + /// The block object + public static Block GetLastPlacedBlock() + { + return new Block(BlockIdentifiers.LatestBlockID); + } - public Block(EGID id) - { - Id = id; - } + public Block(EGID id) + { + Id = id; + } - public Block(uint id) - { - Id = new EGID(id, CommonExclusiveGroups.OWNED_BLOCKS_GROUP); - } - - public EGID Id { get; } + public Block(uint id) + { + Id = new EGID(id, CommonExclusiveGroups.OWNED_BLOCKS_GROUP); + } - /// - /// The block's current position or zero if the block no longer exists. - /// A block is 0.2 wide by default in terms of position. - /// - public float3 Position - { - get => Exists ? MovementEngine.GetPosition(Id.entityID) : float3.zero; - set - { - if (Exists) MovementEngine.MoveBlock(Id.entityID, value); - } - } + protected static void Sync() + { + DeterministicStepCompositionRootPatch.SubmitEntitiesNow(); + } - /// - /// The block's current rotation in degrees or zero if the block doesn't exist. - /// - public float3 Rotation - { - get => Exists ? RotationEngine.GetRotation(Id.entityID) : float3.zero; - set - { - if (Exists) RotationEngine.RotateBlock(Id.entityID, value); - } - } + public EGID Id { get; } - /// - /// The block's non-uniform scale or zero if the block's invalid. Independent of the uniform scaling. - /// - public float3 Scale - { - get => BlockEngine.GetBlockInfo(Id)?.scale ?? float3.zero; - set - { - var def = new ScalingEntityStruct(); - BlockEngine.GetBlockInfo(Id, ref def).scale = value; - } - } + /// + /// The block's current position or zero if the block no longer exists. + /// A block is 0.2 wide by default in terms of position. + /// + public float3 Position + { + get => Exists ? MovementEngine.GetPosition(Id.entityID) : float3.zero; + set + { + if (Exists) MovementEngine.MoveBlock(Id.entityID, value); + } + } - /// - /// The block's uniform scale or zero if the block's invalid. Also sets the non-uniform scale. - /// - public int UniformScale - { - get => BlockEngine.GetBlockInfo(Id)?.desiredScaleFactor ?? 0; - set - { - var def = new BlockPlacementScaleEntityStruct(); - ref var scaleStruct = ref BlockEngine.GetBlockInfo(Id, ref def); - scaleStruct.blockPlacementHeight = scaleStruct.blockPlacementWidth = - scaleStruct.desiredScaleFactor = scaleStruct.snapGridScale = value; - Scale = new float3(value, value, value); - } - } + /// + /// The block's current rotation in degrees or zero if the block doesn't exist. + /// + public float3 Rotation + { + get => Exists ? RotationEngine.GetRotation(Id.entityID) : float3.zero; + set + { + if (Exists) RotationEngine.RotateBlock(Id.entityID, value); + } + } - /// - /// The block's type (ID). Returns BlockIDs.Invalid if the block doesn't exist anymore. - /// - public BlockIDs Type => (BlockIDs) (BlockEngine.GetBlockInfo(Id)?.DBID ?? ushort.MaxValue); + /// + /// The block's non-uniform scale or zero if the block's invalid. Independent of the uniform scaling. + /// + public float3 Scale + { + get => BlockEngine.GetBlockInfo(Id)?.scale ?? float3.zero; + set + { + var def = new ScalingEntityStruct(); + BlockEngine.GetBlockInfo(Id, ref def).scale = value; + } + } - /// - /// The block's color. Returns BlockColors.Default if the block no longer exists. - /// - public BlockColor Color - { - get - { - byte index = BlockEngine.GetBlockInfo(Id)?.indexInPalette ?? byte.MaxValue; - if (index == byte.MaxValue) return new BlockColor {Color = BlockColors.Default}; - return new BlockColor {Color = (BlockColors) (index % 10), Darkness = (byte) (index / 10)}; - } - set - { - var def = new ColourParameterEntityStruct(); - ref var color = ref BlockEngine.GetBlockInfo(Id, ref def); - color.indexInPalette = (byte) (value.Color + value.Darkness * 10); - color.needsUpdate = true; - } - } + /// + /// The block's uniform scale or zero if the block's invalid. Also sets the non-uniform scale. + /// + public int UniformScale + { + get => BlockEngine.GetBlockInfo(Id)?.desiredScaleFactor ?? 0; + set + { + var def = new BlockPlacementScaleEntityStruct(); + ref var scaleStruct = ref BlockEngine.GetBlockInfo(Id, ref def); + scaleStruct.blockPlacementHeight = scaleStruct.blockPlacementWidth = + scaleStruct.desiredScaleFactor = scaleStruct.snapGridScale = value; + Scale = new float3(value, value, value); + } + } - /// - /// Whether the block exists. The other properties will return a default value if the block doesn't exist. - /// - public bool Exists => BlockEngine.BlockExists(Id); + /// + /// The block's type (ID). Returns BlockIDs.Invalid if the block doesn't exist anymore. + /// + public BlockIDs Type => (BlockIDs)(BlockEngine.GetBlockInfo(Id)?.DBID ?? ushort.MaxValue); - /// - /// Returns an array of blocks that are connected to this one. Returns an empty array if the block doesn't exist. - /// - public Block[] GetConnectedCubes() => BlockEngine.GetConnectedBlocks(Id); + /// + /// The block's color. Returns BlockColors.Default if the block no longer exists. + /// + public BlockColor Color + { + get + { + byte index = BlockEngine.GetBlockInfo(Id)?.indexInPalette ?? byte.MaxValue; + if (index == byte.MaxValue) return new BlockColor { Color = BlockColors.Default }; + return new BlockColor { Color = (BlockColors)(index % 10), Darkness = (byte)(index / 10) }; + } + set + { + var def = new ColourParameterEntityStruct(); + ref var color = ref BlockEngine.GetBlockInfo(Id, ref def); + color.indexInPalette = (byte)(value.Color + value.Darkness * 10); + color.needsUpdate = true; + } + } - /// - /// Removes this block. - /// - /// True if the block exists and could be removed. - public bool Remove() => RemovalEngine.RemoveBlock(Id); + /// + /// Whether the block exists. The other properties will return a default value if the block doesn't exist. + /// + public bool Exists => BlockEngine.BlockExists(Id); - public override string ToString() - { - return $"{nameof(Id)}: {Id}, {nameof(Position)}: {Position}, {nameof(Rotation)}: {Rotation}"; - } + /// + /// Returns an array of blocks that are connected to this one. Returns an empty array if the block doesn't exist. + /// + public Block[] GetConnectedCubes() => BlockEngine.GetConnectedBlocks(Id); - public static void Init() - { - GameEngineManager.AddGameEngine(PlacementEngine); - GameEngineManager.AddGameEngine(MovementEngine); - GameEngineManager.AddGameEngine(RotationEngine); - GameEngineManager.AddGameEngine(RemovalEngine); - GameEngineManager.AddGameEngine(BlockEngine); - } - } + /// + /// Removes this block. + /// + /// True if the block exists and could be removed. + public bool Remove() => RemovalEngine.RemoveBlock(Id); + + public override string ToString() + { + return $"{nameof(Id)}: {Id}, {nameof(Position)}: {Position}, {nameof(Rotation)}: {Rotation}"; + } + + public static void Init() + { + GameEngineManager.AddGameEngine(PlacementEngine); + GameEngineManager.AddGameEngine(MovementEngine); + GameEngineManager.AddGameEngine(RotationEngine); + GameEngineManager.AddGameEngine(RemovalEngine); + GameEngineManager.AddGameEngine(BlockEngine); + } + + public T Specialise(bool sameTick = true) where T : Block + { + // What have I gotten myself into? + // C# can't cast to a child of Block unless the object was originally that child type + // And C# doesn't let me make implicit cast operators for child types + // So thanks to Microsoft, we've got this horrible implementation using reflection + ConstructorInfo ctor = typeof(T).GetConstructor(types: new System.Type[] { typeof(EGID) }); + if (ctor == null) + { + throw new BlockSpecializationException("Specialized block constructor does not accept an EGID"); + } + if (sameTick) Sync(); + return (T)ctor.Invoke(new object[] { Id }); + } + +#if DEBUG + public static EntitiesDB entitiesDB + { + get + { + return BlockEngine.GetEntitiesDB(); + } + } +#endif + } } \ No newline at end of file diff --git a/GamecraftModdingAPI/Blocks/BlockEngine.cs b/GamecraftModdingAPI/Blocks/BlockEngine.cs index f678501..d8182ca 100644 --- a/GamecraftModdingAPI/Blocks/BlockEngine.cs +++ b/GamecraftModdingAPI/Blocks/BlockEngine.cs @@ -9,66 +9,78 @@ using GamecraftModdingAPI.Engines; namespace GamecraftModdingAPI.Blocks { - public class BlockEngine : IApiEngine - { - public string Name { get; } = "GamecraftModdingAPIBlockGameEngine"; + public class BlockEngine : IApiEngine + { + public string Name { get; } = "GamecraftModdingAPIBlockGameEngine"; - public EntitiesDB entitiesDB { set; private get; } + public EntitiesDB entitiesDB { set; private get; } - public bool isRemovable => false; + public bool isRemovable => false; - public void Dispose() - { - } + public void Dispose() + { + } - public void Ready() - { - } + public void Ready() + { + } - public Block[] GetConnectedBlocks(EGID blockID) - { - if (!BlockExists(blockID)) return new Block[0]; - Stack cubeStack = new Stack(); - FasterList cubesToProcess = new FasterList(); - ConnectedCubesUtility.TreeTraversal.GetConnectedCubes(entitiesDB, blockID.entityID, cubeStack, cubesToProcess, (in GridConnectionsEntityStruct g) => { return false; }); - var ret = new Block[cubesToProcess.count]; - for (int i = 0; i < cubesToProcess.count; i++) - ret[i] = new Block(cubesToProcess[i]); - return ret; - } + public Block[] GetConnectedBlocks(EGID blockID) + { + if (!BlockExists(blockID)) return new Block[0]; + Stack cubeStack = new Stack(); + FasterList cubesToProcess = new FasterList(); + ConnectedCubesUtility.TreeTraversal.GetConnectedCubes(entitiesDB, blockID.entityID, cubeStack, cubesToProcess, (in GridConnectionsEntityStruct g) => { return false; }); + var ret = new Block[cubesToProcess.count]; + for (int i = 0; i < cubesToProcess.count; i++) + ret[i] = new Block(cubesToProcess[i]); + return ret; + } - /// - /// Get a struct of a block. Can be used to set properties. - /// When only querying parameters, use the other overload for convenience. - /// - /// - /// - /// - /// - public ref T GetBlockInfo(EGID blockID, ref T def) where T : struct, IEntityComponent - { - if (entitiesDB.Exists(blockID)) - return ref entitiesDB.QueryEntity(blockID); - return ref def; - } + /// + /// Get a struct of a block. Can be used to set properties. + /// When only querying parameters, use the other overload for convenience. + /// + /// + /// + /// + /// + public ref T GetBlockInfo(EGID blockID, ref T def) where T : struct, IEntityComponent + { + if (entitiesDB.Exists(blockID)) + return ref entitiesDB.QueryEntity(blockID); + return ref def; + } - /// - /// Get a struct of a block. Can only be used to retrieve information. - /// Use the overload with a default parameter to get the struct by reference to set values. - /// - /// The block's EGID - /// The struct's type to get - /// A copy of the struct or null - public T? GetBlockInfo(EGID blockID) where T : struct, IEntityComponent - { - if (entitiesDB.Exists(blockID)) - return entitiesDB.QueryEntity(blockID); - return null; - } + /// + /// Get a struct of a block. Can only be used to retrieve information. + /// Use the overload with a default parameter to get the struct by reference to set values. + /// + /// The block's EGID + /// The struct's type to get + /// A copy of the struct or null + public T? GetBlockInfo(EGID blockID) where T : struct, IEntityComponent + { + if (entitiesDB.Exists(blockID)) + return entitiesDB.QueryEntity(blockID); + return null; + } - public bool BlockExists(EGID id) - { - return entitiesDB.Exists(id); - } - } + public bool BlockExists(EGID id) + { + return entitiesDB.Exists(id); + } + + public bool GetBlockInfoExists(EGID blockID) where T : struct, IEntityComponent + { + return entitiesDB.Exists(blockID); + } + +#if DEBUG + public EntitiesDB GetEntitiesDB() + { + return entitiesDB; + } +#endif + } } \ No newline at end of file diff --git a/GamecraftModdingAPI/Blocks/BlockExceptions.cs b/GamecraftModdingAPI/Blocks/BlockExceptions.cs new file mode 100644 index 0000000..47af014 --- /dev/null +++ b/GamecraftModdingAPI/Blocks/BlockExceptions.cs @@ -0,0 +1,43 @@ +using System; + +using GamecraftModdingAPI; + +namespace GamecraftModdingAPI.Blocks +{ + public class BlockException : GamecraftModdingAPIException + { + public BlockException() + { + } + + public BlockException(System.String message) : base(message) + { + } + + public BlockException(System.String message, Exception innerException) : base(message, innerException) + { + } + } + + public class BlockTypeException : BlockException + { + public BlockTypeException() + { + } + + public BlockTypeException(string message) : base(message) + { + } + } + + public class BlockSpecializationException : BlockException + { + public BlockSpecializationException() + { + } + + public BlockSpecializationException(string message) : base(message) + { + } + } +} diff --git a/GamecraftModdingAPI/Blocks/ConsoleBlock.cs b/GamecraftModdingAPI/Blocks/ConsoleBlock.cs new file mode 100644 index 0000000..c1dec27 --- /dev/null +++ b/GamecraftModdingAPI/Blocks/ConsoleBlock.cs @@ -0,0 +1,97 @@ +using System; + +using RobocraftX.Blocks; +using Svelto.ECS; +using Unity.Mathematics; + +using GamecraftModdingAPI; +using GamecraftModdingAPI.Utility; + +namespace GamecraftModdingAPI.Blocks +{ + public class ConsoleBlock : Block + { + public static ConsoleBlock PlaceNew(float3 position, + float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0, + int uscale = 1, float3 scale = default, Player player = null) + { + if (PlacementEngine.IsInGame && GameState.IsBuildMode()) + { + try + { + EGID id = PlacementEngine.PlaceBlock(BlockIDs.ConsoleBlock, color, darkness, + position, uscale, scale, player, rotation); + Sync(); + return new ConsoleBlock(id); + } + catch (Exception e) + { + Logging.MetaDebugLog(e); + } + } + + return null; + } + + public ConsoleBlock(EGID id): base(id) + { + if (!BlockEngine.GetBlockInfoExists(this.Id)) + { + throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); + } + } + + public ConsoleBlock(uint id): base(id) + { + if (!BlockEngine.GetBlockInfoExists(this.Id)) + { + throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); + } + } + + // custom console block properties + + public string Command + { + get + { + return BlockEngine.GetBlockInfo(Id)?.commandName ?? null; + } + + set + { + BlockEngine.GetBlockInfo(Id)?.commandName.Set(value); + } + } + + public string Arg1 + { + get => BlockEngine.GetBlockInfo(Id)?.arg1 ?? null; + + set + { + BlockEngine.GetBlockInfo(Id)?.arg1.Set(value); + } + } + + public string Arg2 + { + get => BlockEngine.GetBlockInfo(Id)?.arg2 ?? null; + + set + { + BlockEngine.GetBlockInfo(Id)?.arg2.Set(value); + } + } + + public string Arg3 + { + get => BlockEngine.GetBlockInfo(Id)?.arg3 ?? null; + + set + { + BlockEngine.GetBlockInfo(Id)?.arg3.Set(value); + } + } + } +} diff --git a/GamecraftModdingAPI/Blocks/TextBlock.cs b/GamecraftModdingAPI/Blocks/TextBlock.cs new file mode 100644 index 0000000..bd2a23b --- /dev/null +++ b/GamecraftModdingAPI/Blocks/TextBlock.cs @@ -0,0 +1,92 @@ +using System; + +using Gamecraft.Blocks.GUI; +using Svelto.ECS; +using Unity.Mathematics; + +using GamecraftModdingAPI; +using GamecraftModdingAPI.Utility; + +namespace GamecraftModdingAPI.Blocks +{ + public class TextBlock : Block + { + + public static TextBlock PlaceNew(float3 position, + float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0, + int uscale = 1, float3 scale = default, Player player = null) + { + if (PlacementEngine.IsInGame && GameState.IsBuildMode()) + { + try + { + EGID id = PlacementEngine.PlaceBlock(BlockIDs.TextBlock, color, darkness, + position, uscale, scale, player, rotation); + Sync(); + return new TextBlock(id); + } + catch (Exception e) + { + Logging.MetaDebugLog(e); + } + } + + return null; + } + + public TextBlock(EGID id) : base(id) + { + if (!BlockEngine.GetBlockInfoExists(this.Id)) + { + throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); + } + } + + public TextBlock(uint id) : base(id) + { + if (!BlockEngine.GetBlockInfoExists(this.Id)) + { + throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); + } + } + + // custom text block properties + + /// + /// The text block's current text. + /// + public string Text + { + get + { + return BlockEngine.GetBlockInfo(Id)?.textCurrent ?? null; + } + + set + { + TextBlockDataStruct def = default; + ref TextBlockDataStruct tbds = ref BlockEngine.GetBlockInfo(Id, ref def); + tbds.textCurrent.Set(value); + tbds.textStored.Set(value); + BlockEngine.GetBlockInfo(Id)?.newTextBlockStringContent.Set(value); + } + } + + /// + /// The text block's current text block ID (used in ChangeTextBlockCommand). + /// + public string TextBlockId + { + get + { + return BlockEngine.GetBlockInfo(Id)?.textBlockID ?? null; + } + + set + { + BlockEngine.GetBlockInfo(Id)?.textBlockID.Set(value); + BlockEngine.GetBlockInfo(Id)?.newTextBlockID.Set(value); + } + } + } +} diff --git a/GamecraftModdingAPI/GamecraftModdingAPI.csproj b/GamecraftModdingAPI/GamecraftModdingAPI.csproj index 55339fa..074c5b6 100644 --- a/GamecraftModdingAPI/GamecraftModdingAPI.csproj +++ b/GamecraftModdingAPI/GamecraftModdingAPI.csproj @@ -3,7 +3,7 @@ net472 true - 1.0.2 + 1.1.0 Exmods GNU General Public Licence 3+ https://git.exmods.org/modtainers/GamecraftModdingAPI diff --git a/GamecraftModdingAPI/Utility/DeterministicStepCompositionRootPatch.cs b/GamecraftModdingAPI/Utility/DeterministicStepCompositionRootPatch.cs new file mode 100644 index 0000000..9cabbe0 --- /dev/null +++ b/GamecraftModdingAPI/Utility/DeterministicStepCompositionRootPatch.cs @@ -0,0 +1,25 @@ +using System; + +using RobocraftX.StateSync; +using Svelto.ECS; + +using HarmonyLib; + +namespace GamecraftModdingAPI.Utility +{ + [HarmonyPatch(typeof(DeterministicStepCompositionRoot), "ResetWorld")] + public static class DeterministicStepCompositionRootPatch + { + private static SimpleEntitiesSubmissionScheduler engineRootScheduler; + public static void Postfix(SimpleEntitiesSubmissionScheduler scheduler) + { + engineRootScheduler = scheduler; + } + + internal static void SubmitEntitiesNow() + { + if (engineRootScheduler != null) + engineRootScheduler.SubmitEntities(); + } + } +}