Compare commits

...

29 commits

Author SHA1 Message Date
NGnius (Graham) ae069f6d93 Fix references 2020-12-28 14:08:13 -05:00
NGnius (Graham) 53564b9c56 Remove missing dependencies causing build warnings 2020-10-31 14:41:11 -04:00
NGnius (Graham) d761e4c16a Add hovers 2020-10-31 14:26:15 -04:00
NGnius (Graham) 89f5d9eb43 Add teslas, insect legs and some weapons (t1 laser, updated t5 laser msmg, t3 plasma) 2020-10-30 16:44:24 -04:00
NGnius (Graham) c3dc80fc84 Add RC propellers and fix textblock <color> replacement regex 2020-10-27 15:53:27 -04:00
NGnius (Graham) 3c00d05a3b Add custom stats dumping support, skis and minor tweaks 2020-10-27 12:00:38 -04:00
NGnius (Graham) 0e65267e88 Merge https://git.exmods.org/NorbiPeti/Pixi into master 2020-10-04 22:49:58 -04:00
NGnius (Graham) 85c1342313 Add info for common unsupported audio types 2020-10-03 17:37:03 -04:00
NGnius (Graham) 3c9c60b679 Add text block support to RC importer & general tweaks to make that work 2020-10-03 17:37:03 -04:00
NGnius (Graham) 84fc330b12 Multi-thread optimisation algorithm 2020-10-03 17:37:03 -04:00
NGnius (Graham) d2a2ce52f0 Optimise midi import block placement 2020-10-03 17:37:03 -04:00
NGnius (Graham) 742bcf25ef Create prototype MIDI importer 2020-10-03 17:37:03 -04:00
Norbi Peti 16f833ceda Fix RC glass cube and revert temp text support 2020-09-28 03:02:15 +02:00
Norbi Peti a0ab2ec9e7 Add wings, rudders & rotors, temporary text block support 2020-09-27 02:42:27 +02:00
Norbi Peti 30a3f5001f Ability to rotate blueprints, updated struts and such 2020-09-26 03:18:22 +02:00
Norbi Peti db5ff7223c Blueprint fixes, T5 laser 2020-09-23 23:35:09 +02:00
Norbi Peti 02401f39f9 Add t5 shields 2020-09-21 21:56:20 +02:00
Norbi Peti 60cf8bdd67 Actually add t3 shields and t4 ones too 2020-09-21 00:25:53 +02:00
Norbi Peti ab169fb87c (Re)load blueprints from file, t3 shields
Blueprints can be loaded and reloaded from a file in the game's directory except in a release version
2020-09-20 23:23:33 +02:00
Norbi Peti 5bc0351bd1 Fix thrusters and add some shields 2020-09-19 23:51:17 +02:00
Norbi Peti bd813d852d Allow selections when dumping, added thrusters and modules 2020-09-19 22:30:54 +02:00
NGnius (Graham) 4b35647c0b Use official paint colours for conversion algorithm 2020-09-04 16:00:12 -04:00
NGnius (Graham) 1b126b69c0 Convert to new GCIPA 2020-08-23 10:31:38 -04:00
NGnius (Graham) bb19c084d1 Add default colour quantization support 2020-08-16 11:27:46 -04:00
NGnius (Graham) 90a653b1c4 Remove mock ref 2020-07-24 11:14:57 -04:00
NGnius (Graham) 244be3468b Refactor Pixi import system as per issue #5 2020-07-24 11:05:00 -04:00
NGnius (Graham) acb0c59967 Use geometric (pythagorean) distance for nearest colour calculations 2020-07-24 11:05:00 -04:00
NGnius (Graham) df7ba96434 Update license 2020-06-26 12:51:59 -04:00
NGnius (Graham) 523baec814 Fix missing RC->GC colour mapping 2020-06-14 12:49:10 -04:00
29 changed files with 2577 additions and 692 deletions

View file

@ -1,6 +1,6 @@
MIT License
Copyright (c) <year> <copyright holders>
Copyright (c) 2020 NGnius
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -0,0 +1,37 @@
using System;
using GamecraftModdingAPI;
using GamecraftModdingAPI.Utility;
using Pixi.Common;
namespace Pixi.Audio
{
public class AudioFakeImporter : Importer
{
public int Priority { get; } = 0;
public bool Optimisable { get; } = false;
public string Name { get; } = "AudioWarning~Spell";
public BlueprintProvider BlueprintProvider { get; } = null;
public bool Qualifies(string name)
{
return name.EndsWith(".flac", StringComparison.InvariantCultureIgnoreCase)
|| name.EndsWith(".ogg", StringComparison.InvariantCultureIgnoreCase)
|| name.EndsWith(".mp3", StringComparison.InvariantCultureIgnoreCase)
|| name.EndsWith(".wav", StringComparison.InvariantCultureIgnoreCase)
|| name.EndsWith(".aac", StringComparison.InvariantCultureIgnoreCase);
}
public BlockJsonInfo[] Import(string name)
{
Logging.CommandLogWarning($"Audio importing only works with MIDI (.mid) files, which '{name}' is not.\nThere are many converters online, but for best quality use a MIDI file made from a music transcription.\nFor example, musescore.com has lots of good transcriptions and they offer a 30-day free trial.");
return null;
}
public void PreProcess(string name, ref ProcessedVoxelObjectNotation[] blocks)
{
}
public void PostProcess(string name, ref Block[] blocks)
{
}
}
}

106
Pixi/Audio/AudioTools.cs Normal file
View file

