@@ -122,6 +122,7 @@ namespace GamecraftModdingAPI | |||
new Dictionary<Type, ExclusiveGroupStruct[]> | |||
{ | |||
{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 | |||
} | |||
/// <summary> | |||
@@ -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) | |||
{ | |||
} | |||
} | |||
} |
@@ -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<LogicGate>(); }, "Block.Specialize<LogicGate>() raised an exception: ", "Block.Specialize<LogicGate>() completed without issue."); | |||
if (!Assert.NotNull(b, "Block.Specialize<LogicGate>() 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<LogicGate>() 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<LogicGate>(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 | |||
} |
@@ -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)) | |||
{ | |||
} | |||
} | |||
} |
@@ -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 | |||
/// <summary> | |||
/// Engine which executes signal actions | |||
/// </summary> | |||
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<WiresGroup>.Group); | |||
EntityComponentInitializer wireInitializer = Factory.BuildEntity<WireEntityDescriptor>(wireEGID); | |||
wireInitializer.Init(new WireEntityStruct | |||
{ | |||
sourceBlockEGID = startBlock, | |||
sourcePortUsage = startPort, | |||
destinationBlockEGID = endBlock, | |||
destinationPortUsage = endPort, | |||
}); | |||
return wireEGID; | |||
} | |||
public ref WireEntityStruct GetWire(EGID wire) | |||
{ | |||
if (!entitiesDB.Exists<WireEntityStruct>(wire)) | |||
{ | |||
throw new WiringException($"Wire {wire} does not exist"); | |||
} | |||
return ref entitiesDB.QueryEntity<WireEntityStruct>(wire); | |||
} | |||
public ref PortEntityStruct GetPort(EGID port) | |||
{ | |||
if (!entitiesDB.Exists<PortEntityStruct>(port)) | |||
{ | |||
throw new WiringException($"Port {port} does not exist (yet?)"); | |||
} | |||
return ref entitiesDB.QueryEntity<PortEntityStruct>(port); | |||
} | |||
public ref PortEntityStruct GetPortByOffset(BlockPortsStruct bps, byte portNumber, bool input) | |||
{ | |||
ExclusiveGroup group = input | |||
? NamedExclusiveGroup<InputPortsGroup>.Group | |||
: NamedExclusiveGroup<OutputPortsGroup>.Group; | |||
uint id = (input ? bps.firstInputID : bps.firstOutputID) + portNumber; | |||
EGID egid = new EGID(id, group); | |||
if (!entitiesDB.Exists<PortEntityStruct>(egid)) | |||
{ | |||
throw new WiringException("Port does not exist"); | |||
} | |||
return ref entitiesDB.QueryEntity<PortEntityStruct>(egid); | |||
} | |||
public ref PortEntityStruct GetPortByOffset(Block block, byte portNumber, bool input) | |||
{ | |||
BlockPortsStruct bps = GetFromDbOrInitData<BlockPortsStruct>(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<T>(EGID egid) where T : struct, IEntityComponent | |||
{ | |||
return ref entitiesDB.QueryEntity<T>(egid); | |||
} | |||
public bool Exists<T>(EGID egid) where T : struct, IEntityComponent | |||
{ | |||
return entitiesDB.Exists<T>(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<BlockPortsStruct>(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<BlockPortsStruct>(block, block.Id, out exists); | |||
return new EGID(ports.firstInputID + portUsage, NamedExclusiveGroup<InputPortsGroup>.Group); | |||
} | |||
public EGID MatchBlockOutputToPort(Block block, byte portUsage, out bool exists) | |||
{ | |||
BlockPortsStruct ports = GetFromDbOrInitData<BlockPortsStruct>(block, block.Id, out exists); | |||
return new EGID(ports.firstOutputID + portUsage, NamedExclusiveGroup<OutputPortsGroup>.Group); | |||
} | |||
public ref WireEntityStruct MatchPortToWire(EGID portID, EGID blockID, out bool exists) | |||
{ | |||
ref PortEntityStruct port = ref entitiesDB.QueryEntity<PortEntityStruct>(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<BlockPortsStruct>(startBlock); | |||
startPorts = new EGID[] {new EGID(ports.firstOutputID + startPort, NamedExclusiveGroup<OutputPortsGroup>.Group) }; | |||
} | |||
EGID[] endPorts; | |||
if (startPort == byte.MaxValue) | |||
{ | |||
// search all input ports on destination block | |||
endPorts = GetSignalInputs(endBlock); | |||
} | |||
else | |||
{ | |||
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(endBlock); | |||
endPorts = new EGID[] {new EGID(ports.firstInputID + endPort, NamedExclusiveGroup<InputPortsGroup>.Group) }; | |||
} | |||
EntityCollection<WireEntityStruct> wires = entitiesDB.QueryEntities<WireEntityStruct>(NamedExclusiveGroup<WiresGroup>.Group); | |||
for (int endIndex = 0; endIndex < endPorts.Length; endIndex++) | |||
{ | |||
PortEntityStruct endPES = entitiesDB.QueryEntity<PortEntityStruct>(endPorts[endIndex]); | |||
for (int startIndex = 0; startIndex < startPorts.Length; startIndex++) | |||
{ | |||
PortEntityStruct startPES = entitiesDB.QueryEntity<PortEntityStruct>(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<PortEntityStruct>(portID); | |||
@@ -175,6 +307,69 @@ namespace GamecraftModdingAPI.Blocks | |||
return res.ToArray(); | |||
} | |||
public EGID[] WiredToInput(EGID block, byte port) | |||
{ | |||
WireEntityStruct[] wireEntityStructs = Search(NamedExclusiveGroup<WiresGroup>.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<WiresGroup>.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<T>(ExclusiveGroup group, Func<T, bool> isMatch) where T : struct, IEntityComponent | |||
{ | |||
FasterList<T> results = new FasterList<T>(); | |||
EntityCollection<T> components = entitiesDB.QueryEntities<T>(group); | |||
for (uint i = 0; i < components.count; i++) | |||
{ | |||
if (isMatch(components[i])) | |||
{ | |||
results.Add(components[i]); | |||
} | |||
} | |||
return results.ToArray(); | |||
} | |||
private ref T GetFromDbOrInitData<T>(Block block, EGID id, out bool exists) where T : struct, IEntityComponent | |||
{ | |||
T[] defRef = new T[1]; | |||
if (entitiesDB.Exists<T>(id)) | |||
{ | |||
exists = true; | |||
return ref entitiesDB.QueryEntity<T>(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<T>()) | |||
{ | |||
exists = true; | |||
return ref initializer.Get<T>(); | |||
} | |||
exists = false; | |||
return ref defRef[0]; | |||
} | |||
private EntityCollection<ChannelDataStruct> GetSignalStruct(uint signalID, out uint index, bool input = true) | |||
{ | |||
ExclusiveGroup group = input | |||
@@ -16,18 +16,10 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public SignalingBlock(EGID id) : base(id) | |||
{ | |||
if (!BlockEngine.GetBlockInfoExists<BlockPortsStruct>(this)) | |||
{ | |||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); | |||
} | |||
} | |||
public SignalingBlock(uint id) : base(id) | |||
{ | |||
if (!BlockEngine.GetBlockInfoExists<BlockPortsStruct>(this)) | |||
{ | |||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); | |||
} | |||
} | |||
/// <summary> | |||
@@ -85,5 +77,91 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (BlockPortsStruct st) => st.outputCount); | |||
} | |||
/// <summary> | |||
/// Connect an output on this block to an input on another block. | |||
/// </summary> | |||
/// <param name="sourcePort">Output port number.</param> | |||
/// <param name="destination">Input block.</param> | |||
/// <param name="destinationPort">Input port number.</param> | |||
/// <returns>The wire connection</returns> | |||
/// <exception cref="WiringException">The wire could not be created.</exception> | |||
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); | |||
} | |||
/// <summary> | |||
/// The port's name. | |||
/// This is localized to the user's language, so this is not reliable for port identification. | |||
/// </summary> | |||
/// <param name="port">Port number.</param> | |||
/// <param name="input">Whether the port is an input (true) or an output (false).</param> | |||
/// <returns>The localized port name.</returns> | |||
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; | |||
} | |||
/// <summary> | |||
/// The input port's name. | |||
/// </summary> | |||
/// <param name="port">Input port number.</param> | |||
/// <returns>The port name, localized to the user's language.</returns> | |||
public string InputPortName(byte port) => PortName(port, true); | |||
/// <summary> | |||
/// The output port's name. | |||
/// </summary> | |||
/// <param name="port">Output port number.</param> | |||
/// <returns>The port name, localized to the user's language.</returns> | |||
public string OutputPortName(byte port) => PortName(port, false); | |||
/// <summary> | |||
/// All wires connected to the input port. | |||
/// These wires will always be wired output -> input. | |||
/// </summary> | |||
/// <param name="port">Port number.</param> | |||
/// <returns>Wires connected to the input port.</returns> | |||
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; | |||
} | |||
/// <summary> | |||
/// All wires connected to the output port. | |||
/// These wires will always be wired output -> input. | |||
/// </summary> | |||
/// <param name="port">Port number.</param> | |||
/// <returns>Wires connected to the output port.</returns> | |||
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; | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
/// <summary> | |||
/// An existing wire connection ending at the specified input. | |||
/// If multiple exist, this will return the first one found. | |||
/// </summary> | |||
/// <param name="end">Destination block.</param> | |||
/// <param name="endPort">Port number.</param> | |||
/// <returns>The wire, where the end of the wire is the block port specified, or null if does not exist.</returns> | |||
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; | |||
} | |||
/// <summary> | |||
/// An existing wire connection starting at the specified output. | |||
/// If multiple exist, this will return the first one found. | |||
/// </summary> | |||
/// <param name="start">Source block entity ID.</param> | |||
/// <param name="startPort">Port number.</param> | |||
/// <returns>The wire, where the start of the wire is the block port specified, or null if does not exist.</returns> | |||
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; | |||
} | |||
/// <summary> | |||
/// Construct a wire object from an existing connection. | |||
/// </summary> | |||
/// <param name="start">Starting block ID.</param> | |||
/// <param name="end">Ending block ID.</param> | |||
/// <param name="startPort">Starting port number, or guess if omitted.</param> | |||
/// <param name="endPort">Ending port number, or guess if omitted.</param> | |||
/// <exception cref="WireInvalidException">Guessing failed or wire does not exist.</exception> | |||
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"); | |||
} | |||
} | |||
} | |||
/// <summary> | |||
/// Construct a wire object from an existing wire connection. | |||
/// </summary> | |||
/// <param name="start">Starting block ID.</param> | |||
/// <param name="end">Ending block ID.</param> | |||
/// <param name="startPort">Starting port number.</param> | |||
/// <param name="endPort">Ending port number.</param> | |||
/// <param name="wire">The wire ID.</param> | |||
/// <param name="inputToOutput">Whether the wire direction goes input -> output (true) or output -> input (false, preferred).</param> | |||
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"); | |||
} | |||
} | |||
/// <summary> | |||
/// Construct a wire object from an existing wire connection. | |||
/// </summary> | |||
/// <param name="wireEgid">The wire ID.</param> | |||
public Wire(EGID wireEgid) | |||
{ | |||
this.wireEGID = wireEgid; | |||
WireEntityStruct wire = signalEngine.GetWire(wireEGID); | |||
this.startBlockEGID = wire.sourceBlockEGID; | |||
this.endBlockEGID = wire.destinationBlockEGID; | |||
this.inputToOutput = false; | |||
} | |||
/// <summary> | |||
/// The wire's in-game id. | |||
/// </summary> | |||
public EGID Id | |||
{ | |||
get => wireEGID; | |||
} | |||
/// <summary> | |||
/// The wire's signal value, as a float. | |||
/// </summary> | |||
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; | |||
} | |||
} | |||
/// <summary> | |||
/// The wire's string signal. | |||
/// </summary> | |||
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); | |||
} | |||
} | |||
/// <summary> | |||
/// The wire's raw string signal. | |||
/// </summary> | |||
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; | |||
} | |||
} | |||
/// <summary> | |||
/// The wire's signal id. | |||
/// I'm 50% sure this is useless. | |||
/// </summary> | |||
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; | |||
} | |||
} | |||
/// <summary> | |||
/// The block at the beginning of the wire. | |||
/// </summary> | |||
public SignalingBlock Start | |||
{ | |||
get => new SignalingBlock(startBlockEGID); | |||
} | |||
/// <summary> | |||
/// The port number that the beginning of the wire connects to. | |||
/// </summary> | |||
public byte StartPort | |||
{ | |||
get | |||
{ | |||
WireEntityStruct wire = signalEngine.GetWire(wireEGID); | |||
if (inputToOutput) | |||
{ | |||
return wire.destinationPortUsage; | |||
} | |||
return wire.sourcePortUsage; | |||
} | |||
} | |||
/// <summary> | |||
/// The block at the end of the wire. | |||
/// </summary> | |||
public SignalingBlock End | |||
{ | |||
get => new SignalingBlock(endBlockEGID); | |||
} | |||
/// <summary> | |||
/// The port number that the end of the wire connects to. | |||
/// </summary> | |||
public byte EndPort | |||
{ | |||
get | |||
{ | |||
WireEntityStruct wire = signalEngine.GetWire(wireEGID); | |||
if (inputToOutput) | |||
{ | |||
return wire.sourcePortUsage; | |||
} | |||
return wire.destinationPortUsage; | |||
} | |||
} | |||
/// <summary> | |||
/// 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). | |||
/// </summary> | |||
/// <returns>A copy of the wire object.</returns> | |||
public Wire OutputToInputCopy() | |||
{ | |||
return new Wire(wireEGID); | |||
} | |||
/// <summary> | |||
/// 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). | |||
/// </summary> | |||
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<WireEntityStruct>(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() { } | |||
} | |||
} |
@@ -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. | |||
/// </summary> | |||
[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<string, IEventEmitterEngine> _eventEmitters = new Dictionary<string, IEventEmitterEngine>(); | |||
@@ -2,7 +2,7 @@ | |||
<PropertyGroup> | |||
<TargetFramework>net472</TargetFramework> | |||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> | |||
<Version>1.4.0</Version> | |||
<Version>1.5.0</Version> | |||
<Authors>Exmods</Authors> | |||
<PackageLicenseExpression>GNU General Public Licence 3+</PackageLicenseExpression> | |||
<PackageProjectUrl>https://git.exmods.org/modtainers/GamecraftModdingAPI</PackageProjectUrl> | |||
@@ -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(); | |||
@@ -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<LogicGate>(BlockIDs.NOTLogicBlock, new float3(1, 2, 0)); | |||
LogicGate andBlock = Block.PlaceNew<LogicGate>(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) => | |||
@@ -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; | |||
} | |||
} | |||
} | |||
@@ -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 | |||