From 5602ef9268f023bf761006aae22548eeaaf04408 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Tue, 28 Dec 2021 15:09:01 +0100 Subject: [PATCH] All kinds of fixes of issues during automatic tests - Fixed toggling time running mode - Fixed closing popups - Added support for pressing the buttons on a popup - Added error handling to Main.Init() - Automatically closing the beta message in the test plugin - Fixed Game.EnterGame() causing a crash in the game --- TechbloxModdingAPI/App/AppEngine.cs | 44 ---------- TechbloxModdingAPI/App/Client.cs | 59 +++++++++---- TechbloxModdingAPI/App/ClientAlertTest.cs | 20 +++-- .../App/GameBuildSimEventEngine.cs | 2 + TechbloxModdingAPI/App/GameGameEngine.cs | 33 +++++--- TechbloxModdingAPI/App/GameMenuEngine.cs | 32 ++++--- TechbloxModdingAPI/Input/FakeInput.cs | 2 +- TechbloxModdingAPI/Main.cs | 84 ++++++++++++------- .../Tests/TechbloxModdingAPIPluginTest.cs | 5 ++ TechbloxModdingAPI/Tests/TestRoot.cs | 12 ++- TechbloxModdingAPI/Utility/FullGameFields.cs | 18 ++++ 11 files changed, 188 insertions(+), 123 deletions(-) delete mode 100644 TechbloxModdingAPI/App/AppEngine.cs diff --git a/TechbloxModdingAPI/App/AppEngine.cs b/TechbloxModdingAPI/App/AppEngine.cs deleted file mode 100644 index e4f5dcb..0000000 --- a/TechbloxModdingAPI/App/AppEngine.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; - -using RobocraftX.GUI.MyGamesScreen; -using Svelto.ECS; -using TechbloxModdingAPI.Engines; -using TechbloxModdingAPI.Utility; - -namespace TechbloxModdingAPI.App -{ - public class AppEngine : IFactoryEngine - { - public WrappedHandler EnterMenu; - - public WrappedHandler ExitMenu; - - public IEntityFactory Factory { set; private get; } - - public string Name => "TechbloxModdingAPIAppEngine"; - - public bool isRemovable => false; - - public EntitiesDB entitiesDB { set; private get; } - - public void Dispose() - { - IsInMenu = false; - ExitMenu.Invoke(this, new MenuEventArgs { }); - } - - public void Ready() - { - IsInMenu = true; - EnterMenu.Invoke(this, new MenuEventArgs { }); - } - - // app functionality - - public bool IsInMenu - { - get; - private set; - } = false; - } -} diff --git a/TechbloxModdingAPI/App/Client.cs b/TechbloxModdingAPI/App/Client.cs index 6fe4b4e..89f486f 100644 --- a/TechbloxModdingAPI/App/Client.cs +++ b/TechbloxModdingAPI/App/Client.cs @@ -14,12 +14,12 @@ namespace TechbloxModdingAPI.App /// public class Client { + public static Client Instance { get; } = new Client(); + protected static Func ErrorHandlerInstanceGetter; protected static Action EnqueueError; - protected static Action HandleErrorClosed; - /// /// An event that fires whenever the main menu is loaded. /// @@ -93,14 +93,31 @@ namespace TechbloxModdingAPI.App EnqueueError(errorHandlerInstance, popup); } - // TODO - /*public void CloseCurrentPrompt() + public void CloseCurrentPrompt() + { + object errorHandlerInstance = ErrorHandlerInstanceGetter(); + var popup = GetPopupCloseMethods(errorHandlerInstance); + popup.Close(); + } + + public void SelectFirstPromptButton() + { + object errorHandlerInstance = ErrorHandlerInstanceGetter(); + var popup = GetPopupCloseMethods(errorHandlerInstance); + popup.FirstButton(); + } + + public void SelectSecondPromptButton() { - // RobocraftX.Services.ErrorHandler.Instance.HandlePopupClosed(); - // FIXME: this is a call that is also called when closing, not the actual closing action itself (so it doesn't work) object errorHandlerInstance = ErrorHandlerInstanceGetter(); - HandleErrorClosed(errorHandlerInstance); - }*/ + var popup = GetPopupCloseMethods(errorHandlerInstance); + popup.SecondButton(); + } + + internal void CloseBetaPopup() + { + Game.menuEngine.CloseBetaPopup(); + } internal static void Init() { @@ -113,9 +130,6 @@ namespace TechbloxModdingAPI.App EnqueueError = (Action) AccessTools.Method("TechbloxModdingAPI.App.Client:GenEnqueueError") .MakeGenericMethod(errorHandler, errorHandle) .Invoke(null, new object[0]); - /*HandleErrorClosed = (Action) AccessTools.Method("TechbloxModdingAPI.App.Client:GenHandlePopupClosed") - .MakeGenericMethod(errorHandler) - .Invoke(null, new object[0]);*/ } // Creating delegates once is faster than reflection every time @@ -140,14 +154,23 @@ namespace TechbloxModdingAPI.App return enqueueCasted; } - private static Action GenHandlePopupClosed() + private static (Action Close, Action FirstButton, Action SecondButton) _errorPopup; + + private static (Action Close, Action FirstButton, Action SecondButton) GetPopupCloseMethods(object handler) { - Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler"); - MethodInfo handlePopupClosed = AccessTools.Method(errorHandler, "HandlePopupClosed"); - Action handleSimple = - (Action) Delegate.CreateDelegate(typeof(Action), handlePopupClosed); - Action handleCasted = (object instance) => handleSimple((T) instance); - return handleCasted; + if (_errorPopup.Close != null) + return _errorPopup; + Type errorHandler = handler.GetType(); + FieldInfo field = AccessTools.Field(errorHandler, "errorPopup"); + var errorPopup = (ErrorPopup)field.GetValue(handler); + MethodInfo info = AccessTools.Method(errorPopup.GetType(), "ClosePopup"); + var close = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info); + info = AccessTools.Method(errorPopup.GetType(), "HandleFirstOption"); + var first = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info); + info = AccessTools.Method(errorPopup.GetType(), "HandleSecondOption"); + var second = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info); + _errorPopup = (close, first, second); + return _errorPopup; } } } diff --git a/TechbloxModdingAPI/App/ClientAlertTest.cs b/TechbloxModdingAPI/App/ClientAlertTest.cs index 20d8201..278a826 100644 --- a/TechbloxModdingAPI/App/ClientAlertTest.cs +++ b/TechbloxModdingAPI/App/ClientAlertTest.cs @@ -42,17 +42,25 @@ namespace TechbloxModdingAPI.App [APITestCase(TestType.Menu)] public static void TestPopUp2() { - Client c = new Client(); - c.PromptUser(popup2); - //c.CloseCurrentPrompt(); + Client.Instance.PromptUser(popup2); } [APITestCase(TestType.Menu)] public static void TestPopUp1() { - Client c = new Client(); - c.PromptUser(popup1); - //c.CloseCurrentPrompt(); + Client.Instance.PromptUser(popup1); + } + + [APITestCase(TestType.Menu)] + public static void TestPopUpClose1() + { + Client.Instance.CloseCurrentPrompt(); + } + + [APITestCase(TestType.Menu)] + public static void TestPopUpClose2() + { + Client.Instance.CloseCurrentPrompt(); } } #endif diff --git a/TechbloxModdingAPI/App/GameBuildSimEventEngine.cs b/TechbloxModdingAPI/App/GameBuildSimEventEngine.cs index 67e6769..1846dd2 100644 --- a/TechbloxModdingAPI/App/GameBuildSimEventEngine.cs +++ b/TechbloxModdingAPI/App/GameBuildSimEventEngine.cs @@ -27,12 +27,14 @@ namespace TechbloxModdingAPI.App public JobHandle OnInitializeTimeRunningMode(JobHandle inputDeps) { + Console.WriteLine("Init time running mode"); SimulationMode.Invoke(this, new GameEventArgs { GameName = "", GamePath = "" }); // TODO return inputDeps; } public JobHandle OnInitializeTimeStoppedMode(JobHandle inputDeps) { + Console.WriteLine("Init time stopped mode"); BuildMode.Invoke(this, new GameEventArgs { GameName = "", GamePath = "" }); return inputDeps; } diff --git a/TechbloxModdingAPI/App/GameGameEngine.cs b/TechbloxModdingAPI/App/GameGameEngine.cs index 46167a4..279b170 100644 --- a/TechbloxModdingAPI/App/GameGameEngine.cs +++ b/TechbloxModdingAPI/App/GameGameEngine.cs @@ -1,6 +1,7 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; +using HarmonyLib; +using RobocraftX; using RobocraftX.Common; using RobocraftX.Schedulers; using RobocraftX.SimulationModeState; @@ -9,11 +10,14 @@ using Svelto.Tasks; using Svelto.Tasks.Lean; using RobocraftX.Blocks; using RobocraftX.Common.Loading; +using RobocraftX.Multiplayer; using RobocraftX.ScreenshotTaker; using Techblox.Environment.Transition; using Techblox.GameSelection; + using TechbloxModdingAPI.Blocks; using TechbloxModdingAPI.Engines; +using TechbloxModdingAPI.Input; using TechbloxModdingAPI.Players; using TechbloxModdingAPI.Utility; @@ -52,9 +56,7 @@ namespace TechbloxModdingAPI.App private void OnPlayerJoined(object sender, PlayerEventArgs args) { - Console.WriteLine("Player joined: " + args.PlayerId + " asd"); if (args.Player.Type != PlayerType.Local) return; - Console.WriteLine("Player joined is local asd"); playerJoined = true; Player.Joined -= OnPlayerJoined; CheckJoinEvent(); @@ -112,10 +114,23 @@ namespace TechbloxModdingAPI.App public void ToggleTimeMode() { - if (!entitiesDB.FoundInGroups()) - throw new AppStateException("At least one block must exist in the world to enter simulation"); - SwitchAnimationUtil.Start(entitiesDB); - } + if (TimeRunningModeUtil.IsTimeStoppedMode(entitiesDB)) + FakeInput.ActionInput(toggleMode: true); + else + { + IEnumerator ReloadBuildModeTask() + { + SwitchAnimationUtil.Start(entitiesDB); + while (SwitchAnimationUtil.IsFadeOutActive(entitiesDB)) + yield return (TaskContract)Yield.It; + FullGameFields._multiplayerParams.MultiplayerMode = MultiplayerMode.SinglePlayer; + AccessTools.Method(typeof(FullGameCompositionRoot), "ReloadGame") + .Invoke(FullGameFields.Instance, new object[] { }); + } + + ReloadBuildModeTask().RunOn(ClientLean.UIScheduler); + } + } public EGID[] GetAllBlocksInGame(BlockIDs filter = BlockIDs.Invalid) { @@ -162,13 +177,11 @@ namespace TechbloxModdingAPI.App if (!enteredGame) return; enteredGame = false; loadingFinished = true; - Console.WriteLine("Loading finished - asd"); CheckJoinEvent(); } private void CheckJoinEvent() { - Console.WriteLine($"Check: {loadingFinished} {playerJoined}"); if (!loadingFinished || !playerJoined) return; EnterGame.Invoke(this, new GameEventArgs { GameName = GetGameData().saveName, GamePath = GetGameData().gameID }); IsInGame = true; diff --git a/TechbloxModdingAPI/App/GameMenuEngine.cs b/TechbloxModdingAPI/App/GameMenuEngine.cs index 276bda8..533b112 100644 --- a/TechbloxModdingAPI/App/GameMenuEngine.cs +++ b/TechbloxModdingAPI/App/GameMenuEngine.cs @@ -5,6 +5,7 @@ using HarmonyLib; using RobocraftX; using RobocraftX.GUI; using RobocraftX.GUI.MyGamesScreen; +using RobocraftX.Multiplayer; using Svelto.ECS; using Svelto.ECS.Experimental; using Techblox.GameSelection; @@ -103,19 +104,16 @@ namespace TechbloxModdingAPI.App return EnterGame(mgdes.GameName, mgdes.FileId); } - public bool EnterGame(string gameName, string fileId, bool autoEnterSim = false) + public bool EnterGame(ECSString gameName, string fileId, bool autoEnterSim = false) { - var data = new GameSelectionData - { - gameMode = Techblox.GameSelection.GameMode.PlayGame, - isOnline = false, - saveName = gameName, - saveType = SaveType.ExistingSave, - 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}); + FullGameFields._multiplayerParams.MultiplayerMode = MultiplayerMode.SinglePlayer; + ref var selection = ref entitiesDB.QueryEntity(GameSelectionConstants.GameSelectionEGID); + selection.userContentID.Set(fileId); + selection.triggerStart = true; + selection.saveType = SaveType.ExistingSave; + selection.saveName = gameName; + selection.gameMode = GameMode.PlayGame; + selection.gameID.Set("GAMEID_Road_Track"); //TODO: Expose to the API return true; } @@ -158,6 +156,16 @@ namespace TechbloxModdingAPI.App { return ref entitiesDB.QueryEntity(id); } + + internal void CloseBetaPopup() + { + var (buffer, count) = entitiesDB.QueryEntities(ExclusiveGroup.Search("BetaPopup")); + for (int index = 0; index < count; ++index) + { + entitiesDB.QueryEntity(buffer[index].TogglePanelButtonComponent.targetPanel) + .guiRoot.enabled = false; + } + } } internal class MyGameDataEntityDescriptor_DamnItFJWhyDidYouMakeThisInternal : GenericEntityDescriptor { } diff --git a/TechbloxModdingAPI/Input/FakeInput.cs b/TechbloxModdingAPI/Input/FakeInput.cs index 6beb63f..bcf8367 100644 --- a/TechbloxModdingAPI/Input/FakeInput.cs +++ b/TechbloxModdingAPI/Input/FakeInput.cs @@ -111,7 +111,7 @@ namespace TechbloxModdingAPI.Input ref LocalPlayerInputEntityStruct currentInput = ref inputEngine.GetPlayerInputRef(playerID); //Utility.Logging.CommandLog($"Current sim frame {currentInput.frame}"); // set inputs - if (toggleMode) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ToggleTimeRunningModeTest; //TODO: Test, play + if (toggleMode) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ToggleTimeRunningModePlay; //TODO: Test, play if (forward) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Forward; if (backward) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Backward; if (up) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Up; diff --git a/TechbloxModdingAPI/Main.cs b/TechbloxModdingAPI/Main.cs index f11059b..554debe 100644 --- a/TechbloxModdingAPI/Main.cs +++ b/TechbloxModdingAPI/Main.cs @@ -49,39 +49,42 @@ namespace TechbloxModdingAPI harmony.PatchAll(currentAssembly); } catch (Exception e) - { //Can't use ErrorBuilder or Logging.LogException (which eventually uses ErrorBuilder) yet - Logging.Log(e.ToString()); - Logging.LogWarning("Failed to patch Techblox. Attempting to patch to display error..."); - harmony.Patch(AccessTools.Method(typeof(FullGameCompositionRoot), "OnContextInitialized") - .MakeGenericMethod(typeof(UnityContext)), - new HarmonyMethod(((Action) OnPatchError).Method)); //Can't use lambdas here :( + { + HandleError(e, "Failed to patch Techblox. Attempting to patch to display error...", OnPatchError); return; } - // init utility - Logging.MetaDebugLog($"Initializing Utility"); - Utility.GameState.Init(); - // init block implementors - Logging.MetaDebugLog($"Initializing Blocks"); - // init input - Input.FakeInput.Init(); - // init object-oriented classes - Player.Init(); - Block.Init(); - BlockGroup.Init(); - Wire.Init(); - // init client - Logging.MetaDebugLog($"Initializing Client"); - Client.Init(); - Game.Init(); - // init UI - Logging.MetaDebugLog($"Initializing UI"); - Interface.IMGUI.Constants.Init(); - Interface.IMGUI.IMGUIManager.Init(); - // init anti-anticheat - Logging.MetaDebugLog("Initializing anti-anticheat"); - AntiAntiCheatPatch.Init(harmony); - Logging.MetaLog($"{currentAssembly.GetName().Name} v{currentAssembly.GetName().Version} initialized"); + try + { + // init utility + Logging.MetaDebugLog($"Initializing Utility"); + Utility.GameState.Init(); + // init block implementors + Logging.MetaDebugLog($"Initializing Blocks"); + // init input + Input.FakeInput.Init(); + // init object-oriented classes + Player.Init(); + Block.Init(); + BlockGroup.Init(); + Wire.Init(); + // init client + Logging.MetaDebugLog($"Initializing Client"); + Client.Init(); + Game.Init(); + // init UI + Logging.MetaDebugLog($"Initializing UI"); + Interface.IMGUI.Constants.Init(); + Interface.IMGUI.IMGUIManager.Init(); + // init anti-anticheat + Logging.MetaDebugLog("Initializing anti-anticheat"); + AntiAntiCheatPatch.Init(harmony); + Logging.MetaLog($"{currentAssembly.GetName().Name} v{currentAssembly.GetName().Version} initialized"); + } + catch (Exception e) + { + HandleError(e, "Failed to initialize the API! Attempting to patch to display error...", OnInitError); + } } /// @@ -112,5 +115,26 @@ namespace TechbloxModdingAPI ErrorBuilder.DisplayMustQuitError("Failed to patch Techblox!\n" + "Make sure you're using the latest version of TechbloxModdingAPI or disable mods if the API isn't released yet."); } + + private static void OnInitError() + { + ErrorBuilder.DisplayMustQuitError("Failed to initialize the modding API!\n" + + "Make sure you're using the latest version. If you are, please report the error."); + } + + /// + /// Handles an init error. Logs the exception, a log message, and allows displaying an error in-game. + /// + /// The exception + /// The log message + /// The action to run when the game is ready to display error messages + private static void HandleError(Exception e, string logMsg, Action onInit) + { //Can't use ErrorBuilder or Logging.LogException (which eventually uses ErrorBuilder) yet + Logging.Log(e.ToString()); + Logging.LogWarning(logMsg); + harmony.Patch(AccessTools.Method(typeof(FullGameCompositionRoot), "OnContextInitialized") + .MakeGenericMethod(typeof(UnityContext)), + new HarmonyMethod(onInit.Method)); //Can't use lambdas here :( + } } } diff --git a/TechbloxModdingAPI/Tests/TechbloxModdingAPIPluginTest.cs b/TechbloxModdingAPI/Tests/TechbloxModdingAPIPluginTest.cs index f528f83..124d159 100644 --- a/TechbloxModdingAPI/Tests/TechbloxModdingAPIPluginTest.cs +++ b/TechbloxModdingAPI/Tests/TechbloxModdingAPIPluginTest.cs @@ -343,6 +343,11 @@ namespace TechbloxModdingAPI.Tests Logging.CommandLog(asset); } }).Build(); + Client.EnterMenu += (sender, args) => Scheduler.Schedule(new Once(() => Client.Instance.CloseBetaPopup())); + + Game.Enter += (sender, args) => + Console.WriteLine( + $"Current game selection data: {FullGameFields._gameSelectionData.gameMode} - {FullGameFields._gameSelectionData.saveType}"); #if TEST TestRoot.RunTests(); #endif diff --git a/TechbloxModdingAPI/Tests/TestRoot.cs b/TechbloxModdingAPI/Tests/TestRoot.cs index bd5dc01..16b85d0 100644 --- a/TechbloxModdingAPI/Tests/TestRoot.cs +++ b/TechbloxModdingAPI/Tests/TestRoot.cs @@ -129,7 +129,7 @@ namespace TechbloxModdingAPI.Tests private static IEnumerator GoToGameTests() { - Client app = new Client(); + Client app = Client.Instance; int oldLength = 0; while (app.MyGames.Length == 0 || oldLength != app.MyGames.Length) { @@ -137,7 +137,15 @@ namespace TechbloxModdingAPI.Tests yield return new WaitForSecondsEnumerator(1).Continue(); } yield return Yield.It; - app.MyGames[0].EnterGame(); + try + { + app.MyGames[0].EnterGame(); + } + catch (Exception e) + { + Console.WriteLine("Failed to go to game tests"); + Console.WriteLine(e); + } /*Game newGame = Game.NewGame(); yield return new WaitForSecondsEnumerator(5).Continue(); // wait for sync newGame.EnterGame();*/ diff --git a/TechbloxModdingAPI/Utility/FullGameFields.cs b/TechbloxModdingAPI/Utility/FullGameFields.cs index 0aec467..de9f487 100644 --- a/TechbloxModdingAPI/Utility/FullGameFields.cs +++ b/TechbloxModdingAPI/Utility/FullGameFields.cs @@ -7,6 +7,8 @@ using RobocraftX.Multiplayer; using Svelto.Context; using Svelto.DataStructures; using Svelto.ECS; +using Svelto.ECS.GUI; +using Techblox.GameSelection; using UnityEngine; using Unity.Entities; using Unity.Physics.Systems; @@ -144,6 +146,22 @@ namespace TechbloxModdingAPI.Utility } } + public static SveltoGUI _frontEndGUI + { + get + { + return (SveltoGUI)fgcr?.Field("_frontEndGUI").GetValue(); + } + } + + public static GameSelectionData _gameSelectionData + { + get + { + return (GameSelectionData)fgcr?.Field("_gameSelectionData").GetValue(); + } + } + private static Traverse fgcr; public static void Init(FullGameCompositionRoot instance)