Fixed prefab update for nonexistent blocks Removed Type from block placed/removed event args Added test to check the block ID enum (whether it has any extra or missing IDs) Added test to place every block on the ID enum Added test to set and verify each property of each block type (type-specific properties are also set when they can be through the API) Added support for enumerator test methods with exception handling
521 lines
19 KiB
C#
521 lines
19 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Reflection.Emit;
|
|
using System.Text;
|
|
using TechbloxModdingAPI.App;
|
|
using HarmonyLib;
|
|
using IllusionInjector;
|
|
// test
|
|
using GPUInstancer;
|
|
using Svelto.ECS;
|
|
using RobocraftX.Blocks;
|
|
using RobocraftX.Common;
|
|
using RobocraftX.SimulationModeState;
|
|
using RobocraftX.FrontEnd;
|
|
using Unity.Mathematics;
|
|
using UnityEngine;
|
|
using RobocraftX.Schedulers;
|
|
using Svelto.Tasks.ExtraLean;
|
|
using uREPL;
|
|
using TechbloxModdingAPI.Interface.IMGUI;
|
|
using TechbloxModdingAPI.Tasks;
|
|
using RobocraftX.Common.Input;
|
|
using RobocraftX.CR.MainGame;
|
|
using RobocraftX.GUI.CommandLine;
|
|
using RobocraftX.Multiplayer;
|
|
using RobocraftX.StateSync;
|
|
using Svelto.Context;
|
|
using Svelto.DataStructures;
|
|
using Svelto.Services;
|
|
using TechbloxModdingAPI.Blocks;
|
|
using TechbloxModdingAPI.Commands;
|
|
using TechbloxModdingAPI.Events;
|
|
using TechbloxModdingAPI.Input;
|
|
using TechbloxModdingAPI.Players;
|
|
using TechbloxModdingAPI.Utility;
|
|
using UnityEngine.AddressableAssets;
|
|
using UnityEngine.AddressableAssets.ResourceLocators;
|
|
using UnityEngine.ResourceManagement.AsyncOperations;
|
|
using UnityEngine.ResourceManagement.ResourceLocations;
|
|
using UnityEngine.ResourceManagement.ResourceProviders;
|
|
using Debug = FMOD.Debug;
|
|
using EventType = TechbloxModdingAPI.Events.EventType;
|
|
using Label = TechbloxModdingAPI.Interface.IMGUI.Label;
|
|
using ScalingPermission = DataLoader.ScalingPermission;
|
|
|
|
namespace TechbloxModdingAPI.Tests
|
|
{
|
|
#if DEBUG
|
|
// unused by design
|
|
/// <summary>
|
|
/// Modding API implemented as a standalone IPA Plugin.
|
|
/// Ideally, TechbloxModdingAPI should be loaded by another mod; not itself
|
|
/// </summary>
|
|
public class TechbloxModdingAPIPluginTest : IllusionPlugin.IEnhancedPlugin
|
|
{
|
|
|
|
private static Harmony harmony { get; set; }
|
|
|
|
public override string Name { get; } = Assembly.GetExecutingAssembly().GetName().Name;
|
|
|
|
public override string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
|
|
|
public string HarmonyID { get; } = "org.git.exmods.modtainers.techbloxmoddingapi";
|
|
|
|
public override void OnApplicationQuit()
|
|
{
|
|
Main.Shutdown();
|
|
}
|
|
|
|
public override void OnApplicationStart()
|
|
{
|
|
FileLog.Reset();
|
|
Harmony.DEBUG = true;
|
|
Main.Init();
|
|
Logging.MetaDebugLog($"Version group id {(uint)ApiExclusiveGroups.versionGroup}");
|
|
// in case Steam is not installed/running
|
|
// this will crash the game slightly later during startup
|
|
//SteamInitPatch.ForcePassSteamCheck = true;
|
|
// in case running in a VM
|
|
//MinimumSpecsCheckPatch.ForcePassMinimumSpecCheck = true;
|
|
// disable some Techblox analytics
|
|
//AnalyticsDisablerPatch.DisableAnalytics = true;
|
|
// disable background music
|
|
Logging.MetaDebugLog("Audio Mixers: " + string.Join(",", AudioTools.GetMixers()));
|
|
//AudioTools.SetVolume(0.0f, "Music"); // The game now sets this from settings again after this is called :(
|
|
|
|
//Utility.VersionTracking.Enable();//(very) unstable
|
|
|
|
// debug/test handlers
|
|
#pragma warning disable 0612
|
|
HandlerBuilder.Builder()
|
|
.Name("appinit API debug")
|
|
.Handle(EventType.ApplicationInitialized)
|
|
.OnActivation(() => { Logging.Log("App Inited event!"); })
|
|
.Build();
|
|
|
|
HandlerBuilder.Builder("menuact API debug")
|
|
.Handle(EventType.Menu)
|
|
.OnActivation(() => { Logging.Log("Menu Activated event!"); })
|
|
.OnDestruction(() => { Logging.Log("Menu Destroyed event!"); })
|
|
.Build();
|
|
|
|
HandlerBuilder.Builder("menuswitch API debug")
|
|
.Handle(EventType.MenuSwitchedTo)
|
|
.OnActivation(() => { Logging.Log("Menu Switched To event!"); })
|
|
.Build();
|
|
|
|
HandlerBuilder.Builder("gameact API debug")
|
|
.Handle(EventType.Menu)
|
|
.OnActivation(() => { Logging.Log("Game Activated event!"); })
|
|
.OnDestruction(() => { Logging.Log("Game Destroyed event!"); })
|
|
.Build();
|
|
|
|
HandlerBuilder.Builder("gamerel API debug")
|
|
.Handle(EventType.GameReloaded)
|
|
.OnActivation(() => { Logging.Log("Game Reloaded event!"); })
|
|
.Build();
|
|
|
|
HandlerBuilder.Builder("gameswitch API debug")
|
|
.Handle(EventType.GameSwitchedTo)
|
|
.OnActivation(() => { Logging.Log("Game Switched To event!"); })
|
|
.Build();
|
|
|
|
HandlerBuilder.Builder("simulationswitch API debug")
|
|
.Handle(EventType.SimulationSwitchedTo)
|
|
.OnActivation(() => { Logging.Log("Game Mode Simulation Switched To event!"); })
|
|
.Build();
|
|
|
|
HandlerBuilder.Builder("buildswitch API debug")
|
|
.Handle(EventType.BuildSwitchedTo)
|
|
.OnActivation(() => { Logging.Log("Game Mode Build Switched To event!"); })
|
|
.Build();
|
|
|
|
HandlerBuilder.Builder("menu activated API error thrower test")
|
|
.Handle(EventType.Menu)
|
|
.OnActivation(() => { throw new Exception("Event Handler always throws an exception!"); })
|
|
.Build();
|
|
#pragma warning restore 0612
|
|
/*HandlerBuilder.Builder("enter game from menu test")
|
|
.Handle(EventType.Menu)
|
|
.OnActivation(() =>
|
|
{
|
|
Tasks.Scheduler.Schedule(new Tasks.Repeatable(enterGame, shouldRetry, 0.2f));
|
|
})
|
|
.Build();*/
|
|
|
|
// debug/test commands
|
|
if (Dependency.Hell("ExtraCommands"))
|
|
{
|
|
CommandBuilder.Builder()
|
|
.Name("Exit")
|
|
.Description("Close Techblox immediately, without any prompts")
|
|
.Action(() => { UnityEngine.Application.Quit(); })
|
|
.Build();
|
|
|
|
CommandBuilder.Builder()
|
|
.Name("SetFOV")
|
|
.Description("Set the player camera's field of view")
|
|
.Action((float d) => { UnityEngine.Camera.main.fieldOfView = d; })
|
|
.Build();
|
|
|
|
CommandBuilder.Builder()
|
|
.Name("MoveLastBlock")
|
|
.Description("Move the most-recently-placed block, and any connected blocks by the given offset")
|
|
.Action((float x, float y, float z) =>
|
|
{
|
|
if (GameState.IsBuildMode())
|
|
foreach (var block in Block.GetLastPlacedBlock().GetConnectedCubes())
|
|
block.Position += new Unity.Mathematics.float3(x, y, z);
|
|
else
|
|
Logging.CommandLogError("Blocks can only be moved in Build mode!");
|
|
}).Build();
|
|
|
|
CommandBuilder.Builder()
|
|
.Name("PlaceAluminium")
|
|
.Description("Place a block of aluminium at the given coordinates")
|
|
.Action((float x, float y, float z) =>
|
|
{
|
|
var block = Block.PlaceNew(BlockIDs.Cube, new float3(x, y, z));
|
|
Logging.CommandLog("Block placed with type: " + block.Type);
|
|
})
|
|
.Build();
|
|
|
|
CommandBuilder.Builder()
|
|
.Name("PlaceAluminiumLots")
|
|
.Description("Place a lot of blocks of aluminium at the given coordinates")
|
|
.Action((float x, float y, float z) =>
|
|
{
|
|
Logging.CommandLog("Starting...");
|
|
var sw = Stopwatch.StartNew();
|
|
for (int i = 0; i < 100; i++)
|
|
for (int j = 0; j < 100; j++)
|
|
Block.PlaceNew(BlockIDs.Cube, new float3(x + i, y, z + j));
|
|
//Block.Sync();
|
|
sw.Stop();
|
|
Logging.CommandLog("Finished in " + sw.ElapsedMilliseconds + "ms");
|
|
})
|
|
.Build();
|
|
|
|
Block b = null;
|
|
CommandBuilder.Builder("moveBlockInSim", "Run in build mode first while looking at a block, then in sim to move it up")
|
|
.Action(() =>
|
|
{
|
|
if (b == null)
|
|
{
|
|
b = new Player(PlayerType.Local).GetBlockLookedAt();
|
|
Logging.CommandLog("Block saved: " + b);
|
|
}
|
|
else
|
|
Logging.CommandLog("Block moved to: " + (b.GetSimBody().Position += new float3(0, 2, 0)));
|
|
}).Build();
|
|
|
|
CommandBuilder.Builder("Error", "Throw an error to make sure SimpleCustomCommandEngine's wrapper catches it.")
|
|
.Action(() => { throw new Exception("Error Command always throws an error"); })
|
|
.Build();
|
|
|
|
CommandBuilder.Builder("ColorBlock",
|
|
"Change color of the block looked at if there's any.")
|
|
.Action<string>(str =>
|
|
{
|
|
if (!Enum.TryParse(str, out BlockColors color))
|
|
{
|
|
Logging.CommandLog("Color " + str + " not found! Interpreting as 4 color values.");
|
|
var s = str.Split(' ');
|
|
new Player(PlayerType.Local).GetBlockLookedAt().CustomColor = new float4(float.Parse(s[0]),
|
|
float.Parse(s[1]), float.Parse(s[2]), float.Parse(s[3]));
|
|
return;
|
|
}
|
|
|
|
new Player(PlayerType.Local).GetBlockLookedAt().Color = color;
|
|
Logging.CommandLog("Colored block to " + color);
|
|
|
|
}).Build();
|
|
|
|
CommandBuilder.Builder("GetBlockByID", "Gets a block based on its object identifier and teleports it up.")
|
|
.Action<char>(ch =>
|
|
{
|
|
foreach (var body in SimBody.GetFromObjectID(ch))
|
|
{
|
|
Logging.CommandLog("SimBody: " + body);
|
|
body.Position += new float3(0, 10, 0);
|
|
foreach (var bodyConnectedBody in body.GetConnectedBodies())
|
|
{
|
|
Logging.CommandLog("Moving " + bodyConnectedBody);
|
|
bodyConnectedBody.Position += new float3(0, 10, 0);
|
|
}
|
|
}
|
|
}).Build();
|
|
|
|
/*CommandBuilder.Builder()
|
|
.Name("PlaceConsole")
|
|
.Description("Place a bunch of console block with a given text - entering simulation with them crashes the game as the cmd doesn't exist")
|
|
.Action((float x, float y, float z) =>
|
|
{
|
|
Stopwatch sw = new Stopwatch();
|
|
sw.Start();
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
for (int j = 0; j < 100; j++)
|
|
{
|
|
var block = Block.PlaceNew<ConsoleBlock>(BlockIDs.ConsoleBlock,
|
|
new float3(x + i, y, z + j));
|
|
block.Command = "test_command";
|
|
}
|
|
}
|
|
|
|
sw.Stop();
|
|
Logging.CommandLog($"Blocks placed in {sw.ElapsedMilliseconds} ms");
|
|
})
|
|
.Build();*/
|
|
|
|
/*CommandBuilder.Builder()
|
|
.Name("WireTest")
|
|
.Description("Place two blocks and then wire them together")
|
|
.Action(() =>
|
|
{
|
|
LogicGate notBlock = Block.PlaceNew<LogicGate>(BlockIDs.NOTLogicBlock, new float3(1, 2, 0));
|
|
LogicGate andBlock = Block.PlaceNew<LogicGate>(BlockIDs.ANDLogicBlock, new float3(2, 2, 0));
|
|
// connect NOT Gate output to AND Gate input #2 (ports are zero-indexed, so 1 is 2nd position and 0 is 1st position)
|
|
Wire conn = notBlock.Connect(0, andBlock, 1);
|
|
Logging.CommandLog(conn.ToString());
|
|
})
|
|
.Build();*/
|
|
|
|
CommandBuilder.Builder("TestChunkHealth", "Sets the chunk looked at to the given health.")
|
|
.Action((float val, float max) =>
|
|
{
|
|
var body = new Player(PlayerType.Local).GetSimBodyLookedAt();
|
|
if (body == null) return;
|
|
body.CurrentHealth = val;
|
|
body.InitialHealth = max;
|
|
Logging.CommandLog("Health set to: " + val);
|
|
}).Build();
|
|
|
|
CommandBuilder.Builder("placeBlockGroup", "Places some blocks in a group")
|
|
.Action((float x, float y, float z) =>
|
|
{
|
|
var pos = new float3(x, y, z);
|
|
var group = BlockGroup.Create(new Block(BlockIDs.Cube, pos) {Color = BlockColors.Aqua});
|
|
new Block(BlockIDs.Cube, pos += new float3(1, 0, 0))
|
|
{Color = BlockColors.Blue, BlockGroup = group};
|
|
new Block(BlockIDs.Cube, pos += new float3(1, 0, 0))
|
|
{Color = BlockColors.Green, BlockGroup = group};
|
|
new Block(BlockIDs.Cube, pos + new float3(1, 0, 0))
|
|
{Color = BlockColors.Lime, BlockGroup = group};
|
|
}).Build();
|
|
|
|
CommandBuilder.Builder("placeCustomBlock", "Places a custom block, needs a custom catalog and assets.")
|
|
.Action((float x, float y, float z) =>
|
|
{
|
|
Logging.CommandLog("Block placed: " +
|
|
Block.PlaceNew((BlockIDs) 500, new float3(0, 0, 0)));
|
|
}).Build();
|
|
|
|
CommandBuilder.Builder("toggleTimeMode", "Enters or exits simulation.")
|
|
.Action((float x, float y, float z) =>
|
|
{
|
|
Game.CurrentGame().ToggleTimeMode();
|
|
}).Build();
|
|
|
|
GameClient.SetDebugInfo("InstalledMods", InstalledMods);
|
|
Block.Placed += (sender, args) =>
|
|
Logging.MetaDebugLog("Placed block " + args.Block);
|
|
Block.Removed += (sender, args) =>
|
|
Logging.MetaDebugLog("Removed block " + args.Block);
|
|
|
|
/*
|
|
CommandManager.AddCommand(new SimpleCustomCommandEngine<float>((float d) => { UnityEngine.Camera.main.fieldOfView = d; },
|
|
"SetFOV", "Set the player camera's field of view"));
|
|
CommandManager.AddCommand(new SimpleCustomCommandEngine<float, float, float>(
|
|
(x, y, z) => {
|
|
bool success = TechbloxModdingAPI.Blocks.Movement.MoveConnectedBlocks(
|
|
TechbloxModdingAPI.Blocks.BlockIdentifiers.LatestBlockID,
|
|
new Unity.Mathematics.float3(x, y, z));
|
|
if (!success)
|
|
{
|
|
TechbloxModdingAPI.Utility.Logging.CommandLogError("Blocks can only be moved in Build mode!");
|
|
}
|
|
}, "MoveLastBlock", "Move the most-recently-placed block, and any connected blocks by the given offset"));
|
|
CommandManager.AddCommand(new SimpleCustomCommandEngine<float, float, float>(
|
|
(x, y, z) => { Blocks.Placement.PlaceBlock(Blocks.BlockIDs.Cube, new Unity.Mathematics.float3(x, y, z)); },
|
|
"PlaceAluminium", "Place a block of aluminium at the given coordinates"));
|
|
System.Random random = new System.Random(); // for command below
|
|
CommandManager.AddCommand(new SimpleCustomCommandEngine(
|
|
() => {
|
|
if (!GameState.IsSimulationMode())
|
|
{
|
|
Logging.CommandLogError("You must be in simulation mode for this to work!");
|
|
return;
|
|
}
|
|
Tasks.Repeatable task = new Tasks.Repeatable(() => {
|
|
uint count = 0;
|
|
EGID[] eBlocks = Blocks.Signals.GetElectricBlocks();
|
|
for (uint i = 0u; i < eBlocks.Length; i++)
|
|
{
|
|
uint[] ids = Blocks.Signals.GetSignalIDs(eBlocks[i]);
|
|
for (uint j = 0u; j < ids.Length; j++)
|
|
{
|
|
Blocks.Signals.SetSignalByID(ids[j], (float)random.NextDouble());
|
|
count++;
|
|
}
|
|
}
|
|
Logging.MetaDebugLog($"Did the thing on {count} inputs");
|
|
},
|
|
() => { return GameState.IsSimulationMode(); });
|
|
Tasks.Scheduler.Schedule(task);
|
|
}, "RandomizeSignalsInputs", "Do the thing"));
|
|
*/
|
|
}
|
|
|
|
// dependency test
|
|
if (Dependency.Hell("TechbloxScripting", new Version("0.0.1.0")))
|
|
{
|
|
Logging.LogWarning("You're in TechbloxScripting dependency hell");
|
|
}
|
|
else
|
|
{
|
|
Logging.Log("Compatible TechbloxScripting detected");
|
|
}
|
|
// Interface test
|
|
/*Interface.IMGUI.Group uiGroup = new Group(new Rect(20, 20, 200, 500), "TechbloxModdingAPI_UITestGroup", true);
|
|
Interface.IMGUI.Button button = new Button("TEST");
|
|
button.OnClick += (b, __) => { Logging.MetaDebugLog($"Click on {((Interface.IMGUI.Button)b).Name}");};
|
|
Interface.IMGUI.Button button2 = new Button("TEST2");
|
|
button2.OnClick += (b, __) => { Logging.MetaDebugLog($"Click on {((Interface.IMGUI.Button)b).Name}");};
|
|
Text uiText = new Text("This is text!", multiline: true);
|
|
uiText.OnEdit += (t, txt) => { Logging.MetaDebugLog($"Text in {((Text)t).Name} is now '{txt}'"); };
|
|
Label uiLabel = new Label("Label!");
|
|
Image uiImg = new Image(name:"Behold this texture!");
|
|
uiImg.Enabled = false;
|
|
uiGroup.AddElement(button);
|
|
uiGroup.AddElement(button2);
|
|
uiGroup.AddElement(uiText);
|
|
uiGroup.AddElement(uiLabel);
|
|
uiGroup.AddElement(uiImg);*/
|
|
|
|
/*Addressables.LoadAssetAsync<Texture2D>("Assets/Art/Textures/UI/FrontEndMap/RCX_Blue_Background_5k.jpg")
|
|
.Completed +=
|
|
handle =>
|
|
{
|
|
uiImg.Texture = handle.Result;
|
|
uiImg.Enabled = true;
|
|
Logging.MetaDebugLog($"Got blue bg asset {handle.Result}");
|
|
};*/
|
|
|
|
|
|
CommandBuilder.Builder("enableCompletions")
|
|
.Action(() =>
|
|
{
|
|
var p = Window.selected.main.parameters;
|
|
p.useCommandCompletion = true;
|
|
p.useMonoCompletion = true;
|
|
p.useGlobalClassCompletion = true;
|
|
Log.Output("Submitted: " + Window.selected.submittedCode);
|
|
})
|
|
.Build();
|
|
try
|
|
{
|
|
CustomBlock.RegisterCustomBlock<TestBlock>();
|
|
Logging.MetaDebugLog("Registered test custom block");
|
|
}
|
|
catch (FileNotFoundException)
|
|
{
|
|
Logging.MetaDebugLog("Test custom block catalog not found");
|
|
}
|
|
|
|
CustomBlock.ChangeExistingBlock((ushort) BlockIDs.CarWheel,
|
|
cld => cld.scalingPermission = ScalingPermission.NonUniform);
|
|
|
|
/*((FasterList<GuiInputMap.GuiInputMapElement>)AccessTools.Property(typeof(GuiInputMap), "GuiInputsButtonDown").GetValue(null))
|
|
.Add(new GuiInputMap.GuiInputMapElement(RewiredConsts.Action.ToggleCommandLine, GuiIn))*/
|
|
#if TEST
|
|
TestRoot.RunTests();
|
|
#endif
|
|
}
|
|
|
|
private string modsString;
|
|
private string InstalledMods()
|
|
{
|
|
if (modsString != null) return modsString;
|
|
StringBuilder sb = new StringBuilder("Installed mods:");
|
|
foreach (var plugin in PluginManager.Plugins)
|
|
sb.Append("\n" + plugin.Name + " - " + plugin.Version);
|
|
return modsString = sb.ToString();
|
|
}
|
|
|
|
private bool retry = true;
|
|
|
|
private bool shouldRetry()
|
|
{
|
|
return retry;
|
|
}
|
|
|
|
private void enterGame()
|
|
{
|
|
App.Client app = new App.Client();
|
|
App.Game[] myGames = app.MyGames;
|
|
Logging.MetaDebugLog($"MyGames count {myGames.Length}");
|
|
if (myGames.Length != 0)
|
|
{
|
|
Logging.MetaDebugLog($"MyGames[0] EGID {myGames[0].EGID}");
|
|
retry = false;
|
|
try
|
|
{
|
|
//myGames[0].Description = "test msg pls ignore"; // make sure game exists first
|
|
Logging.MetaDebugLog($"Entering game {myGames[0].Name}");
|
|
myGames[0].EnterGame();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.MetaDebugLog($"Failed to enter game; exception: {e}");
|
|
retry = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Logging.MetaDebugLog("MyGames not populated yet :(");
|
|
}
|
|
}
|
|
|
|
public override void OnUpdate()
|
|
{
|
|
if (UnityEngine.Input.GetKeyDown(KeyCode.End))
|
|
{
|
|
Console.WriteLine("Pressed button to toggle console");
|
|
FakeInput.CustomInput(new LocalInputEntityStruct {commandLineToggleInput = true});
|
|
}
|
|
}
|
|
|
|
[HarmonyPatch]
|
|
public class MinimumSpecsPatch
|
|
{
|
|
public static bool Prefix()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public static MethodInfo TargetMethod()
|
|
{
|
|
return ((Action) MinimumSpecsCheck.CheckRequirementsMet).Method;
|
|
}
|
|
}
|
|
|
|
[CustomBlock("customCatalog.json", "Assets/Prefabs/Cube.prefab", "strAluminiumCube", SortIndex = 12)]
|
|
public class TestBlock : CustomBlock
|
|
{
|
|
public TestBlock(EGID id) : base(id)
|
|
{
|
|
}
|
|
|
|
public TestBlock(uint id) : base(id)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|