A stable modding interface between Techblox and mods https://mod.exmods.org/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

355 lines
14KB

  1. using System;
  2. using Gamecraft.Wires;
  3. using Svelto.ECS;
  4. using Svelto.ECS.Experimental;
  5. using GamecraftModdingAPI.Utility;
  6. namespace GamecraftModdingAPI.Blocks
  7. {
  8. public class Wire
  9. {
  10. internal static SignalEngine signalEngine;
  11. protected EGID startPortEGID;
  12. protected EGID endPortEGID;
  13. protected EGID startBlockEGID;
  14. protected EGID endBlockEGID;
  15. protected EGID wireEGID;
  16. protected bool inputToOutput;
  17. protected byte startPort;
  18. protected byte endPort;
  19. public static Wire Connect(SignalingBlock start, byte startPort, SignalingBlock end, byte endPort)
  20. {
  21. WireEntityStruct wire = signalEngine.CreateNewWire(start.Id, startPort, end.Id, endPort);
  22. return new Wire(wire, start, end);
  23. }
  24. /// <summary>
  25. /// An existing wire connection ending at the specified input.
  26. /// If multiple exist, this will return the first one found.
  27. /// </summary>
  28. /// <param name="end">Destination block.</param>
  29. /// <param name="endPort">Port number.</param>
  30. /// <returns>The wire, where the end of the wire is the block port specified, or null if does not exist.</returns>
  31. public static Wire ConnectedToInputPort(SignalingBlock end, byte endPort)
  32. {
  33. EGID port = signalEngine.MatchBlockInputToPort(end, endPort, out bool exists);
  34. if (!exists) return null;
  35. WireEntityStruct wire = signalEngine.MatchPortToWire(port, end.Id, out exists);
  36. if (exists)
  37. {
  38. return new Wire(new Block(wire.sourceBlockEGID), end, wire.sourcePortUsage, endPort);
  39. }
  40. return null;
  41. }
  42. /// <summary>
  43. /// An existing wire connection starting at the specified output.
  44. /// If multiple exist, this will return the first one found.
  45. /// </summary>
  46. /// <param name="start">Source block entity ID.</param>
  47. /// <param name="startPort">Port number.</param>
  48. /// <returns>The wire, where the start of the wire is the block port specified, or null if does not exist.</returns>
  49. public static Wire ConnectedToOutputPort(SignalingBlock start, byte startPort)
  50. {
  51. EGID port = signalEngine.MatchBlockOutputToPort(start, startPort, out bool exists);
  52. if (!exists) return null;
  53. WireEntityStruct wire = signalEngine.MatchPortToWire(port, start.Id, out exists);
  54. if (exists)
  55. {
  56. return new Wire(start, new Block(wire.destinationBlockEGID), startPort, wire.destinationPortUsage);
  57. }
  58. return null;
  59. }
  60. /// <summary>
  61. /// Construct a wire object from an existing connection.
  62. /// </summary>
  63. /// <param name="start">Starting block ID.</param>
  64. /// <param name="end">Ending block ID.</param>
  65. /// <param name="startPort">Starting port number, or guess if omitted.</param>
  66. /// <param name="endPort">Ending port number, or guess if omitted.</param>
  67. /// <exception cref="WireInvalidException">Guessing failed or wire does not exist.</exception>
  68. public Wire(Block start, Block end, byte startPort = Byte.MaxValue, byte endPort = Byte.MaxValue)
  69. {
  70. startBlockEGID = start.Id;
  71. endBlockEGID = end.Id;
  72. // find block ports
  73. WireEntityStruct wire = signalEngine.MatchBlocksToWire(start.Id, end.Id, out bool exists, startPort, endPort);
  74. if (exists)
  75. {
  76. wireEGID = wire.ID;
  77. endPortEGID = signalEngine.MatchBlockInputToPort(end, wire.destinationPortUsage, out exists);
  78. if (!exists) throw new WireInvalidException("Wire end port not found");
  79. startPortEGID = signalEngine.MatchBlockOutputToPort(start, wire.sourcePortUsage, out exists);
  80. if (!exists) throw new WireInvalidException("Wire start port not found");
  81. inputToOutput = false;
  82. endPort = wire.destinationPortUsage;
  83. startPort = wire.sourcePortUsage;
  84. }
  85. else
  86. {
  87. // flip I/O around and try again
  88. wire = signalEngine.MatchBlocksToWire(end.Id, start.Id, out exists, endPort, startPort);
  89. if (exists)
  90. {
  91. wireEGID = wire.ID;
  92. endPortEGID = signalEngine.MatchBlockOutputToPort(end, wire.sourcePortUsage, out exists);
  93. if (!exists) throw new WireInvalidException("Wire end port not found");
  94. startPortEGID = signalEngine.MatchBlockInputToPort(start, wire.destinationPortUsage, out exists);
  95. if (!exists) throw new WireInvalidException("Wire start port not found");
  96. inputToOutput = true; // end is actually the source
  97. // NB: start and end are handled exactly as they're received as params.
  98. // This makes wire traversal easier, but makes logic in this class a bit more complex
  99. endPort = wire.sourcePortUsage;
  100. startPort = wire.destinationPortUsage;
  101. }
  102. else
  103. {
  104. throw new WireInvalidException("Wire not found");
  105. }
  106. }
  107. }
  108. /// <summary>
  109. /// Construct a wire object from an existing wire connection.
  110. /// </summary>
  111. /// <param name="start">Starting block ID.</param>
  112. /// <param name="end">Ending block ID.</param>
  113. /// <param name="startPort">Starting port number.</param>
  114. /// <param name="endPort">Ending port number.</param>
  115. /// <param name="wire">The wire ID.</param>
  116. /// <param name="inputToOutput">Whether the wire direction goes input -> output (true) or output -> input (false, preferred).</param>
  117. public Wire(Block start, Block end, byte startPort, byte endPort, EGID wire, bool inputToOutput)
  118. {
  119. this.startBlockEGID = start.Id;
  120. this.endBlockEGID = end.Id;
  121. this.inputToOutput = inputToOutput;
  122. this.wireEGID = wire;
  123. if (inputToOutput)
  124. {
  125. endPortEGID = signalEngine.MatchBlockOutputToPort(start, startPort, out bool exists);
  126. if (!exists) throw new WireInvalidException("Wire end port not found");
  127. startPortEGID = signalEngine.MatchBlockInputToPort(end, endPort, out exists);
  128. if (!exists) throw new WireInvalidException("Wire start port not found");
  129. }
  130. else
  131. {
  132. endPortEGID = signalEngine.MatchBlockInputToPort(end, endPort, out bool exists);
  133. if (!exists) throw new WireInvalidException("Wire end port not found");
  134. startPortEGID = signalEngine.MatchBlockOutputToPort(start, startPort, out exists);
  135. if (!exists) throw new WireInvalidException("Wire start port not found");
  136. }
  137. this.startPort = startPort;
  138. this.endPort = endPort;
  139. }
  140. /// <summary>
  141. /// Construct a wire object from an existing wire connection.
  142. /// </summary>
  143. /// <param name="wireEgid">The wire ID.</param>
  144. public Wire(EGID wireEgid)
  145. {
  146. this.wireEGID = wireEgid;
  147. WireEntityStruct wire = signalEngine.GetWire(wireEGID);
  148. this.startBlockEGID = wire.sourceBlockEGID;
  149. this.endBlockEGID = wire.destinationBlockEGID;
  150. this.inputToOutput = false;
  151. endPortEGID = signalEngine.MatchBlockInputToPort(wire.destinationBlockEGID, wire.destinationPortUsage, out bool exists);
  152. if (!exists) throw new WireInvalidException("Wire end port not found");
  153. startPortEGID = signalEngine.MatchBlockOutputToPort(wire.sourceBlockEGID, wire.sourcePortUsage, out exists);
  154. if (!exists) throw new WireInvalidException("Wire start port not found");
  155. this.endPort = wire.destinationPortUsage;
  156. this.startPort = wire.sourcePortUsage;
  157. }
  158. internal Wire(WireEntityStruct wire, SignalingBlock src, SignalingBlock dest)
  159. {
  160. this.wireEGID = wire.ID;
  161. this.startBlockEGID = wire.sourceBlockEGID;
  162. this.endBlockEGID = wire.destinationBlockEGID;
  163. inputToOutput = false;
  164. endPortEGID = signalEngine.MatchBlockInputToPort(dest, wire.destinationPortUsage, out bool exists);
  165. if (!exists) throw new WireInvalidException("Wire end port not found");
  166. startPortEGID = signalEngine.MatchBlockOutputToPort(src, wire.sourcePortUsage, out exists);
  167. if (!exists) throw new WireInvalidException("Wire start port not found");
  168. this.endPort = wire.destinationPortUsage;
  169. this.startPort = wire.sourcePortUsage;
  170. }
  171. /// <summary>
  172. /// The wire's in-game id.
  173. /// </summary>
  174. public EGID Id
  175. {
  176. get => wireEGID;
  177. }
  178. /// <summary>
  179. /// The wire's signal value, as a float.
  180. /// </summary>
  181. public float Float
  182. {
  183. get
  184. {
  185. ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
  186. if (!exists) return 0f;
  187. return cds.valueAsFloat;
  188. }
  189. set
  190. {
  191. ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
  192. if (!exists) return;
  193. cds.valueAsFloat = value;
  194. }
  195. }
  196. /// <summary>
  197. /// The wire's string signal.
  198. /// </summary>
  199. public string String
  200. {
  201. get
  202. {
  203. ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
  204. if (!exists) return "";
  205. return cds.valueAsEcsString;
  206. }
  207. set
  208. {
  209. ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
  210. if (!exists) return;
  211. cds.valueAsEcsString.Set(value);
  212. }
  213. }
  214. /// <summary>
  215. /// The wire's raw string signal.
  216. /// </summary>
  217. public ECSString ECSString
  218. {
  219. get
  220. {
  221. ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
  222. if (!exists) return default;
  223. return cds.valueAsEcsString;
  224. }
  225. set
  226. {
  227. ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
  228. if (!exists) return;
  229. cds.valueAsEcsString = value;
  230. }
  231. }
  232. /// <summary>
  233. /// The wire's signal id.
  234. /// I'm 50% sure this is useless.
  235. /// </summary>
  236. public uint SignalId
  237. {
  238. get
  239. {
  240. ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
  241. if (!exists) return uint.MaxValue;
  242. return cds.valueAsID;
  243. }
  244. set
  245. {
  246. ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
  247. if (!exists) return;
  248. cds.valueAsID = value;
  249. }
  250. }
  251. /// <summary>
  252. /// The block at the beginning of the wire.
  253. /// </summary>
  254. public SignalingBlock Start
  255. {
  256. get => new SignalingBlock(startBlockEGID);
  257. }
  258. /// <summary>
  259. /// The port number that the beginning of the wire connects to.
  260. /// </summary>
  261. public byte StartPort
  262. {
  263. get => startPort;
  264. }
  265. /// <summary>
  266. /// The block at the end of the wire.
  267. /// </summary>
  268. public SignalingBlock End
  269. {
  270. get => new SignalingBlock(endBlockEGID);
  271. }
  272. /// <summary>
  273. /// The port number that the end of the wire connects to.
  274. /// </summary>
  275. public byte EndPort
  276. {
  277. get => endPort;
  278. }
  279. /// <summary>
  280. /// 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.
  281. /// This is simply a different memory configuration and does not affect the in-game wire (which is always output -> input).
  282. /// </summary>
  283. /// <returns>A copy of the wire object.</returns>
  284. public Wire OutputToInputCopy()
  285. {
  286. return new Wire(wireEGID);
  287. }
  288. /// <summary>
  289. /// Convert the wire object to the direction the signal flows.
  290. /// Signals on wires always flow from a block output port to a block input port.
  291. /// This is simply a different memory configuration and does not affect the in-game wire (which is always output -> input).
  292. /// </summary>
  293. public void OutputToInputInPlace()
  294. {
  295. if (inputToOutput)
  296. {
  297. inputToOutput = false;
  298. // swap inputs and outputs
  299. EGID temp = endBlockEGID;
  300. endBlockEGID = startBlockEGID;
  301. startBlockEGID = temp;
  302. temp = endPortEGID;
  303. endPortEGID = startPortEGID;
  304. startPortEGID = temp;
  305. byte tempPortNumber = endPort;
  306. endPort = startPort;
  307. startPort = tempPortNumber;
  308. }
  309. }
  310. public override string ToString()
  311. {
  312. if (signalEngine.Exists<WireEntityStruct>(wireEGID))
  313. {
  314. 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)})";
  315. }
  316. return $"{nameof(Id)}: {Id}, Start{nameof(Start.Id)}: {Start.Id}, End{nameof(End.Id)}: {End.Id}, ({Start.Type}::{StartPort} -> {End.Type}::{EndPort})";
  317. }
  318. internal static void Init() { }
  319. }
  320. }