diff --git a/Automation/bump_version.py b/Automation/bump_version.py new file mode 100755 index 0000000..d3de051 --- /dev/null +++ b/Automation/bump_version.py @@ -0,0 +1,67 @@ +#!/usr/bin/python3 + +import argparse +import re +# this assumes a mostly semver-complient version number + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Increment GamecraftModdingAPI version") + parser.add_argument('version', metavar="VN", type=str, help="The version number to increment, or the index of the number (zero-indexed).") + args = parser.parse_args() + + version_index = -1 + try: + version_index = int(args.version) + except Exception: + if args.version.lower() == "major": + version_index = 0 + elif args.version.lower() == "minor": + version_index = 1 + elif args.version.lower() == "patch": + version_index = 2 + + if version_index < 0: + print("Could not parse version argument.") + exit(version_index) + + print(version_index) + old_version = "" + new_version = "" + + with open("../GamecraftModdingAPI/GamecraftModdingAPI.csproj", "r") as xmlFile: + print("Parsing GamecraftModdingAPI.csproj") + fileStr = xmlFile.read() + versionMatch = re.search(r"(.+)", fileStr) + if versionMatch is None: + print("Unable to find version number in GamecraftModdingAPI.csproj") + exit(1) + old_version = versionMatch.group(1) + versionList = old_version.split(".") + if len(versionList) <= version_index: + print("Invalid version string") + exit(1) + versionList[version_index] = str(int(versionList[version_index]) + 1) + for i in range(version_index + 1, len(versionList)): + try: + int(versionList[i]) + versionList[i] = "0" + except Exception: + tmp = versionList[i].split("-") + tmp[0] = "0" + versionList[i] = "-".join(tmp) + new_version = ".".join(versionList) + print(new_version) + newFileContents = fileStr.replace(""+old_version+"", ""+new_version+"") + + with open("../GamecraftModdingAPI/GamecraftModdingAPI.csproj", "w") as xmlFile: + print("Writing new version to project file") + xmlFile.write(newFileContents) + + with open("../doxygen.conf", "r") as doxFile: + print("Parsing doxygen.conf") + doxStr = doxFile.read() + newFileContents = doxStr.replace("= \"v" + old_version + "\"", "= \"v" + new_version + "\"") + + with open("../doxygen.conf", "w") as doxFile: + print("Writing new version to doxygen config") + doxFile.write(newFileContents) diff --git a/GamecraftModdingAPI/Block.cs b/GamecraftModdingAPI/Block.cs index 2f9a901..0194bae 100644 --- a/GamecraftModdingAPI/Block.cs +++ b/GamecraftModdingAPI/Block.cs @@ -122,6 +122,7 @@ namespace GamecraftModdingAPI new Dictionary { {typeof(ConsoleBlock), new[] {CommonExclusiveGroups.BUILD_CONSOLE_BLOCK_GROUP}}, + {typeof(LogicGate), new [] {CommonExclusiveGroups.BUILD_LOGIC_BLOCK_GROUP}}, {typeof(Motor), new[] {CommonExclusiveGroups.BUILD_MOTOR_BLOCK_GROUP}}, {typeof(MusicBlock), new[] {CommonExclusiveGroups.BUILD_MUSIC_BLOCK_GROUP}}, {typeof(Piston), new[] {CommonExclusiveGroups.BUILD_PISTON_BLOCK_GROUP}}, @@ -420,6 +421,8 @@ namespace GamecraftModdingAPI GameEngineManager.AddGameEngine(BlockEngine); GameEngineManager.AddGameEngine(BlockEventsEngine); GameEngineManager.AddGameEngine(ScalingEngine); + GameEngineManager.AddGameEngine(SignalEngine); + Wire.signalEngine = SignalEngine; // requires same functionality, no need to duplicate the engine } /// diff --git a/GamecraftModdingAPI/Blocks/BlockExceptions.cs b/GamecraftModdingAPI/Blocks/BlockExceptions.cs index 47af014..9949424 100644 --- a/GamecraftModdingAPI/Blocks/BlockExceptions.cs +++ b/GamecraftModdingAPI/Blocks/BlockExceptions.cs @@ -40,4 +40,26 @@ namespace GamecraftModdingAPI.Blocks { } } + + public class WiringException : BlockException + { + public WiringException() + { + } + + public WiringException(string message) : base(message) + { + } + } + + public class WireInvalidException : WiringException + { + public WireInvalidException() + { + } + + public WireInvalidException(string message) : base(message) + { + } + } } diff --git a/GamecraftModdingAPI/Blocks/BlockTests.cs b/GamecraftModdingAPI/Blocks/BlockTests.cs index 6186a1d..9013b3d 100644 --- a/GamecraftModdingAPI/Blocks/BlockTests.cs +++ b/GamecraftModdingAPI/Blocks/BlockTests.cs @@ -4,6 +4,7 @@ using Gamecraft.Wires; using GamecraftModdingAPI; using GamecraftModdingAPI.Tests; +using GamecraftModdingAPI.Utility; namespace GamecraftModdingAPI.Blocks { @@ -96,10 +97,28 @@ namespace GamecraftModdingAPI.Blocks if (!Assert.NotNull(b, "Block.Specialize() returned null, possibly because it failed silently.", "Specialized MusicBlock is not null.")) return; b.IsPlaying = true; // play sfx if (!Assert.Equal(b.IsPlaying, true, $"MusicBlock.IsPlaying {b.IsPlaying} does not equal true, possibly because it failed silently.", "MusicBlock.IsPlaying is set properly.")) return; - if (!Assert.Equal(b.ChannelType, ChannelType.Character, $"MusicBlock.ChannelType {b.ChannelType} does not equal default value, possibly because it failed silently.", "MusicBlock.ChannelType is equal to default.")) return; + if (!Assert.Equal(b.ChannelType, ChannelType.None, $"MusicBlock.ChannelType {b.ChannelType} does not equal default value, possibly because it failed silently.", "MusicBlock.ChannelType is equal to default.")) return; //Assert.Log(b.Track.ToString()); if (!Assert.Equal(b.Track.ToString(), new Guid("3237ff8f-f5f2-4f84-8144-496ca280f8c0").ToString(), $"MusicBlock.Track {b.Track} does not equal default value, possibly because it failed silently.", "MusicBlock.Track is equal to default.")) return; } - } + + [APITestCase(TestType.EditMode)] + public static void TestLogicGate() + { + Block newBlock = Block.PlaceNew(BlockIDs.NOTLogicBlock, Unity.Mathematics.float3.zero + 1); + LogicGate b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler + Assert.Errorless(() => { b = newBlock.Specialise(); }, "Block.Specialize() raised an exception: ", "Block.Specialize() completed without issue."); + if (!Assert.NotNull(b, "Block.Specialize() returned null, possibly because it failed silently.", "Specialized LogicGate is not null.")) return; + if (!Assert.Equal(b.InputCount, 1u, $"LogicGate.InputCount {b.InputCount} does not equal default value, possibly because it failed silently.", "LogicGate.InputCount is default.")) return; + if (!Assert.Equal(b.OutputCount, 1u, $"LogicGate.OutputCount {b.OutputCount} does not equal default value, possibly because it failed silently.", "LogicGate.OutputCount is default.")) return; + if (!Assert.NotNull(b, "Block.Specialize() returned null, possibly because it failed silently.", "Specialized LogicGate is not null.")) return; + //if (!Assert.Equal(b.PortName(0, true), "Input", $"LogicGate.PortName(0, input:true) {b.PortName(0, true)} does not equal default value, possibly because it failed silently.", "LogicGate.PortName(0, input:true) is close enough to default.")) return; + LogicGate target = null; + if (!Assert.Errorless(() => { target = Block.PlaceNew(BlockIDs.ANDLogicBlock, Unity.Mathematics.float3.zero + 2); })) return; + Wire newWire = null; + if (!Assert.Errorless(() => { newWire = b.Connect(0, target, 0);})) return; + if (!Assert.NotNull(newWire, "SignalingBlock.Connect(...) returned null, possible because it failed silently.", "SignalingBlock.Connect(...) returned a non-null value.")) return; + } +} #endif } diff --git a/GamecraftModdingAPI/Blocks/LogicGate.cs b/GamecraftModdingAPI/Blocks/LogicGate.cs new file mode 100644 index 0000000..124bc10 --- /dev/null +++ b/GamecraftModdingAPI/Blocks/LogicGate.cs @@ -0,0 +1,16 @@ +using RobocraftX.Common; +using Svelto.ECS; + +namespace GamecraftModdingAPI.Blocks +{ + public class LogicGate : SignalingBlock + { + public LogicGate(EGID id) : base(id) + { + } + + public LogicGate(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_LOGIC_BLOCK_GROUP)) + { + } + } +} \ No newline at end of file diff --git a/GamecraftModdingAPI/Blocks/SignalEngine.cs b/GamecraftModdingAPI/Blocks/SignalEngine.cs index 13a0b3b..e961423 100644 --- a/GamecraftModdingAPI/Blocks/SignalEngine.cs +++ b/GamecraftModdingAPI/Blocks/SignalEngine.cs @@ -1,4 +1,5 @@ -using Svelto.ECS; +using System; +using Svelto.ECS; using Svelto.DataStructures; using Gamecraft.Wires; @@ -9,7 +10,7 @@ namespace GamecraftModdingAPI.Blocks /// /// Engine which executes signal actions /// - public class SignalEngine : IApiEngine + public class SignalEngine : IApiEngine, IFactoryEngine { public const float POSITIVE_HIGH = 1.0f; public const float NEGATIVE_HIGH = -1.0f; @@ -20,6 +21,8 @@ namespace GamecraftModdingAPI.Blocks public EntitiesDB entitiesDB { set; private get; } + public IEntityFactory Factory { get; set; } + public bool isRemovable => false; public bool IsInGame = false; @@ -34,7 +37,74 @@ namespace GamecraftModdingAPI.Blocks IsInGame = true; } - // implementations for Signal static class + // implementations for block wiring + + public WireEntityStruct CreateNewWire(EGID startBlock, byte startPort, EGID endBlock, byte endPort) + { + EGID wireEGID = new EGID(WiresExclusiveGroups.NewWireEntityId, NamedExclusiveGroup.Group); + EntityComponentInitializer wireInitializer = Factory.BuildEntity(wireEGID); + wireInitializer.Init(new WireEntityStruct + { + sourceBlockEGID = startBlock, + sourcePortUsage = startPort, + destinationBlockEGID = endBlock, + destinationPortUsage = endPort, + ID = wireEGID + }); + return wireInitializer.Get(); + } + + public ref WireEntityStruct GetWire(EGID wire) + { + if (!entitiesDB.Exists(wire)) + { + throw new WiringException($"Wire {wire} does not exist"); + } + return ref entitiesDB.QueryEntity(wire); + } + + public ref PortEntityStruct GetPort(EGID port) + { + if (!entitiesDB.Exists(port)) + { + throw new WiringException($"Port {port} does not exist (yet?)"); + } + return ref entitiesDB.QueryEntity(port); + } + + public ref PortEntityStruct GetPortByOffset(BlockPortsStruct bps, byte portNumber, bool input) + { + ExclusiveGroup group = input + ? NamedExclusiveGroup.Group + : NamedExclusiveGroup.Group; + uint id = (input ? bps.firstInputID : bps.firstOutputID) + portNumber; + EGID egid = new EGID(id, group); + if (!entitiesDB.Exists(egid)) + { + throw new WiringException("Port does not exist"); + } + return ref entitiesDB.QueryEntity(egid); + } + + public ref PortEntityStruct GetPortByOffset(Block block, byte portNumber, bool input) + { + BlockPortsStruct bps = GetFromDbOrInitData(block, block.Id, out bool exists); + if (!exists) + { + throw new BlockException("Block does not exist"); + } + return ref GetPortByOffset(bps, portNumber, input); + } + + public ref T GetComponent(EGID egid) where T : struct, IEntityComponent + { + return ref entitiesDB.QueryEntity(egid); + } + + public bool Exists(EGID egid) where T : struct, IEntityComponent + { + return entitiesDB.Exists(egid); + } public bool SetSignal(EGID blockID, float signal, out uint signalID, bool input = true) { @@ -123,7 +193,7 @@ namespace GamecraftModdingAPI.Blocks return inputs; } - public EGID[] GetSignalOutputs(EGID blockID) + public EGID[] GetSignalOutputs(EGID blockID) { BlockPortsStruct ports = entitiesDB.QueryEntity(blockID); EGID[] outputs = new EGID[ports.outputCount]; @@ -134,6 +204,42 @@ namespace GamecraftModdingAPI.Blocks return outputs; } + public EGID MatchBlockInputToPort(Block block, byte portUsage, out bool exists) + { + BlockPortsStruct ports = GetFromDbOrInitData(block, block.Id, out exists); + return new EGID(ports.firstInputID + portUsage, NamedExclusiveGroup.Group); + } + + public EGID MatchBlockInputToPort(EGID block, byte portUsage, out bool exists) + { + if (!entitiesDB.Exists(block)) + { + exists = false; + return default; + } + exists = true; + BlockPortsStruct ports = entitiesDB.QueryEntity(block); + return new EGID(ports.firstInputID + portUsage, NamedExclusiveGroup.Group); + } + + public EGID MatchBlockOutputToPort(Block block, byte portUsage, out bool exists) + { + BlockPortsStruct ports = GetFromDbOrInitData(block, block.Id, out exists); + return new EGID(ports.firstOutputID + portUsage, NamedExclusiveGroup.Group); + } + + public EGID MatchBlockOutputToPort(EGID block, byte portUsage, out bool exists) + { + if (!entitiesDB.Exists(block)) + { + exists = false; + return default; + } + exists = true; + BlockPortsStruct ports = entitiesDB.QueryEntity(block); + return new EGID(ports.firstOutputID + portUsage, NamedExclusiveGroup.Group); + } + public ref WireEntityStruct MatchPortToWire(EGID portID, EGID blockID, out bool exists) { ref PortEntityStruct port = ref entitiesDB.QueryEntity(portID); @@ -152,6 +258,57 @@ namespace GamecraftModdingAPI.Blocks return ref defRef[0]; } + public ref WireEntityStruct MatchBlocksToWire(EGID startBlock, EGID endBlock, out bool exists, byte startPort = byte.MaxValue, + byte endPort = byte.MaxValue) + { + EGID[] startPorts; + if (startPort == byte.MaxValue) + { + // search all output ports on source block + startPorts = GetSignalOutputs(startBlock); + } + else + { + BlockPortsStruct ports = entitiesDB.QueryEntity(startBlock); + startPorts = new EGID[] {new EGID(ports.firstOutputID + startPort, NamedExclusiveGroup.Group) }; + } + + EGID[] endPorts; + if (startPort == byte.MaxValue) + { + // search all input ports on destination block + endPorts = GetSignalInputs(endBlock); + } + else + { + BlockPortsStruct ports = entitiesDB.QueryEntity(endBlock); + endPorts = new EGID[] {new EGID(ports.firstInputID + endPort, NamedExclusiveGroup.Group) }; + } + + EntityCollection wires = entitiesDB.QueryEntities(NamedExclusiveGroup.Group); + for (int endIndex = 0; endIndex < endPorts.Length; endIndex++) + { + PortEntityStruct endPES = entitiesDB.QueryEntity(endPorts[endIndex]); + for (int startIndex = 0; startIndex < startPorts.Length; startIndex++) + { + PortEntityStruct startPES = entitiesDB.QueryEntity(startPorts[startIndex]); + for (int w = 0; w < wires.count; w++) + { + if ((wires[w].destinationPortUsage == endPES.usage && wires[w].destinationBlockEGID == endBlock) + && (wires[w].sourcePortUsage == startPES.usage && wires[w].sourceBlockEGID == startBlock)) + { + exists = true; + return ref wires[w]; + } + } + } + } + + exists = false; + WireEntityStruct[] defRef = new WireEntityStruct[1]; + return ref defRef[0]; + } + public ref ChannelDataStruct GetChannelDataStruct(EGID portID, out bool exists) { ref PortEntityStruct port = ref entitiesDB.QueryEntity(portID); @@ -175,6 +332,69 @@ namespace GamecraftModdingAPI.Blocks return res.ToArray(); } + public EGID[] WiredToInput(EGID block, byte port) + { + WireEntityStruct[] wireEntityStructs = Search(NamedExclusiveGroup.Group, + (WireEntityStruct wes) => wes.destinationPortUsage == port && wes.destinationBlockEGID == block); + EGID[] result = new EGID[wireEntityStructs.Length]; + for (uint i = 0; i < wireEntityStructs.Length; i++) + { + result[i] = wireEntityStructs[i].ID; + } + + return result; + } + + public EGID[] WiredToOutput(EGID block, byte port) + { + WireEntityStruct[] wireEntityStructs = Search(NamedExclusiveGroup.Group, + (WireEntityStruct wes) => wes.sourcePortUsage == port && wes.sourceBlockEGID == block); + EGID[] result = new EGID[wireEntityStructs.Length]; + for (uint i = 0; i < wireEntityStructs.Length; i++) + { + result[i] = wireEntityStructs[i].ID; + } + + return result; + } + + private T[] Search(ExclusiveGroup group, Func isMatch) where T : struct, IEntityComponent + { + FasterList results = new FasterList(); + EntityCollection components = entitiesDB.QueryEntities(group); + for (uint i = 0; i < components.count; i++) + { + if (isMatch(components[i])) + { + results.Add(components[i]); + } + } + return results.ToArray(); + } + + private ref T GetFromDbOrInitData(Block block, EGID id, out bool exists) where T : struct, IEntityComponent + { + T[] defRef = new T[1]; + if (entitiesDB.Exists(id)) + { + exists = true; + return ref entitiesDB.QueryEntity(id); + } + if (block == null || block.InitData.Group == null) + { + exists = false; + return ref defRef[0]; + } + EntityComponentInitializer initializer = new EntityComponentInitializer(block.Id, block.InitData.Group); + if (initializer.Has()) + { + exists = true; + return ref initializer.Get(); + } + exists = false; + return ref defRef[0]; + } + private EntityCollection GetSignalStruct(uint signalID, out uint index, bool input = true) { ExclusiveGroup group = input diff --git a/GamecraftModdingAPI/Blocks/SignalingBlock.cs b/GamecraftModdingAPI/Blocks/SignalingBlock.cs index 149e450..a137b98 100644 --- a/GamecraftModdingAPI/Blocks/SignalingBlock.cs +++ b/GamecraftModdingAPI/Blocks/SignalingBlock.cs @@ -16,18 +16,10 @@ namespace GamecraftModdingAPI.Blocks { public SignalingBlock(EGID id) : base(id) { - if (!BlockEngine.GetBlockInfoExists(this)) - { - throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); - } } public SignalingBlock(uint id) : base(id) { - if (!BlockEngine.GetBlockInfoExists(this)) - { - throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); - } } /// @@ -85,5 +77,91 @@ namespace GamecraftModdingAPI.Blocks { get => BlockEngine.GetBlockInfo(this, (BlockPortsStruct st) => st.outputCount); } + + /// + /// Connect an output on this block to an input on another block. + /// + /// Output port number. + /// Input block. + /// Input port number. + /// The wire connection + /// The wire could not be created. + public Wire Connect(byte sourcePort, SignalingBlock destination, byte destinationPort) + { + if (sourcePort >= OutputCount) + { + throw new WiringException("Source port does not exist"); + } + + if (destinationPort >= destination.InputCount) + { + throw new WiringException("Destination port does not exist"); + } + return Wire.Connect(this, sourcePort, destination, destinationPort); + } + + /// + /// The port's name. + /// This is localized to the user's language, so this is not reliable for port identification. + /// + /// Port number. + /// Whether the port is an input (true) or an output (false). + /// The localized port name. + public string PortName(byte port, bool input) + { + BlockPortsStruct bps = BlockEngine.GetBlockInfo(this, (BlockPortsStruct a) => a); + PortEntityStruct pes = SignalEngine.GetPortByOffset(this, port, input); + return pes.portNameLocalised; + } + + /// + /// The input port's name. + /// + /// Input port number. + /// The port name, localized to the user's language. + public string InputPortName(byte port) => PortName(port, true); + + /// + /// The output port's name. + /// + /// Output port number. + /// The port name, localized to the user's language. + public string OutputPortName(byte port) => PortName(port, false); + + /// + /// All wires connected to the input port. + /// These wires will always be wired output -> input. + /// + /// Port number. + /// Wires connected to the input port. + public Wire[] ConnectedToInput(byte port) + { + if (port >= InputCount) throw new WiringException($"Port input {port} does not exist"); + EGID[] wireEgids = SignalEngine.WiredToInput(Id, port); + Wire[] wires = new Wire[wireEgids.Length]; + for (uint i = 0; i < wireEgids.Length; i++) + { + wires[i] = new Wire(wireEgids[i]); + } + return wires; + } + + /// + /// All wires connected to the output port. + /// These wires will always be wired output -> input. + /// + /// Port number. + /// Wires connected to the output port. + public Wire[] ConnectedToOutput(byte port) + { + if (port >= OutputCount) throw new WiringException($"Port output {port} does not exist"); + EGID[] wireEgids = SignalEngine.WiredToOutput(Id, port); + Wire[] wires = new Wire[wireEgids.Length]; + for (uint i = 0; i < wireEgids.Length; i++) + { + wires[i] = new Wire(wireEgids[i]); + } + return wires; + } } } diff --git a/GamecraftModdingAPI/Blocks/Wire.cs b/GamecraftModdingAPI/Blocks/Wire.cs new file mode 100644 index 0000000..c29795f --- /dev/null +++ b/GamecraftModdingAPI/Blocks/Wire.cs @@ -0,0 +1,355 @@ +using System; + +using Gamecraft.Wires; +using Svelto.ECS; +using Svelto.ECS.Experimental; + +using GamecraftModdingAPI.Utility; + +namespace GamecraftModdingAPI.Blocks +{ + public class Wire + { + internal static SignalEngine signalEngine; + + protected EGID startPortEGID; + + protected EGID endPortEGID; + + protected EGID startBlockEGID; + + protected EGID endBlockEGID; + + protected EGID wireEGID; + + protected bool inputToOutput; + + protected byte startPort; + + protected byte endPort; + + public static Wire Connect(SignalingBlock start, byte startPort, SignalingBlock end, byte endPort) + { + WireEntityStruct wire = signalEngine.CreateNewWire(start.Id, startPort, end.Id, endPort); + return new Wire(wire); + } + + /// + /// An existing wire connection ending at the specified input. + /// If multiple exist, this will return the first one found. + /// + /// Destination block. + /// Port number. + /// The wire, where the end of the wire is the block port specified, or null if does not exist. + public static Wire ConnectedToInputPort(SignalingBlock end, byte endPort) + { + EGID port = signalEngine.MatchBlockInputToPort(end, endPort, out bool exists); + if (!exists) return null; + WireEntityStruct wire = signalEngine.MatchPortToWire(port, end.Id, out exists); + if (exists) + { + return new Wire(new Block(wire.sourceBlockEGID), end, wire.sourcePortUsage, endPort); + } + return null; + } + + /// + /// An existing wire connection starting at the specified output. + /// If multiple exist, this will return the first one found. + /// + /// Source block entity ID. + /// Port number. + /// The wire, where the start of the wire is the block port specified, or null if does not exist. + public static Wire ConnectedToOutputPort(SignalingBlock start, byte startPort) + { + EGID port = signalEngine.MatchBlockOutputToPort(start, startPort, out bool exists); + if (!exists) return null; + WireEntityStruct wire = signalEngine.MatchPortToWire(port, start.Id, out exists); + if (exists) + { + return new Wire(start, new Block(wire.destinationBlockEGID), startPort, wire.destinationPortUsage); + } + return null; + } + + /// + /// Construct a wire object from an existing connection. + /// + /// Starting block ID. + /// Ending block ID. + /// Starting port number, or guess if omitted. + /// Ending port number, or guess if omitted. + /// Guessing failed or wire does not exist. + public Wire(Block start, Block end, byte startPort = Byte.MaxValue, byte endPort = Byte.MaxValue) + { + startBlockEGID = start.Id; + endBlockEGID = end.Id; + // find block ports + WireEntityStruct wire = signalEngine.MatchBlocksToWire(start.Id, end.Id, out bool exists, startPort, endPort); + if (exists) + { + wireEGID = wire.ID; + endPortEGID = signalEngine.MatchBlockInputToPort(end, wire.destinationPortUsage, out exists); + if (!exists) throw new WireInvalidException("Wire end port not found"); + startPortEGID = signalEngine.MatchBlockOutputToPort(start, wire.sourcePortUsage, out exists); + if (!exists) throw new WireInvalidException("Wire start port not found"); + inputToOutput = false; + endPort = wire.destinationPortUsage; + startPort = wire.sourcePortUsage; + } + else + { + // flip I/O around and try again + wire = signalEngine.MatchBlocksToWire(end.Id, start.Id, out exists, endPort, startPort); + if (exists) + { + wireEGID = wire.ID; + endPortEGID = signalEngine.MatchBlockOutputToPort(end, wire.sourcePortUsage, out exists); + if (!exists) throw new WireInvalidException("Wire end port not found"); + startPortEGID = signalEngine.MatchBlockInputToPort(start, wire.destinationPortUsage, out exists); + if (!exists) throw new WireInvalidException("Wire start port not found"); + inputToOutput = true; // end is actually the source + // NB: start and end are handled exactly as they're received as params. + // This makes wire traversal easier, but makes logic in this class a bit more complex + endPort = wire.sourcePortUsage; + startPort = wire.destinationPortUsage; + } + else + { + throw new WireInvalidException("Wire not found"); + } + } + } + + /// + /// Construct a wire object from an existing wire connection. + /// + /// Starting block ID. + /// Ending block ID. + /// Starting port number. + /// Ending port number. + /// The wire ID. + /// Whether the wire direction goes input -> output (true) or output -> input (false, preferred). + public Wire(Block start, Block end, byte startPort, byte endPort, EGID wire, bool inputToOutput) + { + this.startBlockEGID = start.Id; + this.endBlockEGID = end.Id; + this.inputToOutput = inputToOutput; + this.wireEGID = wire; + if (inputToOutput) + { + endPortEGID = signalEngine.MatchBlockOutputToPort(start, startPort, out bool exists); + if (!exists) throw new WireInvalidException("Wire end port not found"); + startPortEGID = signalEngine.MatchBlockInputToPort(end, endPort, out exists); + if (!exists) throw new WireInvalidException("Wire start port not found"); + } + else + { + endPortEGID = signalEngine.MatchBlockInputToPort(end, endPort, out bool exists); + if (!exists) throw new WireInvalidException("Wire end port not found"); + startPortEGID = signalEngine.MatchBlockOutputToPort(start, startPort, out exists); + if (!exists) throw new WireInvalidException("Wire start port not found"); + } + this.startPort = startPort; + this.endPort = endPort; + } + + /// + /// Construct a wire object from an existing wire connection. + /// + /// The wire ID. + public Wire(EGID wireEgid) + { + this.wireEGID = wireEgid; + WireEntityStruct wire = signalEngine.GetWire(wireEGID); + this.startBlockEGID = wire.sourceBlockEGID; + this.endBlockEGID = wire.destinationBlockEGID; + this.inputToOutput = false; + endPortEGID = signalEngine.MatchBlockInputToPort(wire.destinationBlockEGID, wire.destinationPortUsage, out bool exists); + if (!exists) throw new WireInvalidException("Wire end port not found"); + startPortEGID = signalEngine.MatchBlockOutputToPort(wire.sourceBlockEGID, wire.sourcePortUsage, out exists); + if (!exists) throw new WireInvalidException("Wire start port not found"); + this.endPort = wire.destinationPortUsage; + this.startPort = wire.sourcePortUsage; + } + + internal Wire(WireEntityStruct wire) + { + this.wireEGID = wire.ID; + this.startBlockEGID = wire.sourceBlockEGID; + this.endBlockEGID = wire.destinationBlockEGID; + inputToOutput = false; + endPortEGID = signalEngine.MatchBlockInputToPort(wire.destinationBlockEGID, wire.destinationPortUsage, out bool exists); + if (!exists) throw new WireInvalidException("Wire end port not found"); + startPortEGID = signalEngine.MatchBlockOutputToPort(wire.sourceBlockEGID, wire.sourcePortUsage, out exists); + if (!exists) throw new WireInvalidException("Wire start port not found"); + this.endPort = wire.destinationPortUsage; + this.startPort = wire.sourcePortUsage; + } + + /// + /// The wire's in-game id. + /// + public EGID Id + { + get => wireEGID; + } + + /// + /// The wire's signal value, as a float. + /// + public float Float + { + get + { + ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists); + if (!exists) return 0f; + return cds.valueAsFloat; + } + + set + { + ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists); + if (!exists) return; + cds.valueAsFloat = value; + } + } + + /// + /// The wire's string signal. + /// + public string String + { + get + { + ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists); + if (!exists) return ""; + return cds.valueAsEcsString; + } + + set + { + ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists); + if (!exists) return; + cds.valueAsEcsString.Set(value); + } + } + + /// + /// The wire's raw string signal. + /// + public ECSString ECSString + { + get + { + ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists); + if (!exists) return default; + return cds.valueAsEcsString; + } + + set + { + ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists); + if (!exists) return; + cds.valueAsEcsString = value; + } + } + + /// + /// The wire's signal id. + /// I'm 50% sure this is useless. + /// + public uint SignalId + { + get + { + ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists); + if (!exists) return uint.MaxValue; + return cds.valueAsID; + } + + set + { + ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists); + if (!exists) return; + cds.valueAsID = value; + } + } + + /// + /// The block at the beginning of the wire. + /// + public SignalingBlock Start + { + get => new SignalingBlock(startBlockEGID); + } + + /// + /// The port number that the beginning of the wire connects to. + /// + public byte StartPort + { + get => startPort; + } + + /// + /// The block at the end of the wire. + /// + public SignalingBlock End + { + get => new SignalingBlock(endBlockEGID); + } + + /// + /// The port number that the end of the wire connects to. + /// + public byte EndPort + { + get => endPort; + } + + /// + /// Create a copy of the wire object where the direction of the wire is guaranteed to be from a block output to a block input. + /// This is simply a different memory configuration and does not affect the in-game wire (which is always output -> input). + /// + /// A copy of the wire object. + public Wire OutputToInputCopy() + { + return new Wire(wireEGID); + } + + /// + /// Convert the wire object to the direction the signal flows. + /// Signals on wires always flow from a block output port to a block input port. + /// This is simply a different memory configuration and does not affect the in-game wire (which is always output -> input). + /// + public void OutputToInputInPlace() + { + if (inputToOutput) + { + inputToOutput = false; + // swap inputs and outputs + EGID temp = endBlockEGID; + endBlockEGID = startBlockEGID; + startBlockEGID = temp; + temp = endPortEGID; + endPortEGID = startPortEGID; + startPortEGID = temp; + byte tempPortNumber = endPort; + endPort = startPort; + startPort = tempPortNumber; + } + } + + public override string ToString() + { + if (signalEngine.Exists(wireEGID)) + { + return $"{nameof(Id)}: {Id}, Start{nameof(Start.Id)}: {Start.Id}, End{nameof(End.Id)}: {End.Id}, ({Start.Type}::{StartPort} aka {Start.PortName(StartPort, inputToOutput)}) -> ({End.Type}::{EndPort} aka {End.PortName(EndPort, !inputToOutput)})"; + } + return $"{nameof(Id)}: {Id}, Start{nameof(Start.Id)}: {Start.Id}, End{nameof(End.Id)}: {End.Id}, ({Start.Type}::{StartPort} -> {End.Type}::{EndPort})"; + } + + internal static void Init() { } + } +} \ No newline at end of file diff --git a/GamecraftModdingAPI/Events/DeterministicStepComposeEngineGroupsPatch.cs b/GamecraftModdingAPI/Events/DeterministicStepComposeEngineGroupsPatch.cs index d6e58dc..9f16955 100644 --- a/GamecraftModdingAPI/Events/DeterministicStepComposeEngineGroupsPatch.cs +++ b/GamecraftModdingAPI/Events/DeterministicStepComposeEngineGroupsPatch.cs @@ -18,7 +18,8 @@ namespace GamecraftModdingAPI.Events /// Patch of RobocraftX.StateSync.DeterministicStepCompositionRoot.ComposeEnginesGroups(...) /// //[HarmonyPatch(typeof(DeterministicStepCompositionRoot), "DeterministicCompose")] - [HarmonyPatch] + [Obsolete] + [HarmonyPatch] class GameHostTransitionDeterministicGroupEnginePatch { diff --git a/GamecraftModdingAPI/Events/EmitterBuilder.cs b/GamecraftModdingAPI/Events/EmitterBuilder.cs index 48a6cba..c6a6879 100644 --- a/GamecraftModdingAPI/Events/EmitterBuilder.cs +++ b/GamecraftModdingAPI/Events/EmitterBuilder.cs @@ -4,6 +4,7 @@ using Svelto.ECS; namespace GamecraftModdingAPI.Events { + [Obsolete] public class EmitterBuilder { private string name; diff --git a/GamecraftModdingAPI/Events/EventEngineFactory.cs b/GamecraftModdingAPI/Events/EventEngineFactory.cs index f79dfca..7981303 100644 --- a/GamecraftModdingAPI/Events/EventEngineFactory.cs +++ b/GamecraftModdingAPI/Events/EventEngineFactory.cs @@ -11,6 +11,7 @@ namespace GamecraftModdingAPI.Events /// /// Convenient factories for mod event engines /// + [Obsolete] public static class EventEngineFactory { /// diff --git a/GamecraftModdingAPI/Events/EventManager.cs b/GamecraftModdingAPI/Events/EventManager.cs index efbf377..f021e9f 100644 --- a/GamecraftModdingAPI/Events/EventManager.cs +++ b/GamecraftModdingAPI/Events/EventManager.cs @@ -14,6 +14,7 @@ namespace GamecraftModdingAPI.Events /// Keeps track of event handlers and emitters. /// This is used to add, remove and get API event handlers and emitters. /// + [Obsolete("This will be removed in an upcoming update. Use the new C# event architecture from GamecraftModdingAPI.App")] public static class EventManager { private static Dictionary _eventEmitters = new Dictionary(); diff --git a/GamecraftModdingAPI/Events/GameActivatedComposePatch.cs b/GamecraftModdingAPI/Events/GameActivatedComposePatch.cs index 4481f4b..4142fb3 100644 --- a/GamecraftModdingAPI/Events/GameActivatedComposePatch.cs +++ b/GamecraftModdingAPI/Events/GameActivatedComposePatch.cs @@ -17,6 +17,7 @@ namespace GamecraftModdingAPI.Events /// /// Patch of RobocraftX.FullGameCompositionRoot.ActivateGame() /// + [Obsolete] [HarmonyPatch] class GameActivatedComposePatch { diff --git a/GamecraftModdingAPI/Events/GameReloadedPatch.cs b/GamecraftModdingAPI/Events/GameReloadedPatch.cs index 40f46e1..7228084 100644 --- a/GamecraftModdingAPI/Events/GameReloadedPatch.cs +++ b/GamecraftModdingAPI/Events/GameReloadedPatch.cs @@ -14,6 +14,7 @@ namespace GamecraftModdingAPI.Events /// /// Patch of RobocraftX.FullGameCompositionRoot.ReloadGame() /// + [Obsolete] [HarmonyPatch(typeof(FullGameCompositionRoot), "ReloadGame")] class GameReloadedPatch { diff --git a/GamecraftModdingAPI/Events/GameStateBuildEmitterEngine.cs b/GamecraftModdingAPI/Events/GameStateBuildEmitterEngine.cs index a12433a..725f544 100644 --- a/GamecraftModdingAPI/Events/GameStateBuildEmitterEngine.cs +++ b/GamecraftModdingAPI/Events/GameStateBuildEmitterEngine.cs @@ -12,6 +12,7 @@ namespace GamecraftModdingAPI.Events /// /// Event emitter engine for switching to to build mode. /// + [Obsolete] public class GameStateBuildEmitterEngine : IEventEmitterEngine, IUnorderedInitializeOnTimeStoppedModeEntered { public string Name { get; } = "GamecraftModdingAPIGameStateBuildEventEmitter" ; diff --git a/GamecraftModdingAPI/Events/GameStateSimulationEmitterEngine.cs b/GamecraftModdingAPI/Events/GameStateSimulationEmitterEngine.cs index 5689db9..6e6e2ce 100644 --- a/GamecraftModdingAPI/Events/GameStateSimulationEmitterEngine.cs +++ b/GamecraftModdingAPI/Events/GameStateSimulationEmitterEngine.cs @@ -12,6 +12,7 @@ namespace GamecraftModdingAPI.Events /// /// Event emitter engine for switching to simulation mode. /// + [Obsolete] public class GameStateSimulationEmitterEngine : IEventEmitterEngine, IUnorderedInitializeOnTimeRunningModeEntered { public string Name { get; } = "GamecraftModdingAPIGameStateSimulationEventEmitter" ; diff --git a/GamecraftModdingAPI/Events/GameSwitchedToPatch.cs b/GamecraftModdingAPI/Events/GameSwitchedToPatch.cs index 30bb0d5..dbd63c0 100644 --- a/GamecraftModdingAPI/Events/GameSwitchedToPatch.cs +++ b/GamecraftModdingAPI/Events/GameSwitchedToPatch.cs @@ -18,6 +18,7 @@ namespace GamecraftModdingAPI.Events /// Patch of RobocraftX.FullGameCompositionRoot.ActivateGame() /// (scheduled for execution during RobocraftX.FullGameCompositionRoot.SwitchToGame()) /// + [Obsolete] [HarmonyPatch(typeof(FullGameCompositionRoot), "SwitchToGame")] class GameSwitchedToPatch { diff --git a/GamecraftModdingAPI/Events/HandlerBuilder.cs b/GamecraftModdingAPI/Events/HandlerBuilder.cs index d5d9879..10f3290 100644 --- a/GamecraftModdingAPI/Events/HandlerBuilder.cs +++ b/GamecraftModdingAPI/Events/HandlerBuilder.cs @@ -4,6 +4,7 @@ using Svelto.ECS; namespace GamecraftModdingAPI.Events { + [Obsolete] public class HandlerBuilder { private string name; diff --git a/GamecraftModdingAPI/Events/IEventEmitterEngine.cs b/GamecraftModdingAPI/Events/IEventEmitterEngine.cs index f6fefc5..8917cef 100644 --- a/GamecraftModdingAPI/Events/IEventEmitterEngine.cs +++ b/GamecraftModdingAPI/Events/IEventEmitterEngine.cs @@ -13,6 +13,7 @@ namespace GamecraftModdingAPI.Events /// /// Engine interface to create a ModEventEntityStruct in entitiesDB when a specific event occurs. /// + [Obsolete] public interface IEventEmitterEngine : IFactoryEngine { /// diff --git a/GamecraftModdingAPI/Events/IEventHandlerEngine.cs b/GamecraftModdingAPI/Events/IEventHandlerEngine.cs index 34a14af..228adb8 100644 --- a/GamecraftModdingAPI/Events/IEventHandlerEngine.cs +++ b/GamecraftModdingAPI/Events/IEventHandlerEngine.cs @@ -14,6 +14,7 @@ namespace GamecraftModdingAPI.Events /// /// Engine interface to handle ModEventEntityStruct events emitted by IEventEmitterEngines. /// + [Obsolete] public interface IEventHandlerEngine : IReactionaryEngine { } diff --git a/GamecraftModdingAPI/Events/MenuActivatedPatch.cs b/GamecraftModdingAPI/Events/MenuActivatedPatch.cs index 4237faa..382bac4 100644 --- a/GamecraftModdingAPI/Events/MenuActivatedPatch.cs +++ b/GamecraftModdingAPI/Events/MenuActivatedPatch.cs @@ -15,6 +15,7 @@ namespace GamecraftModdingAPI.Events /// /// Patch of RobocraftX.FullGameCompositionRoot.ActivateMenu() /// + [Obsolete] [HarmonyPatch(typeof(FullGameCompositionRoot), "ActivateMenu")] class MenuActivatedPatch { diff --git a/GamecraftModdingAPI/Events/MenuSwitchedToPatch.cs b/GamecraftModdingAPI/Events/MenuSwitchedToPatch.cs index 52a00b2..30b84da 100644 --- a/GamecraftModdingAPI/Events/MenuSwitchedToPatch.cs +++ b/GamecraftModdingAPI/Events/MenuSwitchedToPatch.cs @@ -15,6 +15,7 @@ namespace GamecraftModdingAPI.Events /// /// Patch of RobocraftX.FullGameCompositionRoot.SwitchToMenu() /// + [Obsolete] [HarmonyPatch(typeof(FullGameCompositionRoot), "SwitchToMenu")] class MenuSwitchedToPatch { diff --git a/GamecraftModdingAPI/Events/SimpleEventEmitterEngine.cs b/GamecraftModdingAPI/Events/SimpleEventEmitterEngine.cs index 0e51823..0ea8170 100644 --- a/GamecraftModdingAPI/Events/SimpleEventEmitterEngine.cs +++ b/GamecraftModdingAPI/Events/SimpleEventEmitterEngine.cs @@ -12,6 +12,7 @@ namespace GamecraftModdingAPI.Events /// /// A simple implementation of IEventEmitterEngine sufficient for most uses /// + [Obsolete] public class SimpleEventEmitterEngine : IEventEmitterEngine { public string Name { get; set; } diff --git a/GamecraftModdingAPI/Events/SimpleEventHandlerEngine.cs b/GamecraftModdingAPI/Events/SimpleEventHandlerEngine.cs index 18314d9..ebce21d 100644 --- a/GamecraftModdingAPI/Events/SimpleEventHandlerEngine.cs +++ b/GamecraftModdingAPI/Events/SimpleEventHandlerEngine.cs @@ -13,6 +13,7 @@ namespace GamecraftModdingAPI.Events /// /// A simple implementation of IEventHandlerEngine sufficient for most uses /// + [Obsolete] public class SimpleEventHandlerEngine : IEventHandlerEngine { public int type { get; set; } diff --git a/GamecraftModdingAPI/GamecraftModdingAPI.csproj b/GamecraftModdingAPI/GamecraftModdingAPI.csproj index 46dffee..7337ebe 100644 --- a/GamecraftModdingAPI/GamecraftModdingAPI.csproj +++ b/GamecraftModdingAPI/GamecraftModdingAPI.csproj @@ -2,7 +2,7 @@ net472 true - 1.4.0-preview + 1.5.0-preview Exmods GNU General Public Licence 3+ https://git.exmods.org/modtainers/GamecraftModdingAPI diff --git a/GamecraftModdingAPI/Main.cs b/GamecraftModdingAPI/Main.cs index aa80e2a..4a8cf24 100644 --- a/GamecraftModdingAPI/Main.cs +++ b/GamecraftModdingAPI/Main.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.Reflection; - +using GamecraftModdingAPI.Blocks; using HarmonyLib; using GamecraftModdingAPI.Utility; @@ -49,6 +49,7 @@ namespace GamecraftModdingAPI harmony.PatchAll(currentAssembly); // init utility Logging.MetaDebugLog($"Initializing Utility"); +#pragma warning disable 0612,0618 Utility.GameState.Init(); Utility.VersionTracking.Init(); // create default event emitters @@ -61,6 +62,7 @@ namespace GamecraftModdingAPI EventManager.AddEventEmitter(new SimpleEventEmitterEngine(EventType.GameSwitchedTo, "GamecraftModdingAPIGameSwitchedToEventEmitter", false)); EventManager.AddEventEmitter(GameHostTransitionDeterministicGroupEnginePatch.buildEngine); EventManager.AddEventEmitter(GameHostTransitionDeterministicGroupEnginePatch.simEngine); +#pragma warning restore 0612,0618 // init block implementors Logging.MetaDebugLog($"Initializing Blocks"); // init inventory @@ -70,6 +72,7 @@ namespace GamecraftModdingAPI // init object-oriented classes Player.Init(); Block.Init(); + Wire.Init(); GameClient.Init(); AsyncUtils.Init(); GamecraftModdingAPI.App.Client.Init(); diff --git a/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs b/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs index d8b50fa..e7bed82 100644 --- a/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs +++ b/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs @@ -68,6 +68,7 @@ namespace GamecraftModdingAPI.Tests //Utility.VersionTracking.Enable();//(very) unstable // debug/test handlers +#pragma warning disable 0612 HandlerBuilder.Builder() .Name("appinit API debug") .Handle(EventType.ApplicationInitialized) @@ -115,7 +116,7 @@ namespace GamecraftModdingAPI.Tests .Handle(EventType.Menu) .OnActivation(() => { throw new Exception("Event Handler always throws an exception!"); }) .Build(); - +#pragma warning restore 0612 /*HandlerBuilder.Builder("enter game from menu test") .Handle(EventType.Menu) .OnActivation(() => @@ -254,6 +255,19 @@ namespace GamecraftModdingAPI.Tests }) .Build(); + CommandBuilder.Builder() + .Name("WireTest") + .Description("Place two blocks and then wire them together") + .Action(() => + { + LogicGate notBlock = Block.PlaceNew(BlockIDs.NOTLogicBlock, new float3(1, 2, 0)); + LogicGate andBlock = Block.PlaceNew(BlockIDs.ANDLogicBlock, new float3(2, 2, 0)); + // connect NOT Gate output to AND Gate input #2 (ports are zero-indexed, so 1 is 2nd position and 0 is 1st position) + Wire conn = notBlock.Connect(0, andBlock, 1); + Logging.CommandLog(conn.ToString()); + }) + .Build(); + GameClient.SetDebugInfo("InstalledMods", InstalledMods); Block.Placed += (sender, args) => Logging.MetaDebugLog("Placed block " + args.Type + " with ID " + args.ID); diff --git a/GamecraftModdingAPI/Tests/TestRoot.cs b/GamecraftModdingAPI/Tests/TestRoot.cs index f39169e..6acb51c 100644 --- a/GamecraftModdingAPI/Tests/TestRoot.cs +++ b/GamecraftModdingAPI/Tests/TestRoot.cs @@ -209,7 +209,7 @@ namespace GamecraftModdingAPI.Tests { Assert.Fail($"Build test '{m}' raised an exception: {e.ToString()}"); } - yield return Yield.It; + yield return Yield.It; } } } diff --git a/GamecraftModdingAPI/Utility/VersionTracking.cs b/GamecraftModdingAPI/Utility/VersionTracking.cs index 780ac38..32b86dc 100644 --- a/GamecraftModdingAPI/Utility/VersionTracking.cs +++ b/GamecraftModdingAPI/Utility/VersionTracking.cs @@ -14,6 +14,7 @@ namespace GamecraftModdingAPI.Utility /// Tracks the API version the current game was built for. /// For compatibility reasons, this must be enabled before it will work. /// + [Obsolete] public static class VersionTracking { private static readonly VersionTrackingEngine versionEngine = new VersionTrackingEngine(); @@ -58,6 +59,7 @@ namespace GamecraftModdingAPI.Utility } + [Obsolete] internal class VersionTrackingEngine : IEventEmitterEngine { public string Name { get; } = "GamecraftModdingAPIVersionTrackingGameEngine"; @@ -94,11 +96,13 @@ namespace GamecraftModdingAPI.Utility public void Emit() { } } + [Obsolete] public struct ModVersionStruct : IEntityComponent { public uint version; } + [Obsolete] public class ModVersionDescriptor: SerializableEntityDescriptor { [HashName("GamecraftModdingAPIVersionV0")] diff --git a/doxygen.conf b/doxygen.conf index d587ecd..bf447b4 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 = "v1.3.0" +PROJECT_NUMBER = "v1.5.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