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.

221 lines
8.1KB

  1. using System;
  2. using System.Diagnostics;
  3. using System.IO;
  4. using System.Net;
  5. using System.Text;
  6. using Unity.Mathematics;
  7. using GamecraftModdingAPI;
  8. using GamecraftModdingAPI.Blocks;
  9. using GamecraftModdingAPI.Commands;
  10. using GamecraftModdingAPI.Players;
  11. using GamecraftModdingAPI.Utility;
  12. using Pixi.Common;
  13. namespace Pixi.Robots
  14. {
  15. public static class RobotCommands
  16. {
  17. internal const double blockSize = 0.2;
  18. public static int CubeSize = 3;
  19. public static void CreateRobotFileCommand()
  20. {
  21. CommandBuilder.Builder()
  22. .Name("PixiBotFile")
  23. .Description("Converts a robot file from RCBUP into Gamecraft blocks. Larger robots will freeze your game until conversion completes.")
  24. .Action<string>(ImportRobotFile)
  25. .Build();
  26. }
  27. public static void CreateRobotCRFCommand()
  28. {
  29. CommandBuilder.Builder()
  30. .Name("PixiBot")
  31. .Description("Downloads a robot from Robocraft's Factory and converts it into Gamecraft blocks. Larger robots will freeze your game until conversion completes.")
  32. .Action<string>(ImportRobotOnline)
  33. .Build();
  34. }
  35. public static void CreatePartDumpCommand()
  36. {
  37. CommandBuilder.Builder()
  38. .Name("DumpVON")
  39. .Description("Dump a block structure to a JSON file compatible with Pixi's internal VON format")
  40. .Action<string>(DumpBlockStructure)
  41. .Build();
  42. }
  43. private static void ImportRobotFile(string filepath)
  44. {
  45. string file;
  46. try
  47. {
  48. file = File.ReadAllText(filepath);
  49. }
  50. catch (Exception e)
  51. {
  52. Logging.CommandLogError($"Failed to load robot data. Reason: {e.Message}");
  53. Logging.MetaLog(e);
  54. return;
  55. }
  56. RobotStruct? robot = CubeUtility.ParseRobotInfo(file);
  57. if (!robot.HasValue)
  58. {
  59. Logging.CommandLogError($"Failed to parse robot data. File format was not recognised.");
  60. return;
  61. }
  62. float3 position = new Player(PlayerType.Local).Position;
  63. position.y += (float)(blockSize * CubeSize * 3); // 3 is roughly the max height of any cube in RC
  64. CubeInfo[] cubes = CubeUtility.ParseCubes(robot.Value);
  65. // move origin closer to player (since bots are rarely built at the garage bay origin)
  66. if (cubes.Length == 0)
  67. {
  68. Logging.CommandLogError($"Robot data contains no cubes");
  69. return;
  70. }
  71. float3 minPosition = cubes[0].position;
  72. for (int c = 0; c < cubes.Length; c++)
  73. {
  74. float3 cubePos = cubes[c].position;
  75. if (cubePos.x < minPosition.x)
  76. {
  77. minPosition.x = cubePos.x;
  78. }
  79. if (cubePos.y < minPosition.y)
  80. {
  81. minPosition.y = cubePos.y;
  82. }
  83. if (cubePos.z < minPosition.z)
  84. {
  85. minPosition.z = cubePos.z;
  86. }
  87. }
  88. Block[][] blocks = new Block[cubes.Length][];
  89. for (int c = 0; c < cubes.Length; c++) // sometimes I wish this were C++
  90. {
  91. CubeInfo cube = cubes[c];
  92. float3 realPosition = ((cube.position - minPosition) * (float)blockSize * CubeSize) + position;
  93. if (cube.block == BlockIDs.TextBlock && !string.IsNullOrEmpty(cube.name))
  94. {
  95. // TextBlock block ID means it's a placeholder
  96. blocks[c] = CubeUtility.BuildBlueprintOrTextBlock(cube, realPosition, CubeSize);
  97. }
  98. else
  99. {
  100. blocks[c] = new Block[] { Block.PlaceNew(cube.block, realPosition, cube.rotation, cube.color, cube.darkness, CubeSize) };
  101. }
  102. }
  103. int blockCount = 0;
  104. for (int c = 0; c < cubes.Length; c++)
  105. {
  106. CubeInfo cube = cubes[c];
  107. // the goal is for this to never evaluate to true (ie all cubes are translated correctly)
  108. if (!string.IsNullOrEmpty(cube.name) && cube.block == BlockIDs.TextBlock && blocks[c].Length == 1)
  109. {
  110. //Logging.MetaLog($"Block is {blocks[c][0].Type} and was placed as {cube.block}");
  111. blocks[c][0].Specialise<TextBlock>().Text = cube.name;
  112. }
  113. blockCount += blocks[c].Length;
  114. }
  115. Logging.CommandLog($"Placed {robot?.name} by {robot?.addedByDisplayName} beside you ({cubes.Length}RC -> {blockCount}GC)");
  116. }
  117. private static void ImportRobotOnline(string robotName)
  118. {
  119. Stopwatch timer = Stopwatch.StartNew();
  120. // download robot data
  121. RobotStruct robot;
  122. try
  123. {
  124. RobotBriefStruct[] botList = RoboAPIUtility.ListRobots(robotName);
  125. if (botList.Length == 0)
  126. throw new Exception("Failed to find robot");
  127. robot = RoboAPIUtility.QueryRobotInfo(botList[0].itemId);
  128. }
  129. catch (Exception e)
  130. {
  131. Logging.CommandLogError($"Failed to download robot data. Reason: {e.Message}");
  132. Logging.MetaLog(e);
  133. timer.Stop();
  134. return;
  135. }
  136. timer.Stop();
  137. Logging.MetaLog($"Completed API calls in {timer.ElapsedMilliseconds}ms");
  138. float3 position = new Player(PlayerType.Local).Position;
  139. position.y += (float)(blockSize * CubeSize * 3); // 3 is roughly the max height of any cube in RC
  140. CubeInfo[] cubes = CubeUtility.ParseCubes(robot);
  141. // move origin closer to player (since bots are rarely built at the garage bay origin)
  142. if (cubes.Length == 0)
  143. {
  144. Logging.CommandLogError($"Robot data contains no cubes");
  145. return;
  146. }
  147. float3 minPosition = cubes[0].position;
  148. for (int c = 0; c < cubes.Length; c++)
  149. {
  150. float3 cubePos = cubes[c].position;
  151. if (cubePos.x < minPosition.x)
  152. {
  153. minPosition.x = cubePos.x;
  154. }
  155. if (cubePos.y < minPosition.y)
  156. {
  157. minPosition.y = cubePos.y;
  158. }
  159. if (cubePos.z < minPosition.z)
  160. {
  161. minPosition.z = cubePos.z;
  162. }
  163. }
  164. Block[][] blocks = new Block[cubes.Length][];
  165. for (int c = 0; c < cubes.Length; c++) // sometimes I wish this were C++
  166. {
  167. CubeInfo cube = cubes[c];
  168. float3 realPosition = ((cube.position - minPosition) * (float)blockSize * CubeSize) + position;
  169. if (cube.block == BlockIDs.TextBlock && !string.IsNullOrEmpty(cube.name))
  170. {
  171. // TextBlock block ID means it's a placeholder
  172. blocks[c] = CubeUtility.BuildBlueprintOrTextBlock(cube, realPosition, CubeSize);
  173. }
  174. else
  175. {
  176. blocks[c] = new Block[] { Block.PlaceNew(cube.block, realPosition, cube.rotation, cube.color, cube.darkness, CubeSize) };
  177. }
  178. }
  179. int blockCount = 0;
  180. for (int c = 0; c < cubes.Length; c++)
  181. {
  182. CubeInfo cube = cubes[c];
  183. // the goal is for this to never evaluate to true (ie all cubes are translated correctly)
  184. if (!string.IsNullOrEmpty(cube.name) && cube.block == BlockIDs.TextBlock && blocks[c].Length == 1)
  185. {
  186. //Logging.MetaLog($"Block is {blocks[c][0].Type} and was placed as {cube.block}");
  187. blocks[c][0].Specialise<TextBlock>().Text = cube.name;
  188. }
  189. blockCount += blocks[c].Length;
  190. }
  191. Logging.CommandLog($"Placed {robot.name} by {robot.addedByDisplayName} beside you ({cubes.Length}RC -> {blockCount}GC)");
  192. }
  193. private static void DumpBlockStructure(string filename)
  194. {
  195. Player local = new Player(PlayerType.Local);
  196. Block baseBlock = local.GetBlockLookedAt();
  197. Block[] blocks = baseBlock.GetConnectedCubes();
  198. bool isBaseScaled = !(baseBlock.Scale.x > 0 && baseBlock.Scale.x < 2 && baseBlock.Scale.y > 0 && baseBlock.Scale.y < 2 && baseBlock.Scale.z > 0 && baseBlock.Scale.z < 2);
  199. if (isBaseScaled)
  200. {
  201. Logging.CommandLogWarning($"Detected scaled base block. This is not currently supported");
  202. }
  203. float3 basePos = baseBlock.Position;
  204. string von = VoxelObjectNotationUtility.SerializeBlocks(blocks, new float[] { basePos.x, basePos.y, basePos.z });
  205. File.WriteAllText(filename, von);
  206. }
  207. }
  208. }