Add wiring API and improve signal support
This commit is contained in:
parent
ca0e6e089d
commit
708dbdd81d
13 changed files with 706 additions and 18 deletions
|
@ -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
|
||||
}
|
||||
|
|
16
GamecraftModdingAPI/Blocks/LogicGate.cs
Normal file
16
GamecraftModdingAPI/Blocks/LogicGate.cs
Normal file
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
338
GamecraftModdingAPI/Blocks/Wire.cs
Normal file
338
GamecraftModdingAPI/Blocks/Wire.cs
Normal file
|
@ -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(() =>
|
||||
|
@ -254,6 +255,19 @@ namespace GamecraftModdingAPI.Tests
|
|||
})
|
||||
.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) =>
|
||||
Logging.MetaDebugLog("Placed block " + args.Type + " with ID " + args.ID);
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue