using System; using Gamecraft.Wires; using Svelto.ECS; using Svelto.ECS.Experimental; using TechbloxModdingAPI.Blocks.Engines; using TechbloxModdingAPI.Utility; namespace TechbloxModdingAPI.Blocks { public class Wire : EcsObjectBase { 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) { var (wire, id) = signalEngine.CreateNewWire(start.Id, startPort, end.Id, endPort); return new Wire(wire, start, end, id); } /// /// 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) { var port = signalEngine.MatchBlockIOToPort(end, endPort, false); if (!port) return null; var wire = signalEngine.MatchPortToWire(port, end.Id, out var egid); return wire ? new Wire(wire.Get().sourceBlockEGID, end.Id, wire.Get().sourcePortUsage, endPort, egid, false) : 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) { var port = signalEngine.MatchBlockIOToPort(start, startPort, true); if (!port) return null; var wire = signalEngine.MatchPortToWire(port, start.Id, out var egid); return wire ? new Wire(start.Id, wire.Get().destinationBlockEGID, startPort, wire.Get().destinationPortUsage, egid, false) : null; } /// /// Construct a wire object froam n 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) : base(ecs => { var th = (Wire)ecs; th.startBlockEGID = start.Id; th.endBlockEGID = end.Id; bool flipped = false; // find block ports EGID wire = signalEngine.MatchBlocksToWire(start.Id, end.Id, startPort, endPort); if (wire == default) { // flip I/O around and try again wire = signalEngine.MatchBlocksToWire(end.Id, start.Id, endPort, startPort); flipped = true; // 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 } if (wire != default) { th.Construct(start.Id, end.Id, startPort, endPort, wire, flipped); } else { throw new WireInvalidException("Wire not found"); } return th.wireEGID; }) { } /// /// 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(start.Id, end.Id, startPort, endPort, wire, inputToOutput) { } private Wire(EGID startBlock, EGID endBlock, byte startPort, byte endPort, EGID wire, bool inputToOutput) : base(wire) { Construct(startBlock, endBlock, startPort, endPort, wire, inputToOutput); } private void Construct(EGID startBlock, EGID endBlock, byte startPort, byte endPort, EGID wire, bool inputToOutput) { this.startBlockEGID = startBlock; this.endBlockEGID = endBlock; this.inputToOutput = inputToOutput; this.wireEGID = wire; endPortEGID = signalEngine.MatchBlockIOToPort(startBlock, startPort, inputToOutput).EGID; if (endPortEGID == default) throw new WireInvalidException("Wire end port not found"); startPortEGID = signalEngine.MatchBlockIOToPort(endBlock, endPort, !inputToOutput).EGID; if (startPortEGID == default) 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) : base(wireEgid) { WireEntityStruct wire = signalEngine.GetWire(wireEGID); Construct(wire.sourceBlockEGID, wire.destinationBlockEGID, wire.sourcePortUsage, wire.destinationPortUsage, wireEgid, false); } private Wire(WireEntityStruct wire, SignalingBlock src, SignalingBlock dest, EGID wireEgid) : this(src, dest, wire.sourcePortUsage, wire.destinationPortUsage, wireEgid, false) { } /// /// The wire's signal value, as a float. /// public float Float { get { return signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsFloat; } set { signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsFloat = value; } } /// /// The wire's string signal. /// public string String { get { return signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsEcsString; } set { signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsEcsString.Set(value); } } /// /// The wire's raw string signal. /// public ECSString ECSString { get { return signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsEcsString; } set { signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsEcsString = value; } } /// /// The wire's signal id. /// I'm 50% sure this is useless. /// public uint SignalId { get { return signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsID; } set { signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsID = value; } } /// /// The block at the beginning of the wire. /// public SignalingBlock Start { get => (SignalingBlock)Block.New(startBlockEGID); } /// /// The port number that the beginning of the wire connects to. /// public byte StartPort { get => startPort; } /// /// The display name of the start port. /// public string StartPortName { get => signalEngine.GetPort(startPortEGID).portNameLocalised; } /// /// The block at the end of the wire. /// public SignalingBlock End { get => (SignalingBlock)Block.New(endBlockEGID); } /// /// The port number that the end of the wire connects to. /// public byte EndPort { get => endPort; } /// /// The display name of the end port. /// public string EndPortName { get => signalEngine.GetPort(endPortEGID).portNameLocalised; } /// /// 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 GetInstance(wireEGID, egid => new Wire(egid)); } /// /// 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 (endBlockEGID, startBlockEGID) = (startBlockEGID, endBlockEGID); var tempPort = endPortEGID; endPortEGID = startPortEGID; startPortEGID = tempPort; (endPort, startPort) = (startPort, endPort); } } 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 {(StartPort != byte.MaxValue ? Start.PortName(StartPort, inputToOutput) : "")}) -> ({End.Type}::{EndPort} aka {(EndPort != byte.MaxValue ? 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() { } } }