From f53d0b63e7bada44db801bf0f86b16dc12a855c7 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Sat, 6 Nov 2021 04:10:00 +0100 Subject: [PATCH] Fix issues uncovered by the tests - Fixed the game enter API - Fixed ToggleTimeMode() placing the player underground by using the full switch animation - Fixed the menu enter/exit events only firing once due to the game keeping the menu loaded - Moved everything from AppEngine to GameMenuEngine for consistency - Reimplemented the Block.Static property, it should actually work again too - Made the block property test only use blocks placed during the previous test, improved error handling and temporarily disabled testing the Material and the Flipped properties as they cause the game to crash --- TechbloxModdingAPI/App/AppEngine.cs | 18 ----- TechbloxModdingAPI/App/Client.cs | 23 +++--- TechbloxModdingAPI/App/Game.cs | 2 +- TechbloxModdingAPI/App/GameGameEngine.cs | 3 +- TechbloxModdingAPI/App/GameMenuEngine.cs | 81 ++++++++++++++++--- TechbloxModdingAPI/Block.cs | 4 +- TechbloxModdingAPI/Blocks/BlockTests.cs | 32 ++++++-- .../Tests/TechbloxModdingAPIPluginTest.cs | 4 +- 8 files changed, 115 insertions(+), 52 deletions(-) diff --git a/TechbloxModdingAPI/App/AppEngine.cs b/TechbloxModdingAPI/App/AppEngine.cs index 3f89f26..e4f5dcb 100644 --- a/TechbloxModdingAPI/App/AppEngine.cs +++ b/TechbloxModdingAPI/App/AppEngine.cs @@ -40,23 +40,5 @@ namespace TechbloxModdingAPI.App get; private set; } = false; - - public Game[] GetMyGames() - { - EntityCollection mgsevs = entitiesDB.QueryEntities(MyGamesScreenExclusiveGroups.MyGames); - var mgsevsB = mgsevs.ToBuffer().buffer; - Game[] games = new Game[mgsevs.count]; - for (int i = 0; i < mgsevs.count; i++) - { - Utility.Logging.MetaDebugLog($"Found game named {mgsevsB[i].GameName}"); - games[i] = new Game(mgsevsB[i].ID); - } - return games; - } - } - - public struct MenuEventArgs - { - } } diff --git a/TechbloxModdingAPI/App/Client.cs b/TechbloxModdingAPI/App/Client.cs index 026b1a2..6fe4b4e 100644 --- a/TechbloxModdingAPI/App/Client.cs +++ b/TechbloxModdingAPI/App/Client.cs @@ -14,22 +14,19 @@ namespace TechbloxModdingAPI.App /// public class Client { - // extensible engine - protected static AppEngine appEngine = new AppEngine(); - - protected static Func ErrorHandlerInstanceGetter; + protected static Func ErrorHandlerInstanceGetter; protected static Action EnqueueError; protected static Action HandleErrorClosed; - /// + /// /// An event that fires whenever the main menu is loaded. /// public static event EventHandler EnterMenu { - add => appEngine.EnterMenu += value; - remove => appEngine.EnterMenu -= value; + add => Game.menuEngine.EnterMenu += value; + remove => Game.menuEngine.EnterMenu -= value; } /// @@ -37,8 +34,8 @@ namespace TechbloxModdingAPI.App /// public static event EventHandler ExitMenu { - add => appEngine.ExitMenu += value; - remove => appEngine.ExitMenu -= value; + add => Game.menuEngine.ExitMenu += value; + remove => Game.menuEngine.ExitMenu -= value; } /// @@ -69,8 +66,8 @@ namespace TechbloxModdingAPI.App { get { - if (!appEngine.IsInMenu) return new Game[0]; - return appEngine.GetMyGames(); + if (!Game.menuEngine.IsInMenu) return Array.Empty(); + return Game.menuEngine.GetMyGames(); } } @@ -80,7 +77,7 @@ namespace TechbloxModdingAPI.App /// true if in menu; false when loading or in a game. public bool InMenu { - get => appEngine.IsInMenu; + get => Game.menuEngine.IsInMenu; } /// @@ -119,8 +116,6 @@ namespace TechbloxModdingAPI.App /*HandleErrorClosed = (Action) AccessTools.Method("TechbloxModdingAPI.App.Client:GenHandlePopupClosed") .MakeGenericMethod(errorHandler) .Invoke(null, new object[0]);*/ - // register engines - MenuEngineManager.AddMenuEngine(appEngine); } // Creating delegates once is faster than reflection every time diff --git a/TechbloxModdingAPI/App/Game.cs b/TechbloxModdingAPI/App/Game.cs index 56854b6..7f52965 100644 --- a/TechbloxModdingAPI/App/Game.cs +++ b/TechbloxModdingAPI/App/Game.cs @@ -23,7 +23,7 @@ namespace TechbloxModdingAPI.App { // extensible engines protected static GameGameEngine gameEngine = new GameGameEngine(); - protected static GameMenuEngine menuEngine = new GameMenuEngine(); + protected internal static GameMenuEngine menuEngine = new GameMenuEngine(); protected static DebugInterfaceEngine debugOverlayEngine = new DebugInterfaceEngine(); protected static GameBuildSimEventEngine buildSimEventEngine = new GameBuildSimEventEngine(); diff --git a/TechbloxModdingAPI/App/GameGameEngine.cs b/TechbloxModdingAPI/App/GameGameEngine.cs index f1ec8e7..2c6e177 100644 --- a/TechbloxModdingAPI/App/GameGameEngine.cs +++ b/TechbloxModdingAPI/App/GameGameEngine.cs @@ -8,6 +8,7 @@ using Svelto.Tasks; using Svelto.Tasks.Lean; using RobocraftX.Blocks; using RobocraftX.ScreenshotTaker; +using Techblox.Environment.Transition; using Techblox.GameSelection; using TechbloxModdingAPI.Blocks; using TechbloxModdingAPI.Engines; @@ -100,7 +101,7 @@ namespace TechbloxModdingAPI.App { if (!entitiesDB.FoundInGroups()) throw new AppStateException("At least one block must exist in the world to enter simulation"); - TimeRunningModeUtil.ToggleTimeRunningState(entitiesDB); + SwitchAnimationUtil.Start(entitiesDB); } public EGID[] GetAllBlocksInGame(BlockIDs filter = BlockIDs.Invalid) diff --git a/TechbloxModdingAPI/App/GameMenuEngine.cs b/TechbloxModdingAPI/App/GameMenuEngine.cs index 2f65016..ac24e3a 100644 --- a/TechbloxModdingAPI/App/GameMenuEngine.cs +++ b/TechbloxModdingAPI/App/GameMenuEngine.cs @@ -1,8 +1,10 @@ using System; +using System.Reflection; using HarmonyLib; using RobocraftX; using RobocraftX.Common; +using RobocraftX.FrontEnd; using RobocraftX.GUI; using RobocraftX.GUI.MyGamesScreen; using Svelto.ECS; @@ -16,6 +18,9 @@ namespace TechbloxModdingAPI.App { public class GameMenuEngine : IFactoryEngine { + public WrappedHandler EnterMenu; + + public WrappedHandler ExitMenu; public IEntityFactory Factory { set; private get; } public string Name => "TechbloxModdingAPIGameInfoGameEngine"; @@ -24,23 +29,43 @@ namespace TechbloxModdingAPI.App public EntitiesDB entitiesDB { set; private get; } + public GameMenuEngine() + { + MenuEnteredEnginePatch.EnteredExitedMenu = () => + { + if (IsInMenu) + EnterMenu.Invoke(this, new MenuEventArgs { }); + else + ExitMenu.Invoke(this, new MenuEventArgs { }); + }; + } + public void Dispose() { - IsInMenu = false; } public void Ready() { - IsInMenu = true; + MenuEnteredEnginePatch.IsInMenu = true; // At first it uses ActivateMenu(), then GoToMenu() which is patched + MenuEnteredEnginePatch.EnteredExitedMenu(); } // game functionality - public bool IsInMenu + public bool IsInMenu => MenuEnteredEnginePatch.IsInMenu; + + public Game[] GetMyGames() { - get; - private set; - } = false; + EntityCollection mgsevs = entitiesDB.QueryEntities(MyGamesScreenExclusiveGroups.MyGames); + var mgsevsB = mgsevs.ToBuffer().buffer; + Game[] games = new Game[mgsevs.count]; + for (int i = 0; i < mgsevs.count; i++) + { + Utility.Logging.MetaDebugLog($"Found game named {mgsevsB[i].GameName}"); + games[i] = new Game(mgsevsB[i].ID); + } + return games; + } public bool CreateMyGame(EGID id, string path = "", uint thumbnailId = 0, string gameName = "", string creatorName = "", string description = "", long createdDate = 0L) { @@ -77,10 +102,10 @@ namespace TechbloxModdingAPI.App { if (!ExistsGameInfo(id)) return false; ref MyGameDataEntityStruct mgdes = ref GetGameInfo(id); - return EnterGame(mgdes.GameName, mgdes.SavedGamePath); + return EnterGame(mgdes.GameName, mgdes.FileId); } - public bool EnterGame(string gameName, string path, bool autoEnterSim = false) + public bool EnterGame(string gameName, string fileId, bool autoEnterSim = false) { GameMode.CurrentMode = autoEnterSim ? RCXMode.Play : RCXMode.Build; var data = new GameSelectionData @@ -89,7 +114,8 @@ namespace TechbloxModdingAPI.App gameType = GameType.MachineEditor, saveName = gameName, saveType = SaveType.ExistingSave, - gameID = path + gameID = "GAMEID_Road_Track", //TODO: Expose to the API + userContentID = fileId }; // the private FullGameCompositionRoot.SwitchToGame() method gets passed to menu items for this reason AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToGame").Invoke(FullGameFields.Instance, new object[]{data}); @@ -138,4 +164,41 @@ namespace TechbloxModdingAPI.App } internal class MyGameDataEntityDescriptor_DamnItFJWhyDidYouMakeThisInternal : GenericEntityDescriptor { } + + [HarmonyPatch] + static class MenuEnteredEnginePatch + { + internal static bool IsInMenu; + internal static Action EnteredExitedMenu; + public static void Postfix() + { + IsInMenu = true; + EnteredExitedMenu(); + } + + public static MethodBase TargetMethod() + { + return AccessTools.Method(typeof(FullGameCompositionRoot), "GoToMenu"); + } + } + + [HarmonyPatch] + static class MenuExitedEnginePatch + { + public static void Prefix() + { + MenuEnteredEnginePatch.IsInMenu = false; + MenuEnteredEnginePatch.EnteredExitedMenu(); + } + + public static MethodBase TargetMethod() + { + return AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToGame"); + } + } + + public struct MenuEventArgs + { + + } } diff --git a/TechbloxModdingAPI/Block.cs b/TechbloxModdingAPI/Block.cs index c2f8dd8..0d730c5 100644 --- a/TechbloxModdingAPI/Block.cs +++ b/TechbloxModdingAPI/Block.cs @@ -386,8 +386,8 @@ namespace TechbloxModdingAPI /// public bool Static { - get => false; - set { } + get => BlockEngine.GetBlockInfo(this).isStatic; + set => BlockEngine.GetBlockInfo(this).isStatic = value; } /// diff --git a/TechbloxModdingAPI/Blocks/BlockTests.cs b/TechbloxModdingAPI/Blocks/BlockTests.cs index f920631..198011c 100644 --- a/TechbloxModdingAPI/Blocks/BlockTests.cs +++ b/TechbloxModdingAPI/Blocks/BlockTests.cs @@ -46,16 +46,20 @@ namespace TechbloxModdingAPI.Blocks "Block ID enum matches the known block types."); } + private static Block[] blocks; // Store placed blocks as some blocks are already present as the workshop and the game save [APITestCase(TestType.EditMode)] public static void TestBlockIDs() { float3 pos = new float3(); - foreach (BlockIDs id in Enum.GetValues(typeof(BlockIDs))) + var values = Enum.GetValues(typeof(BlockIDs)); + blocks = new Block[values.Length - 1]; // Minus the invalid ID + int i = 0; + foreach (BlockIDs id in values) { if (id == BlockIDs.Invalid) continue; try { - Block.PlaceNew(id, pos); + blocks[i++] = Block.PlaceNew(id, pos); pos += 0.2f; } catch (Exception e) @@ -71,8 +75,9 @@ namespace TechbloxModdingAPI.Blocks [APITestCase(TestType.EditMode)] public static IEnumerator TestBlockProperties() { //Uses the result of the previous test case - var blocks = Game.CurrentGame().GetBlocksInGame(); yield return Yield.It; + if (blocks is null) + yield break; for (var index = 0; index < blocks.Length; index++) { if (index % 50 == 0) yield return Yield.It; //The material or flipped status can only be changed 130 times per submission @@ -80,6 +85,7 @@ namespace TechbloxModdingAPI.Blocks if (!block.Exists) continue; foreach (var property in block.GetType().GetProperties()) { + if (property.Name == "Material" || property.Name == "Flipped") continue; // TODO: Crashes in game //Includes specialised block properties if (property.SetMethod == null) continue; var testValues = new (Type, object, Predicate)[] @@ -121,8 +127,24 @@ namespace TechbloxModdingAPI.Blocks yield break; } - property.SetValue(block, valueToUse); - object got = property.GetValue(block); + try + { + property.SetValue(block, valueToUse); + } + catch (Exception e) + { + Assert.Fail($"Failed to set property {block.GetType().Name}.{property.Name} to {valueToUse}\n{e}"); + } + object got; + try + { + got = property.GetValue(block); + } + catch (Exception e) + { + Assert.Fail($"Failed to get property {block.GetType().Name}.{property.Name}\n{e}"); + continue; + } var attr = property.GetCustomAttribute(); if (!predicateToUse(got) && (attr == null || !Equals(attr.PossibleValue, got))) { diff --git a/TechbloxModdingAPI/Tests/TechbloxModdingAPIPluginTest.cs b/TechbloxModdingAPI/Tests/TechbloxModdingAPIPluginTest.cs index 1557f53..75d0cc4 100644 --- a/TechbloxModdingAPI/Tests/TechbloxModdingAPIPluginTest.cs +++ b/TechbloxModdingAPI/Tests/TechbloxModdingAPIPluginTest.cs @@ -215,10 +215,10 @@ namespace TechbloxModdingAPI.Tests .Action(() => Player.LocalPlayer.GetBlockLookedAt().Static = true).Build(); Game.AddPersistentDebugInfo("InstalledMods", InstalledMods); - Block.Placed += (sender, args) => + /*Block.Placed += (sender, args) => Logging.MetaDebugLog("Placed block " + args.Block); Block.Removed += (sender, args) => - Logging.MetaDebugLog("Removed block " + args.Block); + Logging.MetaDebugLog("Removed block " + args.Block);*/ } // dependency test