@ -0,0 +1,106 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using GamecraftModdingAPI.Utility;
using Melanchall.DryWetMidi.Common;
namespace Pixi.Audio
{
public static class AudioTools
{
private static Dictionary<byte, byte> programMap = null;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte TrackType(FourBitNumber channel)
{
return TrackType((byte) channel);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte TrackType(byte channel)
{
if (programMap.ContainsKey(channel)) return programMap[channel];
#if DEBUG
Logging.MetaLog($"Using default value (piano) for channel number {channel}");
#endif
return 5; // Piano
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float VelocityToVolume(SevenBitNumber velocity)
{
// faster key hit means louder note
return 100f * velocity / ((float) SevenBitNumber.MaxValue + 1f);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void GenerateProgramMap()
{
programMap = new Dictionary<byte, byte>
{
{0, 5 /* Piano */},
{1, 5},
{2, 5},
{3, 5},
{4, 5},
{5, 5},
{6, 5},
{7, 5},
{8, 0 /* Kick Drum */},
{9, 0},
{10, 0},
{11, 0},
{12, 0},
{13, 0},
{14, 0},
{15, 0},
{24, 6 /* Guitar 1 (Acoustic) */},
{25, 6},
{26, 6},
{27, 6},
{28, 6},
{29, 7 /* Guitar 2 (Dirty Electric) */},
{30, 7},
{32, 6},
{33, 6},
{34, 6},
{35, 6},
{36, 6},
{37, 6},
{38, 6},
{39, 6},
{56, 8 /* Trumpet */}, // basically all brass & reeds are trumpets... that's how music works right?
{57, 8},
{58, 8},
{59, 8},
{60, 8},
{61, 8},
{62, 8},
{63, 8},
{64, 8},
{65, 8},
{66, 8},
{67, 8},
{68, 8},
{69, 8}, // Nice
{70, 8},
{71, 8},
{72, 8},
{73, 8},
{74, 8},
{75, 8},
{76, 8},
{77, 8},
{78, 8},
{79, 8},
{112, 0},
{113, 0},
{114, 0},
{115, 0},
{116, 0},
{117, 4 /* Tom Drum */},
{118, 4},
{119, 3 /* Open High Hat */},
};
}
}
}

207
Pixi/Audio/MidiImporter.cs Normal file
View file

@ -0,0 +1,207 @@
using System;
using System.Collections.Generic;
using System.Linq;
using GamecraftModdingAPI;
using GamecraftModdingAPI.Players;
using GamecraftModdingAPI.Blocks;
using GamecraftModdingAPI.Utility;
using Melanchall.DryWetMidi.Common;
using Melanchall.DryWetMidi.Core;
using Melanchall.DryWetMidi.Devices;
using Melanchall.DryWetMidi.Interaction;
using Pixi.Common;
using Unity.Mathematics;
namespace Pixi.Audio
{
public class MidiImporter : Importer
{
public int Priority { get; } = 1;
public bool Optimisable { get; } = false;
public string Name { get; } = "Midi~Spell";
public BlueprintProvider BlueprintProvider { get; } = null;
private Dictionary<string, MidiFile> openFiles = new Dictionary<string, MidiFile>();
public static bool ThreeDee = false;
public static float Spread = 1f;
public static byte Key = 0;
public static float VolumeMultiplier = 1f;
public MidiImporter()
{
AudioTools.GenerateProgramMap();
}
public bool Qualifies(string name)
{
return name.EndsWith(".mid", StringComparison.InvariantCultureIgnoreCase)
|| name.EndsWith(".midi", StringComparison.InvariantCultureIgnoreCase);
}
public BlockJsonInfo[] Import(string name)
{
MidiFile midi = MidiFile.Read(name);
openFiles[name] = midi;
Logging.MetaLog($"Found {midi.GetNotes().Count()} notes over {midi.GetDuration<MidiTimeSpan>().TimeSpan} time units");
BlockJsonInfo[] blocks = new BlockJsonInfo[(midi.GetNotes().Count() * 2) + 3];
List<BlockJsonInfo> blocksToBuild = new List<BlockJsonInfo>();
// convert Midi notes to sfx blocks
Dictionary<long, uint> breadthCache = new Dictionary<long, uint>();
Dictionary<long, uint> depthCache = new Dictionary<long, uint>();
HashSet<long> timerCache = new HashSet<long>();
//uint count = 0;
float zdepth = 0;
foreach (Note n in midi.GetNotes())
{
long microTime = n.TimeAs<MetricTimeSpan>(midi.GetTempoMap()).TotalMicroseconds;
float breadth = 0f;
if (!timerCache.Contains(microTime))
{
depthCache[microTime] = (uint)++zdepth;
breadthCache[microTime] = 1;
timerCache.Add(microTime);
blocksToBuild.Add(new BlockJsonInfo
{
name = GamecraftModdingAPI.Blocks.BlockIDs.Timer.ToString(),
position = new float[] { breadth * 0.2f * Spread, 2 * 0.2f, zdepth * 0.2f * Spread},
rotation = new float[] { 0, 0, 0},
color = new float[] { -1, -1, -1},
scale = new float[] { 1, 1, 1},
});
}
else
{
zdepth = depthCache[microTime]; // remember the z-position of notes played at the same moment (so they can be placed adjacent to each other)
breadth += breadthCache[microTime]++; // if multiple notes exist for a given time, place them beside each other on the x-axis
}
blocksToBuild.Add(new BlockJsonInfo
{
name = GamecraftModdingAPI.Blocks.BlockIDs.SFXBlockInstrument.ToString(),
position = new float[] { breadth * 0.2f * Spread, 1 * 0.2f, zdepth * 0.2f * Spread},
rotation = new float[] { 0, 0, 0},
color = new float[] { -1, -1, -1},
scale = new float[] { 1, 1, 1},
});
/*
blocks[count] = new BlockJsonInfo
{
name = GamecraftModdingAPI.Blocks.BlockIDs.Timer.ToString(),
position = new float[] { breadth * 0.2f * Spread, 2 * 0.2f, zdepth * 0.2f * Spread},
rotation = new float[] { 0, 0, 0},
color = new float[] { -1, -1, -1},
scale = new float[] { 1, 1, 1},
};
count++;
blocks[count] = new BlockJsonInfo
{
name = GamecraftModdingAPI.Blocks.BlockIDs.SFXBlockInstrument.ToString(),
position = new float[] { breadth * 0.2f * Spread, 1 * 0.2f, zdepth * 0.2f * Spread},
rotation = new float[] { 0, 0, 0},
color = new float[] { -1, -1, -1},
scale = new float[] { 1, 1, 1},
};
count++;*/
}
// playback IO (reset & play)
blocksToBuild.Add(new BlockJsonInfo
{
name = GamecraftModdingAPI.Blocks.BlockIDs.SimpleConnector.ToString(),
position = new float[] { -0.2f, 3 * 0.2f, 0},
rotation = new float[] { 0, 0, 0},
color = new float[] { -1, -1, -1},
scale = new float[] { 1, 1, 1},
}); // play is second last (placed above stop)
blocksToBuild.Add(new BlockJsonInfo
{
name = GamecraftModdingAPI.Blocks.BlockIDs.SimpleConnector.ToString(),
position = new float[] { -0.2f, 2 * 0.2f, 0},
rotation = new float[] { 0, 0, 0},
color = new float[] { -1, -1, -1},
scale = new float[] { 1, 1, 1},
}); // stop is middle (placed above reset)
blocksToBuild.Add(new BlockJsonInfo
{
name = GamecraftModdingAPI.Blocks.BlockIDs.SimpleConnector.ToString(),
position = new float[] { -0.2f, 1 * 0.2f, 0},
rotation = new float[] { 0, 0, 0},
color = new float[] { -1, -1, -1},
scale = new float[] { 1, 1, 1},
}); // reset is last (placed below stop)
return blocksToBuild.ToArray();
}
public void PreProcess(string name, ref ProcessedVoxelObjectNotation[] blocks)
{
Player p = new Player(PlayerType.Local);
float3 pos = p.Position;
for (int i = 0; i < blocks.Length; i++)
{
blocks[i].position += pos;
}
}
public void PostProcess(string name, ref Block[] blocks)
{
// playback IO
LogicGate startConnector = blocks[blocks.Length - 3].Specialise<LogicGate>();
LogicGate stopConnector = blocks[blocks.Length - 2].Specialise<LogicGate>();
LogicGate resetConnector = blocks[blocks.Length - 1].Specialise<LogicGate>();
uint count = 0;
// generate channel data
byte[] channelPrograms = new byte[16];
for (byte i = 0; i < channelPrograms.Length; i++) // init array
{
channelPrograms[i] = 5; // Piano
}
foreach (TimedEvent e in openFiles[name].GetTimedEvents())
{
if (e.Event.EventType == MidiEventType.ProgramChange)
{
ProgramChangeEvent pce = (ProgramChangeEvent) e.Event;
channelPrograms[pce.Channel] = AudioTools.TrackType(pce.ProgramNumber);
#if DEBUG
Logging.MetaLog($"Detected channel {pce.Channel} as program {pce.ProgramNumber} (index {channelPrograms[pce.Channel]})");
#endif
}
}
Timer t = null;
//count = 0;
foreach (Note n in openFiles[name].GetNotes())
{
while (blocks[count].Type == BlockIDs.Timer)
{
// set timing info
#if DEBUG
Logging.Log($"Handling Timer for notes at {n.TimeAs<MetricTimeSpan>(openFiles[name].GetTempoMap()).TotalMicroseconds * 0.000001f}s");
#endif
t = blocks[count].Specialise<Timer>();
t.Start = 0;
t.End = 0.01f + n.TimeAs<MetricTimeSpan>(openFiles[name].GetTempoMap()).TotalMicroseconds * 0.000001f;
count++;
}
// set notes info
SfxBlock sfx = blocks[count].Specialise<SfxBlock>();
sfx.Pitch = n.NoteNumber - 60 + Key; // In MIDI, 60 is middle C, but GC uses 0 for middle C
sfx.TrackIndex = channelPrograms[n.Channel];
sfx.Is3D = ThreeDee;
sfx.Volume = AudioTools.VelocityToVolume(n.Velocity) * VolumeMultiplier;
count++;
// connect wires
if (t == null) continue; // this should never happen
t.Connect(0, sfx, 0);
startConnector.Connect(0, t, 0);
stopConnector.Connect(0, t, 1);
resetConnector.Connect(0, t, 2);
}
openFiles.Remove(name);
}
}
}

View file

@ -1,17 +0,0 @@
using System;
using GamecraftModdingAPI.Blocks;
namespace Pixi.Common
{
public struct BlockInfo
{
public BlockIDs block;
public BlockColors color;
public byte darkness;
public bool visible;
}
}

View file

@ -1,4 +1,9 @@
using System;
using Unity.Mathematics;
using GamecraftModdingAPI.Blocks;
namespace Pixi.Common
{
public struct BlockJsonInfo
@ -12,5 +17,25 @@ namespace Pixi.Common
public float[] color;
public float[] scale;
internal ProcessedVoxelObjectNotation Process()
{
BlockIDs block = ConversionUtility.BlockIDsToEnum(name.Split('\t')[0]);
return new ProcessedVoxelObjectNotation
{
block = block,
blueprint = block == BlockIDs.Invalid,
color = ColorSpaceUtility.QuantizeToBlockColor(color),
metadata = name,
position = ConversionUtility.FloatArrayToFloat3(position),
rotation = ConversionUtility.FloatArrayToFloat3(rotation),
scale = ConversionUtility.FloatArrayToFloat3(scale),
};
}
public override string ToString()
{
return $"BlockJsonInfo {{ name:{name}, color:(r{color[0]},g{color[1]},b{color[2]}), position:({position[0]},{position[1]},{position[2]}), rotation:({rotation[0]},{rotation[1]},{rotation[2]}), scale:({scale[0]},{scale[1]},{scale[2]})}}";
}
}
}

View file

@ -0,0 +1,9 @@
namespace Pixi.Common
{
public interface BlueprintProvider
{
string Name { get; }
BlockJsonInfo[] Blueprint(string name, BlockJsonInfo root);
}
}

View file

@ -0,0 +1,72 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Reflection;
using Newtonsoft.Json;
namespace Pixi.Common
{
public static class BlueprintUtility
{
public static Dictionary<string, BlockJsonInfo[]> ParseBlueprintFile(string name)
{
StreamReader bluemap = new StreamReader(File.OpenRead(name));
return JsonConvert.DeserializeObject<Dictionary<string, BlockJsonInfo[]>>(bluemap.ReadToEnd());
}
public static Dictionary<string, BlockJsonInfo[]> ParseBlueprintResource(string name)
{
StreamReader bluemap;
#if DEBUG
if (File.Exists(name))
bluemap = File.OpenText(name);
else
#endif
bluemap = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream(name));
using (bluemap)
return JsonConvert.DeserializeObject<Dictionary<string, BlockJsonInfo[]>>(bluemap.ReadToEnd());
}
public static ProcessedVoxelObjectNotation[][] ProcessAndExpandBlocks(string name, BlockJsonInfo[] blocks, BlueprintProvider blueprints)
{
List<ProcessedVoxelObjectNotation[]> expandedBlocks = new List<ProcessedVoxelObjectNotation[]>();
for (int i = 0; i < blocks.Length; i++)
{
ProcessedVoxelObjectNotation root = blocks[i].Process();
if (root.blueprint)
{
if (blueprints == null)
{
throw new NullReferenceException("Blueprint block info found but BlueprintProvider is null");
}
BlockJsonInfo[] blueprint = blueprints.Blueprint(name, blocks[i]);
ProcessedVoxelObjectNotation[] expanded = new ProcessedVoxelObjectNotation[blueprint.Length];
for (int j = 0; j < expanded.Length; j++)
{
expanded[j] = blueprint[j].Process();
}
expandedBlocks.Add(expanded);
}
else
{
expandedBlocks.Add(new ProcessedVoxelObjectNotation[]{root});
}
}
return expandedBlocks.ToArray();
}
public static ProcessedVoxelObjectNotation[] ProcessBlocks(BlockJsonInfo[] blocks)
{
ProcessedVoxelObjectNotation[] procBlocks = new ProcessedVoxelObjectNotation[blocks.Length];
for (int i = 0; i < blocks.Length; i++)
{
procBlocks[i] = blocks[i].Process();
}
return procBlocks;
}
}
}

View file

@ -4,15 +4,22 @@ using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using GamecraftModdingAPI.App;
using GamecraftModdingAPI.Blocks;
using GamecraftModdingAPI.Tasks;
using GamecraftModdingAPI.Utility;
using MiniJSON;
using Svelto.Tasks;
using UnityEngine.ResourceManagement.ResourceLocations;
namespace Pixi.Common
{
public static class ColorSpaceUtility
{
private const float optimal_delta = 0.2f;
private const float optimal_delta = 0.1f;
private static Dictionary<BlockColor, float[]> colorMap = null;
@ -21,7 +28,7 @@ namespace Pixi.Common
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BlockColor QuantizeToBlockColor(Color pixel)
{
if (colorMap == null) BuildColorMap();
//if (colorMap == null) BuildColorMap();
float[] closest = new float[3] { 1, 1, 1 };
BlockColor c = new BlockColor
{
@ -29,22 +36,28 @@ namespace Pixi.Common
Darkness = 0,
};
BlockColor[] keys = colorMap.Keys.ToArray();
float geometricClosest = float.MaxValue;
for (int k = 0; k < keys.Length; k++)
{
float[] color = colorMap[keys[k]];
float[] distance = new float[3] { Math.Abs(pixel.r - color[0]), Math.Abs(pixel.g - color[1]), Math.Abs(pixel.b - color[2]) };
if ((distance[0] + distance[1] + distance[2]) < (closest[0] + closest[1] + closest[2]))
float dist = Mathf.Sqrt(Mathf.Pow(distance[0], 2) + Mathf.Pow(distance[1], 2) + Mathf.Pow(distance[2], 2));
if (dist < geometricClosest)
{
c = keys[k];
closest = distance;
if ((closest[0] + closest[1] + closest[2]) < optimal_delta)
geometricClosest = Mathf.Sqrt(Mathf.Pow(closest[0], 2) + Mathf.Pow(closest[1], 2) + Mathf.Pow(closest[2], 2));
if (geometricClosest < optimal_delta)
{
#if DEBUG
//Logging.MetaLog($"Final delta ({closest[0]},{closest[1]},{closest[2]}) t:{geometricClosest}");
#endif
return c;
}
}
}
#if DEBUG
Logging.MetaLog($"Final delta ({closest[0]},{closest[1]},{closest[2]}) t:{closest[0] + closest[1] + closest[2]}");
//Logging.MetaLog($"Final delta ({closest[0]},{closest[1]},{closest[2]}) t:{geometricClosest}");
#endif
return c;
}
@ -59,13 +72,21 @@ namespace Pixi.Common
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BlockColor QuantizeToBlockColor(float[] pixel)
{
if (pixel.Length < 3 || pixel[0] < 0 || pixel[1] < 0 || pixel[2] < 0)
{
return new BlockColor
{
Color = BlockColors.Default,
Darkness = 0,
};
}
return QuantizeToBlockColor(new Color(pixel[0], pixel[1], pixel[2]));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float[] UnquantizeToArray(BlockColor c)
{
if (colorMap == null) BuildColorMap();
//if (colorMap == null) BuildColorMap();
return colorMap[c];
}
@ -96,23 +117,16 @@ namespace Pixi.Common
});
}
public static void LoadColorMenuEvent(object caller, MenuEventArgs info)
{
Scheduler.Schedule(new AsyncRunner());
}
private static void BuildColorMap()
{
// old manual version for building color map
colorMap = new Dictionary<BlockColor, float[]>();
// TODO create actual color map
foreach (BlockColors c in Enum.GetValues(typeof(BlockColors)))
{
for (byte d = 0; d < 10; d++)
{
BlockColor colorStruct = new BlockColor
{
Color = c,
Darkness = d,
};
colorMap[colorStruct] = new float[3] { 1f, 0f, 1f };
}
}
// this was done manually -- never again
// this was done manually -- never again
// White
colorMap[new BlockColor { Color = BlockColors.White, Darkness = 0 }] = new float[3] { 1f, 1f, 1f};
colorMap[new BlockColor { Color = BlockColors.White, Darkness = 1 }] = new float[3] { 0.88f, 0.98f, 0.99f };
@ -223,6 +237,8 @@ namespace Pixi.Common
colorMap[new BlockColor { Color = BlockColors.Red, Darkness = 7 }] = new float[3] { 0.455f, 0.105f, 0.108f };
colorMap[new BlockColor { Color = BlockColors.Red, Darkness = 8 }] = new float[3] { 0.320f, 0.121f, 0.133f };
colorMap[new BlockColor { Color = BlockColors.Red, Darkness = 9 }] = new float[3] { 0.687f, 0.571f, 0.661f };
// default
colorMap[new BlockColor { Color = BlockColors.Default, Darkness = 0 }] = new float[3] { -1f, -1f, -1f };
}
private static void BuildBotColorMap()
@ -246,11 +262,11 @@ namespace Pixi.Common
botColorMap[18] = new BlockColor { Color = BlockColors.Purple, Darkness = 9 };
botColorMap[19] = new BlockColor { Color = BlockColors.Pink, Darkness = 9 };
botColorMap[20] = new BlockColor { Color = BlockColors.Orange, Darkness = 5 };
botColorMap[20] = new BlockColor { Color = BlockColors.Yellow, Darkness = 3 };
botColorMap[14] = new BlockColor { Color = BlockColors.Green, Darkness = 7 };
botColorMap[21] = new BlockColor { Color = BlockColors.Lime, Darkness = 8 };
botColorMap[22] = new BlockColor { Color = BlockColors.Green, Darkness = 6 };
botColorMap[13] = new BlockColor { Color = BlockColors.Lime, Darkness = 5 };
botColorMap[14] = new BlockColor { Color = BlockColors.Yellow, Darkness = 3 };
botColorMap[21] = new BlockColor { Color = BlockColors.Green, Darkness = 7 };
botColorMap[22] = new BlockColor { Color = BlockColors.Lime, Darkness = 8 };
botColorMap[13] = new BlockColor { Color = BlockColors.Green, Darkness = 6 };
botColorMap[12] = new BlockColor { Color = BlockColors.Lime, Darkness = 5 };
// blue gang
botColorMap[23] = new BlockColor { Color = BlockColors.Blue, Darkness = 8 };
botColorMap[24] = new BlockColor { Color = BlockColors.Aqua, Darkness = 8 };
@ -265,5 +281,42 @@ namespace Pixi.Common
botColorMap[31] = new BlockColor { Color = BlockColors.Pink, Darkness = 4 };
botColorMap[15] = new BlockColor { Color = BlockColors.Red, Darkness = 3 };
}
private class AsyncRunner : ISchedulable
{
public IEnumerator<TaskContract> Run()
{
AsyncOperationHandle<TextAsset> asyncHandle = Addressables.LoadAssetAsync<TextAsset>("colours");
yield return asyncHandle.Continue();
Dictionary<string, object> colourData = Json.Deserialize(asyncHandle.Result.text) as Dictionary<string, object>;
if (colourData == null) yield break;
Client.EnterMenu -= LoadColorMenuEvent;
// Logging.MetaLog((List<object>)((colourData["Colours"] as Dictionary<string, object>)["Data"] as Dictionary<string, object>)["Slots"]);
// Generate color map
List<object> hexColors =
(((colourData["Colours"] as Dictionary<string, object>)?["Data"] as Dictionary<string, object>)?
["Slots"] as List<object>);
int count = 0;
colorMap = new Dictionary<BlockColor, float[]>();
for (byte d = 0; d < 10; d++)
{
foreach (BlockColors c in Enum.GetValues(typeof(BlockColors)))
{
if (c != BlockColors.Default)
{
BlockColor colorStruct = new BlockColor
{
Color = c,
Darkness = d,
};
Color pixel = Images.PixelUtility.PixelHex((string)hexColors[count]);
colorMap[colorStruct] = new float[] {pixel.r, pixel.g, pixel.b};
count++;
}
}
yield return asyncHandle.Continue();
}
}
}
}
}

653
Pixi/Common/CommandRoot.cs Normal file
View file

