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; using Svelto.ECS.Experimental; using Techblox.GameSelection; using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Utility; using GameMode = RobocraftX.Common.GameMode; namespace TechbloxModdingAPI.App { public class GameMenuEngine : IFactoryEngine { public WrappedHandler EnterMenu; public WrappedHandler ExitMenu; public IEntityFactory Factory { set; private get; } public string Name => "TechbloxModdingAPIGameInfoGameEngine"; public bool isRemovable => false; 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() { } public void Ready() { MenuEnteredEnginePatch.IsInMenu = true; // At first it uses ActivateMenu(), then GoToMenu() which is patched MenuEnteredEnginePatch.EnteredExitedMenu(); } // game functionality public bool IsInMenu => MenuEnteredEnginePatch.IsInMenu; 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 bool CreateMyGame(EGID id, string path = "", uint thumbnailId = 0, string gameName = "", string creatorName = "", string description = "", long createdDate = 0L) { EntityInitializer eci = Factory.BuildEntity(id); eci.Init(new MyGameDataEntityStruct { SavedGamePath = new ECSString(path), ThumbnailId = thumbnailId, GameName = new ECSString(gameName), CreatorName = new ECSString(creatorName), GameDescription = new ECSString(description), CreatedDate = createdDate, }); // entitiesDB.PublishEntityChange(id); // this will always fail return true; } public uint HighestID() { EntityCollection games = entitiesDB.QueryEntities(MyGamesScreenExclusiveGroups.MyGames); var gamesB = games.ToBuffer().buffer; uint max = 0; for (int i = 0; i < games.count; i++) { if (gamesB[i].ID.entityID > max) { max = gamesB[i].ID.entityID; } } return max; } public bool EnterGame(EGID id) { if (!ExistsGameInfo(id)) return false; ref MyGameDataEntityStruct mgdes = ref GetGameInfo(id); return EnterGame(mgdes.GameName, mgdes.FileId); } public bool EnterGame(string gameName, string fileId, bool autoEnterSim = false) { GameMode.CurrentMode = autoEnterSim ? RCXMode.Play : RCXMode.Build; var data = new GameSelectionData { gameMode = Techblox.GameSelection.GameMode.PlayGame, gameType = GameType.MachineEditor, 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}); return true; } public bool SetGameName(EGID id, string name) { if (!ExistsGameInfo(id)) return false; GetGameInfo(id).GameName.Set(name); GetGameViewInfo(id).MyGamesSlotComponent.GameName = StringUtil.SanitiseString(name); return true; } public bool SetGameDescription(EGID id, string name) { if (!ExistsGameInfo(id)) return false; GetGameInfo(id).GameDescription.Set(name); GetGameViewInfo(id).MyGamesSlotComponent.GameDescription = StringUtil.SanitiseString(name); return true; } public bool ExistsGameInfo(EGID id) { return entitiesDB.Exists(id); } public ref MyGameDataEntityStruct GetGameInfo(EGID id) { return ref GetComponent(id); } public dynamic GetGameViewInfo(EGID id) { dynamic structOptional = AccessTools.Method("TechbloxModdingAPI.Utility.NativeApiExtensions:QueryEntityOptional", new []{typeof(EntitiesDB), typeof(EGID)}) .MakeGenericMethod(AccessTools.TypeByName("RobocraftX.GUI.MyGamesScreen.MyGamesSlotEntityViewStruct")) .Invoke(null, new object[] {entitiesDB, new EGID(id.entityID, MyGamesScreenExclusiveGroups.GameSlotGuiEntities)}); if (structOptional == null) throw new Exception("Could not get game slot entity"); return structOptional ? structOptional : null; } public ref T GetComponent(EGID id) where T: unmanaged, IEntityComponent { return ref entitiesDB.QueryEntity(id); } } 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 { } }