@@ -0,0 +1,11 @@ | |||
namespace NPort.API | |||
{ | |||
public interface IImporter | |||
{ | |||
string Name { get; } | |||
string Help(string[] args); | |||
void Import(string[] args); | |||
} | |||
} |
@@ -0,0 +1,27 @@ | |||
using System.Collections.Generic; | |||
namespace NPort.API | |||
{ | |||
public static class ImportRegistry | |||
{ | |||
private static List<IImporter> importers = new List<IImporter>(); | |||
public static void AddImporter(IImporter i) | |||
{ | |||
importers.Add(i); | |||
} | |||
internal static void Call(string[] args, string name = null) | |||
{ | |||
if (name == null) name = args[0]; | |||
foreach (var imp in importers) | |||
{ | |||
if (imp.Name == name) | |||
{ | |||
imp.Import(args); | |||
break; | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,36 @@ | |||
using TechbloxModdingAPI; | |||
using TechbloxModdingAPI.Blocks; | |||
using Unity.Mathematics; | |||
namespace NPort.API | |||
{ | |||
public abstract class SimpleImporter : IImporter | |||
{ | |||
public abstract string Name { get; } | |||
public abstract string Help(string[] args); | |||
public void Import(string[] args) | |||
{ | |||
// TODO make this non-blocking (separate thread or maybe async) | |||
if (SimpleImport(args)) | |||
{ | |||
// good! | |||
} | |||
else | |||
{ | |||
TechbloxModdingAPI.Utility.Logging.LogError($"Failed to import {Name}({args})"); | |||
} | |||
} | |||
protected void PlaceBlock(BlockIDs type, float3 position, float3 rotation, BlockColor colour, BlockMaterial material) | |||
{ | |||
// TODO bulk place (cache and place when Import(...) completes?) | |||
Block placedBlock = Block.PlaceNew(type, position); | |||
placedBlock.Color = colour; | |||
placedBlock.Rotation = rotation; | |||
placedBlock.Material = material; | |||
} | |||
public abstract bool SimpleImport(string[] args); | |||
} | |||
} |
@@ -1,12 +1,15 @@ | |||
using System; | |||
using System.Runtime.InteropServices; | |||
namespace NPort.Bindings | |||
{ | |||
// C-style structs used in function signatures | |||
public static class Robocraft | |||
{ | |||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] | |||
public struct FactoryRobotListInfo{ | |||
public uint item_id; | |||
public struct FactoryRobotListInfo | |||
{ | |||
public UInt32 item_id; | |||
public string item_name; | |||
public string item_description; | |||
public string thumbnail; // url | |||
@@ -14,29 +17,235 @@ namespace NPort.Bindings | |||
public string added_by_display_name; | |||
public string added_date; // ISO date | |||
public string expiry_date; // ISO date | |||
public uint cpu; | |||
public uint total_robot_ranking; | |||
public uint rent_count; | |||
public uint buy_count; | |||
public uint buyable; // bool | |||
public UInt32 cpu; | |||
public UInt32 total_robot_ranking; | |||
public UInt32 rent_count; | |||
public UInt32 buy_count; | |||
public UInt32 buyable; // bool | |||
public string removed_date; | |||
public string ban_date; | |||
public uint featured; // bool | |||
public UInt32 featured; // bool | |||
public string banner_message; | |||
public float combat_rating; | |||
public float cosmetic_rating; | |||
public string cube_amounts; // JSON as str | |||
} | |||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] | |||
public struct FactoryRobotGetInfo | |||
{ | |||
public UInt32 item_id; | |||
public string item_name; | |||
public string item_description; | |||
public string thumbnail; // url | |||
public string added_by; | |||
public string added_by_display_name; | |||
public string added_date; // ISO date | |||
public string expiry_date; // ISO date | |||
public UInt32 cpu; | |||
public UInt32 total_robot_ranking; | |||
public UInt32 rent_count; | |||
public UInt32 buy_count; | |||
public UInt32 buyable; // bool | |||
public string removed_date; | |||
public string ban_date; | |||
public UInt32 featured; // bool | |||
public string banner_message; | |||
public float combat_rating; | |||
public float cosmetic_rating; | |||
public string cube_data; | |||
public string colour_data; | |||
public string cube_amounts; // JSON as str | |||
} | |||
public enum FactoryOrderType : Int32 | |||
{ | |||
Suggested = 0, | |||
CombatRating = 1, | |||
CosmeticRating = 2, | |||
Added = 3, | |||
CPU = 4, | |||
MostBought = 5, | |||
} | |||
public enum FactoryMovementType : Int32 | |||
{ | |||
Wheels=100000, | |||
Hovers=200000, | |||
Aerofoils=300000, | |||
Thrusters=400000, | |||
Rudders=500000, | |||
InsectLegs=600000, | |||
MechLegs=700000, | |||
Skis=800000, | |||
TankTreads=900000, | |||
Rotors=1000000, | |||
Sprinters=1100000, | |||
Propellers=1200000 | |||
} | |||
public enum FactoryWeaponType : Int32 | |||
{ | |||
Laser=10000000, | |||
PlasmaLauncher=20000000, | |||
GyroMortar=25000000, | |||
RailCannon=30000000, | |||
NanoDisruptor=40000000, | |||
TeslaBlade=50000000, | |||
AeroflakCannon=60000000, | |||
IonCannon=65000000, | |||
ProtoSeeker=70100000, | |||
ChainShredder=75000000, | |||
} | |||
public enum FactoryTextSearchType : Int32 | |||
{ | |||
All=0, | |||
Player=1, | |||
Name=2, | |||
} | |||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] | |||
public unsafe struct FactorySearchQuery | |||
{ | |||
public Int32* page; | |||
public Int32* items_per_page; | |||
public FactoryOrderType* order; | |||
public string movement_filter; | |||
public string weapon_filter; | |||
public Int32* minimum_cpu; | |||
public Int32* maximum_cpu; | |||
public string text_filter; | |||
public FactoryTextSearchType* text_search_field; | |||
public UInt32* buyable; // bool | |||
public UInt32* prepend_featured_robot; // bool | |||
public UInt32* featured_only; // bool | |||
public UInt32* default_page; // bool | |||
} | |||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] | |||
public struct CubeData | |||
{ | |||
public UInt32 id; | |||
public byte x; | |||
public byte y; | |||
public byte z; | |||
public byte orientation; | |||
public byte colour; | |||
} | |||
// raw C-style bindings | |||
[DllImport("libfj.dll", CallingConvention = CallingConvention.Cdecl)] | |||
static extern UInt32 libfj_factory_front_page(UInt32 size, [In,Out] FactoryRobotListInfo[] array_ptr); | |||
[DllImport("libfj.dll", CallingConvention = CallingConvention.Cdecl)] | |||
static extern UInt32 libfj_factory_search(UInt32 size, [In,Out] FactoryRobotListInfo[] array_ptr, ref FactorySearchQuery search); | |||
[DllImport("libfj.dll", CallingConvention = CallingConvention.Cdecl)] | |||
static extern void libfj_factory_robot(UInt32 item_id, out FactoryRobotGetInfo result_out); | |||
[DllImport("libfj.dll", CallingConvention = CallingConvention.Cdecl)] | |||
static extern UInt32 libfj_factory_robot_cubes(UInt32 size, [In, Out] CubeData[] array_ptr, ref FactoryRobotGetInfo info); | |||
[DllImport("libfj.dll", CallingConvention = CallingConvention.Cdecl)] | |||
static extern void get_factory_front_page(uint size, [In,Out] FactoryRobotListInfo[] array_ptr); | |||
static extern UInt32 libfj_factory_robot_cubes_raw(UInt32 size, [In, Out] CubeData[] array_ptr, string cube_data, string colour_data); | |||
// nicer C#-style wrappers | |||
public static FactoryRobotListInfo[] GetFactoryFrontPage() | |||
{ | |||
uint size = 100; | |||
FactoryRobotListInfo[] arr = new FactoryRobotListInfo[size]; | |||
get_factory_front_page(size, arr); | |||
UInt32 result_count = libfj_factory_front_page(size, arr); | |||
if (result_count < arr.Length) | |||
{ | |||
FactoryRobotListInfo[] result = new FactoryRobotListInfo[result_count]; | |||
Array.Copy(arr, result, result_count); | |||
return result; | |||
} | |||
return arr; | |||
} | |||
public static FactoryRobotListInfo[] SearchFactoryRobots(Int32? page = null, | |||
Int32? items_per_page = null, | |||
FactoryOrderType? order = null, | |||
string movement_filter = null, | |||
string weapon_filter = null, | |||
Int32? max_cpu = null, | |||
Int32? min_cpu = null, | |||
string text_filter = null, | |||
FactoryTextSearchType? text_search_field = null, | |||
bool? buyable = null, | |||
bool? prepend_featured_robot = null, | |||
bool? featured_only = null, | |||
bool? default_page = null) | |||
{ | |||
UInt32 size = (UInt32) (page?? 100); | |||
FactoryRobotListInfo[] arr = new FactoryRobotListInfo[size]; | |||
UInt32 result_count; | |||
unsafe | |||
{ | |||
// this is unnecessary copying, but nullables aren't marshalled correctly (thanks MS!) | |||
Int32 page2 = page ?? 0; | |||
Int32 items_per_page2 = items_per_page ?? 0; | |||
FactoryOrderType order2 = order ?? FactoryOrderType.Suggested; | |||
Int32 maximum_cpu = max_cpu ?? 0; | |||
Int32 minimum_cpu = min_cpu ?? 0; | |||
FactoryTextSearchType text_search_field2 = text_search_field ?? FactoryTextSearchType.All; | |||
UInt32 buyable2 = buyable.HasValue ? (buyable.Value ? 1u : 0u): 0u; | |||
UInt32 prepend_featured_robot2 = prepend_featured_robot.HasValue | |||
? (prepend_featured_robot.Value ? 1u : 0u) | |||
: 0u; | |||
UInt32 featured_only2 = featured_only.HasValue ? (featured_only.Value ? 1u : 0u) : 0u; | |||
UInt32 default_page2 = default_page.HasValue ? (default_page.Value ? 1u : 0u) : 0u; | |||
FactorySearchQuery search = new FactorySearchQuery | |||
{ | |||
page = page.HasValue ? &page2 : null, | |||
items_per_page = items_per_page.HasValue ? &items_per_page2 : null, | |||
order = &order2, | |||
movement_filter = movement_filter, | |||
weapon_filter = weapon_filter, | |||
maximum_cpu = max_cpu.HasValue ? &maximum_cpu : null, | |||
minimum_cpu = min_cpu.HasValue ? &minimum_cpu : null, | |||
text_filter = text_filter, | |||
text_search_field = text_search_field.HasValue ? &text_search_field2 : null, | |||
buyable = buyable.HasValue? &buyable2 : null, | |||
prepend_featured_robot = prepend_featured_robot.HasValue? &prepend_featured_robot2 : null, | |||
featured_only = featured_only.HasValue? &featured_only2 : null, | |||
default_page = default_page.HasValue? &default_page2 : null, | |||
}; | |||
result_count = libfj_factory_search(size, arr, ref search); | |||
} | |||
if (result_count < arr.Length) | |||
{ | |||
FactoryRobotListInfo[] result = new FactoryRobotListInfo[result_count]; | |||
Array.Copy(arr, result, result_count); | |||
return result; | |||
} | |||
return arr; | |||
} | |||
public static FactoryRobotGetInfo GetRobot(UInt32 item_id) | |||
{ | |||
FactoryRobotGetInfo result_out = default; | |||
libfj_factory_robot(item_id, out result_out); | |||
return result_out; | |||
} | |||
public static CubeData[] GetRobotCubes(FactoryRobotGetInfo info) | |||
{ | |||
CubeData[] cubes = new CubeData[info.cpu]; | |||
UInt32 cubeCount = libfj_factory_robot_cubes((UInt32)cubes.Length, cubes, ref info); | |||
if (cubeCount < cubes.Length) | |||
{ | |||
CubeData[] result = new CubeData[cubeCount]; | |||
Array.Copy(cubes, result, cubeCount); | |||
return result; | |||
} | |||
return cubes; | |||
} | |||
} | |||
} |
@@ -0,0 +1,162 @@ | |||
using System.Collections.Generic; | |||
using System.Runtime.CompilerServices; | |||
using NPort.Bindings; | |||
using TechbloxModdingAPI.Blocks; | |||
using TechbloxModdingAPI.Utility; | |||
using Unity.Mathematics; | |||
namespace NPort.Importers | |||
{ | |||
public class RobocraftFactoryImporter : API.SimpleImporter | |||
{ | |||
public override string Name { get; } = "CRF"; | |||
public override string Help(string[] args) | |||
{ | |||
return "Import a robot from Robocraft's Community Robot Factory"; | |||
} | |||
public override bool SimpleImport(string[] args) | |||
{ | |||
if (args.Length < 2) | |||
{ | |||
// user did not specify enough arguments | |||
return false; | |||
} | |||
string text = args[1]; | |||
Robocraft.FactoryRobotListInfo[] robots = Robocraft.SearchFactoryRobots(text_filter: text); | |||
if (robots.Length == 0) | |||
{ | |||
// no results | |||
return false; | |||
} | |||
Robocraft.CubeData[] cubes = Robocraft.GetRobotCubes(Robocraft.GetRobot(robots[0].item_id)); | |||
foreach (var cube in cubes) | |||
{ | |||
float3 rotation = TranslateBlockRotation(cube.orientation); | |||
float3 position = new float3(cube.x, cube.y, cube.z); | |||
BlockColor colour = QuantizeToBlockColor(cube.colour); | |||
PlaceBlock(BlockIDs.Cube, position, rotation, colour, BlockMaterial.SteelBodywork); | |||
break; | |||
} | |||
return true; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
private static float3 TranslateBlockRotation(byte rotation) | |||
{ | |||
// from https://git.exmods.org/NGnius/Pixi | |||
switch (rotation) | |||
{ | |||
case 0: | |||
return new float3(0, 0, 0); // top face, forwards | |||
case 1: | |||
return new float3(0, 0, 90); // left face, forwards | |||
case 2: | |||
return new float3(0, 0, 180); // bottom face, forwards | |||
case 3: | |||
return new float3(0, 0, -90); // front face, down | |||
case 4: | |||
return new float3(0, 90, 0); // top face, right | |||
case 5: | |||
return new float3(0, 90, 90); // front face, right | |||
case 6: | |||
return new float3(-90, -90, 0); // right face, backwards | |||
case 7: | |||
return new float3(0, 90, -90); // back face, right | |||
case 8: | |||
return new float3(0, -90, 90); // back face, left | |||
case 9: | |||
return new float3(0, -90, -90); // front face, left | |||
case 10: | |||
return new float3(90, -90, 0); // left face, down | |||
case 11: | |||
return new float3(90, 90, 0); // right face, forwards | |||
case 12: | |||
return new float3(-90, 90, 0); // left face, up | |||
case 13: | |||
return new float3(0, 90, 180); // bottom face, right | |||
case 14: | |||
return new float3(0, 180, 0); // top face, backwards | |||
case 15: | |||
return new float3(0, 180, 90); // right face, up | |||
case 16: | |||
return new float3(0, 180, 180); // bottom face, backwards | |||
case 17: | |||
return new float3(0, 180, -90); // left face, backwards | |||
case 18: | |||
return new float3(0, -90, 0); // top face, left | |||
case 19: | |||
return new float3(0, -90, 180); // bottom face, left | |||
case 20: | |||
return new float3(90, 0, 0); // front face, down | |||
case 21: | |||
return new float3(90, 180, 0); // back face, down | |||
case 22: | |||
return new float3(-90, 0, 0); // back face, up | |||
case 23: | |||
return new float3(-90, 180, 0); // front face, up | |||
default: | |||
#if DEBUG | |||
Logging.MetaLog($"Unknown rotation {rotation.ToString("X2")}"); | |||
#endif | |||
return float3.zero; | |||
} | |||
// my brain hurts after figuring out all of those rotations | |||
// I wouldn't recommend trying to redo this | |||
} | |||
private static Dictionary<byte, BlockColor> botColorMap = null; | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static BlockColor QuantizeToBlockColor(byte cubeColorEnum) | |||
{ | |||
if (botColorMap == null) BuildBotColorMap(); | |||
return botColorMap[cubeColorEnum]; | |||
} | |||
private static void BuildBotColorMap() | |||
{ | |||
botColorMap = new Dictionary<byte, BlockColor>(); | |||
// standard colours | |||
botColorMap[0] = new BlockColor (BlockColors.White, 0); | |||
botColorMap[1] = new BlockColor (BlockColors.White, 6); | |||
botColorMap[4] = new BlockColor (BlockColors.White, 8); | |||
botColorMap[5] = new BlockColor (BlockColors.Red, 5); | |||
botColorMap[2] = new BlockColor (BlockColors.Orange, 0); | |||
botColorMap[6] = new BlockColor (BlockColors.Yellow, 0); | |||
botColorMap[7] = new BlockColor (BlockColors.Green, 5); | |||
botColorMap[3] = new BlockColor (BlockColors.Aqua, 5); | |||
botColorMap[9] = new BlockColor (BlockColors.Blue, 5); | |||
botColorMap[10] = new BlockColor (BlockColors.Purple, 5); | |||
// premium colours | |||
botColorMap[16] = new BlockColor (BlockColors.Red, 0); | |||
botColorMap[17] = new BlockColor (BlockColors.Red, 7); | |||
botColorMap[11] = new BlockColor (BlockColors.Orange, 6); | |||
botColorMap[18] = new BlockColor (BlockColors.Purple, 9); | |||
botColorMap[19] = new BlockColor (BlockColors.Pink, 9); | |||
botColorMap[20] = new BlockColor (BlockColors.Orange, 5); | |||
botColorMap[14] = new BlockColor (BlockColors.Yellow, 3); | |||
botColorMap[21] = new BlockColor (BlockColors.Green, 7); | |||
botColorMap[22] = new BlockColor (BlockColors.Lime, 8); | |||
botColorMap[13] = new BlockColor (BlockColors.Green, 6); | |||
botColorMap[12] = new BlockColor (BlockColors.Lime, 5); | |||
// blue gang | |||
botColorMap[23] = new BlockColor (BlockColors.Blue, 8); | |||
botColorMap[24] = new BlockColor (BlockColors.Aqua, 8); | |||
botColorMap[25] = new BlockColor (BlockColors.Blue, 7); | |||
botColorMap[26] = new BlockColor (BlockColors.White, 5); | |||
botColorMap[27] = new BlockColor (BlockColors.White, 4); | |||
botColorMap[28] = new BlockColor (BlockColors.Aqua, 4); | |||
botColorMap[29] = new BlockColor (BlockColors.Purple, 8); | |||
// purples & pinks | |||
botColorMap[30] = new BlockColor (BlockColors.Pink, 0); | |||
botColorMap[8] = new BlockColor (BlockColors.Pink, 5); | |||
botColorMap[31] = new BlockColor (BlockColors.Pink, 4); | |||
botColorMap[15] = new BlockColor (BlockColors.Red, 3); | |||
} | |||
} | |||
} |
@@ -8,6 +8,7 @@ | |||
<PackageLicenseExpression>MIT</PackageLicenseExpression> | |||
<PackageProjectUrl>https://git.exmods.org/NGnius/NPort</PackageProjectUrl> | |||
<NeutralLanguage>en-CA</NeutralLanguage> | |||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
@@ -3,6 +3,7 @@ | |||
using IllusionPlugin; | |||
using TechbloxModdingAPI.Utility; | |||
using TechbloxModdingAPI; | |||
using UnityEngine; | |||
namespace NPort | |||
{ | |||
@@ -35,18 +36,22 @@ namespace NPort | |||
Logging.MetaLog($"{Name} has started up"); | |||
#if DEBUG | |||
// TEST | |||
Bindings.Robocraft.FactoryRobotListInfo[] items = Bindings.Robocraft.GetFactoryFrontPage(); | |||
Bindings.Robocraft.FactoryRobotListInfo[] items = Bindings.Robocraft.SearchFactoryRobots(text_filter: "a"); | |||
foreach (var item in items) | |||
{ | |||
Logging.MetaLog($"{item.item_name} by {item.added_by_display_name} ({item.item_id})"); | |||
} | |||
#endif | |||
// TODO use reflection to automagically register stuff? | |||
API.ImportRegistry.AddImporter(new Importers.RobocraftFactoryImporter()); | |||
} | |||
// unused methods | |||
public override void OnFixedUpdate() { } // called once per physics update | |||
public override void OnUpdate() { } // called once per rendered frame (frame update) | |||
public override void OnGUI() | |||
{ | |||
if (GUILayout.Button("Get PIG!!! OINK!")) | |||
{ | |||
API.ImportRegistry.Call(new []{"CRF", "PIG",}); | |||
} | |||
} | |||
} | |||
} |
@@ -1 +1 @@ | |||
Subproject commit aa8faeb42863ae802626ccb6b2ac3291c87ed928 | |||
Subproject commit 16358c2aecdc07c0ac16a8aff8ec213528ac3950 |