Implement Robocraft factory importing
This commit is contained in:
parent
2e314595ac
commit
bfe0c1972c
12 changed files with 719 additions and 24 deletions
|
@ -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)
|
||||
|
|
|
@ -157,11 +157,12 @@ namespace Pixi.Images
|
|||
Color pixel = img.GetPixel(x, y);
|
||||
imgString.Append("<color=");
|
||||
imgString.Append(HexPixel(pixel));
|
||||
imgString.Append(">\u25a0</color>");
|
||||
imgString.Append(">");
|
||||
imgString.Append("\u25a0");
|
||||
}
|
||||
imgString.Append("<br>");
|
||||
}
|
||||
imgString.Append("</cspace></line-height>");
|
||||
imgString.Append("</color></cspace></line-height>");
|
||||
return imgString.ToString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -804,6 +804,15 @@
|
|||
<Reference Include="GamecraftModdingAPI">
|
||||
<HintPath>..\..\ref\Plugins\GamecraftModdingAPI.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="GamecraftModdingAPI">
|
||||
<HintPath>..\..\ref\Plugins\GamecraftModdingAPI.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<!--End Dependencies-->
|
||||
<ItemGroup>
|
||||
<None Remove="cubes-id.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="cubes-id.json" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -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
|
||||
|
|
28
Pixi/Robots/CubeInfo.cs
Normal file
28
Pixi/Robots/CubeInfo.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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<uint, string> map = null;
|
||||
|
||||
public static RobotStruct? ParseRobotInfo(string robotInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
return JsonConvert.DeserializeObject<RobotStruct>(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<Dictionary<uint, string>>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
75
Pixi/Robots/RoboAPIUtility.cs
Normal file
75
Pixi/Robots/RoboAPIUtility.cs
Normal file
|
@ -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<RobotListResponse>(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<RobotInfoResponse>(bodyStr);
|
||||
return rir.response;
|
||||
}
|
||||
}
|
||||
}
|
49
Pixi/Robots/RobotAPIStructs.cs
Normal file
49
Pixi/Robots/RobotAPIStructs.cs
Normal file
|
@ -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<string, int>)
|
||||
}
|
||||
|
||||
public struct RobotList
|
||||
{
|
||||
public RobotBriefStruct[] roboShopItems;
|
||||
}
|
||||
|
||||
public struct RobotListResponse
|
||||
{
|
||||
public RobotList response;
|
||||
|
||||
public int statusCode;
|
||||
}
|
||||
|
||||
public struct RobotInfoResponse
|
||||
{
|
||||
public RobotStruct response;
|
||||
|
||||
public int statusCode;
|
||||
}
|
||||
}
|
|
@ -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<string>(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<string>(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<TextBlock>().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<TextBlock>().Text = cube.placeholder;
|
||||
}
|
||||
}
|
||||
Logging.CommandLog($"Placed {robot.name} by {robot.addedByDisplayName} ({cubes.Length} cubes) beside you");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
30
Pixi/Robots/RobotStruct.cs
Normal file
30
Pixi/Robots/RobotStruct.cs
Normal file
|
@ -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<string, int>)
|
||||
}
|
||||
}
|
1
Pixi/cubes-id.json
Normal file
1
Pixi/cubes-id.json
Normal file
File diff suppressed because one or more lines are too long
55
README.md
55
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.
|
||||
|
|
Loading…
Reference in a new issue