Merge branch 'master' into preview

This commit is contained in:
NGnius (Graham) 2020-08-07 12:23:16 -04:00
commit 167ea5388b
30 changed files with 837 additions and 20 deletions

67
Automation/bump_version.py Executable file
View file

@ -0,0 +1,67 @@
#!/usr/bin/python3
import argparse
import re
# this assumes a mostly semver-complient version number
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Increment GamecraftModdingAPI version")
parser.add_argument('version', metavar="VN", type=str, help="The version number to increment, or the index of the number (zero-indexed).")
args = parser.parse_args()
version_index = -1
try:
version_index = int(args.version)
except Exception:
if args.version.lower() == "major":
version_index = 0
elif args.version.lower() == "minor":
version_index = 1
elif args.version.lower() == "patch":
version_index = 2
if version_index < 0:
print("Could not parse version argument.")
exit(version_index)
print(version_index)
old_version = ""
new_version = ""
with open("../GamecraftModdingAPI/GamecraftModdingAPI.csproj", "r") as xmlFile:
print("Parsing GamecraftModdingAPI.csproj")
fileStr = xmlFile.read()
versionMatch = re.search(r"<Version>(.+)</Version>", fileStr)
if versionMatch is None:
print("Unable to find version number in GamecraftModdingAPI.csproj")
exit(1)
old_version = versionMatch.group(1)
versionList = old_version.split(".")
if len(versionList) <= version_index:
print("Invalid version string")
exit(1)
versionList[version_index] = str(int(versionList[version_index]) + 1)
for i in range(version_index + 1, len(versionList)):
try:
int(versionList[i])
versionList[i] = "0"
except Exception:
tmp = versionList[i].split("-")
tmp[0] = "0"
versionList[i] = "-".join(tmp)
new_version = ".".join(versionList)
print(new_version)
newFileContents = fileStr.replace("<Version>"+old_version+"</Version>", "<Version>"+new_version+"</Version>")
with open("../GamecraftModdingAPI/GamecraftModdingAPI.csproj", "w") as xmlFile:
print("Writing new version to project file")
xmlFile.write(newFileContents)
with open("../doxygen.conf", "r") as doxFile:
print("Parsing doxygen.conf")
doxStr = doxFile.read()
newFileContents = doxStr.replace("= \"v" + old_version + "\"", "= \"v" + new_version + "\"")
with open("../doxygen.conf", "w") as doxFile:
print("Writing new version to doxygen config")
doxFile.write(newFileContents)

View file

@ -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>

View file

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

View file

@ -4,6 +4,7 @@ using Gamecraft.Wires;
using GamecraftModdingAPI;
using GamecraftModdingAPI.Tests;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Blocks
{
@ -96,10 +97,28 @@ namespace GamecraftModdingAPI.Blocks
if (!Assert.NotNull(b, "Block.Specialize<MusicBlock>() returned null, possibly because it failed silently.", "Specialized MusicBlock is not null.")) return;
b.IsPlaying = true; // play sfx
if (!Assert.Equal(b.IsPlaying, true, $"MusicBlock.IsPlaying {b.IsPlaying} does not equal true, possibly because it failed silently.", "MusicBlock.IsPlaying is set properly.")) return;
if (!Assert.Equal(b.ChannelType, ChannelType.Character, $"MusicBlock.ChannelType {b.ChannelType} does not equal default value, possibly because it failed silently.", "MusicBlock.ChannelType is equal to default.")) return;
if (!Assert.Equal(b.ChannelType, ChannelType.None, $"MusicBlock.ChannelType {b.ChannelType} does not equal default value, possibly because it failed silently.", "MusicBlock.ChannelType is equal to default.")) return;
//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
}

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

View file

