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.

196 lines
6.8KB

  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;
  64. CubeInfo[] cubes = CubeUtility.ParseCubes(robot.Value);
  65. Block[][] blocks = new Block[cubes.Length][];
  66. for (int c = 0; c < cubes.Length; c++) // sometimes I wish this were C++
  67. {
  68. CubeInfo cube = cubes[c];
  69. float3 realPosition = (cube.position * (float)blockSize * CubeSize) + position;
  70. if (cube.block == BlockIDs.TextBlock && !string.IsNullOrEmpty(cube.name))
  71. {
  72. // TextBlock block ID means it's a placeholder
  73. blocks[c] = CubeUtility.BuildBlueprintOrTextBlock(cube, realPosition, CubeSize);
  74. }
  75. else
  76. {
  77. blocks[c] = new Block[] { Block.PlaceNew(cube.block, realPosition, cube.rotation, cube.color, cube.darkness, CubeSize) };
  78. }
  79. }
  80. // build placeholders
  81. // Note: this is a separate loop because everytime a new block is placed,
  82. // a slow Sync() call is required to access it's properties.
  83. // This way, one Sync() call is needed, instead of O(cubes.Length) calls
  84. for (int c = 0; c < cubes.Length; c++)
  85. {
  86. CubeInfo cube = cubes[c];
  87. // the goal is for this to never evaluate to true (ie all cubes are translated correctly)
  88. if (!string.IsNullOrEmpty(cube.name) && cube.block == BlockIDs.TextBlock && blocks[c].Length == 1)
  89. {
  90. //Logging.MetaLog($"Block is {blocks[c][0].Type} and was placed as {cube.block}");
  91. blocks[c][0].Specialise<TextBlock>().Text = cube.name;
  92. }
  93. }
  94. Logging.CommandLog($"Placed {robot.Value.name} by {robot.Value.addedByDisplayName} ({cubes.Length} cubes) beside you");
  95. }
  96. private static void ImportRobotOnline(string robotName)
  97. {
  98. Stopwatch timer = Stopwatch.StartNew();
  99. // download robot data
  100. RobotStruct robot;
  101. try
  102. {
  103. RobotBriefStruct[] botList = RoboAPIUtility.ListRobots(robotName);
  104. if (botList.Length == 0)
  105. throw new Exception("Failed to find robot");
  106. robot = RoboAPIUtility.QueryRobotInfo(botList[0].itemId);
  107. }
  108. catch (Exception e)
  109. {
  110. Logging.CommandLogError($"Failed to download robot data. Reason: {e.Message}");
  111. Logging.MetaLog(e);
  112. timer.Stop();
  113. return;
  114. }
  115. timer.Stop();
  116. Logging.MetaLog($"Completed API calls in {timer.ElapsedMilliseconds}ms");
  117. float3 position = new Player(PlayerType.Local).Position;
  118. position.y += (float)(blockSize * CubeSize * 3); // 3 is roughly the max height of any cube in RC
  119. CubeInfo[] cubes = CubeUtility.ParseCubes(robot);
  120. // move origin closer to player (since bots are rarely built at the garage bay origin)
  121. if (cubes.Length == 0)
  122. {
  123. Logging.CommandLogError($"Robot data contains no cubes");
  124. return;
  125. }
  126. float3 minPosition = cubes[0].position;
  127. for (int c = 0; c < cubes.Length; c++)
  128. {
  129. float3 cubePos = cubes[c].position;
  130. if (cubePos.x < minPosition.x)
  131. {
  132. minPosition.x = cubePos.x;
  133. }
  134. if (cubePos.y < minPosition.y)
  135. {
  136. minPosition.y = cubePos.y;
  137. }
  138. if (cubePos.z < minPosition.z)
  139. {
  140. minPosition.z = cubePos.z;
  141. }
  142. }
  143. Block[][] blocks = new Block[cubes.Length][];
  144. for (int c = 0; c < cubes.Length; c++) // sometimes I wish this were C++
  145. {
  146. CubeInfo cube = cubes[c];
  147. float3 realPosition = ((cube.position - minPosition) * (float)blockSize * CubeSize) + position;
  148. if (cube.block == BlockIDs.TextBlock && !string.IsNullOrEmpty(cube.name))
  149. {
  150. // TextBlock block ID means it's a placeholder
  151. blocks[c] = CubeUtility.BuildBlueprintOrTextBlock(cube, realPosition, CubeSize);
  152. }
  153. else
  154. {
  155. blocks[c] = new Block[] { Block.PlaceNew(cube.block, realPosition, cube.rotation, cube.color, cube.darkness, CubeSize) };
  156. }
  157. }
  158. int blockCount = 0;
  159. for (int c = 0; c < cubes.Length; c++)
  160. {
  161. CubeInfo cube = cubes[c];
  162. // the goal is for this to never evaluate to true (ie all cubes are translated correctly)
  163. if (!string.IsNullOrEmpty(cube.name) && cube.block == BlockIDs.TextBlock && blocks[c].Length == 1)
  164. {
  165. //Logging.MetaLog($"Block is {blocks[c][0].Type} and was placed as {cube.block}");
  166. blocks[c][0].Specialise<TextBlock>().Text = cube.name;
  167. }
  168. blockCount += blocks[c].Length;
  169. }
  170. Logging.CommandLog($"Placed {robot.name} by {robot.addedByDisplayName} beside you ({cubes.Length}RC -> {blockCount}GC)");
  171. }
  172. private static void DumpBlockStructure(string filename)
  173. {
  174. Player local = new Player(PlayerType.Local);
  175. Block baseBlock = local.GetBlockLookedAt();
  176. Block[] blocks = baseBlock.GetConnectedCubes();
  177. if (blocks.Length == 0) return;
  178. float3 basePos = baseBlock.Position;
  179. string von = VoxelObjectNotationUtility.SerializeBlocks(blocks, new float[] { basePos.x, basePos.y, basePos.z });
  180. File.WriteAllText(filename, von);
  181. }
  182. }
  183. }