301 lines
9.3 KiB
C#
301 lines
9.3 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// In fact a static class managing static things
|
|
/// </summary>
|
|
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; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// If set CUI will also check this folder when loading textures
|
|
/// </summary>
|
|
public static string PGNAssets
|
|
{
|
|
get => TextureManager.PGNAssets;
|
|
set => TextureManager.PGNAssets = value;
|
|
}
|
|
|
|
private static List<CUI> Instances = new List<CUI>();
|
|
/// <summary>
|
|
/// The singleton
|
|
/// </summary>
|
|
public static CUI Instance
|
|
{
|
|
get
|
|
{
|
|
if (Instances.Count == 0) return null;
|
|
return Instances.First();
|
|
}
|
|
set
|
|
{
|
|
Instances.Clear();
|
|
if (value != null) Instances.Add(value);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Orchestrates Drawing and updates, there could be only one
|
|
/// CUI.Main is located under vanilla GUI
|
|
/// </summary>
|
|
public static CUIMainComponent Main => Instance?.main;
|
|
/// <summary>
|
|
/// Orchestrates Drawing and updates, there could be only one
|
|
/// CUI.TopMain is located above vanilla GUI
|
|
/// </summary>
|
|
public static CUIMainComponent TopMain => Instance?.topMain;
|
|
/// <summary>
|
|
/// Snapshot of mouse and keyboard state
|
|
/// </summary>
|
|
public static CUIInput Input => Instance?.input;
|
|
/// <summary>
|
|
/// Safe texture manager
|
|
/// </summary
|
|
public static CUITextureManager TextureManager => Instance?.textureManager;
|
|
/// <summary>
|
|
/// Adapter to vanilla focus system, don't use
|
|
/// </summary>
|
|
public static CUIFocusResolver FocusResolver => Instance?.focusResolver;
|
|
public static CUILuaRegistrar LuaRegistrar => Instance?.luaRegistrar;
|
|
|
|
public static CUIComponent FocusedComponent
|
|
{
|
|
get => FocusResolver.FocusedCUIComponent;
|
|
set => FocusResolver.FocusedCUIComponent = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This affects logging
|
|
/// </summary>
|
|
public static bool Debug;
|
|
/// <summary>
|
|
/// Will break the mod if it's compiled
|
|
/// </summary>
|
|
public static bool UseCursedPatches { get; set; } = false;
|
|
/// <summary>
|
|
/// It's important to set it, if 2 CUIs try to add a hook with same id one won't be added
|
|
/// </summary>
|
|
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();
|
|
|
|
/// <summary>
|
|
/// Called on first Initialize
|
|
/// </summary>
|
|
public static event Action OnInit;
|
|
/// <summary>
|
|
/// Called on last Dispose
|
|
/// </summary>
|
|
public static event Action OnDispose;
|
|
public static bool Disposed { get; set; } = true;
|
|
public static event Action<TextInputEventArgs> OnWindowTextInput;
|
|
public static event Action<TextInputEventArgs> OnWindowKeyDown;
|
|
//public static event Action<TextInputEventArgs> 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<Func<bool>> IsBlockingPredicates => Instance?.isBlockingPredicates;
|
|
private List<Func<bool>> isBlockingPredicates = new List<Func<bool>>();
|
|
/// <summary>
|
|
/// In theory multiple mods could use same CUI instance,
|
|
/// i clean it up when UserCount drops to 0
|
|
/// </summary>
|
|
public static int UserCount = 0;
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// An object that contains current mouse and keyboard states
|
|
/// It scans states at the start on Main.Update
|
|
/// </summary>
|
|
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" };
|
|
}
|
|
|
|
/// <summary>
|
|
/// Should be called in IAssemblyPlugin.Initialize
|
|
/// \todo make it CUI instance member when plugin system settle
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Should be called in IAssemblyPlugin.Dispose
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|
|
} |