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() { }
}
}