using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Diagnostics; using System.IO; using Barotrauma; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Graphics; using HarmonyLib; using System.Runtime.CompilerServices; [assembly: IgnoresAccessChecksTo("Barotrauma")] [assembly: IgnoresAccessChecksTo("DedicatedServer")] [assembly: IgnoresAccessChecksTo("BarotraumaCore")] namespace QICrabUI { /// /// In fact a static class managing static things /// public partial class CUI { public static Vector2 GameScreenSize => new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight); public static Rectangle GameScreenRect => new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight); private static string modDir; public static string ModDir { get => modDir; set { modDir = value; LuaFolder = Path.Combine(value, @"Lua"); } } public static bool UseLua { get; set; } = true; public static string LuaFolder { get; set; } private static string assetsPath; public static string AssetsPath { get => assetsPath; set { assetsPath = value; PalettesPath = Path.Combine(value, @"Palettes"); } } public static string CUITexturePath = "CUI.png"; public static string PalettesPath { get; set; } /// /// If set CUI will also check this folder when loading textures /// public static string PGNAssets { get => TextureManager.PGNAssets; set => TextureManager.PGNAssets = value; } private static List Instances = new List(); /// /// The singleton /// public static CUI Instance { get { if (Instances.Count == 0) return null; return Instances.First(); } set { Instances.Clear(); if (value != null) Instances.Add(value); } } /// /// Orchestrates Drawing and updates, there could be only one /// CUI.Main is located under vanilla GUI /// public static CUIMainComponent Main => Instance?.main; /// /// Orchestrates Drawing and updates, there could be only one /// CUI.TopMain is located above vanilla GUI /// public static CUIMainComponent TopMain => Instance?.topMain; /// /// Snapshot of mouse and keyboard state /// public static CUIInput Input => Instance?.input; /// /// Safe texture manager /// Instance?.textureManager; /// /// Adapter to vanilla focus system, don't use /// public static CUIFocusResolver FocusResolver => Instance?.focusResolver; public static CUILuaRegistrar LuaRegistrar => Instance?.luaRegistrar; public static CUIComponent FocusedComponent { get => FocusResolver.FocusedCUIComponent; set => FocusResolver.FocusedCUIComponent = value; } /// /// This affects logging /// public static bool Debug; /// /// Will break the mod if it's compiled /// public static bool UseCursedPatches { get; set; } = false; /// /// It's important to set it, if 2 CUIs try to add a hook with same id one won't be added /// public static string HookIdentifier { get => hookIdentifier; set { hookIdentifier = value?.Replace(' ', '_'); } } private static string hookIdentifier = ""; public static string CUIHookID => $"QICrabUI.{HookIdentifier}"; public static Harmony harmony; public static Random Random = new Random(); /// /// Called on first Initialize /// public static event Action OnInit; /// /// Called on last Dispose /// public static event Action OnDispose; public static bool Disposed { get; set; } = true; public static event Action OnWindowTextInput; public static event Action OnWindowKeyDown; //public static event Action OnWindowKeyUp; //TODO this doesn't trigger when you press menu button, i need to go inside thet method public static event Action OnPauseMenuToggled; public static void InvokeOnPauseMenuToggled() => OnPauseMenuToggled?.Invoke(); public static bool InputBlockingMenuOpen { get { if (IsBlockingPredicates == null) return false; return IsBlockingPredicates.Any(p => p()); } } public static List> IsBlockingPredicates => Instance?.isBlockingPredicates; private List> isBlockingPredicates = new List>(); /// /// In theory multiple mods could use same CUI instance, /// i clean it up when UserCount drops to 0 /// public static int UserCount = 0; /// /// An object that contains current mouse and keyboard states /// It scans states at the start on Main.Update /// private CUIInput input = new CUIInput(); private CUIMainComponent main; private CUIMainComponent topMain; private CUITextureManager textureManager = new CUITextureManager(); private CUIFocusResolver focusResolver = new CUIFocusResolver(); private CUILuaRegistrar luaRegistrar = new CUILuaRegistrar(); public static void ReEmitWindowTextInput(object sender, TextInputEventArgs e) => OnWindowTextInput?.Invoke(e); public static void ReEmitWindowKeyDown(object sender, TextInputEventArgs e) => OnWindowKeyDown?.Invoke(e); //public static void ReEmitWindowKeyUp(object sender, TextInputEventArgs e) => OnWindowKeyUp?.Invoke(e); private void CreateMains() { main = new CUIMainComponent() { AKA = "Main Component" }; topMain = new CUIMainComponent() { AKA = "Top Main Component" }; } /// /// Should be called in IAssemblyPlugin.Initialize /// \todo make it CUI instance member when plugin system settle /// public static void Initialize() { CUIDebug.Log($"CUI.Initialize {HookIdentifier} Instance:[{Instance?.GetHashCode()}] UserCount:{UserCount}", Color.Lime); if (Instance == null) { Disposed = false; Instance = new CUI(); Stopwatch sw = Stopwatch.StartNew(); if (HookIdentifier == null || HookIdentifier == "") CUI.Warning($"Warning: CUI.HookIdentifier is not set, this mod may conflict with other mods that use CUI"); InitStatic(); // this should init only static stuff that doesn't depend on instance OnInit?.Invoke(); Instance.CreateMains(); GameMain.Instance.Window.TextInput += ReEmitWindowTextInput; GameMain.Instance.Window.KeyDown += ReEmitWindowKeyDown; //GameMain.Instance.Window.KeyUp += ReEmitWindowKeyUp; CUIDebug.Log($"CUI.OnInit?.Invoke took {sw.ElapsedMilliseconds}ms"); sw.Restart(); harmony = new Harmony(CUIHookID); PatchAll(); CUIDebug.Log($"CUI.PatchAll took {sw.ElapsedMilliseconds}ms"); AddCommands(); sw.Restart(); LuaRegistrar.Register(); CUIDebug.Log($"CUI.LuaRegistrar.Register took {sw.ElapsedMilliseconds}ms"); } UserCount++; CUIDebug.Log($"CUI.Initialized {HookIdentifier} Instance:[{Instance?.GetHashCode()}] UserCount:{UserCount}", Color.Lime); } public static void OnLoadCompleted() { //Idk doesn't work //CUIMultiModResolver.FindOtherInputs(); } /// /// Should be called in IAssemblyPlugin.Dispose /// public static void Dispose() { CUIDebug.Log($"CUI.Dispose {HookIdentifier} Instance:[{Instance?.GetHashCode()}] UserCount:{UserCount}", Color.Lime); UserCount--; if (UserCount <= 0) { RemoveCommands(); // harmony.UnpatchAll(harmony.Id); harmony.UnpatchAll(); TextureManager.Dispose(); CUIDebugEventComponent.CapturedIDs.Clear(); OnDispose?.Invoke(); Disposed = true; Instance.isBlockingPredicates.Clear(); Errors.Clear(); LuaRegistrar.Deregister(); Instance = null; UserCount = 0; CUIDebug.Log($"CUI.Disposed {HookIdentifier} Instance:[{Instance?.GetHashCode()}] UserCount:{UserCount}", Color.Lime); } GameMain.Instance.Window.TextInput -= ReEmitWindowTextInput; GameMain.Instance.Window.KeyDown -= ReEmitWindowKeyDown; //GameMain.Instance.Window.KeyUp -= ReEmitWindowKeyUp; } //HACK Why it's set to run in static constructor? // it runs perfectly fine in CUI.Initialize //TODO component inits doesn't depend on the order // why am i responsible for initing them here? internal static void InitStatic() { CUIExtensions.InitStatic(); CUIInterpolate.InitStatic(); CUIAnimation.InitStatic(); CUIReflection.InitStatic(); CUIMultiModResolver.InitStatic(); CUIPalette.InitStatic(); CUIMap.CUIMapLink.InitStatic(); CUIMenu.InitStatic(); CUIComponent.InitStatic(); CUITypeMetaData.InitStatic(); CUIStyleLoader.InitStatic(); } } }