@ -0,0 +1,653 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using UnityEngine;
using Unity.Mathematics;
using Svelto.ECS;
using GamecraftModdingAPI;
using GamecraftModdingAPI.Blocks;
using GamecraftModdingAPI.Commands;
using GamecraftModdingAPI.Utility;
using Svelto.DataStructures;
namespace Pixi.Common
{
/// <summary>
/// Command implementation.
/// CommandRoot.Pixi is the root of all Pixi calls from the CLI
/// </summary>
public class CommandRoot : ICustomCommandEngine
{
public void Ready()
{
CommandRegistrationHelper.Register<string>(Name, (name) => tryOrCommandLogError(() => this.Pixi(null, name)), Description);
CommandRegistrationHelper.Register<string, string>(Name+"2", this.Pixi, "Import something into Gamecraft using magic. Usage: Pixi \"importer\" \"myfile.png\"");
}
public EntitiesDB entitiesDB { get; set; }
public void Dispose()
{
CommandRegistrationHelper.Unregister(Name);
CommandRegistrationHelper.Unregister(Name+"2");
}
public string Name { get; } = "Pixi";
public bool isRemovable { get; } = false;
public string Description { get; } = "Import something into Gamecraft using magic. Usage: Pixi \"myfile.png\"";
public Dictionary<int, Importer[]> importers = new Dictionary<int, Importer[]>();
public static ThreadSafeDictionary<int, bool> optimisableBlockCache = new ThreadSafeDictionary<int, bool>();
public const float BLOCK_SIZE = 0.2f;
public const float DELTA = BLOCK_SIZE / 2048;
public static int OPTIMISATION_PASSES = 2;
public static int GROUP_SIZE = 32;
// optimisation algorithm constants
private static float3[] cornerMultiplicands1 = new float3[8]
{
new float3(1, 1, 1),
new float3(1, 1, -1),
new float3(-1, 1, 1),
new float3(-1, 1, -1),
new float3(-1, -1, 1),
new float3(-1, -1, -1),
new float3(1, -1, 1),
new float3(1, -1, -1),
};
private static float3[] cornerMultiplicands2 = new float3[8]
{
new float3(1, 1, 1),
new float3(1, 1, -1),
new float3(1, -1, 1),
new float3(1, -1, -1),
new float3(-1, 1, 1),
new float3(-1, 1, -1),
new float3(-1, -1, 1),
new float3(-1, -1, -1),
};
private static int[][] cornerFaceMappings = new int[][]
{
new int[] {0, 1, 2, 3}, // top
new int[] {2, 3, 4, 5}, // left
new int[] {4, 5, 6, 7}, // bottom
new int[] {6, 7, 0, 1}, // right
new int[] {0, 2, 4, 6}, // back
new int[] {1, 3, 5, 7}, // front
};
private static int[][] oppositeFaceMappings = new int[][]
{
new int[] {6, 7, 4, 5}, // bottom
new int[] {0, 1, 6, 7}, // right
new int[] {2, 3, 0, 1}, // top
new int[] {4, 5, 2, 3}, // left
new int[] {1, 3, 5, 7}, // front
new int[] {0, 2, 4, 6}, // back
};
public CommandRoot()
{
CommandManager.AddCommand(this);
}
public void Inject(Importer imp)
{
if (importers.ContainsKey(imp.Priority))
{
// extend array by 1 and place imp at the end
Importer[] oldArr = importers[imp.Priority];
Importer[] newArr = new Importer[oldArr.Length + 1];
for (int i = 0; i < oldArr.Length; i++)
{
newArr[i] = oldArr[i];
}
newArr[oldArr.Length] = imp;
importers[imp.Priority] = newArr;
}
else
{
importers[imp.Priority] = new Importer[] {imp};
}
}
private void Pixi(string importerName, string name)
{
// organise priorities
int[] priorities = importers.Keys.ToArray();
Array.Sort(priorities);
Array.Reverse(priorities); // higher priorities go first
// find relevant importer
Importer magicImporter = null;
foreach (int p in priorities)
{
Importer[] imps = importers[p];
for (int i = 0; i < imps.Length; i++)
{
//Logging.MetaLog($"Now checking importer {imps[i].Name}");
if ((importerName == null && imps[i].Qualifies(name))
|| (importerName != null && imps[i].Name.Contains(importerName)))
{
magicImporter = imps[i];
break;
}
}
if (magicImporter != null) break;
}
if (magicImporter == null)
{
Logging.CommandLogError("Unsupported file or string.");
return;
}
#if DEBUG
Logging.MetaLog($"Using '{magicImporter.Name}' to import '{name}'");
#endif
// import blocks
BlockJsonInfo[] blocksInfo = magicImporter.Import(name);
if (blocksInfo == null || blocksInfo.Length == 0)
{
#if DEBUG
Logging.CommandLogError($"Importer {magicImporter.Name} didn't provide any blocks to import. Mission Aborted!");
#endif
return;
}
ProcessedVoxelObjectNotation[][] procVONs;
BlueprintProvider blueprintProvider = magicImporter.BlueprintProvider;
if (blueprintProvider == null)
{
// convert block info to API-compatible format
procVONs = new ProcessedVoxelObjectNotation[][] {BlueprintUtility.ProcessBlocks(blocksInfo)};
}
else
{
// expand blueprints and convert block info
procVONs = BlueprintUtility.ProcessAndExpandBlocks(name, blocksInfo, magicImporter.BlueprintProvider);
}
// reduce block placements by grouping neighbouring similar blocks
// (after flattening block data representation)
List<ProcessedVoxelObjectNotation> optVONs = new List<ProcessedVoxelObjectNotation>();
for (int arr = 0; arr < procVONs.Length; arr++)
{
for (int elem = 0; elem < procVONs[arr].Length; elem++)
{
optVONs.Add(procVONs[arr][elem]);
}
}
#if DEBUG
Logging.MetaLog($"Imported {optVONs.Count} blocks for '{name}'");
#endif
int blockCountPreOptimisation = optVONs.Count;
if (magicImporter.Optimisable)
{
for (int pass = 0; pass < OPTIMISATION_PASSES; pass++)
{
OptimiseBlocks(ref optVONs, (pass + 1) * GROUP_SIZE);
#if DEBUG
Logging.MetaLog($"Optimisation pass {pass} completed");
#endif
}
#if DEBUG
Logging.MetaLog($"Optimised down to {optVONs.Count} blocks for '{name}'");
#endif
}
ProcessedVoxelObjectNotation[] optVONsArr = optVONs.ToArray();
magicImporter.PreProcess(name, ref optVONsArr);
// place blocks
Block[] blocks = new Block[optVONsArr.Length];
for (int i = 0; i < optVONsArr.Length; i++)
{
ProcessedVoxelObjectNotation desc = optVONsArr[i];
if (desc.block != BlockIDs.Invalid)
{
Block b = Block.PlaceNew(desc.block, desc.position, desc.rotation, desc.color.Color,
desc.color.Darkness, 1, desc.scale);
blocks[i] = b;
}
#if DEBUG
else
{
Logging.LogWarning($"Found invalid block at index {i}\n\t{optVONsArr[i].ToString()}");
}
#endif
}
// handle special block parameters
PostProcessSpecialBlocks(ref optVONsArr, ref blocks);
// post processing
magicImporter.PostProcess(name, ref blocks);
if (magicImporter.Optimisable && blockCountPreOptimisation > blocks.Length)
{
Logging.CommandLog($"Imported {blocks.Length} blocks using {magicImporter.Name} ({blockCountPreOptimisation/blocks.Length}x ratio)");
}
else
{
Logging.CommandLog($"Imported {blocks.Length} blocks using {magicImporter.Name}");
}
}
private void OptimiseBlocks(ref List<ProcessedVoxelObjectNotation> optVONs, int chunkSize)
{
// Reduce blocks to place to reduce lag while placing and from excessive blocks in the world.
// Blocks are reduced by grouping similar blocks that are touching (before they're placed)
// multithreaded because this is an expensive (slow) operation
int item = 0;
ProcessedVoxelObjectNotation[][] groups = new ProcessedVoxelObjectNotation[optVONs.Count / chunkSize][];
Thread[] tasks = new Thread[groups.Length];
while (item < groups.Length)
{
groups[item] = new ProcessedVoxelObjectNotation[chunkSize];
optVONs.CopyTo(item * chunkSize, groups[item], 0, chunkSize);
int tmpItem = item; // scope is dumb
tasks[item] = new Thread(() =>
{
groups[tmpItem] = groupBlocksBestEffort(groups[tmpItem], tmpItem);
});
tasks[item].Start();
item++;
}
#if DEBUG
Logging.MetaLog($"Created {groups.Length} + 1? groups");
#endif
// final group
ProcessedVoxelObjectNotation[] finalGroup = null;
Thread finalThread = null;
if (optVONs.Count > item * chunkSize)
{
//finalGroup = optVONs.GetRange(item * GROUP_SIZE, optVONs.Count - (item * GROUP_SIZE)).ToArray();
finalGroup = new ProcessedVoxelObjectNotation[optVONs.Count - (item * chunkSize)];
optVONs.CopyTo(item * chunkSize, finalGroup, 0, optVONs.Count - (item * chunkSize));
finalThread = new Thread(() =>
{
finalGroup = groupBlocksBestEffort(finalGroup, -1);
});
finalThread.Start();
}
// gather results
List<ProcessedVoxelObjectNotation> result = new List<ProcessedVoxelObjectNotation>();
for (int i = 0; i < groups.Length; i++)
{
#if DEBUG
Logging.MetaLog($"Waiting for completion of task {i}");
#endif
tasks[i].Join();
result.AddRange(groups[i]);
}
if (finalThread != null)
{
#if DEBUG
Logging.MetaLog($"Waiting for completion of final task");
#endif
finalThread.Join();
result.AddRange(finalGroup);
}
optVONs = result;
}
private static ProcessedVoxelObjectNotation[] groupBlocksBestEffort(ProcessedVoxelObjectNotation[] blocksToOptimise, int id)
{
// a really complicated algorithm to determine if two similar blocks are touching (before they're placed)
// the general concept:
// two blocks are touching when they have a common face (equal to 4 corners on the cube, where the 4 corners aren't completely opposite each other)
// between the two blocks, the 8 corners that aren't in common are the corners for the merged block
//
// to merge the 2 blocks, switch out the 4 common corners of one block with the nearest non-common corners from the other block
// i.e. swap the common face on block A with the face opposite the common face of block B
// to prevent a nonsensical face (rotated compared to other faces), the corners of the face should be swapped out with the corresponding corner which shares an edge
//
// note: e.g. if common face on block A is its top, the common face of block B is not necessarily the bottom face because blocks can be rotated differently
// this means it's not safe to assume that block A's common face (top) can be swapped with block B's non-common opposite face (top) to get the merged block
//
// note2: this does not work with blocks which aren't cubes (i.e. any block where rotation matters)
try
{
#if DEBUG
Stopwatch timer = Stopwatch.StartNew();
#endif
FasterList<ProcessedVoxelObjectNotation> optVONs = new FasterList<ProcessedVoxelObjectNotation>(blocksToOptimise);
int item = 0;
while (item < optVONs.count - 1)
{
#if DEBUG
Logging.MetaLog($"({id}) Now grouping item {item}/{optVONs.count} ({100f * item/(float)optVONs.count}%)");
#endif
bool isItemUpdated = false;
ProcessedVoxelObjectNotation itemVON = optVONs[item];
if (isOptimisableBlock(itemVON.block))
{
float3[] itemCorners = calculateCorners(itemVON);
int seeker = item + 1; // despite this, assume that seeker goes thru the entire list (not just blocks after item)
while (seeker < optVONs.count)
{
if (seeker == item)
{
seeker++;
}
else
{
ProcessedVoxelObjectNotation seekerVON = optVONs[seeker];
//Logging.MetaLog($"Comparing {itemVON} and {seekerVON}");
float3[] seekerCorners = calculateCorners(seekerVON);
int[][] mapping = findMatchingCorners(itemCorners, seekerCorners);
if (mapping.Length != 0
&& itemVON.block == seekerVON.block
&& itemVON.color.Color == seekerVON.color.Color
&& itemVON.color.Darkness == seekerVON.color.Darkness
&& isOptimisableBlock(seekerVON.block)) // match found
{
// switch out corners based on mapping
//Logging.MetaLog($"Corners {float3ArrToString(itemCorners)}\nand {float3ArrToString(seekerCorners)}");
//Logging.MetaLog($"Mappings (len:{mapping[0].Length}) {mapping[0][0]} -> {mapping[1][0]}\n{mapping[0][1]} -> {mapping[1][1]}\n{mapping[0][2]} -> {mapping[1][2]}\n{mapping[0][3]} -> {mapping[1][3]}\n");
for (byte i = 0; i < 4; i++)
{
itemCorners[mapping[0][i]] = seekerCorners[mapping[1][i]];
}
// remove 2nd block, since it's now part of the 1st block
//Logging.MetaLog($"Removing {seekerVON}");
optVONs.RemoveAt(seeker);
if (seeker < item)
{
item--; // note: this will never become less than 0
}
isItemUpdated = true;
// regenerate info
//Logging.MetaLog($"Final corners {float3ArrToString(itemCorners)}");
updateVonFromCorners(itemCorners, ref itemVON);
itemCorners = calculateCorners(itemVON);
//Logging.MetaLog($"Merged block is {itemVON}");
}
else
{
seeker++;
}
}
}
if (isItemUpdated)
{
optVONs[item] = itemVON;
//Logging.MetaLog($"Optimised block is now {itemVON}");
}
item++;
}
else
{
item++;
}
}
#if DEBUG
timer.Stop();
Logging.MetaLog($"({id}) Completed best effort grouping of range in {timer.ElapsedMilliseconds}ms");
#endif
return optVONs.ToArray();
}
catch (Exception e)
{
Logging.MetaLog($"({id}) Exception occured...\n{e.ToString()}");
}
return blocksToOptimise;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float3[] calculateCorners(ProcessedVoxelObjectNotation von)
{
float3[] corners = new float3[8];
Quaternion rotation = Quaternion.Euler(von.rotation);
float3 rotatedScale = rotation * von.scale;
float3 trueCenter = von.position;
// generate corners
for (int i = 0; i < corners.Length; i++)
{
corners[i] = trueCenter + BLOCK_SIZE * (cornerMultiplicands1[i] * rotatedScale / 2);
}
return corners;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void updateVonFromCorners(float3[] corners, ref ProcessedVoxelObjectNotation von)
{
float3 newCenter = sumOfFloat3Arr(corners) / corners.Length;
float3 newPosition = newCenter;
Quaternion rot = Quaternion.Euler(von.rotation);
float3 rotatedScale = 2 * (corners[0] - newCenter) / BLOCK_SIZE;
von.scale = Quaternion.Inverse(rot) * rotatedScale;
von.position = newPosition;
//Logging.MetaLog($"Updated VON scale {von.scale} (absolute {rotatedScale})");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int[][] findMatchingCorners(float3[] corners1, float3[] corners2)
{
float3[][] faces1 = facesFromCorners(corners1);
float3[][] faces2 = facesFromCorners(corners2);
for (byte i = 0; i < faces1.Length; i++)
{
for (byte j = 0; j < faces2.Length; j++)
{
//Logging.MetaLog($"Checking faces {float3ArrToString(faces1[i])} and {float3ArrToString(faces2[j])}");
int[] match = matchFace(faces1[i], faces2[j]);
if (match.Length != 0)
{
//Logging.MetaLog($"Matched faces {float3ArrToString(faces1[i])} and {float3ArrToString(faces2[j])}");
// translate from face mapping to corner mapping
for (byte k = 0; k < match.Length; k++)
{
match[k] = oppositeFaceMappings[j][match[k]];
}
return new int[][] {cornerFaceMappings[i], match}; // {{itemCorners index}, {seekerCorners index}}
}
}
}
return new int[0][];
}
// this assumes the corners are in the order that calculateCorners outputs
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float3[][] facesFromCorners(float3[] corners)
{
return new float3[][]
{
new float3[] {corners[0], corners[1], corners[2], corners[3]}, // top
new float3[] {corners[2], corners[3], corners[4], corners[5]}, // left
new float3[] {corners[4], corners[5], corners[6], corners[7]}, // bottom
new float3[] {corners[6], corners[7], corners[0], corners[1]}, // right
new float3[] {corners[0], corners[2], corners[4], corners[6]}, // back
new float3[] {corners[1], corners[3], corners[5], corners[7]}, // front
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int[] matchFace(float3[] face1, float3[] face2)
{
int[] result = new int[4];
byte count = 0;
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
//Logging.MetaLog($"Comparing {face1[i]} and {face1[i]} ({Mathf.Abs(face1[i].x - face2[j].x)} & {Mathf.Abs(face1[i].y - face2[j].y)} & {Mathf.Abs(face1[i].z - face2[j].z)} vs {DELTA})");
// if (face1[i] == face2[j])
if (Mathf.Abs(face1[i].x - face2[j].x) < DELTA
&& Mathf.Abs(face1[i].y - face2[j].y) < DELTA
&& Mathf.Abs(face1[i].z - face2[j].z) < DELTA)
{
count++;
result[i] = j; // map corners to each other
break;
}
}
}
//Logging.MetaLog($"matched {count}/4");
if (count == 4)
{
return result;
}
return new int[0];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float3 sumOfFloat3Arr(float3[] arr)
{
float3 total = float3.zero;
for (int i = 0; i < arr.Length; i++)
{
total += arr[i];
}
return total;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool isOptimisableBlock(BlockIDs block)
{
if (optimisableBlockCache.ContainsKey((int) block))
{
return optimisableBlockCache[(int) block];
}
bool result = block.ToString().EndsWith("Cube", StringComparison.InvariantCultureIgnoreCase);
optimisableBlockCache[(int) block] = result;
return result;
}
private static void PostProcessSpecialBlocks(ref ProcessedVoxelObjectNotation[] pVONs, ref Block[] blocks)
{
// populate block attributes using metadata field from ProcessedVoxelObjectNotation
for (int i = 0; i < pVONs.Length; i++)
{
switch (pVONs[i].block)
{
case BlockIDs.TextBlock:
string[] textSplit = pVONs[i].metadata.Split('\t');
if (textSplit.Length > 1)
{
TextBlock tb = blocks[i].Specialise<TextBlock>();
tb.Text = textSplit[1];
if (textSplit.Length > 2)
{
tb.TextBlockId = textSplit[2];
}
}
break;
case BlockIDs.ConsoleBlock:
string[] cmdSplit = pVONs[i].metadata.Split('\t');
if (cmdSplit.Length > 1)
{
ConsoleBlock cb = blocks[i].Specialise<ConsoleBlock>();
cb.Command = cmdSplit[1];
if (cmdSplit.Length > 2)
{
cb.Arg1 = cmdSplit[2];
if (cmdSplit.Length > 3)
{
cb.Arg1 = cmdSplit[3];
if (cmdSplit.Length > 4)
{
cb.Arg1 = cmdSplit[4];
}
}
}
}
break;
case BlockIDs.DampedSpring:
string[] springSplit = pVONs[i].metadata.Split('\t');
if (springSplit.Length > 1 && float.TryParse(springSplit[1], out float stiffness))
{
DampedSpring d = blocks[i].Specialise<DampedSpring>();
d.Stiffness = stiffness;
if (springSplit.Length > 2 && float.TryParse(springSplit[2], out float damping))
{
d.Damping = damping;
}
}
break;
case BlockIDs.ServoAxle:
case BlockIDs.ServoHinge:
case BlockIDs.PneumaticAxle:
case BlockIDs.PneumaticHinge:
string[] servoSplit = pVONs[i].metadata.Split('\t');
if (servoSplit.Length > 1 && float.TryParse(servoSplit[1], out float minAngle))
{
Servo s = blocks[i].Specialise<Servo>();
s.MinimumAngle = minAngle;
if (servoSplit.Length > 2 && float.TryParse(servoSplit[2], out float maxAngle))
{
s.MaximumAngle = maxAngle;
if (servoSplit.Length > 3 && float.TryParse(servoSplit[3], out float maxForce))
{
s.MaximumForce = maxForce;
if (servoSplit.Length > 4 && bool.TryParse(servoSplit[4], out bool reverse))
{
s.Reverse = reverse;
}
}
}
}
break;
case BlockIDs.MotorM:
case BlockIDs.MotorS:
string[] motorSplit = pVONs[i].metadata.Split('\t');
if (motorSplit.Length > 1 && float.TryParse(motorSplit[1], out float topSpeed))
{
Motor m = blocks[i].Specialise<Motor>();
m.TopSpeed = topSpeed;
if (motorSplit.Length > 2 && float.TryParse(motorSplit[2], out float torque))
{
m.Torque = torque;
if (motorSplit.Length > 3 && bool.TryParse(motorSplit[3], out bool reverse))
{
m.Reverse = reverse;
}
}
}
break;
default: break; // do nothing
}
}
}
private static string float3ArrToString(float3[] arr)
{
string result = "[";
foreach (float3 f in arr)
{
result += f.ToString() + ", ";
}
return result.Substring(0, result.Length - 2) + "]";
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void tryOrCommandLogError(Action toTry)
{
try
{
toTry();
}
catch (Exception e)
{
#if DEBUG
Logging.CommandLogError("RIP Pixi\n" + e);
#else
Logging.CommandLogError("Pixi failed (reason: " + e.Message + ")");
#endif
Logging.LogWarning("Pixi Error\n" + e);
}
}
}
}

View file

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Unity.Mathematics;
using GamecraftModdingAPI.Blocks;
namespace Pixi.Common
{
public static class ConversionUtility
{
private static Dictionary<string, BlockIDs> blockEnumMap = null;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void loadBlockEnumMap()
{
blockEnumMap = new Dictionary<string, BlockIDs>();
foreach(BlockIDs e in Enum.GetValues(typeof(BlockIDs)))
{
blockEnumMap[e.ToString()] = e;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BlockIDs BlockIDsToEnum(string name)
{
if (blockEnumMap == null) loadBlockEnumMap();
if (blockEnumMap.ContainsKey(name)) return blockEnumMap[name];
return BlockIDs.Invalid;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float3 FloatArrayToFloat3(float[] vec)
{
if (vec.Length < 3) return float3.zero;
return new float3(vec[0], vec[1], vec[2]);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float[] Float3ToFloatArray(float3 vec)
{
return new float[3] {vec.x, vec.y, vec.z};
}
}
}

27
Pixi/Common/Importer.cs Normal file
View file

@ -0,0 +1,27 @@
using GamecraftModdingAPI;
namespace Pixi.Common
{
/// <summary>
/// Thing importer.
/// This imports the thing by converting it to a common block format that Pixi can understand.
/// </summary>
public interface Importer
{
int Priority { get; }
bool Optimisable { get; }
string Name { get; }
BlueprintProvider BlueprintProvider { get; }
bool Qualifies(string name);
BlockJsonInfo[] Import(string name);
void PreProcess(string name, ref ProcessedVoxelObjectNotation[] blocks);
void PostProcess(string name, ref Block[] blocks);
}
}

View file

@ -0,0 +1,40 @@
using Unity.Mathematics;
using GamecraftModdingAPI.Blocks;
namespace Pixi.Common
{
public struct ProcessedVoxelObjectNotation
{
public BlockIDs block;
public BlockColor color;
public bool blueprint;
public float3 position;
public float3 rotation;
public float3 scale;
public string metadata;
internal BlockJsonInfo VoxelObjectNotation()
{
return new BlockJsonInfo
{
name = block == BlockIDs.Invalid ? metadata.Split(' ')[0] : block.ToString(),
color = ColorSpaceUtility.UnquantizeToArray(color),
position = ConversionUtility.Float3ToFloatArray(position),
rotation = ConversionUtility.Float3ToFloatArray(rotation),
scale = ConversionUtility.Float3ToFloatArray(scale),
};
}
public override string ToString()
{
return $"ProcessedVoxelObjectNotation {{ block:{block}, color:{color.Color}-{color.Darkness}, blueprint:{blueprint}, position:{position}, rotation:{rotation}, scale:{scale}}} ({metadata})";
}
}
}

View file

@ -42,7 +42,7 @@ namespace Pixi.Common
public static BlockJsonInfo JsonObject(Block block, float[] origin = null)
{
if (origin == null) origin = origin_base;
return new BlockJsonInfo
BlockJsonInfo jsonInfo = new BlockJsonInfo
{
name = block.Type.ToString(),
position = new float[3] { block.Position.x - origin[0], block.Position.y - origin[1], block.Position.z - origin[2]},
@ -50,6 +50,37 @@ namespace Pixi.Common
color = ColorSpaceUtility.UnquantizeToArray(block.Color),
scale = new float[3] {block.Scale.x, block.Scale.y, block.Scale.z},
};
// custom stats for special blocks
switch (block.Type)
{
case BlockIDs.TextBlock:
TextBlock t = block.Specialise<TextBlock>();
jsonInfo.name += "\t" + t.Text + "\t" + t.TextBlockId;
break;
case BlockIDs.ConsoleBlock:
ConsoleBlock c = block.Specialise<ConsoleBlock>();
jsonInfo.name += "\t" + c.Command + "\t" + c.Arg1 + "\t" + c.Arg2 + "\t" + c.Arg3;
break;
case BlockIDs.DampedSpring:
DampedSpring d = block.Specialise<DampedSpring>();
jsonInfo.name += "\t" + d.Stiffness + "\t" + d.Damping;
break;
case BlockIDs.ServoAxle:
case BlockIDs.ServoHinge:
case BlockIDs.PneumaticAxle:
case BlockIDs.PneumaticHinge:
Servo s = block.Specialise<Servo>();
jsonInfo.name += "\t" + s.MinimumAngle + "\t" + s.MaximumAngle + "\t" + s.MaximumForce + "\t" +
s.Reverse;
break;
case BlockIDs.MotorM:
case BlockIDs.MotorS:
Motor m = block.Specialise<Motor>();
jsonInfo.name += "\t" + m.TopSpeed + "\t" + m.Torque + "\t" + m.Reverse;
break;
default: break;
}
return jsonInfo;
}
public static BlockIDs NameToEnum(BlockJsonInfo block)

View file

@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.IO;
using Unity.Mathematics;
using UnityEngine;
using GamecraftModdingAPI;
using GamecraftModdingAPI.Blocks;
using GamecraftModdingAPI.Players;
using GamecraftModdingAPI.Utility;
using Pixi.Common;
namespace Pixi.Images
{
public class ImageCanvasImporter : Importer
{
public static float3 Rotation = float3.zero;
public static uint Thiccness = 1;
public int Priority { get; } = 1;
public bool Optimisable { get; } = true;
public string Name { get; } = "ImageCanvas~Spell";
public BlueprintProvider BlueprintProvider { get; } = null;
public ImageCanvasImporter()
{
GamecraftModdingAPI.App.Client.EnterMenu += ColorSpaceUtility.LoadColorMenuEvent;
}
public bool Qualifies(string name)
{
//Logging.MetaLog($"Qualifies received name {name}");
return name.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase)
|| name.EndsWith(".jpg", StringComparison.InvariantCultureIgnoreCase);
}
public BlockJsonInfo[] Import(string name)
{
// Load image file and convert to Gamecraft blocks
Texture2D img = new Texture2D(64, 64);
// load file into texture
try
{
byte[] imgData = File.ReadAllBytes(name);
img.LoadImage(imgData);
}
catch (Exception e)
{
Logging.CommandLogError($"Failed to load picture data. Reason: {e.Message}");
Logging.MetaLog(e.Message + "\n" + e.StackTrace);
return new BlockJsonInfo[0];
}
//Logging.CommandLog($"Image size: {img.width}x{img.height}");
Player p = new Player(PlayerType.Local);
string pickedBlock = p.SelectedBlock == BlockIDs.Invalid ? BlockIDs.AluminiumCube.ToString() : p.SelectedBlock.ToString();
Quaternion imgRotation = Quaternion.Euler(Rotation);
BlockJsonInfo[] blocks = new BlockJsonInfo[img.width * img.height];
// convert the image to blocks
// optimisation occurs later
for (int x = 0; x < img.width; x++)
{
for (int y = 0; y < img.height; y++)
{
Color pixel = img.GetPixel(x, y);
float3 position = (imgRotation * (new float3((x * CommandRoot.BLOCK_SIZE),y * CommandRoot.BLOCK_SIZE,0)));
BlockJsonInfo qPixel = new BlockJsonInfo
{
name = pixel.a > 0.75 ? pickedBlock : BlockIDs.GlassCube.ToString(),
color = new float[] {pixel.r, pixel.g, pixel.b},
rotation = ConversionUtility.Float3ToFloatArray(Rotation),
position = ConversionUtility.Float3ToFloatArray(position),
scale = new float[] { 1, 1, Thiccness},
};
if (pixel.a < 0.5f) qPixel.name = BlockIDs.Invalid.ToString();
blocks[(x * img.height) + y] = qPixel;
}
}
return blocks;
}
public void PreProcess(string name, ref ProcessedVoxelObjectNotation[] blocks)
{
Player p = new Player(PlayerType.Local);
float3 pos = p.Position;
for (int i = 0; i < blocks.Length; i++)
{
blocks[i].position += pos;
}
}
public void PostProcess(string name, ref Block[] blocks) { }
}
}

View file

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Unity.Mathematics;
using UnityEngine;
using GamecraftModdingAPI;
using GamecraftModdingAPI.Blocks;
using GamecraftModdingAPI.Players;
using GamecraftModdingAPI.Utility;
using Pixi.Common;
namespace Pixi.Images
{
public class ImageCommandImporter : Importer
{
public int Priority { get; } = 0;
public bool Optimisable { get; } = false;
public string Name { get; } = "ImageConsole~Spell";
public BlueprintProvider BlueprintProvider { get; } = null;
private Dictionary<string, string> commandBlockContents = new Dictionary<string, string>();
public bool Qualifies(string name)
{
return name.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase)
|| name.EndsWith(".jpg", StringComparison.InvariantCultureIgnoreCase);
}
public BlockJsonInfo[] Import(string name)
{
// Thanks to Nullpersona for the idea
// Load image file and convert to Gamecraft blocks
Texture2D img = new Texture2D(64, 64);
// load file into texture
try
{
byte[] imgData = File.ReadAllBytes(name);
img.LoadImage(imgData);
}
catch (Exception e)
{
Logging.CommandLogError($"Failed to load picture data. Reason: {e.Message}");
Logging.MetaLog(e.Message + "\n" + e.StackTrace);
return new BlockJsonInfo[0];
}
string text = PixelUtility.TextureToString(img); // conversion
// save console's command
commandBlockContents[name] = text;
return new BlockJsonInfo[]
{
new BlockJsonInfo
{
color = new float[] {-1f, -1f, -1f},
name = "ConsoleBlock"
}
};
}
public void PreProcess(string name, ref ProcessedVoxelObjectNotation[] blocks)
{
Player p = new Player(PlayerType.Local);
float3 pos = p.Position;
for (int i = 0; i < blocks.Length; i++)
{
blocks[i].position += pos;
}
}
public void PostProcess(string name, ref Block[] blocks)
{
// populate console block
AsyncUtils.WaitForSubmission(); // just in case
ConsoleBlock cb = blocks[0].Specialise<ConsoleBlock>();
cb.Command = "ChangeTextBlockCommand";
cb.Arg1 = "TextBlockID";
cb.Arg2 = commandBlockContents[name];
cb.Arg3 = "";
commandBlockContents.Remove(name);
}
}
}

View file

@ -1,230 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Security.Cryptography;
using UnityEngine;
using Unity.Mathematics;
using Svelto.ECS.Experimental;
using Svelto.ECS;
using GamecraftModdingAPI.Blocks;
using GamecraftModdingAPI.Commands;
using GamecraftModdingAPI.Players;
using GamecraftModdingAPI.Utility;
using GamecraftModdingAPI;
using Pixi.Common;
namespace Pixi.Images
{
public static class ImageCommands
{
public const uint PIXEL_WARNING_THRESHOLD = 25_000;
// hash length to display after Pixi in text block id field
public const uint HASH_LENGTH = 6;
private static double blockSize = 0.2;
private static uint thiccness = 1;
public static float3 Rotation = float3.zero;
public static void CreateThiccCommand()
{
CommandBuilder.Builder()
.Name("PixiThicc")
.Description("Set the image thickness for Pixi2D. Use this if you'd like add depth to a 2D image after importing.")
.Action<int>((d) => {
if (d > 0)
{
thiccness = (uint)d;
}
else Logging.CommandLogError("");
})
.Build();
}
public static void CreateImportCommand()
{
CommandBuilder.Builder()
.Name("Pixi2D")
.Description("Converts an image to blocks. Larger images will freeze your game until conversion completes.")
.Action<string>(Pixelate2DFile)
.Build();
}
public static void CreateTextCommand()
{
CommandBuilder.Builder()
.Name("PixiText")
.Description("Converts an image to coloured text in a new text block. Larger images may cause save issues.")
.Action<string>(Pixelate2DFileToTextBlock)
.Build();
}
public static void CreateTextConsoleCommand()
{
CommandBuilder.Builder()
.Name("PixiConsole")
.Description("Converts an image to a ChangeTextBlockCommand in a new console block. The first parameter is the image filepath and the second parameter is the text block id. Larger images may cause save issues.")
.Action<string, string>(Pixelate2DFileToCommand)
.Build();
}
public static void Pixelate2DFile(string filepath)
{
// Load image file and convert to Gamecraft blocks
Texture2D img = new Texture2D(64, 64);
// load file into texture
try
{
byte[] imgData = File.ReadAllBytes(filepath);
img.LoadImage(imgData);
}
catch (Exception e)
{
Logging.CommandLogError($"Failed to load picture data. Reason: {e.Message}");
Logging.MetaLog(e.Message + "\n" + e.StackTrace);
return;
}
Logging.CommandLog($"Image size: {img.width}x{img.height}");
Player p = new Player(PlayerType.Local);
float3 position = p.Position;
BlockIDs pickedBlock = p.SelectedBlock == BlockIDs.Invalid ? BlockIDs.AluminiumCube : p.SelectedBlock;
uint blockCount = 0;
Quaternion imgRotation = Quaternion.Euler(Rotation);
position += (float3)(imgRotation * new float3(1f, (float)blockSize, 0f));
float3 basePosition = position;
Stopwatch timer = Stopwatch.StartNew();
// convert the image to blocks
// this groups same-colored pixels in the same column into a single block to reduce the block count
// any further pixel-grouping optimisations (eg 2D grouping) risk increasing conversion time higher than O(x*y)
for (int x = 0; x < img.width; x++)
{
BlockInfo qVoxel = new BlockInfo
{
block = BlockIDs.AbsoluteMathsBlock, // impossible canvas block
color = BlockColors.Default,
darkness = 10,
visible = false,
};
float3 scale = new float3(1, 1, thiccness);
//position.x += (float)(blockSize);
for (int y = 0; y < img.height; y++)
{
//position.y += (float)blockSize;
Color pixel = img.GetPixel(x, y);
BlockInfo qPixel = PixelUtility.QuantizePixel(pixel);
if (qPixel.darkness != qVoxel.darkness
|| qPixel.color != qVoxel.color
|| qPixel.visible != qVoxel.visible
|| qPixel.block != qVoxel.block)
{
if (y != 0)
{
if (qVoxel.visible)
{
position = basePosition + (float3)(imgRotation * (new float3(0,1,0) * (float)((y * blockSize + (y - scale.y) * blockSize) / 2) + new float3(1, 0, 0) * (float)(x * blockSize)));
BlockIDs blockType = qVoxel.block == BlockIDs.AluminiumCube ? pickedBlock : qVoxel.block;
Block.PlaceNew(blockType, position, rotation: Rotation,color: qVoxel.color, darkness: qVoxel.darkness, scale: scale);
blockCount++;
}
scale = new float3(1, 1, thiccness);
}
qVoxel = qPixel;
}
else
{
scale.y += 1;
}
}
if (qVoxel.visible)
{
position = basePosition + (float3)(imgRotation * (new float3(0, 1, 0) * (float)((img.height * blockSize + (img.height - scale.y) * blockSize) / 2) + new float3(1, 0, 0) * (float)(x * blockSize)));
BlockIDs blockType = qVoxel.block == BlockIDs.AluminiumCube ? pickedBlock : qVoxel.block;
Block.PlaceNew(blockType, position, rotation: Rotation, color: qVoxel.color, darkness: qVoxel.darkness, scale: scale);
blockCount++;
}
//position.y = zero_y;
}
timer.Stop();
Logging.CommandLog($"Placed {img.width}x{img.height} image beside you ({blockCount} blocks total, {blockCount * 100 / (img.width * img.height)}%)");
Logging.MetaLog($"Placed {blockCount} in {timer.ElapsedMilliseconds}ms (saved {(img.width * img.height) - blockCount} blocks -- {blockCount * 100 / (img.width * img.height)}% original size) for {filepath}");
}
public static void Pixelate2DFileToTextBlock(string filepath)
{
// Thanks to TheGreenGoblin for the idea (and the working Python implementation for reference)
// Load image file and convert to Gamecraft blocks
Texture2D img = new Texture2D(64, 64);
// load file into texture
try
{
byte[] imgData = File.ReadAllBytes(filepath);
img.LoadImage(imgData);
}
catch (Exception e)
{
Logging.CommandLogError($"Failed to load picture data. Reason: {e.Message}");
Logging.MetaLog(e.Message + "\n" + e.StackTrace);
return;
}
float3 position = new Player(PlayerType.Local).Position;
position.x += 1f;
position.y += (float)blockSize;
Stopwatch timer = Stopwatch.StartNew();
string text = PixelUtility.TextureToString(img);
TextBlock textBlock = TextBlock.PlaceNew(position, scale: new float3(Mathf.Ceil(img.width / 16), 1, Mathf.Ceil(img.height / 16)));
textBlock.Text = text;
byte[] textHash;
using (HashAlgorithm hasher = SHA256.Create())
textHash = hasher.ComputeHash(Encoding.UTF8.GetBytes(text));
string textId = "Pixi_";
// every byte converts to 2 hexadecimal characters so hash length needs to be halved
for (int i = 0; i < HASH_LENGTH/2 && i < textHash.Length; i++)
{
textId += textHash[i].ToString("X2");
}
textBlock.TextBlockId = textId;
timer.Stop();
Logging.CommandLog($"Placed {img.width}x{img.height} image in text block named {textId} beside you ({text.Length} characters)");
Logging.MetaLog($"Completed image text block {textId} synthesis in {timer.ElapsedMilliseconds}ms containing {text.Length} characters for {img.width*img.height} pixels");
}
public static void Pixelate2DFileToCommand(string filepath, string textBlockId)
{
// Thanks to Nullpersonan for the idea
// Load image file and convert to Gamecraft blocks
Texture2D img = new Texture2D(64, 64);
// load file into texture
try
{
byte[] imgData = File.ReadAllBytes(filepath);
img.LoadImage(imgData);
}
catch (Exception e)
{
Logging.CommandLogError($"Failed to load picture data. Reason: {e.Message}");
Logging.MetaLog(e.Message + "\n" + e.StackTrace);
return;
}
float3 position = new Player(PlayerType.Local).Position;
position.x += 1f;
position.y += (float)blockSize;
Stopwatch timer = Stopwatch.StartNew();
float zero_y = position.y;
string text = PixelUtility.TextureToString(img); // conversion
ConsoleBlock console = ConsoleBlock.PlaceNew(position);
// set console's command
console.Command = "ChangeTextBlockCommand";
console.Arg1 = textBlockId;
console.Arg2 = text;
console.Arg3 = "";
Logging.CommandLog($"Placed {img.width}x{img.height} image in console block beside you ({text.Length} characters)");
Logging.MetaLog($"Completed image console block {textBlockId} synthesis in {timer.ElapsedMilliseconds}ms containing {text.Length} characters for {img.width * img.height} pixels");
}
}
}

View file

@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Unity.Mathematics;
using UnityEngine;
using GamecraftModdingAPI;
using GamecraftModdingAPI.Blocks;
using GamecraftModdingAPI.Players;
using GamecraftModdingAPI.Utility;
using Pixi.Common;
namespace Pixi.Images
{
public class ImageTextBlockImporter : Importer
{
public int Priority { get; } = 0;
public bool Optimisable { get; } = false;
public string Name { get; } = "ImageText~Spell";
public BlueprintProvider BlueprintProvider { get; } = null;
private Dictionary<string, string[]> textBlockContents = new Dictionary<string, string[]>();
public bool Qualifies(string name)
{
return name.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase)
|| name.EndsWith(".jpg", StringComparison.InvariantCultureIgnoreCase);
}
public BlockJsonInfo[] Import(string name)
{
Texture2D img = new Texture2D(64, 64);
// load file into texture
try
{
byte[] imgData = File.ReadAllBytes(name);
img.LoadImage(imgData);
}
catch (Exception e)
{
Logging.CommandLogError($"Failed to load picture data. Reason: {e.Message}");
Logging.MetaLog(e.Message + "\n" + e.StackTrace);
return new BlockJsonInfo[0];
}
string text = PixelUtility.TextureToString(img);
// generate text block name
byte[] textHash;
using (HashAlgorithm hasher = SHA256.Create())
textHash = hasher.ComputeHash(Encoding.UTF8.GetBytes(text));
string textId = "Pixi_";
for (int i = 0; i < 2 && i < textHash.Length; i++)
{
textId += textHash[i].ToString("X2");
}
// save text block info for post-processing
textBlockContents[name] = new string[2] { textId, text};
return new BlockJsonInfo[1]
{
new BlockJsonInfo
{
color = new float[] {-1f, -1f, -1f},
name = "TextBlock",
position = new float[] {0f, 0f, 0f},
rotation = new float[] {0f, 0f, 0f},
scale = new float[] {Mathf.Ceil(img.width / 16f), 1f, Mathf.Ceil(img.height / 16f)}
}
};
}
public void PreProcess(string name, ref ProcessedVoxelObjectNotation[] blocks)
{
Player p = new Player(PlayerType.Local);
float3 pos = p.Position;
for (int i = 0; i < blocks.Length; i++)
{
blocks[i].position += pos;
}
}
public void PostProcess(string name, ref Block[] blocks)
{
// populate text block
AsyncUtils.WaitForSubmission(); // just in case
TextBlock tb = blocks[0].Specialise<TextBlock>();
tb.TextBlockId = textBlockContents[name][0];
tb.Text = textBlockContents[name][1];
textBlockContents.Remove(name);
}
}
}

View file

@ -14,42 +14,42 @@ namespace Pixi.Images
public static class PixelUtility
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BlockInfo QuantizePixel(Color pixel)
{
#if DEBUG
Logging.MetaLog($"Color (r:{pixel.r}, g:{pixel.g}, b:{pixel.b})");
#endif
BlockColor c = ColorSpaceUtility.QuantizeToBlockColor(pixel);
BlockInfo result = new BlockInfo
{
block = pixel.a > 0.75 ? BlockIDs.AluminiumCube : BlockIDs.GlassCube,
color = c.Color,
darkness = c.Darkness,
visible = pixel.a > 0.5f,
};
#if DEBUG
Logging.MetaLog($"Quantized {result.color} (b:{result.block} d:{result.darkness} v:{result.visible})");
#endif
return result;
}
public static string HexPixel(Color pixel)
public static string HexPixel(Color pixel)
{
return "#"+ColorUtility.ToHtmlStringRGBA(pixel);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Color PixelHex(string hex)
{
if (ColorUtility.TryParseHtmlString(hex, out Color result))
{
return result;
}
return default;
}
public static string TextureToString(Texture2D img)
{
StringBuilder imgString = new StringBuilder("<cspace=-0.13em><line-height=40%>");
bool lastPixelAssigned = false;
Color lastPixel = new Color();
for (int y = img.height-1; y >= 0 ; y--) // text origin is top right, but img origin is bottom right
{
for (int x = 0; x < img.width; x++)
{
Color pixel = img.GetPixel(x, y);
imgString.Append("<color=");
imgString.Append(HexPixel(pixel));
imgString.Append(">");
if (!lastPixelAssigned || lastPixel != pixel)
{
imgString.Append("<color=");
imgString.Append(HexPixel(pixel));
imgString.Append(">");
lastPixel = pixel;
if (!lastPixelAssigned)
{
lastPixelAssigned = true;
}
}
imgString.Append("\u25a0");
}
imgString.Append("<br>");

View file

@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Version>0.4.0</Version>
<Version>1.1.0</Version>
<Authors>NGnius</Authors>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://git.exmods.org/NGnius/Pixi</PackageProjectUrl>
@ -24,30 +24,6 @@
<HintPath>..\ref\Gamecraft_Data\Managed\IllusionPlugin.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\IllusionPlugin.dll</HintPath>
</Reference>
<Reference Include="JWT">
<HintPath>..\ref\Gamecraft_Data\Managed\JWT.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\JWT.dll</HintPath>
</Reference>
<Reference Include="Unity.Burst.Unsafe">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Burst.Unsafe.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Unity.Burst.Unsafe.dll</HintPath>
</Reference>
<Reference Include="Rewired_Core">
<HintPath>..\ref\Gamecraft_Data\Managed\Rewired_Core.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Rewired_Core.dll</HintPath>
</Reference>
<Reference Include="Rewired_Windows">
<HintPath>..\ref\Gamecraft_Data\Managed\Rewired_Windows.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Rewired_Windows.dll</HintPath>
</Reference>
<Reference Include="mscorlib">
<HintPath>..\ref\Gamecraft_Data\Managed\mscorlib.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\mscorlib.dll</HintPath>
</Reference>
<Reference Include="Accessibility">
<HintPath>..\ref\Gamecraft_Data\Managed\Accessibility.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Accessibility.dll</HintPath>
</Reference>
<Reference Include="Analytics">
<HintPath>..\ref\Gamecraft_Data\Managed\Analytics.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Analytics.dll</HintPath>
@ -64,22 +40,26 @@
<HintPath>..\ref\Gamecraft_Data\Managed\Authentication.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Authentication.dll</HintPath>
</Reference>
<Reference Include="BlockEntityFactory">
<HintPath>..\ref\Gamecraft_Data\Managed\BlockEntityFactory.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\BlockEntityFactory.dll</HintPath>
</Reference>
<Reference Include="Blocks.HUDFeedbackBlocks">
<HintPath>..\ref\Gamecraft_Data\Managed\Blocks.HUDFeedbackBlocks.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Blocks.HUDFeedbackBlocks.dll</HintPath>
</Reference>
<Reference Include="ClusterToWireConversion.Mock">
<HintPath>..\ref\Gamecraft_Data\Managed\ClusterToWireConversion.Mock.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\ClusterToWireConversion.Mock.dll</HintPath>
</Reference>
<Reference Include="CommandLine">
<HintPath>..\ref\Gamecraft_Data\Managed\CommandLine.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\CommandLine.dll</HintPath>
</Reference>
<Reference Include="CommandLineCompositionRoot">
<HintPath>..\ref\Gamecraft_Data\Managed\CommandLineCompositionRoot.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\CommandLineCompositionRoot.dll</HintPath>
</Reference>
<Reference Include="ConsoleBlockComposotionRoot">
<HintPath>..\ref\Gamecraft_Data\Managed\ConsoleBlockComposotionRoot.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\ConsoleBlockComposotionRoot.dll</HintPath>
</Reference>
<Reference Include="ConsoleCommand">
<HintPath>..\ref\Gamecraft_Data\Managed\ConsoleCommand.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\ConsoleCommand.dll</HintPath>
</Reference>
<Reference Include="DataLoader">
<HintPath>..\ref\Gamecraft_Data\Managed\DataLoader.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\DataLoader.dll</HintPath>
@ -88,14 +68,14 @@
<HintPath>..\ref\Gamecraft_Data\Managed\DDNA.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\DDNA.dll</HintPath>
</Reference>
<Reference Include="FMODUnity">
<HintPath>..\ref\Gamecraft_Data\Managed\FMODUnity.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\FMODUnity.dll</HintPath>
</Reference>
<Reference Include="Facepunch.Steamworks.Win64">
<HintPath>..\ref\Gamecraft_Data\Managed\Facepunch.Steamworks.Win64.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Facepunch.Steamworks.Win64.dll</HintPath>
</Reference>
<Reference Include="FMOD">
<HintPath>..\ref\Gamecraft_Data\Managed\FMOD.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\FMOD.dll</HintPath>
</Reference>
<Reference Include="FullGame">
<HintPath>..\ref\Gamecraft_Data\Managed\FullGame.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\FullGame.dll</HintPath>
@ -104,6 +84,14 @@
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.AudioBlocks.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.AudioBlocks.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.BlockEntityFactory">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.BlockEntityFactory.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.BlockEntityFactory.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.BlockGroups">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.BlockGroups.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.BlockGroups.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.Blocks.ConsoleBlock">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Blocks.ConsoleBlock.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Blocks.ConsoleBlock.dll</HintPath>
@ -112,10 +100,18 @@
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Blocks.DamagingSurfaceBlock.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Blocks.DamagingSurfaceBlock.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.Blocks.DestructionBlocks">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Blocks.DestructionBlocks.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Blocks.DestructionBlocks.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.Blocks.GenericPhysicsBlocks">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Blocks.GenericPhysicsBlocks.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Blocks.GenericPhysicsBlocks.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.Blocks.LightBlock">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Blocks.LightBlock.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Blocks.LightBlock.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.Blocks.LogicBlock">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Blocks.LogicBlock.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Blocks.LogicBlock.dll</HintPath>
@ -124,10 +120,18 @@
<HintPath>..\ref\Gamecraft_Data\Managed\GameCraft.Blocks.ProjectileBlock.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\GameCraft.Blocks.ProjectileBlock.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.Blocks.TextBlock.CompositionRoot">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Blocks.TextBlock.CompositionRoot.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Blocks.TextBlock.CompositionRoot.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.Blocks.TimerBlock">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Blocks.TimerBlock.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Blocks.TimerBlock.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.BlocksEntityDescriptors">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.BlocksEntityDescriptors.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.BlocksEntityDescriptors.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.CharacterVulnerability">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.CharacterVulnerability.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.CharacterVulnerability.dll</HintPath>
@ -136,22 +140,94 @@
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.CharacterVulnerabilityGui.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.CharacterVulnerabilityGui.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.ColourPalette">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.ColourPalette.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.ColourPalette.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.Damage">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Damage.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Damage.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.Effects">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Effects.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Effects.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.ExplosionFragments">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.ExplosionFragments.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.ExplosionFragments.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.GraphicsSettings">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GraphicsSettings.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.GraphicsSettings.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.GUI.BlueprintInventory">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.BlueprintInventory.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.BlueprintInventory.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.GUI.BlueprintInventoryMock">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.BlueprintInventoryMock.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.BlueprintInventoryMock.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.GUI.Blueprints">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.Blueprints.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.Blueprints.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.GUI.BlueprintSets">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.BlueprintSets.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.BlueprintSets.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.GUI.ConsoleBlock">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.ConsoleBlock.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.ConsoleBlock.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.GUI.GameOptionsScreen">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.GameOptionsScreen.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.GameOptionsScreen.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.GUI.GraphicsScreen">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.GraphicsScreen.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.GraphicsScreen.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.GUI.Hotbar.Blocks">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.Hotbar.Blocks.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.Hotbar.Blocks.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.GUI.Hotbar.Colours">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.Hotbar.Colours.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.Hotbar.Colours.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.GUI.HUDFeedbackBlocks">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.HUDFeedbackBlocks.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.HUDFeedbackBlocks.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.GUI.ModeBar">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.ModeBar.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.ModeBar.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.GUI.OptionsScreen">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.OptionsScreen.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.OptionsScreen.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.GUI.TabsBar.Blocks">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.TabsBar.Blocks.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.TabsBar.Blocks.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.GUI.TabsBar.Blueprints">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.TabsBar.Blueprints.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.TabsBar.Blueprints.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.GUI.TabsBar.Colours">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.TabsBar.Colours.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.TabsBar.Colours.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.GUI.TabsBar.Common">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.TabsBar.Common.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.TabsBar.Common.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.GUI.TimeModeClock">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.TimeModeClock.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.TimeModeClock.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.GUI.Tweaks">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.Tweaks.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.Tweaks.dll</HintPath>
@ -168,14 +244,50 @@
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.WorldSpaceGuis.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.WorldSpaceGuis.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.GUIs.Hotbar.BlueprintsHotbar">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GUIs.Hotbar.BlueprintsHotbar.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.GUIs.Hotbar.BlueprintsHotbar.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.InventoryTimeRunning">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.InventoryTimeRunning.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.InventoryTimeRunning.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.JointBlocks">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.JointBlocks.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.JointBlocks.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.Music">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Music.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Music.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.NetStrings">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.NetStrings.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.NetStrings.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.PerformanceWarnings">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.PerformanceWarnings.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.PerformanceWarnings.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.PickupBlck">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.PickupBlck.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.PickupBlck.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.PickupsCommon">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.PickupsCommon.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.PickupsCommon.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.PopupMessage">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.PopupMessage.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.PopupMessage.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.Projectiles">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Projectiles.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Projectiles.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.Serialization">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Serialization.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Serialization.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.Tweaks">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Tweaks.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Tweaks.dll</HintPath>
@ -184,6 +296,10 @@
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Tweaks.Mockup.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Tweaks.Mockup.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.VisualEffects.Decals">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.VisualEffects.Decals.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.VisualEffects.Decals.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.VisualEffects">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.VisualEffects.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.VisualEffects.dll</HintPath>
@ -192,10 +308,6 @@
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Wires.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Wires.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.Wires.Input">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Wires.Input.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Wires.Input.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.Wires.Mockup">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Wires.Mockup.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Wires.Mockup.dll</HintPath>
@ -204,6 +316,10 @@
<HintPath>..\ref\Gamecraft_Data\Managed\GameState.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\GameState.dll</HintPath>
</Reference>
<Reference Include="GhostShark.Outline">
<HintPath>..\ref\Gamecraft_Data\Managed\GhostShark.Outline.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\GhostShark.Outline.dll</HintPath>
</Reference>
<Reference Include="GPUInstancer">
<HintPath>..\ref\Gamecraft_Data\Managed\GPUInstancer.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\GPUInstancer.dll</HintPath>
@ -228,18 +344,6 @@
<HintPath>..\ref\Gamecraft_Data\Managed\MultiplayerTest.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\MultiplayerTest.dll</HintPath>
</Reference>
<Reference Include="netstandard">
<HintPath>..\ref\Gamecraft_Data\Managed\netstandard.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\netstandard.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>..\ref\Gamecraft_Data\Managed\Newtonsoft.Json.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="Novell.Directory.Ldap">
<HintPath>..\ref\Gamecraft_Data\Managed\Novell.Directory.Ldap.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Novell.Directory.Ldap.dll</HintPath>
</Reference>
<Reference Include="RCX.ScreenshotTaker">
<HintPath>..\ref\Gamecraft_Data\Managed\RCX.ScreenshotTaker.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RCX.ScreenshotTaker.dll</HintPath>
@ -308,6 +412,22 @@
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.GUI.Hotbar">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.Hotbar.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.Hotbar.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.GUI.Inventory.BlocksInventory">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.Inventory.BlocksInventory.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.Inventory.BlocksInventory.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.GUI.Inventory.ColourInventory">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.Inventory.ColourInventory.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.Inventory.ColourInventory.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.GUI.Inventory">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.Inventory.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.Inventory.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.GUI.RemoveBlock">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.RemoveBlock.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.RemoveBlock.dll</HintPath>
@ -316,6 +436,10 @@
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.ScaleGhost.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.ScaleGhost.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.GUI.TabsBar">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.TabsBar.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.TabsBar.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.GUIs.WorkshopPrefabs">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GUIs.WorkshopPrefabs.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.GUIs.WorkshopPrefabs.dll</HintPath>
@ -344,10 +468,18 @@
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Multiplayer.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.Multiplayer.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Multiplayer.GUI">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Multiplayer.GUI.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.Multiplayer.GUI.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Multiplayer.NetworkEntityStream">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Multiplayer.NetworkEntityStream.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.Multiplayer.NetworkEntityStream.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Multiplayer.Serializers">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Multiplayer.Serializers.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.Multiplayer.Serializers.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.MultiplayerInput">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.MultiplayerInput.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.MultiplayerInput.dll</HintPath>
@ -392,10 +524,6 @@
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.SaveGameDialog.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.SaveGameDialog.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Serializers">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Serializers.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.Serializers.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Services">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Services.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.Services.dll</HintPath>
@ -416,17 +544,25 @@
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX_TextBlock.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX_TextBlock.dll</HintPath>
</Reference>
<Reference Include="RobocratX.SimulationCompositionRoot">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocratX.SimulationCompositionRoot.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocratX.SimulationCompositionRoot.dll</HintPath>
<Reference Include="RobocratX.SimulationMockCompositionRoot">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocratX.SimulationMockCompositionRoot.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocratX.SimulationMockCompositionRoot.dll</HintPath>
</Reference>
<Reference Include="SpawningPointCompositionRoot">
<HintPath>..\ref\Gamecraft_Data\Managed\SpawningPointCompositionRoot.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\SpawningPointCompositionRoot.dll</HintPath>
</Reference>
<Reference Include="SpecializedDescriptors">
<HintPath>..\ref\Gamecraft_Data\Managed\SpecializedDescriptors.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\SpecializedDescriptors.dll</HintPath>
</Reference>
<Reference Include="StringFormatter">
<HintPath>..\ref\Gamecraft_Data\Managed\StringFormatter.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\StringFormatter.dll</HintPath>
</Reference>
<Reference Include="Svelto.Common_3">
<HintPath>..\ref\Gamecraft_Data\Managed\Svelto.Common_3.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Svelto.Common_3.dll</HintPath>
<Reference Include="Svelto.Common">
<HintPath>..\ref\Gamecraft_Data\Managed\Svelto.Common.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Svelto.Common.dll</HintPath>
</Reference>
<Reference Include="Svelto.ECS">
<HintPath>..\ref\Gamecraft_Data\Managed\Svelto.ECS.dll</HintPath>
@ -440,6 +576,10 @@
<HintPath>..\ref\Gamecraft_Data\Managed\Svelto.Tasks.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Svelto.Tasks.dll</HintPath>
</Reference>
<Reference Include="UltimateDecals">
<HintPath>..\ref\Gamecraft_Data\Managed\UltimateDecals.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\UltimateDecals.dll</HintPath>
</Reference>
<Reference Include="Unity.Addressables">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Addressables.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Unity.Addressables.dll</HintPath>
@ -456,6 +596,10 @@
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Collections.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Unity.Collections.dll</HintPath>
</Reference>
<Reference Include="Unity.Collections.LowLevel.ILSupport">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Collections.LowLevel.ILSupport.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Unity.Collections.LowLevel.ILSupport.dll</HintPath>
</Reference>
<Reference Include="Unity.Deformations">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Deformations.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Unity.Deformations.dll</HintPath>
@ -468,6 +612,10 @@
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Entities.Hybrid.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Unity.Entities.Hybrid.dll</HintPath>
</Reference>
<Reference Include="Unity.InternalAPIEngineBridge.012">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.InternalAPIEngineBridge.012.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Unity.InternalAPIEngineBridge.012.dll</HintPath>
</Reference>
<Reference Include="Unity.Jobs">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Jobs.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Unity.Jobs.dll</HintPath>
@ -484,6 +632,10 @@
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Mathematics.Extensions.Hybrid.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Unity.Mathematics.Extensions.Hybrid.dll</HintPath>
</Reference>
<Reference Include="Unity.MemoryProfiler">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.MemoryProfiler.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Unity.MemoryProfiler.dll</HintPath>
</Reference>
<Reference Include="Unity.Physics">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Physics.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Unity.Physics.dll</HintPath>
@ -496,10 +648,6 @@
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Platforms.Common.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Unity.Platforms.Common.dll</HintPath>
</Reference>
<Reference Include="Unity.Postprocessing.Runtime">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Postprocessing.Runtime.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Unity.Postprocessing.Runtime.dll</HintPath>
</Reference>
<Reference Include="Unity.Properties">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Properties.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Unity.Properties.dll</HintPath>
@ -540,9 +688,9 @@
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.ResourceManager.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Unity.ResourceManager.dll</HintPath>
</Reference>
<Reference Include="Unity.Scenes.Hybrid">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Scenes.Hybrid.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Unity.Scenes.Hybrid.dll</HintPath>
<Reference Include="Unity.Scenes">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Scenes.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Unity.Scenes.dll</HintPath>
</Reference>
<Reference Include="Unity.ScriptableBuildPipeline">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.ScriptableBuildPipeline.dll</HintPath>
@ -568,6 +716,66 @@
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Transforms.Hybrid.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Unity.Transforms.Hybrid.dll</HintPath>
</Reference>
<Reference Include="Unity.VisualEffectGraph.Runtime">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.VisualEffectGraph.Runtime.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Unity.VisualEffectGraph.Runtime.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UI">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.UI.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\UnityEngine.UI.dll</HintPath>
</Reference>
<Reference Include="uREPL">
<HintPath>..\ref\Gamecraft_Data\Managed\uREPL.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\uREPL.dll</HintPath>
</Reference>
<Reference Include="VisualProfiler">
<HintPath>..\ref\Gamecraft_Data\Managed\VisualProfiler.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\VisualProfiler.dll</HintPath>
</Reference>
<Reference Include="Accessibility">
<HintPath>..\ref\Gamecraft_Data\Managed\Accessibility.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Accessibility.dll</HintPath>
</Reference>
<Reference Include="Facepunch.Steamworks.Win64">
<HintPath>..\ref\Gamecraft_Data\Managed\Facepunch.Steamworks.Win64.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Facepunch.Steamworks.Win64.dll</HintPath>
</Reference>
<Reference Include="JWT">
<HintPath>..\ref\Gamecraft_Data\Managed\JWT.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\JWT.dll</HintPath>
</Reference>
<Reference Include="mscorlib">
<HintPath>..\ref\Gamecraft_Data\Managed\mscorlib.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\mscorlib.dll</HintPath>
</Reference>
<Reference Include="netstandard">
<HintPath>..\ref\Gamecraft_Data\Managed\netstandard.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\netstandard.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>..\ref\Gamecraft_Data\Managed\Newtonsoft.Json.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="Novell.Directory.Ldap">
<HintPath>..\ref\Gamecraft_Data\Managed\Novell.Directory.Ldap.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Novell.Directory.Ldap.dll</HintPath>
</Reference>
<Reference Include="Rewired_Core">
<HintPath>..\ref\Gamecraft_Data\Managed\Rewired_Core.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Rewired_Core.dll</HintPath>
</Reference>
<Reference Include="Rewired_Windows">
<HintPath>..\ref\Gamecraft_Data\Managed\Rewired_Windows.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Rewired_Windows.dll</HintPath>
</Reference>
<Reference Include="Unity.Burst.Unsafe">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Burst.Unsafe.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Unity.Burst.Unsafe.dll</HintPath>
</Reference>
<Reference Include="Unity.Collections.LowLevel.ILSupport">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Collections.LowLevel.ILSupport.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Unity.Collections.LowLevel.ILSupport.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.AccessibilityModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.AccessibilityModule.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\UnityEngine.AccessibilityModule.dll</HintPath>
@ -736,14 +944,14 @@
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.TLSModule.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\UnityEngine.TLSModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UI">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.UI.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\UnityEngine.UI.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UIElementsModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.UIElementsModule.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\UnityEngine.UIElementsModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UIElementsNativeModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.UIElementsNativeModule.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\UnityEngine.UIElementsNativeModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UIModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.UIModule.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\UnityEngine.UIModule.dll</HintPath>
@ -800,6 +1008,10 @@
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.VideoModule.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\UnityEngine.VideoModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.VirtualTexturingModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.VirtualTexturingModule.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\UnityEngine.VirtualTexturingModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.VRModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.VRModule.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\UnityEngine.VRModule.dll</HintPath>
@ -812,15 +1024,6 @@
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.XRModule.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\UnityEngine.XRModule.dll</HintPath>
</Reference>
<Reference Include="uREPL">
<HintPath>..\ref\Gamecraft_Data\Managed\uREPL.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\uREPL.dll</HintPath>
</Reference>
<Reference Include="VisualProfiler">
<HintPath>..\ref\Gamecraft_Data\Managed\VisualProfiler.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\VisualProfiler.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<!--End Dependencies-->
<ItemGroup>
@ -831,4 +1034,13 @@
<EmbeddedResource Include="cubes-id.json" />
<EmbeddedResource Include="blueprints.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ILRepack" Version="2.0.18" />
<PackageReference Include="Melanchall.DryWetMidi" Version="5.1.1" />
</ItemGroup>
<Target Name="StaticLinkMergeAfterBuild" AfterTargets="Build">
<Exec Command="$(PkgILRepack)\tools\ILRepack.exe /ndebug /out:bin\$(Configuration)\net472\Pixi.dll bin\$(Configuration)\net472\Pixi.dll bin\$(Configuration)\net472\Melanchall.DryWetMidi.dll" />
</Target>
</Project>

View file

@ -8,22 +8,23 @@ using Unity.Mathematics; // float3
using IllusionPlugin;
using GamecraftModdingAPI.Utility;
using Pixi.Audio;
using Pixi.Common;
using Pixi.Images;
using Pixi.Robots;
namespace Pixi
{
public class PixiPlugin : IPlugin // the Illusion Plugin Architecture (IPA) will ignore classes that don't implement IPlugin'
public class PixiPlugin : IEnhancedPlugin // the Illusion Plugin Architecture (IPA) will ignore classes that don't implement IPlugin'
{
public string Name { get; } = Assembly.GetExecutingAssembly().GetName().Name; // Pixi
public override string Name { get; } = Assembly.GetExecutingAssembly().GetName().Name; // Pixi
// To change the name, change the project's name
public string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString(); // 0.1.0 (for now)
public override string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString();
// To change the version, change <Version>#.#.#</Version> in Pixi.csproj
// called when Gamecraft shuts down
public void OnApplicationQuit()
public override void OnApplicationQuit()
{
// Shutdown this mod
Logging.LogDebug($"{Name} has shutdown");
@ -33,37 +34,32 @@ namespace Pixi
}
// called when Gamecraft starts up
public void OnApplicationStart()
public override void OnApplicationStart()
{
// Initialize the Gamecraft modding API first
GamecraftModdingAPI.Main.Init();
// check out the modding API docs here: https://mod.exmods.org/
// Initialize Pixi mod
// 2D image functionality
ImageCommands.CreateThiccCommand();
ImageCommands.CreateImportCommand();
ImageCommands.CreateTextCommand();
ImageCommands.CreateTextConsoleCommand();
CommandRoot root = new CommandRoot();
// 2D Image Functionality
root.Inject(new ImageCanvasImporter());
root.Inject(new ImageTextBlockImporter());
root.Inject(new ImageCommandImporter());
// Robot functionality
RobotCommands.CreateRobotCRFCommand();
RobotCommands.CreateRobotFileCommand();
var robot = new RobotInternetImporter();
root.Inject(robot);
//RobotCommands.CreateRobotCRFCommand();
//RobotCommands.CreateRobotFileCommand();
#if DEBUG
// Development functionality
RobotCommands.CreatePartDumpCommand();
((RobotBlueprintProvider) robot.BlueprintProvider).AddDebugCommands();
root.Inject(new TestImporter());
#endif
Logging.LogDebug($"{Name} has started up");
// Audio functionality
root.Inject(new MidiImporter());
root.Inject(new AudioFakeImporter());
}
// unused methods
public void OnFixedUpdate() { } // called once per physics update
public void OnLevelWasInitialized(int level) { } // called after a level is initialized
public void OnLevelWasLoaded(int level) { } // called after a level is loaded
public void OnUpdate() { } // called once per rendered frame (frame update)
}
}

View file

@ -75,7 +75,7 @@ namespace Pixi.Robots
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, cubeId = cubeId };
CubeInfo result = new CubeInfo {visible = true, cubeId = cubeId, scale = new float3(1, 1, 1)};
TranslateBlockColour(colour, ref result);
TranslateBlockPosition(x, y, z, ref result);
TranslateBlockRotation(rotation, ref result);
@ -188,18 +188,37 @@ namespace Pixi.Robots
{
// I hope these colours are accurate, I just guessed
// TODO colour accuracy (lol that won't ever happen)
#if DEBUG
Logging.MetaLog($"Cube colour {colour}");
#endif
BlockColor c = ColorSpaceUtility.QuantizeToBlockColor(colour);
result.color = c.Color;
result.darkness = c.Darkness;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void TranslateBlockId(uint cubeId, ref CubeInfo result)
public static string CubeIdDescription(uint cubeId)
{
if (map == null)
if (map == null)
{
StreamReader cubemap = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("Pixi.cubes-id.json"));
map = JsonConvert.DeserializeObject<Dictionary<uint, string>>(cubemap.ReadToEnd());
map = JsonConvert.DeserializeObject<Dictionary<uint, string>>(cubemap.ReadToEnd());
}
if (!map.ContainsKey(cubeId))
{
return "Unknown cube #" + cubeId.ToString();
//result.rotation = float3.zero;
}
return map[cubeId];
}
[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))
@ -212,81 +231,34 @@ namespace Pixi.Robots
#endif
}
string cubeName = map[cubeId];
string gcName = cubeName.Contains("glass") || cubeName.Contains("windshield")
? "Glass"
: "Aluminium";
if (cubeName.Contains("round"))
gcName += "Rounded";
if (cubeName.Contains("cube"))
{
result.block = BlockIDs.AluminiumCube;
result.rotation = float3.zero;
}
gcName += "Cube";
else if (cubeName.Contains("prism") || cubeName.Contains("edge"))
{
if (cubeName.Contains("round"))
{
if (cubeName.Contains("glass") || cubeName.Contains("windshield"))
{
result.block = BlockIDs.GlassRoundedSlope;
} else
result.block = BlockIDs.AluminiumRoundedSlope;
}
else
{
if (cubeName.Contains("glass") || cubeName.Contains("windshield"))
{
result.block = BlockIDs.GlassSlope;
} else
result.block = BlockIDs.AluminiumSlope;
}
}
gcName += "Slope";
else if (cubeName.Contains("inner"))
{
if (cubeName.Contains("round"))
{
if (cubeName.Contains("glass") || cubeName.Contains("windshield"))
{
result.block = BlockIDs.GlassRoundedSlicedCube;
} else
result.block = BlockIDs.AluminiumRoundedSlicedCube;
}
else
{
if (cubeName.Contains("glass") || cubeName.Contains("windshield"))
{
result.block = BlockIDs.GlassSlicedCube;
} else
result.block = BlockIDs.AluminiumSlicedCube;
}
}
gcName += "SlicedCube";
else if (cubeName.Contains("tetra") || cubeName.Contains("corner"))
{
if (cubeName.Contains("round"))
{
if (cubeName.Contains("glass") || cubeName.Contains("windshield"))
{
result.block = BlockIDs.GlassRoundedCorner;
} else
result.block = BlockIDs.AluminiumRoundedCorner;
}
else
{
if (cubeName.Contains("glass") || cubeName.Contains("windshield"))
{
result.block = BlockIDs.GlassCorner;
} else
result.block = BlockIDs.AluminiumCorner;
}
}
gcName += "Corner";
else if (cubeName.Contains("pyramid"))
{
result.block = BlockIDs.AluminiumPyramidSegment;
}
gcName += "PyramidSegment";
else if (cubeName.Contains("cone"))
{
result.block = BlockIDs.AluminiumConeSegment;
}
gcName += "ConeSegment";
else
{
result.block = BlockIDs.TextBlock;
result.name = cubeName;
return;
}
BlockIDs id = VoxelObjectNotationUtility.NameToEnum(gcName);
result.block = id;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -311,12 +283,12 @@ namespace Pixi.Robots
Logging.LogWarning($"Found empty blueprint for {cube.name} (id:{cube.cubeId}), is the blueprint correct?");
return new Block[0];
}
float3 defaultCorrectionVec = new float3((float)(0), (float)(RobotCommands.blockSize), (float)(0));
float3 defaultCorrectionVec = new float3((float)(0), (float)(CommandRoot.BLOCK_SIZE), (float)(0));
float3 baseRot = new float3(blueprint[0].rotation[0], blueprint[0].rotation[1], blueprint[0].rotation[2]);
float3 baseScale = new float3(blueprint[0].scale[0], blueprint[0].scale[1], blueprint[0].scale[2]);
Block[] placedBlocks = new Block[blueprint.Length];
bool isBaseScaled = !(blueprint[0].scale[1] > 0f && blueprint[0].scale[1] < 2f);
float3 correctionVec = isBaseScaled ? (float3)(Quaternion.Euler(baseRot) * baseScale / 2) * (float)-RobotCommands.blockSize : -defaultCorrectionVec;
float3 correctionVec = isBaseScaled ? (float3)(Quaternion.Euler(baseRot) * baseScale / 2) * (float)-CommandRoot.BLOCK_SIZE : -defaultCorrectionVec;
// FIXME scaled base blocks cause the blueprint to be placed in the wrong location (this also could be caused by a bug in DumpVON command)
if (isBaseScaled)
{

View file

@ -0,0 +1,140 @@
using System;
using System.Collections.Generic;
using System.IO;
using Svelto.DataStructures;
using Unity.Mathematics;
using UnityEngine;
using GamecraftModdingAPI.Blocks;
using GamecraftModdingAPI.Commands;
using GamecraftModdingAPI.Utility;
using Newtonsoft.Json;
using Pixi.Common;
namespace Pixi.Robots
{
public class RobotBlueprintProvider : BlueprintProvider
{
public string Name { get; } = "RobotBlueprintProvider";
private Dictionary<string, BlockJsonInfo[]> botprints = null;
private RobotInternetImporter parent;
public RobotBlueprintProvider(RobotInternetImporter rii)
{
parent = rii;
}
public BlockJsonInfo[] Blueprint(string name, BlockJsonInfo root)
{
if (botprints == null)
{
botprints = BlueprintUtility.ParseBlueprintResource("Pixi.blueprints.json");
}
if (!botprints.ContainsKey(root.name) || RobotInternetImporter.CubeSize != 3)
{
BlockJsonInfo copy = root;
copy.name = $"TextBlock\t{root.name} ({CubeUtility.CubeIdDescription(uint.Parse(root.name))})\tPixi";
return new BlockJsonInfo[1] {copy};
}
BlockJsonInfo[] blueprint = botprints[root.name];
BlockJsonInfo[] adjustedBlueprint = new BlockJsonInfo[blueprint.Length];
Quaternion cubeQuaternion = Quaternion.Euler(ConversionUtility.FloatArrayToFloat3(root.rotation));
if (blueprint.Length == 0)
{
Logging.LogWarning($"Found empty blueprint for {root.name} (during '{name}'), is the blueprint correct?");
return new BlockJsonInfo[0];
}
// move blocks to correct position & rotation
float3 defaultCorrectionVec = new float3((float)(0), (float)(CommandRoot.BLOCK_SIZE), (float)(0));
float3 baseRot = new float3(blueprint[0].rotation[0], blueprint[0].rotation[1], blueprint[0].rotation[2]);
float3 baseScale = new float3(blueprint[0].scale[0], blueprint[0].scale[1], blueprint[0].scale[2]);
//Block[] placedBlocks = new Block[blueprint.Length];
bool isBaseScaled = !(blueprint[0].scale[1] > 0f && blueprint[0].scale[1] < 2f);
float3 correctionVec = isBaseScaled ? (float3)(Quaternion.Euler(baseRot) * baseScale / 2) * (float)-CommandRoot.BLOCK_SIZE : -defaultCorrectionVec;
// FIXME scaled base blocks cause the blueprint to be placed in the wrong location (this also could be caused by a bug in DumpVON command)
if (isBaseScaled)
{
Logging.LogWarning($"Found blueprint with scaled base block for {root.name} (during '{name}'), this is not currently supported");
}
float3 rootPos = ConversionUtility.FloatArrayToFloat3(root.position);
for (int i = 0; i < blueprint.Length; i++)
{
BlockColor blueprintBlockColor = ColorSpaceUtility.QuantizeToBlockColor(blueprint[i].color);
float[] physicalColor = blueprintBlockColor.Color == BlockColors.White && blueprintBlockColor.Darkness == 0 ? root.color : blueprint[i].color;
float3 bluePos = ConversionUtility.FloatArrayToFloat3(blueprint[i].position);
float3 blueScale = ConversionUtility.FloatArrayToFloat3(blueprint[i].scale);
float3 blueRot = ConversionUtility.FloatArrayToFloat3(blueprint[i].rotation);
float3 physicalLocation = (float3)(cubeQuaternion * bluePos) + rootPos;// + (blueprintSizeRotated / 2);
//physicalLocation.x += blueprintSize.x / 2;
physicalLocation += (float3)(cubeQuaternion * (correctionVec));
//physicalLocation.y -= (float)(RobotCommands.blockSize * scale / 2);
//float3 physicalScale = (float3)(cubeQuaternion * blueScale); // this actually over-rotates when combined with rotation
float3 physicalScale = blueScale;
float3 physicalRotation = (cubeQuaternion * Quaternion.Euler(blueRot)).eulerAngles;
#if DEBUG
Logging.MetaLog($"Placing blueprint block at {physicalLocation} rot{physicalRotation} scale{physicalScale}");
Logging.MetaLog($"Location math check original:{bluePos} rotated: {(float3)(cubeQuaternion * bluePos)} actualPos: {rootPos} result: {physicalLocation}");
Logging.MetaLog($"Scale math check original:{blueScale} rotation: {(float3)cubeQuaternion.eulerAngles} result: {physicalScale}");
Logging.MetaLog($"Rotation math check original:{blueRot} rotated: {(cubeQuaternion * Quaternion.Euler(blueRot))} result: {physicalRotation}");
#endif
adjustedBlueprint[i] = new BlockJsonInfo
{
color = physicalColor,
name = blueprint[i].name,
position = ConversionUtility.Float3ToFloatArray(physicalLocation),
rotation = ConversionUtility.Float3ToFloatArray(physicalRotation),
scale = ConversionUtility.Float3ToFloatArray(physicalScale)
};
}
return adjustedBlueprint;
}
#if DEBUG
public void AddDebugCommands()
{
CommandBuilder.Builder("PixiReload", "Reloads the robot blueprints")
.Action(() => botprints = null).Build();
CommandBuilder.Builder("RotateBlueprint",
"Rotates a blueprint with a given ID and dumps the result to a file. 1 means 90 degrees.")
.Action<string>(RotateBlueprint).Build();
}
private void RotateBlueprint(string parameters)
{
var p = parameters.Split(' ');
string id = p[0];
var xyz = new int[3];
for (int i = 0; i < xyz.Length; i++)
xyz[i] = int.Parse(p[i + 1]) * 90;
if (botprints == null)
{
botprints = BlueprintUtility.ParseBlueprintResource("Pixi.blueprints.json");
}
if (!botprints.ContainsKey(id))
{
Logging.CommandLogWarning("Blueprint with that ID not found.");
return;
}
var bp = botprints[id];
var rotChange = Quaternion.Euler(xyz[0], xyz[1], xyz[2]);
for (var i = 0; i < bp.Length; i++)
{
ref var info = ref bp[i];
var pos = ConversionUtility.FloatArrayToFloat3(info.position);
info.position = ConversionUtility.Float3ToFloatArray(rotChange * pos);
var rot = Quaternion.Euler(ConversionUtility.FloatArrayToFloat3(info.rotation));
info.rotation = ConversionUtility.Float3ToFloatArray((rotChange * rot).eulerAngles);
}
File.WriteAllText(id, JsonConvert.SerializeObject(bp));
Logging.CommandLog("Blueprint rotated " + rotChange.eulerAngles + " and dumped");
}
#endif
}
}

View file

@ -18,29 +18,7 @@ namespace Pixi.Robots
{
public static class RobotCommands
{
internal const double blockSize = 0.2;
public static int CubeSize = 3;
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.")
.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.")
.Action<string>(ImportRobotOnline)
.Build();
}
public static void CreatePartDumpCommand()
public static void CreatePartDumpCommand()
{
CommandBuilder.Builder()
.Name("DumpVON")
@ -49,164 +27,13 @@ namespace Pixi.Robots
.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 * CubeSize * 3); // 3 is roughly the max height of any cube in RC
CubeInfo[] cubes = CubeUtility.ParseCubes(robot.Value);
// move origin closer to player (since bots are rarely built at the garage bay origin)
if (cubes.Length == 0)
{
Logging.CommandLogError($"Robot data contains no cubes");
return;
}
float3 minPosition = cubes[0].position;
for (int c = 0; c < cubes.Length; c++)
{
float3 cubePos = cubes[c].position;
if (cubePos.x < minPosition.x)
{
minPosition.x = cubePos.x;
}
if (cubePos.y < minPosition.y)
{
minPosition.y = cubePos.y;
}
if (cubePos.z < minPosition.z)
{
minPosition.z = cubePos.z;
}
}
Block[][] blocks = new Block[cubes.Length][];
for (int c = 0; c < cubes.Length; c++) // sometimes I wish this were C++
{
CubeInfo cube = cubes[c];
float3 realPosition = ((cube.position - minPosition) * (float)blockSize * CubeSize) + position;
if (cube.block == BlockIDs.TextBlock && !string.IsNullOrEmpty(cube.name))
{
// TextBlock block ID means it's a placeholder
blocks[c] = CubeUtility.BuildBlueprintOrTextBlock(cube, realPosition, CubeSize);
}
else
{
blocks[c] = new Block[] { Block.PlaceNew(cube.block, realPosition, cube.rotation, cube.color, cube.darkness, CubeSize) };
}
}
int blockCount = 0;
for (int c = 0; c < cubes.Length; c++)
{
CubeInfo cube = cubes[c];
// the goal is for this to never evaluate to true (ie all cubes are translated correctly)
if (!string.IsNullOrEmpty(cube.name) && cube.block == BlockIDs.TextBlock && blocks[c].Length == 1)
{
//Logging.MetaLog($"Block is {blocks[c][0].Type} and was placed as {cube.block}");
blocks[c][0].Specialise<TextBlock>().Text = cube.name;
}
blockCount += blocks[c].Length;
}
Logging.CommandLog($"Placed {robot?.name} by {robot?.addedByDisplayName} beside you ({cubes.Length}RC -> {blockCount}GC)");
}
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 * CubeSize * 3); // 3 is roughly the max height of any cube in RC
CubeInfo[] cubes = CubeUtility.ParseCubes(robot);
// move origin closer to player (since bots are rarely built at the garage bay origin)
if (cubes.Length == 0)
{
Logging.CommandLogError($"Robot data contains no cubes");
return;
}
float3 minPosition = cubes[0].position;
for (int c = 0; c < cubes.Length; c++)
{
float3 cubePos = cubes[c].position;
if (cubePos.x < minPosition.x)
{
minPosition.x = cubePos.x;
}
if (cubePos.y < minPosition.y)
{
minPosition.y = cubePos.y;
}
if (cubePos.z < minPosition.z)
{
minPosition.z = cubePos.z;
}
}
Block[][] blocks = new Block[cubes.Length][];
for (int c = 0; c < cubes.Length; c++) // sometimes I wish this were C++
{
CubeInfo cube = cubes[c];
float3 realPosition = ((cube.position - minPosition) * (float)blockSize * CubeSize) + position;
if (cube.block == BlockIDs.TextBlock && !string.IsNullOrEmpty(cube.name))
{
// TextBlock block ID means it's a placeholder
blocks[c] = CubeUtility.BuildBlueprintOrTextBlock(cube, realPosition, CubeSize);
}
else
{
blocks[c] = new Block[] { Block.PlaceNew(cube.block, realPosition, cube.rotation, cube.color, cube.darkness, CubeSize) };
}
}
int blockCount = 0;
for (int c = 0; c < cubes.Length; c++)
{
CubeInfo cube = cubes[c];
// the goal is for this to never evaluate to true (ie all cubes are translated correctly)
if (!string.IsNullOrEmpty(cube.name) && cube.block == BlockIDs.TextBlock && blocks[c].Length == 1)
{
//Logging.MetaLog($"Block is {blocks[c][0].Type} and was placed as {cube.block}");
blocks[c][0].Specialise<TextBlock>().Text = cube.name;
}
blockCount += blocks[c].Length;
}
Logging.CommandLog($"Placed {robot.name} by {robot.addedByDisplayName} beside you ({cubes.Length}RC -> {blockCount}GC)");
}
private static void DumpBlockStructure(string filename)
{
Player local = new Player(PlayerType.Local);
Block baseBlock = local.GetBlockLookedAt();
Block[] blocks = baseBlock.GetConnectedCubes();
Block[] blocks = local.GetSelectedBlocks();
if (blocks.Length == 0)
blocks = baseBlock.GetConnectedCubes();
bool isBaseScaled = !(baseBlock.Scale.x > 0 && baseBlock.Scale.x < 2 && baseBlock.Scale.y > 0 && baseBlock.Scale.y < 2 && baseBlock.Scale.z > 0 && baseBlock.Scale.z < 2);
if (isBaseScaled)
{

View file

@ -0,0 +1,149 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using Svelto.DataStructures;
using Unity.Mathematics;
using UnityEngine;
using GamecraftModdingAPI;
using GamecraftModdingAPI.Blocks;
using GamecraftModdingAPI.Players;
using GamecraftModdingAPI.Utility;
using Pixi.Common;
namespace Pixi.Robots
{
public class RobotInternetImporter : Importer
{
public int Priority { get; } = -100;
public bool Optimisable { get; } = false;
public string Name { get; } = "RobocraftRobot~Spell";
public BlueprintProvider BlueprintProvider { get; }
public static int CubeSize = 3;
public RobotInternetImporter()
{
BlueprintProvider = new RobotBlueprintProvider(this);
}
public bool Qualifies(string name)
{
string[] extensions = name.Split('.');
return extensions.Length == 1
|| !extensions[extensions.Length - 1].Contains(" ");
}
public BlockJsonInfo[] Import(string name)
{
// download robot data
RobotStruct robot;
try
{
RobotBriefStruct[] botList = RoboAPIUtility.ListRobots(name);
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);
return new BlockJsonInfo[0];
}
CubeInfo[] cubes = CubeUtility.ParseCubes(robot);
// move bot closer to origin (since bots are rarely built at the garage bay origin of the bottom south-west corner)
if (cubes.Length == 0)
{
Logging.CommandLogError($"Robot data contains no cubes");
return new BlockJsonInfo[0];
}
float3 minPosition = cubes[0].position;
for (int c = 0; c < cubes.Length; c++)
{
float3 cubePos = cubes[c].position;
if (cubePos.x < minPosition.x)
{
minPosition.x = cubePos.x;
}
if (cubePos.y < minPosition.y)
{
minPosition.y = cubePos.y;
}
if (cubePos.z < minPosition.z)
{
minPosition.z = cubePos.z;
}
}
BlockJsonInfo[] blocks = new BlockJsonInfo[cubes.Length];
for (int c = 0; c < cubes.Length; c++)
{
ref CubeInfo cube = ref cubes[c];
float3 realPosition = ((cube.position - minPosition) * CommandRoot.BLOCK_SIZE * CubeSize);
if (cube.block == BlockIDs.TextBlock && !string.IsNullOrEmpty(cube.name))
{
// TextBlock block ID means it's a placeholder
blocks[c] = new BlockJsonInfo
{
color = ColorSpaceUtility.UnquantizeToArray(cube.color, cube.darkness),
name = cube.cubeId.ToString(),
position = ConversionUtility.Float3ToFloatArray(realPosition),
rotation = ConversionUtility.Float3ToFloatArray(cube.rotation),
scale = ConversionUtility.Float3ToFloatArray(cube.scale)
};
}
else
{
blocks[c] = new BlockJsonInfo
{
color = ColorSpaceUtility.UnquantizeToArray(cube.color, cube.darkness),
name = cube.block.ToString(),
position = ConversionUtility.Float3ToFloatArray(realPosition),
rotation = ConversionUtility.Float3ToFloatArray(cube.rotation),
scale = ConversionUtility.Float3ToFloatArray(cube.scale * CubeSize)
};
}
}
return blocks;
}
public void PreProcess(string name, ref ProcessedVoxelObjectNotation[] blocks)
{
Player p = new Player(PlayerType.Local);
float3 pos = p.Position;
for (int i = 0; i < blocks.Length; i++)
{
blocks[i].position += pos;
}
// set textblock colors (replace <color="white"> with <color=#HEX> in textblocks)
Regex pattern = new Regex("<color=((?:\"white\")|(?:white))>", RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
for (int i = 0; i < blocks.Length; i++)
{
if (blocks[i].block == BlockIDs.TextBlock)
{
// TODO this blindly replaces color tags anywhere in metadata, not just ones that will go in the TextBlock's text field
#if DEBUG
Logging.MetaLog($"Replacing text field in block with colour {blocks[i].color} with #{ColorUtility.ToHtmlStringRGBA(ColorSpaceUtility.UnquantizeToColor(blocks[i].color))}");
#endif
blocks[i].metadata = pattern.Replace(
blocks[i].metadata,
$"<color=#{ColorUtility.ToHtmlStringRGBA(ColorSpaceUtility.UnquantizeToColor(blocks[i].color))}>");
// NOTE: Regex.Replace replaces the whole match string only when there's a capture group (it's dumb, idk why).
// The non-capturing groups may be messing with .NET or something
}
}
}
public void PostProcess(string name, ref Block[] blocks)
{
}
}
}

52
Pixi/TestImporter.cs Normal file
View file

@ -0,0 +1,52 @@
using System;
using GamecraftModdingAPI;
using GamecraftModdingAPI.Blocks;
using GamecraftModdingAPI.Players;
using Pixi.Common;
using Unity.Mathematics;
namespace Pixi
{
public class TestImporter : Importer
{
public int Priority { get; } = 0;
public bool Optimisable { get; } = false;
public string Name { get; } = "Test~Spell";
public BlueprintProvider BlueprintProvider { get; } = null;
public bool Qualifies(string name)
{
return name.Equals("test", StringComparison.InvariantCultureIgnoreCase);
}
public BlockJsonInfo[] Import(string name)
{
return new[]
{
new BlockJsonInfo
{
name = BlockIDs.TextBlock.ToString() +
"\ttext that is preserved through the whole import process and ends up in the text block\ttextblockIDs_sux",
position = new[] {0f, 0f, 0f},
rotation = new[] {0f, 0f, 0f},
color = new[] {0f, 0f, 0f},
scale = new[] {1f, 1f, 1f},
}
};
}
public void PreProcess(string name, ref ProcessedVoxelObjectNotation[] blocks)
{
Player p = new Player(PlayerType.Local);
float3 pos = p.Position;
for (int i = 0; i < blocks.Length; i++)
{
blocks[i].position += pos;
}
}
public void PostProcess(string name, ref Block[] blocks)
{
// meh
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -22,49 +22,40 @@ and `@"[dog name]"` could be a value like `@"Clifford"` or `@"doggo"`.
### Commands
`PixiText @"[image]"` converts an image to text and places a text block with that text beside you.
`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.
`Pixi @"[thing]"` to import `thing` into your Gamecraft game.
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 capable of black magic (yet).
execute the command `Pixi @"pixel_art.png"` to load the image as blocks.
It's important to include the file extension, since that's what makes Pixi's magic work.
**EXPERIMENTAL**
If you know the name of the Pixi spell you want, you can also use
`PixiBot @"[bot]"` downloads a bot from Robocraft's community Factory and places it beside you.
`Pixi2 "[spell]" @"[thing]"` to use `spell` to import `thing` into your Gamecraft game.
`PixiBotFile @"[bot]"` converts a `.bot` file from [rcbup](https://github.com/NGnius/rcbup) to blocks and places it beside you.
`PixiThicc [depth]` sets the block thickness, a positive integer value, for `Pixi2D` image conversion.
The default thickness is 1.
Some commands also have hidden features, like image rotation.
Talk to NGnius on the Exmods Discord server or read the Pixi's source code to figure that out.
Some commands also have hidden features, like image rotation and bot scaling.
Talk to NGnius on the Exmods Discord server or read Pixi's source code to figure that out.
### Behaviour
PixiText and PixiConsole share the same image conversion system.
ImageText and ImageConsole share the same image conversion system.
The conversion system converts every pixel to a [color tag](http://digitalnativestudios.com/textmeshpro/docs/rich-text/#color) followed by a square text character.
For PixiText, the resulting character string is set to the text field of the text block that the command places.
For PixiConsole, the character string is automatically set to a console block in the form `ChangeTextBlockCommand [text block id] [character string]`.
Due to limitations in Gamecraft, larger images will crash your game.
For ImageText, the resulting character string is set to the text field of the text block that the command places.
For ImageConsole, the character string is automatically set to a console block in the form `ChangeTextBlockCommand [text block id] [character string]`.
Due to limitations in Gamecraft, larger images will crash your game!!!
Pixi2D takes an image file and converts every pixel to a coloured block.
Pixi2D uses an algorithm to convert each pixel in an image into the closest paint colour, but colour accuracy will never be as good as a regular image.
ImageCanvas takes an image file and converts every pixel to a coloured block.
ImageCanvas uses an algorithm to convert each pixel in an image into the closest paint colour, but colour accuracy will never be as good as a regular image.
Pixi2D's colour-conversion algorithm also uses pixel transparency so you can cut out shapes.
ImageCanvas' colour-conversion algorithm also uses pixel transparency so you can cut out shapes.
A pixel which has opacity of less than 50% will be ignored.
A pixel which has an opacity between 75% and 50% will be converted into a glass cube.
A pixel which has an opacity greater than 75% will be converted into the block you're holding (or aluminium if you've got your hand selected).
This only works with `.PNG` image files since the `.JPG` format doesn't support image transparency.
Pixi2D also optimises block placement, since images have a lot of pixels.
ImageCanvas also optimises block placement, since images have a lot of pixels.
The blocks grouping ratio is displayed in the command line output once image importing is completed.
PixiBot and PixiBotFile convert a robot to equivalent Gamecraft blocks.
RobocraftRobot converts a robot to equivalent Gamecraft blocks.
If the conversion algorithm encounters a block it cannot convert, it will place a text block, with the block name, instead.
## Development
@ -77,7 +68,7 @@ Show your love by offering your help!
- Report any bugs that you encounter while using Pixi.
- Report an idea for an improvement to Pixi or for a new file format.
For questions, concerns or reports, please contact NGnius in the [Exmods Discord server](https://discord.exmods.org).
For questions, concerns, or any other inquiry, please contact NGnius in the [Exmods Discord server](https://discord.exmods.org).
### Setup
@ -96,27 +87,30 @@ Pixi also requires the [GamecraftModdingAPI](https://git.exmods.org/modtainers/G
### Building
After you've completed the setup, open the solution file `Pixi.sln` in your prefered C# .NET/Mono development environment.
I'd recommend Visual Studio Community Edition or JetBrains Rider for Windows and Monodevelop for Linux.
I'd recommend Visual Studio Community Edition for Windows or JetBrains Rider for Linux.
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/2CtWzZT).
If it doesn't work and you can't figure out why, ask for help on the [Exmods Discord server](https://discord.exmods.org).
# Acknowledgements
PixiBot uses the Factory to download robots, which involves a partial re-implementation of [rcbup](https://github.com/NGnius/rcbup).
RobocraftRobot uses the Factory to download robots, which involves a partial re-implementation of [rcbup](https://github.com/NGnius/rcbup).
Robot parsing uses information from [RobocraftAssembler](https://github.com/dddontshoot/RoboCraftAssembler).
Gamecraft interactions use the [GamecraftModdingAPI](https://git.exmods.org/modtainers/GamecraftModdingAPI).
MIDI file processing uses an integrated copy of melanchall's [DryWetMidi](https://github.com/melanchall/drywetmidi) library, licensed under the [MIT License](https://github.com/melanchall/drywetmidi/blob/develop/LICENSE).
Thanks to **TheGreenGoblin** and their Python app for converting images to coloured square characters, which inspired the PixiConsole and PixiText commands.
Thanks to **Mr. Rotor** for all of the Robocraft blocks used in the PixiBot and PixiBotFile commands.
# Disclaimer
Pixi, Exmods and NGnius are not endorsed or supported by Gamecraft or FreeJam.
Pixi source code and releases are available free of charge as open-source software for the purpose of modding Gamecraft.
Modify Gamecraft at your own risk.
Read the LICENSE file for licensing information.
Read the LICENSE file for official licensing information.
Pixi, Exmods and NGnius are not endorsed or supported by Gamecraft or FreeJam.
Please don't sue this project or its contributors (that's what all disclaimers boil down to, right?).
Pixi is not magic and is actually just sufficiently advanced technology that's indistinguishable from magic.
Pixi is actually just sufficiently advanced technology that's indistinguishable from magic.