A stable modding interface between Techblox and mods https://mod.exmods.org/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

370 lines
14KB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Reflection;
  5. using System.Text;
  6. using TechbloxModdingAPI.App;
  7. using HarmonyLib;
  8. using IllusionInjector;
  9. // test
  10. using RobocraftX.FrontEnd;
  11. using Unity.Mathematics;
  12. using UnityEngine;
  13. using RobocraftX.Common.Input;
  14. using Svelto.Tasks;
  15. using Svelto.Tasks.Lean;
  16. using TechbloxModdingAPI.Blocks;
  17. using TechbloxModdingAPI.Commands;
  18. using TechbloxModdingAPI.Input;
  19. using TechbloxModdingAPI.Players;
  20. using TechbloxModdingAPI.Tasks;
  21. using TechbloxModdingAPI.Utility;
  22. namespace TechbloxModdingAPI.Tests
  23. {
  24. #if DEBUG
  25. // unused by design
  26. /// <summary>
  27. /// Modding API implemented as a standalone IPA Plugin.
  28. /// Ideally, TechbloxModdingAPI should be loaded by another mod; not itself
  29. /// </summary>
  30. class TechbloxModdingAPIPluginTest : IllusionPlugin.IEnhancedPlugin
  31. {
  32. private static Harmony harmony { get; set; }
  33. public override string Name { get; } = Assembly.GetExecutingAssembly().GetName().Name;
  34. public override string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString();
  35. public string HarmonyID { get; } = "org.git.exmods.modtainers.techbloxmoddingapi";
  36. public override void OnApplicationQuit()
  37. {
  38. Main.Shutdown();
  39. }
  40. public override void OnApplicationStart()
  41. {
  42. FileLog.Reset();
  43. Harmony.DEBUG = true;
  44. Main.Init();
  45. Logging.MetaDebugLog($"Version group id {(uint)ApiExclusiveGroups.versionGroup}");
  46. // disable background music
  47. Logging.MetaDebugLog("Audio Mixers: " + string.Join(",", AudioTools.GetMixers()));
  48. //AudioTools.SetVolume(0.0f, "Music"); // The game now sets this from settings again after this is called :(
  49. //Utility.VersionTracking.Enable();//(very) unstable
  50. // debug/test handlers
  51. Client.EnterMenu += (sender, args) => throw new Exception("Test handler always throws an exception!");
  52. // debug/test commands
  53. if (Dependency.Hell("ExtraCommands"))
  54. {
  55. CommandBuilder.Builder()
  56. .Name("Exit")
  57. .Description("Close Techblox immediately, without any prompts")
  58. .Action(() => { UnityEngine.Application.Quit(); })
  59. .Build();
  60. CommandBuilder.Builder()
  61. .Name("SetFOV")
  62. .Description("Set the player camera's field of view")
  63. .Action((float d) => { UnityEngine.Camera.main.fieldOfView = d; })
  64. .Build();
  65. CommandBuilder.Builder()
  66. .Name("MoveLastBlock")
  67. .Description("Move the most-recently-placed block, and any connected blocks by the given offset")
  68. .Action((float x, float y, float z) =>
  69. {
  70. if (GameState.IsBuildMode())
  71. foreach (var block in Block.GetLastPlacedBlock().GetConnectedCubes())
  72. block.Position += new Unity.Mathematics.float3(x, y, z);
  73. else
  74. Logging.CommandLogError("Blocks can only be moved in Build mode!");
  75. }).Build();
  76. CommandBuilder.Builder()
  77. .Name("PlaceAluminium")
  78. .Description("Place a block of aluminium at the given coordinates")
  79. .Action((float x, float y, float z) =>
  80. {
  81. var block = Block.PlaceNew(BlockIDs.Cube, new float3(x, y, z));
  82. Logging.CommandLog("Block placed with type: " + block.Type);
  83. })
  84. .Build();
  85. CommandBuilder.Builder()
  86. .Name("PlaceAluminiumLots")
  87. .Description("Place a lot of blocks of aluminium at the given coordinates")
  88. .Action((float x, float y, float z) =>
  89. {
  90. Logging.CommandLog("Starting...");
  91. var sw = Stopwatch.StartNew();
  92. for (int i = 0; i < 100; i++)
  93. for (int j = 0; j < 100; j++)
  94. Block.PlaceNew(BlockIDs.Cube, new float3(x + i, y, z + j));
  95. sw.Stop();
  96. Logging.CommandLog("Finished in " + sw.ElapsedMilliseconds + "ms");
  97. })
  98. .Build();
  99. Block b = null;
  100. CommandBuilder.Builder("moveBlockInSim", "Run in build mode first while looking at a block, then in sim to move it up")
  101. .Action(() =>
  102. {
  103. if (b == null)
  104. {
  105. b = new Player(PlayerType.Local).GetBlockLookedAt();
  106. Logging.CommandLog("Block saved: " + b);
  107. }
  108. else
  109. Logging.CommandLog("Block moved to: " + (b.GetSimBody().Position += new float3(0, 2, 0)));
  110. }).Build();
  111. CommandBuilder.Builder("Error", "Throw an error to make sure SimpleCustomCommandEngine's wrapper catches it.")
  112. .Action(() => { throw new Exception("Error Command always throws an error"); })
  113. .Build();
  114. CommandBuilder.Builder("ColorBlock",
  115. "Change color of the block looked at if there's any.")
  116. .Action<string>(str =>
  117. {
  118. if (!Enum.TryParse(str, out BlockColors color))
  119. {
  120. Logging.CommandLog("Color " + str + " not found! Interpreting as 4 color values.");
  121. var s = str.Split(' ');
  122. new Player(PlayerType.Local).GetBlockLookedAt().CustomColor = new float4(float.Parse(s[0]),
  123. float.Parse(s[1]), float.Parse(s[2]), float.Parse(s[3]));
  124. return;
  125. }
  126. new Player(PlayerType.Local).GetBlockLookedAt().Color = color;
  127. Logging.CommandLog("Colored block to " + color);
  128. }).Build();
  129. CommandBuilder.Builder("MoveBlockByID", "Gets a block based on its object identifier and teleports it up.")
  130. .Action<char>(ch =>
  131. {
  132. foreach (var body in SimBody.GetFromObjectID(ch))
  133. {
  134. Logging.CommandLog("SimBody: " + body);
  135. body.Position += new float3(0, 10, 0);
  136. foreach (var bodyConnectedBody in body.GetConnectedBodies())
  137. {
  138. Logging.CommandLog("Moving " + bodyConnectedBody);
  139. bodyConnectedBody.Position += new float3(0, 10, 0);
  140. }
  141. }
  142. }).Build();
  143. CommandBuilder.Builder("TestChunkHealth", "Sets the chunk looked at to the given health.")
  144. .Action((float val, float max) =>
  145. {
  146. var body = new Player(PlayerType.Local).GetSimBodyLookedAt();
  147. if (body == null) return;
  148. body.CurrentHealth = val;
  149. body.InitialHealth = max;
  150. Logging.CommandLog("Health set to: " + val);
  151. }).Build();
  152. CommandBuilder.Builder("placeBlockGroup", "Places some blocks in a group")
  153. .Action((float x, float y, float z) =>
  154. {
  155. var pos = new float3(x, y, z);
  156. var group = BlockGroup.Create(new Block(BlockIDs.Cube, pos) {Color = BlockColors.Aqua});
  157. new Block(BlockIDs.Cube, pos += new float3(1, 0, 0))
  158. {Color = BlockColors.Blue, BlockGroup = group};
  159. new Block(BlockIDs.Cube, pos += new float3(1, 0, 0))
  160. {Color = BlockColors.Green, BlockGroup = group};
  161. new Block(BlockIDs.Cube, pos + new float3(1, 0, 0))
  162. {Color = BlockColors.Lime, BlockGroup = group};
  163. }).Build();
  164. CommandBuilder.Builder("placeCustomBlock", "Places a custom block, needs a custom catalog and assets.")
  165. .Action((float x, float y, float z) =>
  166. {
  167. Logging.CommandLog("Block placed: " +
  168. Block.PlaceNew((BlockIDs) 500, new float3(0, 0, 0)));
  169. }).Build();
  170. CommandBuilder.Builder("toggleTimeMode", "Enters or exits simulation.")
  171. .Action((float x, float y, float z) =>
  172. {
  173. Game.CurrentGame().ToggleTimeMode();
  174. }).Build();
  175. CommandBuilder.Builder("testColorBlock", "Tests coloring a block to default color")
  176. .Action(() => Player.LocalPlayer.GetBlockLookedAt().Color = BlockColors.Default).Build();
  177. CommandBuilder.Builder("testMaterialBlock", "Tests materialing a block to default material")
  178. .Action(() => Player.LocalPlayer.GetBlockLookedAt().Material = BlockMaterial.Default).Build();
  179. CommandBuilder.Builder("testGameName", "Tests changing the game name")
  180. .Action(() => Game.CurrentGame().Name = "Test").Build();
  181. CommandBuilder.Builder("makeBlockStatic", "Makes a block you look at static")
  182. .Action(() => Player.LocalPlayer.GetBlockLookedAt().Static = true).Build();
  183. Game.AddPersistentDebugInfo("InstalledMods", InstalledMods);
  184. Block.Placed += (sender, args) =>
  185. Logging.MetaDebugLog("Placed block " + args.Block);
  186. Block.Removed += (sender, args) =>
  187. Logging.MetaDebugLog("Removed block " + args.Block);
  188. }
  189. // dependency test
  190. if (Dependency.Hell("TechbloxScripting", new Version("0.0.1.0")))
  191. {
  192. Logging.LogWarning("You're in TechbloxScripting dependency hell");
  193. }
  194. else
  195. {
  196. Logging.Log("Compatible TechbloxScripting detected");
  197. }
  198. // Interface test
  199. /*Group uiGroup = new Group(new Rect(20, 20, 200, 500), "TechbloxModdingAPI_UITestGroup", true);
  200. var button = new Button("TEST");
  201. button.OnClick += (b, __) => { Logging.MetaDebugLog($"Click on {((Interface.IMGUI.Button)b).Name}");};
  202. var button2 = new Button("TEST2");
  203. button2.OnClick += (b, __) => { Logging.MetaDebugLog($"Click on {((Interface.IMGUI.Button)b).Name}");};
  204. Text uiText = new Text("<Input!>", multiline: true);
  205. uiText.OnEdit += (t, txt) => { Logging.MetaDebugLog($"Text in {((Text)t).Name} is now '{txt}'"); };
  206. Label uiLabel = new Label("Label!");
  207. Image uiImg = new Image(name:"Behold this texture!");
  208. uiImg.Enabled = false;
  209. uiGroup.AddElement(button);
  210. uiGroup.AddElement(button2);
  211. uiGroup.AddElement(uiText);
  212. uiGroup.AddElement(uiLabel);
  213. uiGroup.AddElement(uiImg);*/
  214. /*Addressables.LoadAssetAsync<Texture2D>("Assets/Art/Textures/UI/FrontEndMap/RCX_Blue_Background_5k.jpg")
  215. .Completed +=
  216. handle =>
  217. {
  218. uiImg.Texture = handle.Result;
  219. uiImg.Enabled = true;
  220. Logging.MetaDebugLog($"Got blue bg asset {handle.Result}");
  221. };*/
  222. /*((FasterList<GuiInputMap.GuiInputMapElement>)AccessTools.Property(typeof(GuiInputMap), "GuiInputsButtonDown").GetValue(null))
  223. .Add(new GuiInputMap.GuiInputMapElement(RewiredConsts.Action.ToggleCommandLine, GuiIn))*/
  224. /*Game.Enter += (sender, e) =>
  225. {
  226. ushort lastKey = ushort.MaxValue;
  227. foreach (var kv in FullGameFields._dataDb.GetValues<CubeListData>()
  228. .OrderBy(kv=>ushort.Parse(kv.Key)))
  229. {
  230. var data = (CubeListData) kv.Value;
  231. ushort currentKey = ushort.Parse(kv.Key);
  232. var toReplace = new Dictionary<string, string>
  233. {
  234. {"Scalable", ""}, {"Qtr", "Quarter"}, {"RNeg", "Rounded Negative"},
  235. {"Neg", "Negative"}, {"Tetra", "Tetrahedron"},
  236. {"RWedge", "Rounded Wedge"}, {"RTetra", "Rounded Tetrahedron"}
  237. };
  238. string name = LocalizationService.Localize(data.CubeNameKey).Replace(" ", "");
  239. foreach (var rkv in toReplace)
  240. {
  241. name = Regex.Replace(name, "([^A-Za-z])" + rkv.Key + "([^A-Za-z])", "$1" + rkv.Value + "$2");
  242. }
  243. Console.WriteLine($"{name}{(currentKey != lastKey + 1 ? $" = {currentKey}" : "")},");
  244. lastKey = currentKey;
  245. }
  246. };*/
  247. /*Game.Enter += (sender, e) =>
  248. {
  249. Console.WriteLine("Materials:\n" + FullGameFields._dataDb.GetValues<MaterialPropertiesData>()
  250. .Select(kv => $"{kv.Key}: {((MaterialPropertiesData) kv.Value).Name}")
  251. .Aggregate((a, b) => a + "\n" + b));
  252. };*/
  253. CommandBuilder.Builder("takeScreenshot", "Enables the screenshot taker")
  254. .Action(() =>
  255. {
  256. Game.CurrentGame().EnableScreenshotTaker();
  257. }).Build();
  258. CommandBuilder.Builder("testPositionDefault", "Tests the Block.Position property's default value.")
  259. .Action(() =>
  260. {
  261. IEnumerator<TaskContract> Loop()
  262. {
  263. for (int i = 0; i < 2; i++)
  264. {
  265. Console.WriteLine("A");
  266. var block = Block.PlaceNew(BlockIDs.Cube, 1);
  267. Console.WriteLine("B");
  268. while (!block.Exists)
  269. yield return Yield.It;
  270. Console.WriteLine("C");
  271. block.Remove();
  272. Console.WriteLine("D");
  273. while (block.Exists)
  274. yield return Yield.It;
  275. Console.WriteLine("E - Pos: " + block.Position);
  276. block.Position = 4;
  277. Console.WriteLine("F - Pos: " + block.Position);
  278. }
  279. }
  280. Loop().RunOn(Scheduler.leanRunner);
  281. }).Build();
  282. CommandBuilder.Builder("importAssetBundle")
  283. .Action(() =>
  284. {
  285. Logging.CommandLog("Importing asset bundle...");
  286. var ab = AssetBundle.LoadFromFile(
  287. @"filepath");
  288. Logging.CommandLog("Imported asset bundle: " + ab);
  289. var assets = ab.LoadAllAssets();
  290. Logging.CommandLog("Loaded " + assets.Length + " assets");
  291. foreach (var asset in assets)
  292. {
  293. Logging.CommandLog(asset);
  294. }
  295. }).Build();
  296. #if TEST
  297. TestRoot.RunTests();
  298. #endif
  299. }
  300. private string modsString;
  301. private string InstalledMods()
  302. {
  303. if (modsString != null) return modsString;
  304. StringBuilder sb = new StringBuilder("Installed mods:");
  305. foreach (var plugin in PluginManager.Plugins)
  306. sb.Append("\n" + plugin.Name + " - " + plugin.Version);
  307. return modsString = sb.ToString();
  308. }
  309. public override void OnUpdate()
  310. {
  311. if (UnityEngine.Input.GetKeyDown(KeyCode.End))
  312. {
  313. Console.WriteLine("Pressed button to toggle console");
  314. FakeInput.CustomInput(new LocalCosmeticInputEntityComponent {commandLineToggleInput = true});
  315. }
  316. }
  317. [HarmonyPatch]
  318. public class MinimumSpecsPatch
  319. {
  320. public static bool Prefix()
  321. {
  322. return false;
  323. }
  324. public static MethodInfo TargetMethod()
  325. {
  326. return ((Action) MinimumSpecsCheck.CheckRequirementsMet).Method;
  327. }
  328. }
  329. }
  330. #endif
  331. }