Browse Source

Implement Robocraft CRF importing (untested -- incomplete GUI)

tags/v0.0.1
NGnius (Graham) 3 years ago
parent
commit
6f5439e517
8 changed files with 468 additions and 17 deletions
  1. +11
    -0
      NPort/API/IImporter.cs
  2. +27
    -0
      NPort/API/ImportRegistry.cs
  3. +36
    -0
      NPort/API/SimpleImporter.cs
  4. +219
    -10
      NPort/Bindings/Robocraft.cs
  5. +162
    -0
      NPort/Importers/RobocraftFactoryImporter.cs
  6. +1
    -0
      NPort/NPort.csproj
  7. +11
    -6
      NPort/NPortPlugin.cs
  8. +1
    -1
      clibfj

+ 11
- 0
NPort/API/IImporter.cs View File

@@ -0,0 +1,11 @@
namespace NPort.API
{
public interface IImporter
{
string Name { get; }

string Help(string[] args);

void Import(string[] args);
}
}

+ 27
- 0
NPort/API/ImportRegistry.cs View File

@@ -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;
}
}
}
}
}

+ 36
- 0
NPort/API/SimpleImporter.cs View File

@@ -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);
}
}

+ 219
- 10
NPort/Bindings/Robocraft.cs View File

@@ -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;
}
}
}

+ 162
- 0
NPort/Importers/RobocraftFactoryImporter.cs View File

@@ -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);
}
}
}

+ 1
- 0
NPort/NPort.csproj View File

@@ -8,6 +8,7 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://git.exmods.org/NGnius/NPort</PackageProjectUrl>
<NeutralLanguage>en-CA</NeutralLanguage>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>


+ 11
- 6
NPort/NPortPlugin.cs View File

@@ -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
clibfj

@@ -1 +1 @@
Subproject commit aa8faeb42863ae802626ccb6b2ac3291c87ed928
Subproject commit 16358c2aecdc07c0ac16a8aff8ec213528ac3950

Loading…
Cancel
Save