@ -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,74 @@ namespace GamecraftModdingAPI.Blocks
IsInGame = true;
}
// implementations for Signal static class
// implementations for block wiring
public WireEntityStruct 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,
ID = wireEGID
});
return wireInitializer.Get<WireEntityStruct>();
}
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 +193,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 +204,42 @@ 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 MatchBlockInputToPort(EGID block, byte portUsage, out bool exists)
{
if (!entitiesDB.Exists<BlockPortsStruct>(block))
{
exists = false;
return default;
}
exists = true;
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(block);
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 EGID MatchBlockOutputToPort(EGID block, byte portUsage, out bool exists)
{
if (!entitiesDB.Exists<BlockPortsStruct>(block))
{
exists = false;
return default;
}
exists = true;
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(block);
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 +258,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 +332,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

View file

@ -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;
}
}
}

View file

@ -0,0 +1,355 @@
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);
}
/// <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;
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");
}
}
}
/// <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");
}
this.startPort = startPort;
this.endPort = endPort;
}
/// <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;
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)
{
this.wireEGID = wire.ID;
this.startBlockEGID = wire.sourceBlockEGID;
this.endBlockEGID = wire.destinationBlockEGID;
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;
}
/// <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 => startPort;
}
/// <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 => endPort;
}
/// <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 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).
/// </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;
byte tempPortNumber = endPort;
endPort = startPort;
startPort = tempPortNumber;
}
}
public override string ToString()
{
if (signalEngine.Exists<WireEntityStruct>(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() { }
}
}

View file

@ -18,7 +18,8 @@ namespace GamecraftModdingAPI.Events
/// Patch of RobocraftX.StateSync.DeterministicStepCompositionRoot.ComposeEnginesGroups(...)
/// </summary>
//[HarmonyPatch(typeof(DeterministicStepCompositionRoot), "DeterministicCompose")]
[HarmonyPatch]
[Obsolete]
[HarmonyPatch]
class GameHostTransitionDeterministicGroupEnginePatch
{

View file

@ -4,6 +4,7 @@ using Svelto.ECS;
namespace GamecraftModdingAPI.Events
{
[Obsolete]
public class EmitterBuilder
{
private string name;

View file

@ -11,6 +11,7 @@ namespace GamecraftModdingAPI.Events
/// <summary>
/// Convenient factories for mod event engines
/// </summary>
[Obsolete]
public static class EventEngineFactory
{
/// <summary>

View file

@ -14,6 +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("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>();

View file

@ -17,6 +17,7 @@ namespace GamecraftModdingAPI.Events
/// <summary>
/// Patch of RobocraftX.FullGameCompositionRoot.ActivateGame()
/// </summary>
[Obsolete]
[HarmonyPatch]
class GameActivatedComposePatch
{

View file

@ -14,6 +14,7 @@ namespace GamecraftModdingAPI.Events
/// <summary>
/// Patch of RobocraftX.FullGameCompositionRoot.ReloadGame()
/// </summary>
[Obsolete]
[HarmonyPatch(typeof(FullGameCompositionRoot), "ReloadGame")]
class GameReloadedPatch
{

View file

@ -12,6 +12,7 @@ namespace GamecraftModdingAPI.Events
/// <summary>
/// Event emitter engine for switching to to build mode.
/// </summary>
[Obsolete]
public class GameStateBuildEmitterEngine : IEventEmitterEngine, IUnorderedInitializeOnTimeStoppedModeEntered
{
public string Name { get; } = "GamecraftModdingAPIGameStateBuildEventEmitter" ;

View file

@ -12,6 +12,7 @@ namespace GamecraftModdingAPI.Events
/// <summary>
/// Event emitter engine for switching to simulation mode.
/// </summary>
[Obsolete]
public class GameStateSimulationEmitterEngine : IEventEmitterEngine, IUnorderedInitializeOnTimeRunningModeEntered
{
public string Name { get; } = "GamecraftModdingAPIGameStateSimulationEventEmitter" ;

View file

@ -18,6 +18,7 @@ namespace GamecraftModdingAPI.Events
/// Patch of RobocraftX.FullGameCompositionRoot.ActivateGame()
/// (scheduled for execution during RobocraftX.FullGameCompositionRoot.SwitchToGame())
/// </summary>
[Obsolete]
[HarmonyPatch(typeof(FullGameCompositionRoot), "SwitchToGame")]
class GameSwitchedToPatch
{

View file

@ -4,6 +4,7 @@ using Svelto.ECS;
namespace GamecraftModdingAPI.Events
{
[Obsolete]
public class HandlerBuilder
{
private string name;

View file

@ -13,6 +13,7 @@ namespace GamecraftModdingAPI.Events
/// <summary>
/// Engine interface to create a ModEventEntityStruct in entitiesDB when a specific event occurs.
/// </summary>
[Obsolete]
public interface IEventEmitterEngine : IFactoryEngine
{
/// <summary>

View file

@ -14,6 +14,7 @@ namespace GamecraftModdingAPI.Events
/// <summary>
/// Engine interface to handle ModEventEntityStruct events emitted by IEventEmitterEngines.
/// </summary>
[Obsolete]
public interface IEventHandlerEngine : IReactionaryEngine<ModEventEntityStruct>
{
}

View file

@ -15,6 +15,7 @@ namespace GamecraftModdingAPI.Events
/// <summary>
/// Patch of RobocraftX.FullGameCompositionRoot.ActivateMenu()
/// </summary>
[Obsolete]
[HarmonyPatch(typeof(FullGameCompositionRoot), "ActivateMenu")]
class MenuActivatedPatch
{

View file

@ -15,6 +15,7 @@ namespace GamecraftModdingAPI.Events
/// <summary>
/// Patch of RobocraftX.FullGameCompositionRoot.SwitchToMenu()
/// </summary>
[Obsolete]
[HarmonyPatch(typeof(FullGameCompositionRoot), "SwitchToMenu")]
class MenuSwitchedToPatch
{

View file

@ -12,6 +12,7 @@ namespace GamecraftModdingAPI.Events
/// <summary>
/// A simple implementation of IEventEmitterEngine sufficient for most uses
/// </summary>
[Obsolete]
public class SimpleEventEmitterEngine : IEventEmitterEngine
{
public string Name { get; set; }

View file

@ -13,6 +13,7 @@ namespace GamecraftModdingAPI.Events
/// <summary>
/// A simple implementation of IEventHandlerEngine sufficient for most uses
/// </summary>
[Obsolete]
public class SimpleEventHandlerEngine : IEventHandlerEngine
{
public int type { get; set; }

View file

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Version>1.4.0-preview</Version>
<Version>1.5.0-preview</Version>
<Authors>Exmods</Authors>
<PackageLicenseExpression>GNU General Public Licence 3+</PackageLicenseExpression>
<PackageProjectUrl>https://git.exmods.org/modtainers/GamecraftModdingAPI</PackageProjectUrl>

View file

@ -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();

View file

@ -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);

View file

@ -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;
}
}
}

View file

@ -14,6 +14,7 @@ namespace GamecraftModdingAPI.Utility
/// Tracks the API version the current game was built for.
/// For compatibility reasons, this must be enabled before it will work.
/// </summary>
[Obsolete]
public static class VersionTracking
{
private static readonly VersionTrackingEngine versionEngine = new VersionTrackingEngine();
@ -58,6 +59,7 @@ namespace GamecraftModdingAPI.Utility
}
[Obsolete]
internal class VersionTrackingEngine : IEventEmitterEngine
{
public string Name { get; } = "GamecraftModdingAPIVersionTrackingGameEngine";
@ -94,11 +96,13 @@ namespace GamecraftModdingAPI.Utility
public void Emit() { }
}
[Obsolete]
public struct ModVersionStruct : IEntityComponent
{
public uint version;
}
[Obsolete]
public class ModVersionDescriptor: SerializableEntityDescriptor<ModVersionDescriptor._ModVersionDescriptor>
{
[HashName("GamecraftModdingAPIVersionV0")]

View file

@ -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