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.

460 lines
16KB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Reflection;
  7. using System.Reflection.Emit;
  8. using System.Text;
  9. using HarmonyLib;
  10. using IllusionInjector;
  11. // test
  12. using GPUInstancer;
  13. using Svelto.ECS;
  14. using RobocraftX.Blocks;
  15. using RobocraftX.Common;
  16. using RobocraftX.SimulationModeState;
  17. using RobocraftX.FrontEnd;
  18. using Unity.Mathematics;
  19. using UnityEngine;
  20. using RobocraftX.Schedulers;
  21. using Svelto.Tasks.ExtraLean;
  22. using uREPL;
  23. using GamecraftModdingAPI.Commands;
  24. using GamecraftModdingAPI.Events;
  25. using GamecraftModdingAPI.Utility;
  26. using GamecraftModdingAPI.Blocks;
  27. using GamecraftModdingAPI.Players;
  28. using EventType = GamecraftModdingAPI.Events.EventType;
  29. namespace GamecraftModdingAPI.Tests
  30. {
  31. #if DEBUG
  32. // unused by design
  33. /// <summary>
  34. /// Modding API implemented as a standalone IPA Plugin.
  35. /// Ideally, GamecraftModdingAPI should be loaded by another mod; not itself
  36. /// </summary>
  37. public class GamecraftModdingAPIPluginTest : IllusionPlugin.IEnhancedPlugin
  38. {
  39. private static Harmony harmony { get; set; }
  40. public override string Name { get; } = Assembly.GetExecutingAssembly().GetName().Name;
  41. public override string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString();
  42. public string HarmonyID { get; } = "org.git.exmods.modtainers.gamecraftmoddingapi";
  43. public override void OnApplicationQuit()
  44. {
  45. GamecraftModdingAPI.Main.Shutdown();
  46. }
  47. public override void OnApplicationStart()
  48. {
  49. FileLog.Reset();
  50. Harmony.DEBUG = true;
  51. GamecraftModdingAPI.Main.Init();
  52. Logging.MetaDebugLog($"Version group id {(uint)ApiExclusiveGroups.versionGroup}");
  53. // in case Steam is not installed/running
  54. // this will crash the game slightly later during startup
  55. //SteamInitPatch.ForcePassSteamCheck = true;
  56. // in case running in a VM
  57. //MinimumSpecsCheckPatch.ForcePassMinimumSpecCheck = true;
  58. // disable some Gamecraft analytics
  59. //AnalyticsDisablerPatch.DisableAnalytics = true;
  60. // disable background music
  61. Logging.MetaDebugLog("Audio Mixers: " + string.Join(",", AudioTools.GetMixers()));
  62. //AudioTools.SetVolume(0.0f, "Music"); // The game now sets this from settings again after this is called :(
  63. //Utility.VersionTracking.Enable();//(very) unstable
  64. // debug/test handlers
  65. #pragma warning disable 0612
  66. HandlerBuilder.Builder()
  67. .Name("appinit API debug")
  68. .Handle(EventType.ApplicationInitialized)
  69. .OnActivation(() => { Logging.Log("App Inited event!"); })
  70. .Build();
  71. HandlerBuilder.Builder("menuact API debug")
  72. .Handle(EventType.Menu)
  73. .OnActivation(() => { Logging.Log("Menu Activated event!"); })
  74. .OnDestruction(() => { Logging.Log("Menu Destroyed event!"); })
  75. .Build();
  76. HandlerBuilder.Builder("menuswitch API debug")
  77. .Handle(EventType.MenuSwitchedTo)
  78. .OnActivation(() => { Logging.Log("Menu Switched To event!"); })
  79. .Build();
  80. HandlerBuilder.Builder("gameact API debug")
  81. .Handle(EventType.Menu)
  82. .OnActivation(() => { Logging.Log("Game Activated event!"); })
  83. .OnDestruction(() => { Logging.Log("Game Destroyed event!"); })
  84. .Build();
  85. HandlerBuilder.Builder("gamerel API debug")
  86. .Handle(EventType.GameReloaded)
  87. .OnActivation(() => { Logging.Log("Game Reloaded event!"); })
  88. .Build();
  89. HandlerBuilder.Builder("gameswitch API debug")
  90. .Handle(EventType.GameSwitchedTo)
  91. .OnActivation(() => { Logging.Log("Game Switched To event!"); })
  92. .Build();
  93. HandlerBuilder.Builder("simulationswitch API debug")
  94. .Handle(EventType.SimulationSwitchedTo)
  95. .OnActivation(() => { Logging.Log("Game Mode Simulation Switched To event!"); })
  96. .Build();
  97. HandlerBuilder.Builder("buildswitch API debug")
  98. .Handle(EventType.BuildSwitchedTo)
  99. .OnActivation(() => { Logging.Log("Game Mode Build Switched To event!"); })
  100. .Build();
  101. HandlerBuilder.Builder("menu activated API error thrower test")
  102. .Handle(EventType.Menu)
  103. .OnActivation(() => { throw new Exception("Event Handler always throws an exception!"); })
  104. .Build();
  105. #pragma warning restore 0612
  106. /*HandlerBuilder.Builder("enter game from menu test")
  107. .Handle(EventType.Menu)
  108. .OnActivation(() =>
  109. {
  110. Tasks.Scheduler.Schedule(new Tasks.Repeatable(enterGame, shouldRetry, 0.2f));
  111. })
  112. .Build();*/
  113. // debug/test commands
  114. if (Dependency.Hell("ExtraCommands"))
  115. {
  116. CommandBuilder.Builder()
  117. .Name("Exit")
  118. .Description("Close Gamecraft immediately, without any prompts")
  119. .Action(() => { UnityEngine.Application.Quit(); })
  120. .Build();
  121. CommandBuilder.Builder()
  122. .Name("SetFOV")
  123. .Description("Set the player camera's field of view")
  124. .Action((float d) => { UnityEngine.Camera.main.fieldOfView = d; })
  125. .Build();
  126. CommandBuilder.Builder()
  127. .Name("MoveLastBlock")
  128. .Description("Move the most-recently-placed block, and any connected blocks by the given offset")
  129. .Action((float x, float y, float z) =>
  130. {
  131. if (GameState.IsBuildMode())
  132. foreach (var block in Block.GetLastPlacedBlock().GetConnectedCubes())
  133. block.Position += new Unity.Mathematics.float3(x, y, z);
  134. else
  135. GamecraftModdingAPI.Utility.Logging.CommandLogError("Blocks can only be moved in Build mode!");
  136. }).Build();
  137. CommandBuilder.Builder()
  138. .Name("PlaceAluminium")
  139. .Description("Place a block of aluminium at the given coordinates")
  140. .Action((float x, float y, float z) =>
  141. {
  142. var block = Block.PlaceNew(BlockIDs.AluminiumCube, new float3(x, y, z));
  143. Logging.CommandLog("Block placed with type: " + block.Type);
  144. })
  145. .Build();
  146. CommandBuilder.Builder()
  147. .Name("PlaceAluminiumLots")
  148. .Description("Place a lot of blocks of aluminium at the given coordinates")
  149. .Action((float x, float y, float z) =>
  150. {
  151. Logging.CommandLog("Starting...");
  152. var sw = Stopwatch.StartNew();
  153. for (int i = 0; i < 100; i++)
  154. for (int j = 0; j < 100; j++)
  155. Block.PlaceNew(BlockIDs.AluminiumCube, new float3(x + i, y, z + j));
  156. //Block.Sync();
  157. sw.Stop();
  158. Logging.CommandLog("Finished in " + sw.ElapsedMilliseconds + "ms");
  159. })
  160. .Build();
  161. Block b = null;
  162. CommandBuilder.Builder("moveBlockInSim", "Run in build mode first while looking at a block, then in sim to move it up")
  163. .Action(() =>
  164. {
  165. if (b == null)
  166. {
  167. b = new Player(PlayerType.Local).GetBlockLookedAt();
  168. Logging.CommandLog("Block saved: " + b);
  169. }
  170. else
  171. Logging.CommandLog("Block moved to: " + (b.GetSimBody().Position += new float3(0, 2, 0)));
  172. }).Build();
  173. CommandBuilder.Builder("Error", "Throw an error to make sure SimpleCustomCommandEngine's wrapper catches it.")
  174. .Action(() => { throw new Exception("Error Command always throws an error"); })
  175. .Build();
  176. CommandBuilder.Builder("ColorBlock",
  177. "Change color of the block looked at if there's any.")
  178. .Action<string>(str =>
  179. {
  180. if (!Enum.TryParse(str, out BlockColors color))
  181. {
  182. Logging.CommandLog("Color " + str + " not found! Interpreting as 4 color values.");
  183. var s = str.Split(' ');
  184. new Player(PlayerType.Local).GetBlockLookedAt().CustomColor = new float4(float.Parse(s[0]),
  185. float.Parse(s[1]), float.Parse(s[2]), float.Parse(s[3]));
  186. return;
  187. }
  188. new Player(PlayerType.Local).GetBlockLookedAt().Color =
  189. new BlockColor { Color = color };
  190. Logging.CommandLog("Colored block to " + color);
  191. }).Build();
  192. CommandBuilder.Builder("GetBlockByID", "Gets a block based on its object identifier and teleports it up.")
  193. .Action<char>(ch =>
  194. {
  195. foreach (var body in SimBody.GetFromObjectID(ch))
  196. {
  197. Logging.CommandLog("SimBody: " + body);
  198. body.Position += new float3(0, 10, 0);
  199. foreach (var bodyConnectedBody in body.GetConnectedBodies())
  200. {
  201. Logging.CommandLog("Moving " + bodyConnectedBody);
  202. bodyConnectedBody.Position += new float3(0, 10, 0);
  203. }
  204. }
  205. }).Build();
  206. CommandBuilder.Builder()
  207. .Name("PlaceConsole")
  208. .Description("Place a bunch of console block with a given text - entering simulation with them crashes the game as the cmd doesn't exist")
  209. .Action((float x, float y, float z) =>
  210. {
  211. Stopwatch sw = new Stopwatch();
  212. sw.Start();
  213. for (int i = 0; i < 100; i++)
  214. {
  215. for (int j = 0; j < 100; j++)
  216. {
  217. var block = Block.PlaceNew<ConsoleBlock>(BlockIDs.ConsoleBlock,
  218. new float3(x + i, y, z + j));
  219. block.Command = "test_command";
  220. }
  221. }
  222. sw.Stop();
  223. Logging.CommandLog($"Blocks placed in {sw.ElapsedMilliseconds} ms");
  224. })
  225. .Build();
  226. CommandBuilder.Builder()
  227. .Name("WireTest")
  228. .Description("Place two blocks and then wire them together")
  229. .Action(() =>
  230. {
  231. LogicGate notBlock = Block.PlaceNew<LogicGate>(BlockIDs.NOTLogicBlock, new float3(1, 2, 0));
  232. LogicGate andBlock = Block.PlaceNew<LogicGate>(BlockIDs.ANDLogicBlock, new float3(2, 2, 0));
  233. // connect NOT Gate output to AND Gate input #2 (ports are zero-indexed, so 1 is 2nd position and 0 is 1st position)
  234. Wire conn = notBlock.Connect(0, andBlock, 1);
  235. Logging.CommandLog(conn.ToString());
  236. })
  237. .Build();
  238. CommandBuilder.Builder("TestChunkHealth", "Sets the chunk looked at to the given health.")
  239. .Action((float val, float max) =>
  240. {
  241. var body = new Player(PlayerType.Local).GetSimBodyLookedAt();
  242. if (body == null) return;
  243. body.CurrentHealth = val;
  244. body.InitialHealth = max;
  245. Logging.CommandLog("Health set to: " + val);
  246. }).Build();
  247. CommandBuilder.Builder("placeBlockGroup", "Places some blocks in a group")
  248. .Action((float x, float y, float z) =>
  249. {
  250. var pos = new float3(x, y, z);
  251. var group = BlockGroup.Create(Block.PlaceNew(BlockIDs.AluminiumCube, pos,
  252. color: BlockColors.Aqua));
  253. Block.PlaceNew(BlockIDs.AluminiumCube, pos += new float3(1, 0, 0), color: BlockColors.Blue)
  254. .BlockGroup = group;
  255. Block.PlaceNew(BlockIDs.AluminiumCube, pos += new float3(1, 0, 0), color: BlockColors.Green)
  256. .BlockGroup = group;
  257. Block.PlaceNew(BlockIDs.AluminiumCube, pos += new float3(1, 0, 0), color: BlockColors.Lime)
  258. .BlockGroup = group;
  259. }).Build();
  260. CommandBuilder.Builder("placeCustomBlock", "Places a custom block, needs a custom catalog and assets.")
  261. .Action((float x, float y, float z) =>
  262. {
  263. Logging.CommandLog("Block placed: " +
  264. Block.PlaceNew<TestBlock>((BlockIDs) 500, new float3(0, 0, 0)));
  265. }).Build();
  266. GameClient.SetDebugInfo("InstalledMods", InstalledMods);
  267. Block.Placed += (sender, args) =>
  268. Logging.MetaDebugLog("Placed block " + args.Type + " with ID " + args.ID);
  269. Block.Removed += (sender, args) =>
  270. Logging.MetaDebugLog("Removed block " + args.Type + " with ID " + args.ID);
  271. /*
  272. CommandManager.AddCommand(new SimpleCustomCommandEngine<float>((float d) => { UnityEngine.Camera.main.fieldOfView = d; },
  273. "SetFOV", "Set the player camera's field of view"));
  274. CommandManager.AddCommand(new SimpleCustomCommandEngine<float, float, float>(
  275. (x, y, z) => {
  276. bool success = GamecraftModdingAPI.Blocks.Movement.MoveConnectedBlocks(
  277. GamecraftModdingAPI.Blocks.BlockIdentifiers.LatestBlockID,
  278. new Unity.Mathematics.float3(x, y, z));
  279. if (!success)
  280. {
  281. GamecraftModdingAPI.Utility.Logging.CommandLogError("Blocks can only be moved in Build mode!");
  282. }
  283. }, "MoveLastBlock", "Move the most-recently-placed block, and any connected blocks by the given offset"));
  284. CommandManager.AddCommand(new SimpleCustomCommandEngine<float, float, float>(
  285. (x, y, z) => { Blocks.Placement.PlaceBlock(Blocks.BlockIDs.AluminiumCube, new Unity.Mathematics.float3(x, y, z)); },
  286. "PlaceAluminium", "Place a block of aluminium at the given coordinates"));
  287. System.Random random = new System.Random(); // for command below
  288. CommandManager.AddCommand(new SimpleCustomCommandEngine(
  289. () => {
  290. if (!GameState.IsSimulationMode())
  291. {
  292. Logging.CommandLogError("You must be in simulation mode for this to work!");
  293. return;
  294. }
  295. Tasks.Repeatable task = new Tasks.Repeatable(() => {
  296. uint count = 0;
  297. EGID[] eBlocks = Blocks.Signals.GetElectricBlocks();
  298. for (uint i = 0u; i < eBlocks.Length; i++)
  299. {
  300. uint[] ids = Blocks.Signals.GetSignalIDs(eBlocks[i]);
  301. for (uint j = 0u; j < ids.Length; j++)
  302. {
  303. Blocks.Signals.SetSignalByID(ids[j], (float)random.NextDouble());
  304. count++;
  305. }
  306. }
  307. Logging.MetaDebugLog($"Did the thing on {count} inputs");
  308. },
  309. () => { return GameState.IsSimulationMode(); });
  310. Tasks.Scheduler.Schedule(task);
  311. }, "RandomizeSignalsInputs", "Do the thing"));
  312. */
  313. }
  314. // dependency test
  315. if (Dependency.Hell("GamecraftScripting", new Version("0.0.1.0")))
  316. {
  317. Logging.LogWarning("You're in GamecraftScripting dependency hell");
  318. }
  319. else
  320. {
  321. Logging.Log("Compatible GamecraftScripting detected");
  322. }
  323. CommandBuilder.Builder("enableCompletions")
  324. .Action(() =>
  325. {
  326. var p = Window.selected.main.parameters;
  327. p.useCommandCompletion = true;
  328. p.useMonoCompletion = true;
  329. p.useGlobalClassCompletion = true;
  330. Log.Output("Submitted: " + Window.selected.submittedCode);
  331. })
  332. .Build();
  333. CustomBlock.Prep().RunOn(ExtraLean.UIScheduler);
  334. try
  335. {
  336. CustomBlock.RegisterCustomBlock<TestBlock>();
  337. Logging.MetaDebugLog("Registered test custom block");
  338. }
  339. catch (FileNotFoundException)
  340. {
  341. Logging.MetaDebugLog("Test custom block catalog not found");
  342. }
  343. #if TEST
  344. TestRoot.RunTests();
  345. #endif
  346. }
  347. private string modsString;
  348. private string InstalledMods()
  349. {
  350. if (modsString != null) return modsString;
  351. StringBuilder sb = new StringBuilder("Installed mods:");
  352. foreach (var plugin in PluginManager.Plugins)
  353. sb.Append("\n" + plugin.Name + " - " + plugin.Version);
  354. return modsString = sb.ToString();
  355. }
  356. private bool retry = true;
  357. private bool shouldRetry()
  358. {
  359. return retry;
  360. }
  361. private void enterGame()
  362. {
  363. App.Client app = new App.Client();
  364. App.Game[] myGames = app.MyGames;
  365. Logging.MetaDebugLog($"MyGames count {myGames.Length}");
  366. if (myGames.Length != 0)
  367. {
  368. Logging.MetaDebugLog($"MyGames[0] EGID {myGames[0].EGID}");
  369. retry = false;
  370. try
  371. {
  372. //myGames[0].Description = "test msg pls ignore"; // make sure game exists first
  373. Logging.MetaDebugLog($"Entering game {myGames[0].Name}");
  374. myGames[0].EnterGame();
  375. }
  376. catch (Exception e)
  377. {
  378. Logging.MetaDebugLog($"Failed to enter game; exception: {e}");
  379. retry = true;
  380. }
  381. }
  382. else
  383. {
  384. Logging.MetaDebugLog("MyGames not populated yet :(");
  385. }
  386. }
  387. [HarmonyPatch]
  388. public class MinimumSpecsPatch
  389. {
  390. public static bool Prefix()
  391. {
  392. return false;
  393. }
  394. public static MethodInfo TargetMethod()
  395. {
  396. return ((Action) MinimumSpecsCheck.CheckRequirementsMet).Method;
  397. }
  398. }
  399. [CustomBlock("customCatalog.json", "Assets/Prefabs/Cube.prefab", "strAluminiumCube", SortIndex = 12)]
  400. public class TestBlock : CustomBlock
  401. {
  402. public TestBlock(EGID id) : base(id)
  403. {
  404. }
  405. public TestBlock(uint id) : base(id)
  406. {
  407. }
  408. }
  409. }
  410. #endif
  411. }