diff --git a/Pixi/Images/ImageCommands.cs b/Pixi/Images/ImageCommands.cs index 16ae848..d8de541 100644 --- a/Pixi/Images/ImageCommands.cs +++ b/Pixi/Images/ImageCommands.cs @@ -143,7 +143,7 @@ namespace Pixi.Images //position.y = zero_y; } Logging.CommandLog($"Placed {img.width}x{img.height} image beside you ({blockCount} blocks total)"); - Logging.MetaLog($"Saved {(img.width * img.height) - blockCount} blocks ({img.width * img.height / blockCount}x) while placing {filepath}"); + Logging.MetaLog($"Saved {(img.width * img.height) - blockCount} blocks ({blockCount / (img.width * img.height)}%) while placing {filepath}"); } public static void Pixelate2DFileToTextBlock(string filepath) diff --git a/Pixi/Images/PixelUtility.cs b/Pixi/Images/PixelUtility.cs index e66717d..1f05ee2 100644 --- a/Pixi/Images/PixelUtility.cs +++ b/Pixi/Images/PixelUtility.cs @@ -157,11 +157,12 @@ namespace Pixi.Images Color pixel = img.GetPixel(x, y); imgString.Append("\u25a0"); + imgString.Append(">"); + imgString.Append("\u25a0"); } imgString.Append("
"); } - imgString.Append(""); + imgString.Append(""); return imgString.ToString(); } } diff --git a/Pixi/Pixi.csproj b/Pixi/Pixi.csproj index 8dcfb60..733188e 100644 --- a/Pixi/Pixi.csproj +++ b/Pixi/Pixi.csproj @@ -804,6 +804,15 @@ ..\..\ref\Plugins\GamecraftModdingAPI.dll + + ..\..\ref\Plugins\GamecraftModdingAPI.dll + + + + + + + diff --git a/Pixi/PixiPlugin.cs b/Pixi/PixiPlugin.cs index 13680d7..baa3446 100644 --- a/Pixi/PixiPlugin.cs +++ b/Pixi/PixiPlugin.cs @@ -7,13 +7,10 @@ using UnityEngine; using Unity.Mathematics; // float3 using IllusionPlugin; -using GamecraftModdingAPI; -using GamecraftModdingAPI.Commands; using GamecraftModdingAPI.Utility; -using GamecraftModdingAPI.Blocks; -using GamecraftModdingAPI.Players; using Pixi.Images; +using Pixi.Robots; namespace Pixi { @@ -29,7 +26,7 @@ namespace Pixi public void OnApplicationQuit() { // Shutdown this mod - GamecraftModdingAPI.Utility.Logging.LogDebug($"{Name} has shutdown"); + Logging.LogDebug($"{Name} has shutdown"); // Shutdown the Gamecraft modding API last GamecraftModdingAPI.Main.Shutdown(); @@ -48,8 +45,11 @@ namespace Pixi ImageCommands.CreateImportCommand(); ImageCommands.CreateTextCommand(); ImageCommands.CreateTextConsoleCommand(); - - GamecraftModdingAPI.Utility.Logging.LogDebug($"{Name} has started up"); + // Robot functionality + RobotCommands.CreateRobotCRFCommand(); + RobotCommands.CreateRobotFileCommand(); + + Logging.LogDebug($"{Name} has started up"); } // unused methods diff --git a/Pixi/Robots/CubeInfo.cs b/Pixi/Robots/CubeInfo.cs new file mode 100644 index 0000000..842b4b5 --- /dev/null +++ b/Pixi/Robots/CubeInfo.cs @@ -0,0 +1,28 @@ +using System; +using Unity.Mathematics; +using GamecraftModdingAPI.Blocks; + +namespace Pixi.Robots +{ + public struct CubeInfo + { + // so you can't inherit from structs in C#... + // this is an extension of BlockInfo + public BlockIDs block; + + public BlockColors color; + + public byte darkness; + + public bool visible; + + // additions + public float3 rotation; + + public float3 position; + + public float3 scale; + + public string placeholder; + } +} diff --git a/Pixi/Robots/CubeUtility.cs b/Pixi/Robots/CubeUtility.cs index dff02d2..449f3e9 100644 --- a/Pixi/Robots/CubeUtility.cs +++ b/Pixi/Robots/CubeUtility.cs @@ -1,7 +1,373 @@ using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; + +using RobocraftX.Common; +using Newtonsoft.Json; +using Unity.Mathematics; + +using GamecraftModdingAPI.Blocks; +using GamecraftModdingAPI.Utility; + namespace Pixi.Robots { public static class CubeUtility { - } + private static Dictionary map = null; + + public static RobotStruct? ParseRobotInfo(string robotInfo) + { + try + { + return JsonConvert.DeserializeObject(robotInfo); + } + catch (Exception e) + { + Logging.MetaLog(e); + return null; + } + } + + public static CubeInfo[] ParseCubes(RobotStruct robot) + { + return ParseCubes(robot.cubeData, robot.colourData); + } + + public static CubeInfo[] ParseCubes(string cubeData, string colourData) + { + BinaryBufferReader cubes = new BinaryBufferReader(Convert.FromBase64String(cubeData), 0); + BinaryBufferReader colours = new BinaryBufferReader(Convert.FromBase64String(colourData), 0); + uint cubeCount = cubes.ReadUint(); + uint colourCount = colours.ReadUint(); + if (cubeCount != colourCount) + { + Logging.MetaLog("Something is fucking broken"); + return null; + } + Logging.MetaLog($"Detected {cubeCount} cubes"); + CubeInfo[] result = new CubeInfo[cubeCount]; + for (int cube = 0; cube < cubeCount; cube++) + { + result[cube] = TranslateSpacialEnumerations( + cubes.ReadUint(), + cubes.ReadByte(), + cubes.ReadByte(), + cubes.ReadByte(), + cubes.ReadByte(), + colours.ReadByte(), + colours.ReadByte(), + colours.ReadByte(), + colours.ReadByte() + ); + } + return result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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) + { + if (x != colour_x || z != colour_z || y != colour_y) return default; + CubeInfo result = new CubeInfo { visible = true }; + TranslateBlockColour(colour, ref result); + TranslateBlockPosition(x, y, z, ref result); + TranslateBlockRotation(rotation, ref result); + TranslateBlockId(cubeId, ref result); +#if DEBUG + Logging.MetaLog($"Cube {cubeId} ({x}, {y}, {z}) rot:{rotation} decoded as {result.block} {result.position} rot: {result.rotation} color: {result.color} {result.darkness}"); +#endif + return result; + } + + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void TranslateBlockRotation(byte rotation, ref CubeInfo result) + { + // face refers to the face of the block connected to the bottom of the current one + // nvm, they're all incorrect + switch (rotation) + { + case 0: + result.rotation = new float3(0, 0, 0); // top face, forwards + break; + case 1: + result.rotation = new float3(0, 0, 90); // left face, forwards + break; + case 2: + result.rotation = new float3(0, 0, 180); // bottom face, forwards + break; + case 3: + result.rotation = new float3(0, 0, -90); // front face, down + break; + case 4: + result.rotation = new float3(0, 90, 0); // top face, right + break; + case 5: + result.rotation = new float3(0, 90, 90); // front face, right + break; + case 6: + result.rotation = new float3(-90, -90, 0); // right face, backwards + break; + case 7: + result.rotation = new float3(0, 90, -90); // back face, right + break; + case 8: + result.rotation = new float3(0, -90, 90); // back face, left + break; + case 9: + result.rotation = new float3(0, -90, -90); // front face, left + break; + case 10: + result.rotation = new float3(90, -90, 0); // left face, down + break; + case 11: + result.rotation = new float3(90, 90, 0); // right face, forwards + break; + case 12: + result.rotation = new float3(-90, 90, 0); // left face, up + break; + case 13: + result.rotation = new float3(0, 90, 180); // bottom face, right + break; + case 14: + result.rotation = new float3(0, 180, 0); // top face, backwards + break; + case 15: + result.rotation = new float3(0, 180, 90); // right face, up + break; + case 16: + result.rotation = new float3(0, 180, 180); // bottom face, backwards + break; + case 17: + result.rotation = new float3(0, 180, -90); // left face, backwards + break; + case 18: + result.rotation = new float3(0, -90, 0); // top face, left + break; + case 19: + result.rotation = new float3(0, -90, 180); // bottom face, left + break; + case 20: + result.rotation = new float3(90, 0, 0); // front face, down + break; + case 21: + result.rotation = new float3(90, 180, 0); // back face, down + break; + case 22: + result.rotation = new float3(-90, 0, 0); // back face, up + break; + case 23: + result.rotation = new float3(-90, 180, 0); // front face, up + break; + default: +#if DEBUG + Logging.MetaLog($"Unknown rotation {rotation.ToString("X2")}"); +#endif + result.rotation = float3.zero; + break; + } + // my brain hurts after figuring out all of those rotations + // I wouldn't recommend trying to redo this + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void TranslateBlockPosition(byte x, byte y, byte z, ref CubeInfo result) + { + // for some reason, z is forwards in garage bays + result.position = new float3(x, y, z); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void TranslateBlockColour(byte colour, ref CubeInfo result) + { + // I hope these colours are accurate, I just guessed + // TODO colour accuracy (lol that won't ever happen) + switch (colour) + { + case 0: + result.color = BlockColors.White; + result.darkness = 0; + break; + case 1: + result.color = BlockColors.White; + result.darkness = 5; + break; + case 2: + result.color = BlockColors.Orange; + result.darkness = 0; + break; + case 3: + result.color = BlockColors.Blue; + result.darkness = 2; + break; + case 4: + result.color = BlockColors.White; + result.darkness = 8; + break; + case 5: + result.color = BlockColors.Red; + result.darkness = 0; + break; + case 6: + result.color = BlockColors.Yellow; + result.darkness = 0; + break; + case 7: + result.color = BlockColors.Green; + result.darkness = 0; + break; + case 8: + result.color = BlockColors.Purple; + result.darkness = 0; + break; + case 9: + result.color = BlockColors.Blue; + result.darkness = 7; + break; + case 10: + result.color = BlockColors.Purple; + result.darkness = 5; + break; + case 11: + result.color = BlockColors.Orange; + result.darkness = 7; + break; + case 12: + result.color = BlockColors.Green; + result.darkness = 3; + break; + case 13: + result.color = BlockColors.Green; + result.darkness = 2; + break; + case 14: + result.color = BlockColors.Pink; + result.darkness = 3; + break; + case 15: + result.color = BlockColors.Pink; + result.darkness = 2; + break; + case 16: + result.color = BlockColors.Red; + result.darkness = 2; + break; + case 17: + result.color = BlockColors.Orange; + result.darkness = 8; + break; + case 18: + result.color = BlockColors.Red; + result.darkness = 7; + break; + case 19: + result.color = BlockColors.Pink; + result.darkness = 0; + break; + case 20: + result.color = BlockColors.Yellow; + result.darkness = 2; + break; + case 21: + result.color = BlockColors.Green; + result.darkness = 7; + break; + case 22: + result.color = BlockColors.Green; + result.darkness = 8; + break; + case 23: + result.color = BlockColors.Blue; + result.darkness = 8; + break; + case 24: + result.color = BlockColors.Aqua; + result.darkness = 7; + break; + case 25: + result.color = BlockColors.Blue; + result.darkness = 6; + break; + case 26: + result.color = BlockColors.Aqua; + result.darkness = 5; + break; + case 27: + result.color = BlockColors.Blue; + result.darkness = 4; + break; + case 28: + result.color = BlockColors.Aqua; + result.darkness = 3; + break; + case 29: + result.color = BlockColors.Blue; + result.darkness = 5; + break; + case 30: + result.color = BlockColors.Purple; + result.darkness = 3; + break; + case 31: + result.color = BlockColors.Purple; + result.darkness = 1; + break; + default: + result.color = BlockColors.Aqua; + result.darkness = 0; + break; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void TranslateBlockId(uint cubeId, ref CubeInfo result) + { + if (map == null) + { + StreamReader cubemap = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("Pixi.cubes-id.json")); + map = JsonConvert.DeserializeObject>(cubemap.ReadToEnd()); + } + + if (!map.ContainsKey(cubeId)) + { + result.block = BlockIDs.TextBlock; + result.placeholder = "Unknown cube #" + cubeId.ToString(); + //result.rotation = float3.zero; +#if DEBUG + Logging.MetaLog($"Unknown cubeId {cubeId}"); +#endif + } + string cubeName = map[cubeId]; + if (cubeName.Contains("cube")) + { + result.block = BlockIDs.AluminiumCube; + result.rotation = float3.zero; + } + else if (cubeName.Contains("prism") || cubeName.Contains("edge")) + { + result.block = BlockIDs.AluminiumSlope; + } + else if (cubeName.Contains("inner")) + { + result.block = BlockIDs.AluminiumSlicedCube; + } + else if (cubeName.Contains("tetra") || cubeName.Contains("corner")) + { + result.block = BlockIDs.AluminiumCorner; + } + else if (cubeName.Contains("pyramid")) + { + result.block = BlockIDs.AluminiumPyramidSegment; + } + else if (cubeName.Contains("cone")) + { + result.block = BlockIDs.AluminiumConeSegment; + } + else + { + result.block = BlockIDs.TextBlock; + result.placeholder = cubeName; + } + } + } } diff --git a/Pixi/Robots/RoboAPIUtility.cs b/Pixi/Robots/RoboAPIUtility.cs new file mode 100644 index 0000000..81c9d27 --- /dev/null +++ b/Pixi/Robots/RoboAPIUtility.cs @@ -0,0 +1,75 @@ +using System; +using System.IO; +using System.Net; +using System.Text; +using Newtonsoft.Json; + +using GamecraftModdingAPI.Utility; + +namespace Pixi.Robots +{ + public static class RoboAPIUtility + { + private const string ROBOT_API_LIST_URL = "https://factory.robocraftgame.com/api/roboShopItems/list"; + + private const string ROBOT_API_GET_URL = "https://factory.robocraftgame.com/api/roboShopItems/get/"; + + private const string ROBOT_API_TOKEN = "Web eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJQdWJsaWNJZCI6IjEyMyIsIkRpc3BsYXlOYW1lIjoiVGVzdCIsIlJvYm9jcmFmdE5hbWUiOiJGYWtlQ1JGVXNlciIsIkZsYWdzIjpbXSwiaXNzIjoiRnJlZWphbSIsInN1YiI6IldlYiIsImlhdCI6MTU0NTIyMzczMiwiZXhwIjoyNTQ1MjIzNzkyfQ.ralLmxdMK9rVKPZxGng8luRIdbTflJ4YMJcd25dKlqg"; + + public static RobotBriefStruct[] ListRobots(string searchFilter, int pageSize = 10, bool playerFilter = false) + { + // pageSize <= 2 seems to retrieve items unreliably + string bodyJson = $"{{\"page\": 1, \"pageSize\": {pageSize}, \"order\": 0, \"playerFilter\": {playerFilter.ToString().ToLower()}, \"movementFilter\": \"100000,200000,300000,400000,500000,600000,700000,800000,900000,1000000,1100000,1200000\", \"movementCategoryFilter\": \"100000,200000,300000,400000,500000,600000,700000,800000,900000,1000000,1100000,1200000\", \"weaponFilter\": \"10000000,20000000,25000000,30000000,40000000,50000000,60000000,65000000,70100000,75000000\", \"weaponCategoryFilter\": \"10000000,20000000,25000000,30000000,40000000,50000000,60000000,65000000,70100000,75000000\", \"minimumCpu\": -1, \"maximumCpu\": -1, \"minimumRobotRanking\": 0, \"maximumRobotRanking\": 1000000000, \"textFilter\": \"{searchFilter}\", \"textSearchField\": 0, \"buyable\": true, \"prependFeaturedRobot\": false, \"featuredOnly\": false, \"defaultPage\": false}}"; + byte[] reqBody = Encoding.UTF8.GetBytes(bodyJson); +#if DEBUG + Logging.MetaLog($"POST body\n{bodyJson}"); +#endif + // download robot list + // FIXME this blocks main thread + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(ROBOT_API_LIST_URL); + // request + request.Method = "POST"; + request.ContentLength = reqBody.Length; + request.ContentType = "application/json"; + request.Headers.Add(HttpRequestHeader.Authorization, ROBOT_API_TOKEN); + request.Accept = "application/json; charset=utf-8"; // HTTP Status 500 without + Stream body; + body = request.GetRequestStream(); + body.Write(reqBody, 0, reqBody.Length); + body.Close(); + // response + HttpWebResponse response; + response = (HttpWebResponse)request.GetResponse(); + // regular Stream was unreliable + // because they could read everything before everything was availabe + StreamReader respReader = new StreamReader(response.GetResponseStream()); + string bodyStr = respReader.ReadToEnd(); + RobotListResponse rlr = JsonConvert.DeserializeObject(bodyStr); + return rlr.response.roboShopItems; + } + + public static RobotStruct QueryRobotInfo(int robotId) + { + // download robot info + // FIXME this blocks main thread + string url = ROBOT_API_GET_URL + robotId.ToString(); + Logging.MetaLog(url); + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); + // request + request.Method = "GET"; + request.ContentType = "application/json"; + request.Accept = "application/json; charset=utf-8"; // HTTP Status 500 without + request.Headers.Add(HttpRequestHeader.Authorization, ROBOT_API_TOKEN); + // response + HttpWebResponse response; + response = (HttpWebResponse)request.GetResponse(); + // regular Stream was unreliable + // because they could read everything before everything was availabe + StreamReader body = new StreamReader(response.GetResponseStream()); + string bodyStr = body.ReadToEnd(); + response.Close(); + RobotInfoResponse rir = JsonConvert.DeserializeObject(bodyStr); + return rir.response; + } + } +} diff --git a/Pixi/Robots/RobotAPIStructs.cs b/Pixi/Robots/RobotAPIStructs.cs new file mode 100644 index 0000000..8f976fd --- /dev/null +++ b/Pixi/Robots/RobotAPIStructs.cs @@ -0,0 +1,49 @@ +using System; +namespace Pixi.Robots +{ + public struct RobotBriefStruct + { + public int itemId; + + public string itemName; + + public string itemDescription; + + public string thumbnail; + + public string addedBy; + + public string addedByDisplayName; + + public int cpu; + + public int totalRobotRanking; + + public string cubeData; + + public string colourData; + + public bool featured; + + public string cubeAmounts; // this is sent incorrectly by the API server (it's actually a Dictionary) + } + + public struct RobotList + { + public RobotBriefStruct[] roboShopItems; + } + + public struct RobotListResponse + { + public RobotList response; + + public int statusCode; + } + + public struct RobotInfoResponse + { + public RobotStruct response; + + public int statusCode; + } +} diff --git a/Pixi/Robots/RobotCommands.cs b/Pixi/Robots/RobotCommands.cs index bdaabcf..20c955e 100644 --- a/Pixi/Robots/RobotCommands.cs +++ b/Pixi/Robots/RobotCommands.cs @@ -1,7 +1,114 @@ using System; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Text; + +using Unity.Mathematics; + +using GamecraftModdingAPI; +using GamecraftModdingAPI.Blocks; +using GamecraftModdingAPI.Commands; +using GamecraftModdingAPI.Players; +using GamecraftModdingAPI.Utility; + namespace Pixi.Robots { public static class RobotCommands { + private static double blockSize = 0.2; + + public static void CreateRobotFileCommand() + { + CommandBuilder.Builder() + .Name("PixiBotFile") + .Description("Converts a robot file from RCBUP into Gamecraft blocks. Larger robots will freeze your game until conversion completes. (Pixi)") + .Action(ImportRobotFile) + .Build(); + } + + public static void CreateRobotCRFCommand() + { + CommandBuilder.Builder() + .Name("PixiBot") + .Description("Downloads a robot from Robocraft's Factory and converts it into Gamecraft blocks. Larger robots will freeze your game until conversion completes. (Pixi)") + .Action(ImportRobotOnline) + .Build(); + } + + private static void ImportRobotFile(string filepath) + { + string file; + try + { + file = File.ReadAllText(filepath); + } + catch (Exception e) + { + Logging.CommandLogError($"Failed to load robot data. Reason: {e.Message}"); + Logging.MetaLog(e); + return; + } + RobotStruct? robot = CubeUtility.ParseRobotInfo(file); + if (!robot.HasValue) + { + Logging.CommandLogError($"Failed to parse robot data. File format was not recognised."); + return; + } + float3 position = new Player(PlayerType.Local).Position; + position.y += (float)blockSize; + CubeInfo[] cubes = CubeUtility.ParseCubes(robot.Value); + for (int c = 0; c < cubes.Length; c++) // sometimes I wish this were C++ + { + CubeInfo cube = cubes[c]; + float3 realPosition = (cube.position * (float)blockSize) + position; + Block newBlock = Block.PlaceNew(cube.block, realPosition, cube.rotation, cube.color, cube.darkness, scale: cube.scale); + // the goal is for this to never evaluate to true (ie all cubes are translated correctly) + if (!string.IsNullOrEmpty(cube.placeholder) && cube.block == BlockIDs.TextBlock) + { + newBlock.Specialise().Text = cube.placeholder; + } + } + Logging.CommandLog($"Placed {robot.Value.name} by {robot.Value.addedByDisplayName} ({cubes.Length} cubes) beside you"); + } + + private static void ImportRobotOnline(string robotName) + { + Stopwatch timer = Stopwatch.StartNew(); + // download robot data + RobotStruct robot; + try + { + RobotBriefStruct[] botList = RoboAPIUtility.ListRobots(robotName); + if (botList.Length == 0) + throw new Exception("Failed to find robot"); + robot = RoboAPIUtility.QueryRobotInfo(botList[0].itemId); + + } + catch (Exception e) + { + Logging.CommandLogError($"Failed to download robot data. Reason: {e.Message}"); + Logging.MetaLog(e); + timer.Stop(); + return; + } + timer.Stop(); + Logging.MetaLog($"Completed API calls in {timer.ElapsedMilliseconds}ms"); + float3 position = new Player(PlayerType.Local).Position; + position.y += (float)blockSize; + CubeInfo[] cubes = CubeUtility.ParseCubes(robot); + for (int c = 0; c < cubes.Length; c++) // sometimes I wish this were C++ + { + CubeInfo cube = cubes[c]; + float3 realPosition = (cube.position * (float)blockSize) + position; + Block newBlock = Block.PlaceNew(cube.block, realPosition, cube.rotation, cube.color, cube.darkness, scale: cube.scale); + // the goal is for this to never evaluate to true (ie all cubes are translated correctly) + if (!string.IsNullOrEmpty(cube.placeholder) && cube.block == BlockIDs.TextBlock) + { + newBlock.Specialise().Text = cube.placeholder; + } + } + Logging.CommandLog($"Placed {robot.name} by {robot.addedByDisplayName} ({cubes.Length} cubes) beside you"); + } } } diff --git a/Pixi/Robots/RobotStruct.cs b/Pixi/Robots/RobotStruct.cs new file mode 100644 index 0000000..f5dca0e --- /dev/null +++ b/Pixi/Robots/RobotStruct.cs @@ -0,0 +1,30 @@ +using System; +namespace Pixi.Robots +{ + public struct RobotStruct + { + public int id; + + public string name; + + public string description; + + public string thumbnail; + + public string addedBy; + + public string addedByDisplayName; + + public int cpu; + + public int totalRobotRanking; + + public string cubeData; + + public string colourData; + + public bool featured; + + public string cubeAmounts; // this is sent incorrectly by the API server (it's actually a Dictionary) + } +} diff --git a/Pixi/cubes-id.json b/Pixi/cubes-id.json new file mode 100644 index 0000000..79fd283 --- /dev/null +++ b/Pixi/cubes-id.json @@ -0,0 +1 @@ +{"3795589065": "armored cube t1", "3523011797": "armored cube t2", "1428281037": "armored cube t3", "148547544": "armored cube t4", "3902577141": "armored cube t5", "3217516252": "armored cube t6", "1602312433": "armored cube t7", "3130466754": "armored cube t8", "936473914": "armored cube t9", "1661086464": "armored inner t1", "140476067": "armored inner t2", "3558861316": "armored inner t3", "3514383790": "armored inner t4", "1772266812": "armored inner t5", "1717894314": "armored inner t6", "3737084984": "armored inner t7", "1664400308": "armored inner t8", "2704279673": "armored inner t9", "3133051372": "armored prism t1", "3521365071": "armored prism t2", "228806888": "armored prism t3", "147162946": "armored prism t4", "2954764240": "armored prism t5", "3218622022": "armored prism t6", "117694164": "armored prism t7", "3129475416": "armored prism t8", "3159626035": "armored prism t9", "3288882012": "armored tetra t1", "2941587199": "armored tetra t2", "1930795608": "armored tetra t3", "1987268082": "armored tetra t4", "3467171168": "armored tetra t5", "3245247734": "armored tetra t6", "2041910372": "armored tetra t7", "3292449768": "armored tetra t8", "3265763652": "armored tetra t9", "227205318": "t1 cube medium / armored cube t10", "227917916": "t1 edge medium / armored prism t10", "1931676396": "t1 corner medium / armored tetra t10", "3559488176": "t1 inner medium / armored inner t10", "447126572": "t1 edge round medium", "2589418111": "t1 corner round medium", "1837286858": "t1 inner round medium", "1538778047": "t1 edge slope medium", "557016830": "t1 corner slope medium", "495400745": "t1 inner slope medium", "1352234106": "t1 cone medium", "2686602224": "t1 pyramid medium", "3367514907": "t1 cube medium with c6 logo (substituted)", "2392661891": "t1 cube medium with carbon letters (substituted)", "1654632428": "heavy chassis cube", "4181414569": "heavy chassis inner", "2686466102": "heavy chassis prism", "2157492751": "heavy chassis tetra", "123901970": "t1 cube heavy (substituted)", "1225928721": "t1 edge heavy (substituted)", "222074329": "t1 corner heavy (substituted)", "2577871627": "t1 inner heavy (substituted)", "368051733": "t1 edge round heavy (substituted)", "2148353926": "t1 corner round heavy (substituted)", "665761850": "t1 inner round heavy (substituted)", "1334508924": "t1 edge slope heavy (substituted)", "3665280239": "t1 corner slope heavy (substituted)", "2111062867": "t1 inner slope heavy (substituted)", "3886136035": "t1 cone heavy (substituted)", "4184274974": "t1 pyramid heavy(substituted)", "2295832800": "light chassis cube (substituted)", "3920646105": "light chassis inner (substituted)", "2231909735": "light chassis prism (substituted)", "1754827363": "light chassis tetra (substituted)", "1972224393": "t1 cube light (substituted)", "4157111053": "t1 edge light (substituted)", "1347255791": "t1 corner light (substituted)", "1941682825": "t1 inner light (substituted)", "1993583577": "t1 edge round light (substituted)", "582052348": "t1 corner round light (substituted)", "1578378954": "t1 inner round light (substituted)", "3093572869": "t1 edge slope light (substituted)", "3959877408": "t1 corner slope light (substituted)", "2426642454": "t1 inner slope light (substituted)", "1422041599": "t1 cone light (substituted)", "885415789": "t1 pyramid light (substituted)", "1576857358": "t5 cube compact", "327960333": "t5 edge compact", "126353766": "t5 corner compact", "2378018821": "t5 inner compact", "676446887": "t5 edge round compact", "1728084091": "t5 corner round compact", "925214026": "t5 inner round compact", "1915435470": "t5 edge slope compact", "1881278783": "t5 corner slope compact", "1834966563": "t5 inner slope compact", "1979640496": "t5 cone compact", "979123471": "t5 pyramid compact", "3327812742": "glass cube / windshield cube (substituted)", "4235300269": "glass edge / windshield prism (substituted)", "1094997616": "glass corner / windshield tetra (substituted)", "445857183": "glass inner / windshield inner (substituted)", "3629216992": "glass edge round (substituted)", "2380494106": "glass corner round (substituted)", "3066776961": "glass inner round (substituted)", "150161008": "neon cube (substituted)", "1183051379": "neon edge (substituted)", "2333317284": "neon corner (substituted)", "1616217456": "neon inner (substituted)", "2623263862": "neon edge round (substituted)", "3195590950": "neon corner round (substituted)", "1774712137": "neon inner round (substituted)", "3447434946": "neon pyramid (substituted)", "183067052": "retro cube (substituted)", "1150929327": "retro edge (substituted)", "1387792676": "retro corner (substituted)", "3985049715": "retro inner (substituted)", "3305279060": "armored helium t4 (substituted).", "1914654544": "armored helium t6 (substituted)", "2001668174": "armored helium t8 (substituted)", "3226650954": "helium / armored helium t10 (substituted)", "3425997036": "helium tank (substituted)", "1818686721": "t2 rod", "1312806203": "t2 rod short", "3489647384": "t2 rod long", "2454932271": "t2 rod arc", "3568811832": "t2 rod arc short", "394503911": "t2 rod diagonal 2d", "1316425218": "t2 rod diagonal 2d short", "3705632066": "t2 rod diagonal 3d", "1750720388": "t2 rod plus", "651695911": "t2 rod frame", "4265819694": "t2 rod cross", "4262490755": "t3 strut", "1648206355": "t3 strut short", "3084940372": "t3 strut long", "743433251": "t3 strut arc", "4206005137": "t3 strut arc short", "2126360617": "t3 strut slice", "2608938070": "t3 strut slice short", "3999885169": "t3 strut ramp", "3903776284": "t3 strut ramp short", "14694903": "t3 strut diagonal 2d", "3562021587": "t3 strut diagonal 2d short", "511164535": "t3 strut plus", "787409845": "t3 strut cross", "1521447199": "t3 strut skewed plus", "2002864041": "t3 strut diagonal 3d right", "2028802540": "t3 strut diagonal 3d left", "3923036903": "t3 strut diagonal 3d right short", "1319987665": "t3 strut diagonal 3d left short", "1810876016": "t0 wheel (substituted)", "932552373": "t0 steering wheel (substituted)", "4172855773": "t1 wheel (substituted)", "169839823": "t1 steering wheel (substituted)", "2924196438": "t2 wheel", "2958657062": "t2 steering wheel", "3028949761": "t3 wheel", "1177366035": "t3 steering wheel", "1223384335": "t4 wheel", "345048522": "t4 steering wheel", "3301162279": "t5 wheel (substituted)", "48208435": "t5 steering wheel (substituted)", "4050988656": "t1 tank track (substituted)", "1691117057": "t2 tank track", "184017928": "t3 tank track", "5728820": "t4 tank track (substituted)", "431289744": "t5 tank track (substituted)", "3066502430": "t0 mech leg (substituted)", "76192516": "t1 mech leg", "1789991181": "t2 mech leg", "1611765553": "t3 mech leg (substituted)", "2452579137": "t4 mech leg", "4140335767": "t5 mech leg", "3345472872": "t3 sprinter leg", "1169080311": "t4 sprinter leg (substituted)", "2474131397": "t4 sprinter leg carbon 6 (substituted)", "2535778622": "not used / walker leg t7 (substituted)", "127661231": "t3 insect leg / walker leg t8", "1889322041": "not used / walker leg t9 (substituted)", "1668760037": "t4 insect leg / walker leg t10", "2671394050": "t3 insect leg blade", "875393229": "t4 insect leg spider", "3080624795": "t2 steering ski", "347391203": "t2 ski", "1851713730": "t0 hovers / hover blade t2 (substituted)", "947628072": "not used / hover blade t3 (substituted)", "3170960304": "t1 hovers / hover blade t4 (substituted)", "1333565630": "not used / hover blade t5 (substituted)", "1639979618": "t2 hovers / hover blade t6", "3508014365": "not used / hover blade t7 (substituted)", "465491285": "t3 hovers / hover blade t8", "2787065227": "not used / hover blade t9 (substituted)", "3347041415": "t4 hovers / hover blade t10 (substituted)", "3835205776": "t5 hovers", "2217471839": "t5 hovers goldern", "3947193935": "t2 rotors (substituted)", "3789940851": "t3 rotors (substituted)", "1802864184": "t4 rotors (substituted)", "3980928804": "t2 wing (substituted)", "1515578912": "not used / aerofoil t5 (substituted)", "2205394221": "t3 wing / aerofoil t6 (substituted)", "879649833": "not used / aerofoil t7 (substituted)", "2312317713": "t4 wing / aerofoil t8", "1053747733": "not used / aerofoil t9 (substituted)", "2464192350": "t5 wing / aerofoil t10", "2865954846": "t4 wing bat", "2863842421": "t5 wing bat (substituted)", "2369778644": "t2 rudder (substituted)", "975319760": "not used / rudder t5 (substituted)", "3808719325": "t3 rudder / rudder t6 (substituted)", "1410969817": "not used / rudder t7 (substituted)", "3919904737": "t4 rudder / rudder t8 (substituted)", "1589198565": "not used / rudder t9 (substituted)", "2608683011": "t5 rudder / rudder t10 (substituted)", "2928920064": "t4 rudder bat", "844778245": "t1 thruster / thruster t2", "4276453881": "not used / thruster t3 (substituted)", "2498076128": "t2 thruster / thruster t4", "1572019677": "not used / thruster t5 (substituted)", "3764614578": "t3 thruster / thruster t6", "3285682302": "not used / thruster t7 (substituted)", "3938712462": "t4 thruster / thruster t8", "3033577704": "not used / thruster t9 (substituted)", "2969949587": "t5 thruster / thruster t10", "944347375": "t5 thruster carbon 6 (substituted)", "3286209719": "t3 prop", "2784549582": "t4 prop (substituted)", "1337963121": "not used / front mount smg t1 (substituted)", "3402350870": "t0 front laser / front mount smg t2 (substituted)", "776342052": "not used / front mount smg t3 (substituted)", "2494440442": "t1 front laser / front mount smg t4 (substituted)", "1497447090": "not used / front mount smg t5 (substituted)", "1584562849": "t2 front laser / front mount smg t6 (substituted)", "3341135633": "not used / front mount smg t7 (substituted)", "2675516967": "t3 front laser / front mount smg t8 (substituted)", "2955059079": "not used / front mount smg t9 (substituted)", "1436911484": "t4 front laser / front mount smg t10 (substituted)", "2929054885": "not used / top mount smg t1 (substituted)", "3809895395": "t0 laser / top mount smg t2 (substituted)", "2985370719": "not used / top mount smg t3 (substituted)", "3178463503": "t1 laser / top mount smg t4", "3338015945": "not used / top mount smg t5 (substituted)", "2007965780": "t2 laser / top mount smg t6 (substituted)", "1485996394": "not used / top mount smg t7 (substituted)", "3064235218": "t3 laser / top mount smg t8 (substituted)", "798339580": "not used / top mount smg t9 (substituted)", "2088248713": "t4 laser / top mount smg t10 (substituted)", "3209000021": "t5 laser", "3851710054": "t5 laser (gold)", "3632954889": "t4 laser carbon 6 (substituted)", "1199458351": "t5 laser carbon 6 (substituted)", "2655409492": "t0 plasma", "1412971690": "not used / plasma launcher t3", "1293532710": "t1 plasma / plasma launcher t4", "3810917806": "not used / plasma launcher t5", "2446895092": "t2 plasma / plasma launcher t6", "3866475184": "not used / plasma launcher t7", "3953551555": "t3 plasma / plasma launcher t8", "1365801908": "not used / plasma launcher t9", "929526033": "t4 plasma / plasma launcher t10", "171448327": "t5 plasma", "1593059007": "t4 plasma carbon 6", "2943284935": "t5 plasma (gold)", "1640043587": "t1 rail / rail cannon t4", "360854742": "rail cannon t5", "2870850840": "t2 rail / rail cannon t6", "282227656": "rail cannon t7", "1779831198": "t3 rail / rail cannon t8", "2815406796": "rail cannon t9", "2697638085": "t4 rail / rail cannon t10", "2083340214": "t5 rail", "447003593": "t5 rail (gold)", "1761903423": "t2 nano / nanotech disruptor t6", "3726197307": "nanotech disruptor t7", "1671695619": "t3 nano / nanotech disruptor t8", "3568946183": "nanotech disruptor t9", "2364175165": "t4 nano / nanotech disruptor t10", "2017654588": "t2 tesla", "3479123512": "t3 tesla", "2703353899": "t4 tesla", "48150119": "t3 seeker", "350778877": "t4 seeker", "2419806013": "t5 seeker", "3947892032": "t4 flak", "73607413": "t5 flak", "1318129004": "t4 chaingun", "1795842454": "t5 chaingun", "3541341953": "t4 ion", "1184153980": "t5 ion", "105639928": "t5 ion (gold)", "1885850433": "t5 mortar", "1692614192": "energy module", "3263542661": "disc shield module", "3987245394": "blink module", "4230376397": "ghost modue", "658488560": "emp module", "344080878": "windowmaker module", "1264237222": "t2 electroshield a left", "2965095316": "t2 electroshield a right", "2457663915": "t2 electroshield b left", "1771929753": "t2 electroshield b right", "627128495": "t2 electroshield c left", "3732947357": "t2 electroshield c right", "540114865": "t3 electroshield a left", "3687843459": "t3 electroshield a right", "2536292021": "t3 electroshield b left", "1825425287": "t3 electroshield b right", "1309343160": "t3 electroshield c left", "3052110986": "t3 electroshield c right", "4179049660": "t4 electroshield a left", "49694094": "t4 electroshield a right", "1152021380": "t4 electroshield b left", "3209366198": "t4 electroshield b right", "4088902272": "t4 electroshield c left", "139905970": "t4 electroshield c right", "1889152405": "t5 electroshield a left / electroplate r t10", "3823106277": "t5 electroshield a right / electroplate l t10", "3001609712": "t5 electroshield b left", "3365106304": "t5 electroshield b right", "991790322": "t5 electroshield c left", "1097210754": "t5 electroshield c right", "2860532867": "electroplate l t3", "2949645213": "electroplate l t4", "416137881": "electroplate l t5", "3253436820": "electroplate l t6", "1995849872": "electroplate l t7", "3410694056": "electroplate l t8", "2086063788": "electroplate l t9", "3746649421": "electroplate r t3", "3657503315": "electroplate r t4", "1830605655": "electroplate r t5", "3023736922": "electroplate r t6", "52908382": "electroplate r t7", "3197771366": "electroplate r t8", "159899490": "electroplate r t9", "3411783298": "robopass season 2 holoflag", "2663263630": "bunny holoflag", "4175295691": "uk / uk country badge", "4166201860": "italy / italy country badge", "4124816651": "crossbones", "4070927930": "turkey", "4065608086": "russia / russia country badge", "3559676835": "usa / usa country badge", "3447406366": "ireland / ireland country badge", "3330651823": "kazakhstan / kazakhstan country badge", "3210649465": "v", "3114896263": "spain / spain country badge", "2730691972": "poland / poland country badge", "2596479965": "germany / germany country badge", "2345098755": "alien", "1958111892": "robocraft / breached badge", "1798659636": "belgium", "1796059677": "iceland", "1786715320": "sweden / sweden country badge", "1768151340": "belarus / belarus country badge", "1385335421": "canada / canada country badge", "932603502": "denmark", "664490215": "australia / australian country badge", "507555577": "4", "354715741": "argentina / argentina country badge", "88051662": "holland / netherlands country badge", "68277216": "ukraine / ukraine country badge", "2243089188": "brazil country badge / brazil country badge", "3910121366": "china country badge / china country badge", "2373812955": "france country badge / france country badge", "2020064218": "japan country badge / japan country badge", "460733827": "south korea country badge / south korea country badge", "3186524732": "not used / pilot seat", "1427441647": "pilot seat gene (see camoflagegodpe.bot)", "2530225659": "cockpit top left", "3970675339": "cockpit top right", "2404311559": "speedometer (yes i'm sure bay5052-7425329.bot)", "1367205243": "headlamp / headlamp", "1679554409": "exhaust stack left", "3489925411": "exhaust stack right", "149580437": "exhaust blower", "108680691": "spike pin", "955529870": "spike needle", "688123941": "spike dagger", "346272734": "balloon", "2782198650": "robot name banner / robot name banner", "3610833057": "vapor trail single / vapor trail l1", "236893100": "vapor trail twin / vapor trail l2", "2759478449": "alignment rectifier t1", "2103185340": "alignment rectifier t3", "3393310392": "alignment rectifier t5", "3474033062": "alignment rectifier t7", "2014070946": "alignment rectifier t9", "4200145444": "dev supporter", "3370765914": "gold dev supporter", "3123991804": "enemy radar t1", "591201606": "enemy radar t5", "1413101008": "enemy radar t9", "3805965640": "radar jammer t3", "1439149132": "radar jammer t5", "1352133458": "radar jammer t7", "3884591702": "radar jammer t9", "2460830034": "radar receiver t5", "2549943884": "radar receiver t7", "551604040": "radar receiver t9", "3536989267": "eagle face left", "2829693731": "eagle face right", "1797279669": "eagle feathers left", "291229893": "eagle feathers right", "38268230": "eagle neck left", "2017227318": "eagle neck right", "2479898264": "mech 7 ear left", "3920339432": "mech 7 ear right", "451055140": "mech 7 jaw left", "1620644180": "mech 7 jaw right", "3561288328": "mech 7 nose left", "2923392504": "mech 7 nose right"} \ No newline at end of file diff --git a/README.md b/README.md index 2f663fe..94e5bee 100644 --- a/README.md +++ b/README.md @@ -5,33 +5,52 @@ Think of it like automatic pixel art. ## Installation -To install the Pixi mod, copy `Pixi.dll` (from the latest release) into the `Plugins` folder in Gamecraft's main folder. -You'll also need [GamecraftModdingAPI](https://git.exmods.org/modtainers/GamecraftModdingAPI) installed and Gamecraft patched with [GCIPA](https://git.exmods.org/modtainers/GCIPA/releases). +To install the Pixi mod, copy `Pixi.dll` (from the latest release) into the `Plugins` folder in Gamecraft's main folder. +You'll also need [GamecraftModdingAPI](https://git.exmods.org/modtainers/GamecraftModdingAPI) installed and Gamecraft patched with [GCIPA](https://git.exmods.org/modtainers/GCIPA/releases). ## Usage Pixi adds new commands to Gamecraft's command line to import images into a game. -Since Pixi places vanilla Gamecraft blocks, imported images should be visible without Pixi installed. +Since Pixi places vanilla Gamecraft blocks, imported images should be visible without Pixi installed. ### Commands -`PixiScale [width] [height]` sets the block canvas size (usually you'll want this to be the same size as your image). -When conversion using `Pixi2D` is done, if the canvas is larger than your image the image will be repeated. -If the canvas is smaller than your image, the image will be cropped. +`PixiText @"[image]"` converts an image to text and places a text block with that text beside you. -`Pixi2D "[image]"` converts an image to blocks and places it beside where you're standing (along the xy-plane). +`PixiConsole @"[image]" "[text block id]"` converts an image to text and places a console block beside you which changes the specified text block. + +`Pixi2D @"[image]"` converts an image to blocks and places it beside you (along the xy-plane). + +Anything between `[` and `]` characters is a command argument you must provide by replacing everything inside and including the square brackets. +An argument like `[dog name]` is an argument named "dog name" and could be a value like `Clifford` or `doggo`, +and `@"[dog name]"` could be a value like `@"Clifford"` or `@"doggo"`. + +For example, if you want to add an image called `pixel_art.png`, stored in Gamecraft's installation directory, +execute the command `Pixi2D @"pixel_art.png"` to load the image as blocks. +It's important to include the file extension, since Pixi isn't psychic (yet). + +**EXPERIMENTAL** + +`PixiBot @"[bot]"` downloads a bot from Robocraft's community Factory and places it beside you. + +`PixiBotFile @"[bot]"` converts a `.bot` file from [rcbup](https://github.com/NGnius/rcbup) to blocks and places it beside you. + +**NOTE** + +Do not forget the `@"` before and `"` after the command argument, otherwise the command won't work. If your image is not stored in the same folder as Gamecraft, you should specify the full filepath (eg `C:\path\to\image.png`) to the image. This works best with `.PNG` images, but `.JPG` also works -- you just won't be able to use transparency-based features. +Optionally, if you know your command argument won't have a backslash `\` in it, you can omit the `@` symbol. -For example, if you want to add an image called `pixel_art.png`, -with a resolution of 1920x1080, stored in Gamecraft's installation directory, -execute the command `PixiScale 1920 1080` to set the size and then `Pixi2D "pixel_art.png"` to load the image. +`PixiThicc [depth]` sets the block thickness for `Pixi2D` image conversion. +The depth should be a positive whole number, like 3 or 42, and not 3.14 or -42. +The default thickness is 1. ### Behaviour Pixi takes an image file and converts every pixel to a coloured block. Unfortunately, an image file supports over 6 million colours and Gamecraft only has 100 paint colours (and only 90 are used by Pixi). -Pixi uses an algorithm to convert each pixel an image into the closest paint colour, but colour accuracy will never be as good as a regular image. +Pixi uses an algorithm to convert each pixel an image into the closest paint colour, but colour accuracy will never be as good as a regular image. Pixi's colour-conversion algorithm also uses pixel transparency to you can cut out shapes. A pixel which has opacity of less than 75% will be not be converted into a solid block. @@ -48,7 +67,7 @@ Pixi could import that image with less than 500K blocks, which will still hurt G ## Suggestions and Bugs If you find a bug or have an idea for an improvement to Pixi, please create an [issue](https://git.exmods.org/NGnius/Pixi/issues) with an in-depth description. -If you'd like to discuss your issue instead, talk to NGnius on the [Exmods Discord server](https://discord.gg/xjnFxQV). +If you'd like to discuss your issue instead, talk to NGnius on the [Exmods Discord server](https://discord.gg/xjnFxQV). ## Development @@ -56,7 +75,7 @@ Show your love by offering your help! ### Setup -Pixi's development environment is similar to most Gamecraft mods, since it's based on HelloModdingWorld's configuration. +Pixi's development environment is similar to most Gamecraft mods, since it's based on HelloModdingWorld's configuration. This project requires most of Gamecraft's `.dll` files to function correctly. Most, but not all, of these files are stored in Gamecraft's `Gamecraft_Data\Managed` folder. @@ -75,3 +94,13 @@ I'd recommend Visual Studio Community Edition or JetBrains Rider for Windows and If you've successfully completed setup, you should be able to build the Pixi project without errors. If it doesn't work and you can't figure out why, ask for help on the [Exmods Discord server](https://discord.gg/xjnFxQV). + +# Disclaimer + +Pixi, Exmods and NGnius are not endorsed or supported by Gamecraft or FreeJam. +Modify Gamecraft at your own risk. +Read the LICENSE file for licensing information. +Please don't sue this project's contributors (that's what all disclaimers boil down to, right?). + +Pixi is not a psychic overlord which secretly rules the world. +Well, not this world at least.