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 19ad7e6..5447f6c 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 { @@ -100,6 +101,24 @@ namespace GamecraftModdingAPI.Blocks //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..e710e24 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,73 @@ namespace GamecraftModdingAPI.Blocks IsInGame = true; } - // implementations for Signal static class + // implementations for block wiring + + public EGID 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, + }); + return wireEGID; + } + + 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 +192,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 +203,18 @@ 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 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 ref WireEntityStruct MatchPortToWire(EGID portID, EGID blockID, out bool exists) { ref PortEntityStruct port = ref entitiesDB.QueryEntity(portID); @@ -152,6 +233,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 +307,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..9a73135 --- /dev/null +++ b/GamecraftModdingAPI/Blocks/Wire.cs @@ -0,0 +1,338 @@ +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; + + public static Wire Connect(SignalingBlock start, byte startPort, SignalingBlock end, byte endPort) + { + EGID wireEgid = signalEngine.CreateNewWire(start.Id, startPort, end.Id, endPort); + return new Wire(start, end, startPort, endPort, wireEgid, false); + } + + /// + /// 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; + } + 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 + } + 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"); + } + } + + /// + /// 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; + } + + /// + /// 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 + { + WireEntityStruct wire = signalEngine.GetWire(wireEGID); + if (inputToOutput) + { + return wire.destinationPortUsage; + } + return wire.sourcePortUsage; + } + } + + /// + /// 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 + { + WireEntityStruct wire = signalEngine.GetWire(wireEGID); + if (inputToOutput) + { + return wire.sourcePortUsage; + } + return wire.destinationPortUsage; + } + } + + /// + /// 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 flows 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; + } + } + + public override string ToString() + { + if (signalEngine.Exists(wireEGID)) + { + return $"{nameof(Id)}: {Id}, Start{nameof(Start.Id)}: {Start.Id}, End{nameof(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(Id)}: {End.Id}, ({Start.Type}::{StartPort}) -> ({End.Type}::{EndPort})"; + } + + internal static void Init() { } + } +} \ No newline at end of file diff --git a/GamecraftModdingAPI/Events/EventManager.cs b/GamecraftModdingAPI/Events/EventManager.cs index a1c757e..f021e9f 100644 --- a/GamecraftModdingAPI/Events/EventManager.cs +++ b/GamecraftModdingAPI/Events/EventManager.cs @@ -14,7 +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] + [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/GamecraftModdingAPI.csproj b/GamecraftModdingAPI/GamecraftModdingAPI.csproj index a7c108a..32c780b 100644 --- a/GamecraftModdingAPI/GamecraftModdingAPI.csproj +++ b/GamecraftModdingAPI/GamecraftModdingAPI.csproj @@ -2,7 +2,7 @@ net472 true - 1.4.0 + 1.5.0 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..639125c 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(() => @@ -253,6 +254,19 @@ namespace GamecraftModdingAPI.Tests Logging.CommandLog($"Blocks placed in {sw.ElapsedMilliseconds} ms"); }) .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) => 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/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