Magically import images and more into Gamecraft as blocks
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.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

370 lines
14KB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Reflection;
  5. using System.Runtime.CompilerServices;
  6. using RobocraftX.Common;
  7. using Newtonsoft.Json;
  8. using Unity.Mathematics;
  9. using UnityEngine;
  10. using GamecraftModdingAPI.Blocks;
  11. using GamecraftModdingAPI.Utility;
  12. using GamecraftModdingAPI;
  13. using Pixi.Common;
  14. namespace Pixi.Robots
  15. {
  16. public static class CubeUtility
  17. {
  18. private static Dictionary<uint, string> map = null;
  19. private static Dictionary<uint, BlockJsonInfo[]> blueprintMap = null;
  20. public static RobotStruct? ParseRobotInfo(string robotInfo)
  21. {
  22. try
  23. {
  24. return JsonConvert.DeserializeObject<RobotStruct>(robotInfo);
  25. }
  26. catch (Exception e)
  27. {
  28. Logging.MetaLog(e);
  29. return null;
  30. }
  31. }
  32. public static CubeInfo[] ParseCubes(RobotStruct robot)
  33. {
  34. return ParseCubes(robot.cubeData, robot.colourData);
  35. }
  36. public static CubeInfo[] ParseCubes(string cubeData, string colourData)
  37. {
  38. BinaryBufferReader cubes = new BinaryBufferReader(Convert.FromBase64String(cubeData), 0);
  39. BinaryBufferReader colours = new BinaryBufferReader(Convert.FromBase64String(colourData), 0);
  40. uint cubeCount = cubes.ReadUint();
  41. uint colourCount = colours.ReadUint();
  42. if (cubeCount != colourCount)
  43. {
  44. Logging.MetaLog("Something is fucking broken");
  45. return null;
  46. }
  47. Logging.MetaLog($"Detected {cubeCount} cubes");
  48. CubeInfo[] result = new CubeInfo[cubeCount];
  49. for (int cube = 0; cube < cubeCount; cube++)
  50. {
  51. result[cube] = TranslateSpacialEnumerations(
  52. cubes.ReadUint(),
  53. cubes.ReadByte(),
  54. cubes.ReadByte(),
  55. cubes.ReadByte(),
  56. cubes.ReadByte(),
  57. colours.ReadByte(),
  58. colours.ReadByte(),
  59. colours.ReadByte(),
  60. colours.ReadByte()
  61. );
  62. }
  63. return result;
  64. }
  65. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  66. public static CubeInfo TranslateSpacialEnumerations(uint cubeId, byte x, byte y, byte z, byte rotation, byte colour, byte colour_x, byte colour_y, byte colour_z)
  67. {
  68. if (x != colour_x || z != colour_z || y != colour_y) return default;
  69. CubeInfo result = new CubeInfo { visible = true, cubeId = cubeId };
  70. TranslateBlockColour(colour, ref result);
  71. TranslateBlockPosition(x, y, z, ref result);
  72. TranslateBlockRotation(rotation, ref result);
  73. TranslateBlockId(cubeId, ref result);
  74. #if DEBUG
  75. Logging.MetaLog($"Cube {cubeId} ({x}, {y}, {z}) rot:{rotation} decoded as {result.block} {result.position} rot: {result.rotation} color: {result.color} {result.darkness}");
  76. #endif
  77. return result;
  78. }
  79. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  80. private static void TranslateBlockRotation(byte rotation, ref CubeInfo result)
  81. {
  82. // face refers to the face of the block connected to the bottom of the current one
  83. // nvm, they're all incorrect
  84. switch (rotation)
  85. {
  86. case 0:
  87. result.rotation = new float3(0, 0, 0); // top face, forwards
  88. break;
  89. case 1:
  90. result.rotation = new float3(0, 0, 90); // left face, forwards
  91. break;
  92. case 2:
  93. result.rotation = new float3(0, 0, 180); // bottom face, forwards
  94. break;
  95. case 3:
  96. result.rotation = new float3(0, 0, -90); // front face, down
  97. break;
  98. case 4:
  99. result.rotation = new float3(0, 90, 0); // top face, right
  100. break;
  101. case 5:
  102. result.rotation = new float3(0, 90, 90); // front face, right
  103. break;
  104. case 6:
  105. result.rotation = new float3(-90, -90, 0); // right face, backwards
  106. break;
  107. case 7:
  108. result.rotation = new float3(0, 90, -90); // back face, right
  109. break;
  110. case 8:
  111. result.rotation = new float3(0, -90, 90); // back face, left
  112. break;
  113. case 9:
  114. result.rotation = new float3(0, -90, -90); // front face, left
  115. break;
  116. case 10:
  117. result.rotation = new float3(90, -90, 0); // left face, down
  118. break;
  119. case 11:
  120. result.rotation = new float3(90, 90, 0); // right face, forwards
  121. break;
  122. case 12:
  123. result.rotation = new float3(-90, 90, 0); // left face, up
  124. break;
  125. case 13:
  126. result.rotation = new float3(0, 90, 180); // bottom face, right
  127. break;
  128. case 14:
  129. result.rotation = new float3(0, 180, 0); // top face, backwards
  130. break;
  131. case 15:
  132. result.rotation = new float3(0, 180, 90); // right face, up
  133. break;
  134. case 16:
  135. result.rotation = new float3(0, 180, 180); // bottom face, backwards
  136. break;
  137. case 17:
  138. result.rotation = new float3(0, 180, -90); // left face, backwards
  139. break;
  140. case 18:
  141. result.rotation = new float3(0, -90, 0); // top face, left
  142. break;
  143. case 19:
  144. result.rotation = new float3(0, -90, 180); // bottom face, left
  145. break;
  146. case 20:
  147. result.rotation = new float3(90, 0, 0); // front face, down
  148. break;
  149. case 21:
  150. result.rotation = new float3(90, 180, 0); // back face, down
  151. break;
  152. case 22:
  153. result.rotation = new float3(-90, 0, 0); // back face, up
  154. break;
  155. case 23:
  156. result.rotation = new float3(-90, 180, 0); // front face, up
  157. break;
  158. default:
  159. #if DEBUG
  160. Logging.MetaLog($"Unknown rotation {rotation.ToString("X2")}");
  161. #endif
  162. result.rotation = float3.zero;
  163. break;
  164. }
  165. // my brain hurts after figuring out all of those rotations
  166. // I wouldn't recommend trying to redo this
  167. }
  168. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  169. private static void TranslateBlockPosition(byte x, byte y, byte z, ref CubeInfo result)
  170. {
  171. // for some reason, z is forwards in garage bays
  172. result.position = new float3(x, y, z);
  173. }
  174. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  175. private static void TranslateBlockColour(byte colour, ref CubeInfo result)
  176. {
  177. // I hope these colours are accurate, I just guessed
  178. // TODO colour accuracy (lol that won't ever happen)
  179. #if DEBUG
  180. Logging.MetaLog($"Cube colour {colour}");
  181. #endif
  182. BlockColor c = ColorSpaceUtility.QuantizeToBlockColor(colour);
  183. result.color = c.Color;
  184. result.darkness = c.Darkness;
  185. }
  186. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  187. private static void TranslateBlockId(uint cubeId, ref CubeInfo result)
  188. {
  189. if (map == null)
  190. {
  191. StreamReader cubemap = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("Pixi.cubes-id.json"));
  192. map = JsonConvert.DeserializeObject<Dictionary<uint, string>>(cubemap.ReadToEnd());
  193. }
  194. if (!map.ContainsKey(cubeId))
  195. {
  196. result.block = BlockIDs.TextBlock;
  197. result.name = "Unknown cube #" + cubeId.ToString();
  198. //result.rotation = float3.zero;
  199. #if DEBUG
  200. Logging.MetaLog($"Unknown cubeId {cubeId}");
  201. #endif
  202. }
  203. string cubeName = map[cubeId];
  204. if (cubeName.Contains("cube"))
  205. {
  206. result.block = BlockIDs.AluminiumCube;
  207. result.rotation = float3.zero;
  208. }
  209. else if (cubeName.Contains("prism") || cubeName.Contains("edge"))
  210. {
  211. if (cubeName.Contains("round"))
  212. {
  213. if (cubeName.Contains("glass") || cubeName.Contains("windshield"))
  214. {
  215. result.block = BlockIDs.GlassRoundedSlope;
  216. } else
  217. result.block = BlockIDs.AluminiumRoundedSlope;
  218. }
  219. else
  220. {
  221. if (cubeName.Contains("glass") || cubeName.Contains("windshield"))
  222. {
  223. result.block = BlockIDs.GlassSlope;
  224. } else
  225. result.block = BlockIDs.AluminiumSlope;
  226. }
  227. }
  228. else if (cubeName.Contains("inner"))
  229. {
  230. if (cubeName.Contains("round"))
  231. {
  232. if (cubeName.Contains("glass") || cubeName.Contains("windshield"))
  233. {
  234. result.block = BlockIDs.GlassRoundedSlicedCube;
  235. } else
  236. result.block = BlockIDs.AluminiumRoundedSlicedCube;
  237. }
  238. else
  239. {
  240. if (cubeName.Contains("glass") || cubeName.Contains("windshield"))
  241. {
  242. result.block = BlockIDs.GlassSlicedCube;
  243. } else
  244. result.block = BlockIDs.AluminiumSlicedCube;
  245. }
  246. }
  247. else if (cubeName.Contains("tetra") || cubeName.Contains("corner"))
  248. {
  249. if (cubeName.Contains("round"))
  250. {
  251. if (cubeName.Contains("glass") || cubeName.Contains("windshield"))
  252. {
  253. result.block = BlockIDs.GlassRoundedCorner;
  254. } else
  255. result.block = BlockIDs.AluminiumRoundedCorner;
  256. }
  257. else
  258. {
  259. if (cubeName.Contains("glass") || cubeName.Contains("windshield"))
  260. {
  261. result.block = BlockIDs.GlassCorner;
  262. } else
  263. result.block = BlockIDs.AluminiumCorner;
  264. }
  265. }
  266. else if (cubeName.Contains("pyramid"))
  267. {
  268. result.block = BlockIDs.AluminiumPyramidSegment;
  269. }
  270. else if (cubeName.Contains("cone"))
  271. {
  272. result.block = BlockIDs.AluminiumConeSegment;
  273. }
  274. else
  275. {
  276. result.block = BlockIDs.TextBlock;
  277. result.name = cubeName;
  278. }
  279. }
  280. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  281. public static Block[] BuildBlueprintOrTextBlock(CubeInfo cube, float3 actualPosition, int scale = 3)
  282. {
  283. // actualPosition is the middle of the cube
  284. if (blueprintMap == null) LoadBlueprintMap();
  285. if (!blueprintMap.ContainsKey(cube.cubeId) || scale != 3)
  286. {
  287. #if DEBUG
  288. Logging.LogWarning($"Missing blueprint for {cube.name} (id:{cube.cubeId}), substituting {cube.block}");
  289. #endif
  290. return new Block[] { Block.PlaceNew(cube.block, actualPosition, cube.rotation, cube.color, cube.darkness, scale: cube.scale) };
  291. }
  292. #if DEBUG
  293. Logging.MetaLog($"Found blueprint for {cube.name} (id:{cube.cubeId})");
  294. #endif
  295. Quaternion cubeQuaternion = Quaternion.Euler(cube.rotation);
  296. BlockJsonInfo[] blueprint = blueprintMap[cube.cubeId];
  297. if (blueprint.Length == 0)
  298. {
  299. Logging.LogWarning($"Found empty blueprint for {cube.name} (id:{cube.cubeId}), is the blueprint correct?");
  300. return new Block[0];
  301. }
  302. float3 defaultCorrectionVec = new float3((float)(0), (float)(RobotCommands.blockSize), (float)(0));
  303. float3 baseRot = new float3(blueprint[0].rotation[0], blueprint[0].rotation[1], blueprint[0].rotation[2]);
  304. float3 baseScale = new float3(blueprint[0].scale[0], blueprint[0].scale[1], blueprint[0].scale[2]);
  305. Block[] placedBlocks = new Block[blueprint.Length];
  306. bool isBaseScaled = !(blueprint[0].scale[1] > 0f && blueprint[0].scale[1] < 2f);
  307. float3 correctionVec = isBaseScaled ? (float3)(Quaternion.Euler(baseRot) * baseScale / 2) * (float)-RobotCommands.blockSize : -defaultCorrectionVec;
  308. // FIXME scaled base blocks cause the blueprint to be placed in the wrong location (this also could be caused by a bug in DumpVON command)
  309. if (isBaseScaled)
  310. {
  311. Logging.LogWarning($"Found blueprint with scaled base block for {cube.name} (id:{cube.cubeId}), this is not currently supported");
  312. }
  313. for (int i = 0; i < blueprint.Length; i++)
  314. {
  315. BlockColor blueprintBlockColor = ColorSpaceUtility.QuantizeToBlockColor(blueprint[i].color);
  316. BlockColors blockColor = blueprintBlockColor.Color == BlockColors.White && blueprintBlockColor.Darkness == 0 ? cube.color : blueprintBlockColor.Color;
  317. byte blockDarkness = blueprintBlockColor.Color == BlockColors.White && blueprintBlockColor.Darkness == 0 ? cube.darkness : blueprintBlockColor.Darkness;
  318. float3 bluePos = new float3(blueprint[i].position[0], blueprint[i].position[1], blueprint[i].position[2]);
  319. float3 blueScale = new float3(blueprint[i].scale[0], blueprint[i].scale[1], blueprint[i].scale[2]);
  320. float3 blueRot = new float3(blueprint[i].rotation[0], blueprint[i].rotation[1], blueprint[i].rotation[2]);
  321. float3 physicalLocation = (float3)(cubeQuaternion * bluePos) + actualPosition;// + (blueprintSizeRotated / 2);
  322. //physicalLocation.x += blueprintSize.x / 2;
  323. physicalLocation += (float3)(cubeQuaternion * (correctionVec));
  324. //physicalLocation.y -= (float)(RobotCommands.blockSize * scale / 2);
  325. //float3 physicalScale = (float3)(cubeQuaternion * blueScale); // this actually over-rotates when combined with rotation
  326. float3 physicalScale = blueScale;
  327. float3 physicalRotation = (cubeQuaternion * Quaternion.Euler(blueRot)).eulerAngles;
  328. #if DEBUG
  329. Logging.MetaLog($"Placing blueprint block at {physicalLocation} rot{physicalRotation} scale{physicalScale}");
  330. Logging.MetaLog($"Location math check original:{bluePos} rotated: {(float3)(cubeQuaternion * bluePos)} actualPos: {actualPosition} result: {physicalLocation}");
  331. Logging.MetaLog($"Scale math check original:{blueScale} rotation: {(float3)cubeQuaternion.eulerAngles} result: {physicalScale}");
  332. Logging.MetaLog($"Rotation math check original:{blueRot} rotated: {(cubeQuaternion * Quaternion.Euler(blueRot))} result: {physicalRotation}");
  333. #endif
  334. placedBlocks[i] = Block.PlaceNew(VoxelObjectNotationUtility.NameToEnum(blueprint[i].name),
  335. physicalLocation,
  336. physicalRotation,
  337. blockColor,
  338. blockDarkness,
  339. scale: physicalScale);
  340. }
  341. #if DEBUG
  342. Logging.MetaLog($"Placed {placedBlocks.Length} blocks for blueprint {cube.name} (id:{cube.cubeId})");
  343. #endif
  344. return placedBlocks;
  345. }
  346. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  347. private static void LoadBlueprintMap()
  348. {
  349. StreamReader bluemap = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("Pixi.blueprints.json"));
  350. blueprintMap = JsonConvert.DeserializeObject<Dictionary<uint, BlockJsonInfo[]>>(bluemap.ReadToEnd());
  351. }
  352. }
  353. }