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, start, end); } /// /// 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, SignalingBlock src, SignalingBlock dest) { this.wireEGID = wire.ID; this.startBlockEGID = wire.sourceBlockEGID; this.endBlockEGID = wire.destinationBlockEGID; inputToOutput = false; endPortEGID = signalEngine.MatchBlockInputToPort(dest, wire.destinationPortUsage, out bool exists); if (!exists) throw new WireInvalidException("Wire end port not found"); startPortEGID = signalEngine.MatchBlockOutputToPort(src, 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() { } } }