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.

407 lines
15KB

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