- 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 gametags/v2.2.0
@@ -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<MenuEventArgs> EnterMenu; | |||
public WrappedHandler<MenuEventArgs> 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; | |||
} | |||
} |
@@ -14,12 +14,12 @@ namespace TechbloxModdingAPI.App | |||
/// </summary> | |||
public class Client | |||
{ | |||
public static Client Instance { get; } = new Client(); | |||
protected static Func<object> ErrorHandlerInstanceGetter; | |||
protected static Action<object, Error> EnqueueError; | |||
protected static Action<object> HandleErrorClosed; | |||
/// <summary> | |||
/// An event that fires whenever the main menu is loaded. | |||
/// </summary> | |||
@@ -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<object, Error>) AccessTools.Method("TechbloxModdingAPI.App.Client:GenEnqueueError") | |||
.MakeGenericMethod(errorHandler, errorHandle) | |||
.Invoke(null, new object[0]); | |||
/*HandleErrorClosed = (Action<object>) 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<object> GenHandlePopupClosed<T>() | |||
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<T> handleSimple = | |||
(Action<T>) Delegate.CreateDelegate(typeof(Action<T>), handlePopupClosed); | |||
Action<object> 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; | |||
} | |||
} | |||
} |
@@ -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 |
@@ -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; | |||
} | |||
@@ -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<BlockTagEntityStruct>()) | |||
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<TaskContract> 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; | |||
@@ -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<GameSelectionComponent>(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<T>(id); | |||
} | |||
internal void CloseBetaPopup() | |||
{ | |||
var (buffer, count) = entitiesDB.QueryEntities<TogglePanelButtonEntityViewStruct>(ExclusiveGroup.Search("BetaPopup")); | |||
for (int index = 0; index < count; ++index) | |||
{ | |||
entitiesDB.QueryEntity<GUIEntityViewStruct>(buffer[index].TogglePanelButtonComponent.targetPanel) | |||
.guiRoot.enabled = false; | |||
} | |||
} | |||
} | |||
internal class MyGameDataEntityDescriptor_DamnItFJWhyDidYouMakeThisInternal : GenericEntityDescriptor<MyGameDataEntityStruct> { } | |||
@@ -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; | |||
@@ -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<FullGameCompositionRoot>)), | |||
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); | |||
} | |||
} | |||
/// <summary> | |||
@@ -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."); | |||
} | |||
/// <summary> | |||
/// Handles an init error. Logs the exception, a log message, and allows displaying an error in-game. | |||
/// </summary> | |||
/// <param name="e">The exception</param> | |||
/// <param name="logMsg">The log message</param> | |||
/// <param name="onInit">The action to run when the game is ready to display error messages</param> | |||
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<FullGameCompositionRoot>)), | |||
new HarmonyMethod(onInit.Method)); //Can't use lambdas here :( | |||
} | |||
} | |||
} |
@@ -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 | |||
@@ -129,7 +129,7 @@ namespace TechbloxModdingAPI.Tests | |||
private static IEnumerator<TaskContract> 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();*/ | |||
@@ -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) | |||