using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Logging;
using BetterInfoUI.Common;
using BetterInfoUI.Configuration;
using BetterInfoUI.Framework;
using BetterInfoUI.Hooks;
using BetterInfoUI.Models;
using BetterInfoUI.Services.Game;
using BetterInfoUI.Services.Infrastructure;
using BetterInfoUI.Services.UI;
using BetterInfoUI.Trackers;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("NastyaLove")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.0.1.0")]
[assembly: AssemblyInformationalVersion("0.0.1+13fd3dc0b4ff98bf2ed41fb8481b7da91173fa83")]
[assembly: AssemblyProduct("BetterInfoUI")]
[assembly: AssemblyTitle("BetterInfoUI")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.1.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
internal sealed class NullableAttribute : Attribute
{
public readonly byte[] NullableFlags;
public NullableAttribute(byte P_0)
{
NullableFlags = new byte[1] { P_0 };
}
public NullableAttribute(byte[] P_0)
{
NullableFlags = P_0;
}
}
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
internal sealed class NullableContextAttribute : Attribute
{
public readonly byte Flag;
public NullableContextAttribute(byte P_0)
{
Flag = P_0;
}
}
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace BepInEx
{
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
[Conditional("CodeGeneration")]
internal sealed class BepInAutoPluginAttribute : Attribute
{
public BepInAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
{
}
}
}
namespace BepInEx.Preloader.Core.Patching
{
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
[Conditional("CodeGeneration")]
internal sealed class PatcherAutoPluginAttribute : Attribute
{
public PatcherAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
{
}
}
}
namespace BetterInfoUI
{
internal static class Core
{
private static bool _hasInitialized;
public static FrameMonitor FrameMonitor;
public static FramePerformanceData LastFramePerformance;
public static volatile bool IsFrameMonitoringEnabled = true;
public static bool IsDebug { get; private set; }
public static ManualLogSource Logger => Plugin.Logger;
public static ConfigService Config { get; private set; }
public static void Initialize()
{
if (!_hasInitialized)
{
IsDebug = typeof(Plugin).Assembly.GetCustomAttribute<AssemblyConfigurationAttribute>()?.Configuration == "Debug";
LoggerService.Create().Initialize(splitLogMode: true);
GameContext.Initialize();
Config = new ConfigService().Initialize();
GameContext.RegisterSingleton(Config);
GameContext.RegisterSingleton(new CharacterService());
GameContext.RegisterSingleton(new FontService());
GameContext.RegisterSingleton(new AfflictionService());
GameContext.RegisterSingleton(new StaminaUIService(GameContext.Get<ConfigService>(), GameContext.Get<FontService>()));
GameContext.RegisterSingleton(new AfflictionUIService(GameContext.Get<ConfigService>(), GameContext.Get<FontService>()));
GameContext.RegisterSingleton(new RunLifecycleService(GameContext.Get<ConfigService>(), GameContext.Get<AfflictionUIService>()).Initialize());
StaminaBarUpdatePatch.Initialize();
StaminaBarStartPatch.Initialize();
BarAfflictionUpdatePatch.Initialize();
StaminaBarChangeBarPatch.Initialize();
TaskRunner.Initialize();
GameFrame.Initialize();
if (IsFrameMonitoringEnabled)
{
FrameMonitor = new FrameMonitor(75L, 150L);
LoggerService.Info($"Frame monitoring enabled: LagThreshold={75}ms, CriticalLag={150}ms", "Core");
}
LoggerService.Info("Core initialized! Version: v" + Plugin.Version, "Core", printToConsole: true);
_hasInitialized = true;
}
}
}
public class GameFrame : MonoBehaviour
{
public delegate void GameFrameUpdateEventHandler();
private static GameFrame _instance;
public static event GameFrameUpdateEventHandler OnUpdate;
public static event GameFrameUpdateEventHandler OnLateUpdate;
public void Awake()
{
LoggerService.Debug("Awake called", "GameFrame", printToConsole: true);
Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
}
public void Update()
{
try
{
if (Core.IsFrameMonitoringEnabled)
{
Core.FrameMonitor.StartFrame();
}
GameFrame.OnUpdate?.Invoke();
}
catch (Exception arg)
{
LoggerService.Error($"Error dispatching OnUpdate event:\n{arg}", "GameFrame", printToConsole: true);
}
}
public void LateUpdate()
{
try
{
GameFrame.OnLateUpdate?.Invoke();
if (Core.IsFrameMonitoringEnabled)
{
Core.LastFramePerformance = Core.FrameMonitor.EndFrame();
if (Core.LastFramePerformance.Type == FrameType.CriticalLag)
{
LoggerService.Warning($"Critical lag detected: {Core.LastFramePerformance.DelayMs}ms delay " + $"(frame time: {Core.LastFramePerformance.FrameTime}ms)", "TaskRunner");
}
}
}
catch (Exception arg)
{
LoggerService.Error($"Error dispatching OnLateUpdate event:\n{arg}", "GameFrame", printToConsole: true);
}
}
public static void Initialize()
{
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
LoggerService.Debug("Creating GameFrame instance...", "GameFrame");
_instance = new GameObject("BetterInfoUI_GameFrame").AddComponent<GameFrame>();
LoggerService.Info($"GameFrame instance created: {(Object)(object)_instance != (Object)null}", "GameFrame");
if (!((Object)(object)_instance == (Object)null))
{
LoggerService.Info("GameFrame instance name: " + ((Object)((Component)_instance).gameObject).name, "GameFrame");
LoggerService.Info($"GameFrame instance type: {((object)_instance).GetType()}", "GameFrame");
}
}
public static void DestroyInstance()
{
Core.Logger.LogInfo((object)"[GameFrame]: Destroying instance...");
if (GameFrame.OnUpdate != null)
{
Delegate[] invocationList = GameFrame.OnUpdate.GetInvocationList();
int num = invocationList.Length;
Delegate[] array = invocationList;
for (int i = 0; i < array.Length; i++)
{
OnUpdate -= (GameFrameUpdateEventHandler)array[i];
}
Core.Logger.LogDebug((object)string.Format("[{0}]: OnUpdate subscribers removed: {1}", "GameFrame", num));
GameFrame.OnUpdate = null;
}
if (GameFrame.OnLateUpdate != null)
{
Delegate[] invocationList2 = GameFrame.OnLateUpdate.GetInvocationList();
int num2 = invocationList2.Length;
Delegate[] array = invocationList2;
for (int i = 0; i < array.Length; i++)
{
OnLateUpdate -= (GameFrameUpdateEventHandler)array[i];
}
Core.Logger.LogInfo((object)string.Format("[{0}]: OnLateUpdate subscribers removed: {1}", "GameFrame", num2));
GameFrame.OnLateUpdate = null;
}
if ((Object)(object)_instance == (Object)null)
{
Core.Logger.LogInfo((object)"[GameFrame]: Instance is null, nothing to destroy");
return;
}
Core.Logger.LogInfo((object)("[GameFrame]: Destroying instance: " + ((Object)((Component)_instance).gameObject).name));
Object.Destroy((Object)(object)((Component)_instance).gameObject);
_instance = null;
Core.Logger.LogInfo((object)"[GameFrame]: Instance destroyed");
}
}
[BepInPlugin("BetterInfoUI", "BetterInfoUI", "0.0.1")]
public class Plugin : BaseUnityPlugin
{
private readonly Harmony _harmony = new Harmony("BetterInfoUI");
internal static ManualLogSource Logger;
public const string Id = "BetterInfoUI";
internal static Plugin Instance { get; private set; }
public static string Name => "BetterInfoUI";
public static string Version => "0.0.1";
public void Awake()
{
Instance = this;
Logger = ((BaseUnityPlugin)this).Logger;
Logger.LogInfo((object)"Plugin BetterInfoUI is loaded!");
Core.Initialize();
_harmony.PatchAll(Assembly.GetExecutingAssembly());
}
public void OnDestroy()
{
if (GameContext.TryGet<RunLifecycleService>(out var service))
{
service.Dispose();
}
if (GameContext.TryGet<StaminaUIService>(out var service2))
{
service2.Cleanup();
}
if (GameContext.TryGet<AfflictionUIService>(out var service3))
{
service3.Cleanup();
}
}
}
}
namespace BetterInfoUI.Utils
{
public class TimeUtility
{
public static string GetNumWithTextSuffix(long num, string oneSfx, string twoSfx, string fiveSfx, string sep = " ")
{
string text2 = new string(num.ToString().Reverse().ToArray());
if (text2.Length > 1 && text2[1] == '1')
{
return Result(num, fiveSfx);
}
switch (text2[0])
{
case '1':
return Result(num, oneSfx);
case '2':
case '3':
case '4':
return Result(num, twoSfx);
default:
return Result(num, fiveSfx);
}
string Result(long number, string text)
{
return $"{number}{sep}{text}";
}
}
}
}
namespace BetterInfoUI.Trackers
{
public sealed class AfflictionTextTracker : MonoBehaviour
{
private STATUSTYPE _statusType;
private BarAffliction _affliction;
private RectTransform _textRect;
private float _verticalOffset;
public void Initialize(BarAffliction affliction, float verticalOffset)
{
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
//IL_0021: Unknown result type (might be due to invalid IL or missing references)
_affliction = affliction;
_verticalOffset = verticalOffset;
_textRect = ((Component)this).GetComponent<RectTransform>();
_statusType = affliction.afflictionType;
}
public void UpdateAffliction(BarAffliction affliction)
{
//IL_0013: Unknown result type (might be due to invalid IL or missing references)
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
if (!((Object)(object)affliction == (Object)null))
{
_affliction = affliction;
_statusType = affliction.afflictionType;
}
}
public void UpdateWidth(float width)
{
//IL_001b: Unknown result type (might be due to invalid IL or missing references)
//IL_0025: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)_textRect != (Object)null)
{
_textRect.sizeDelta = new Vector2(width, _textRect.sizeDelta.y);
}
}
private void LateUpdate()
{
//IL_0031: Unknown result type (might be due to invalid IL or missing references)
//IL_0036: Unknown result type (might be due to invalid IL or missing references)
//IL_003d: Unknown result type (might be due to invalid IL or missing references)
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
//IL_0056: Unknown result type (might be due to invalid IL or missing references)
//IL_0060: Unknown result type (might be due to invalid IL or missing references)
//IL_008c: Unknown result type (might be due to invalid IL or missing references)
if (!Object.op_Implicit((Object)(object)_affliction) || !Object.op_Implicit((Object)(object)_textRect))
{
return;
}
RectTransform component = ((Component)_affliction).GetComponent<RectTransform>();
if (!Object.op_Implicit((Object)(object)component))
{
return;
}
Vector3 position = ((Transform)component).position;
((Transform)_textRect).position = new Vector3(position.x, position.y + _verticalOffset, ((Transform)_textRect).position.z);
float num = 0f;
if (Object.op_Implicit((Object)(object)Character.observedCharacter))
{
num = Character.observedCharacter.refs.afflictions.GetCurrentStatus(_statusType) * 100f;
}
TextMeshProUGUI val = default(TextMeshProUGUI);
if (num > 0f && ((Component)this).TryGetComponent<TextMeshProUGUI>(ref val))
{
ConfigService config = Core.Config;
string text = ((config != null && (config.Current?.ShowPercentageSign).GetValueOrDefault()) ? $"{num:F0}%" : $"{num:F0}");
if (((TMP_Text)val).text != text)
{
((TMP_Text)val).text = text;
}
}
bool flag = num > 0f;
if (((Component)this).gameObject.activeSelf != flag)
{
((Component)this).gameObject.SetActive(flag);
}
}
}
}
namespace BetterInfoUI.Services.UI
{
public sealed class AfflictionUIService
{
[CompilerGenerated]
private ConfigService <configService>P;
[CompilerGenerated]
private FontService <fontService>P;
private const TextAlignmentOptions AfflictionAlignment = 514;
private readonly Dictionary<STATUSTYPE, TextMeshProUGUI> _texts;
private Canvas _canvas;
public AfflictionUIService(ConfigService configService, FontService fontService)
{
<configService>P = configService;
<fontService>P = fontService;
_texts = new Dictionary<STATUSTYPE, TextMeshProUGUI>();
base..ctor();
}
public void HandleAfflictionUpdate(BarAffliction affliction, StaminaBar staminaBar)
{
//IL_0104: Unknown result type (might be due to invalid IL or missing references)
//IL_0034: Unknown result type (might be due to invalid IL or missing references)
//IL_0020: Unknown result type (might be due to invalid IL or missing references)
//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
try
{
if (!AfflictionService.TryGetModel(affliction, out var model) || (!model.IsActive && !_texts.ContainsKey(model.Type)))
{
return;
}
TextMeshProUGUI val = EnsureText(model.Type, affliction);
if (!((Object)(object)val == (Object)null))
{
PluginConfig current = <configService>P.Current;
((TMP_Text)val).fontSize = current.AfflictionFontSize;
((TMP_Text)val).outlineWidth = current.TextOutlineThickness;
((TMP_Text)val).text = (current.ShowPercentageSign ? $"{model.ValuePercent:F0}%" : $"{model.ValuePercent:F0}");
((Graphic)val).color = ResolveAfflictionColor(model.Type, affliction);
bool flag = model.ValuePercent > 0f;
if (((Component)val).gameObject.activeSelf != flag)
{
((Component)val).gameObject.SetActive(flag);
}
}
}
catch (Exception ex)
{
LoggerService.Error($"Error updating affliction text for {affliction?.afflictionType}: {ex.Message}", "AfflictionUIService", <configService>P.Current.VerboseLogging);
}
}
public void HandleBarChanged(StaminaBar staminaBar)
{
//IL_0038: Unknown result type (might be due to invalid IL or missing references)
//IL_003d: Unknown result type (might be due to invalid IL or missing references)
//IL_003e: Unknown result type (might be due to invalid IL or missing references)
//IL_0056: Unknown result type (might be due to invalid IL or missing references)
//IL_005f: Unknown result type (might be due to invalid IL or missing references)
try
{
if ((Object)(object)staminaBar == (Object)null || (Object)(object)Character.observedCharacter == (Object)null)
{
return;
}
foreach (STATUSTYPE value2 in Enum.GetValues(typeof(STATUSTYPE)))
{
if (AfflictionService.TryGetValue(value2, out var value) && !(value <= 0f) && !_texts.ContainsKey(value2) && AfflictionService.TryFindAffliction(staminaBar, value2, out var affliction))
{
HandleAfflictionUpdate(affliction, staminaBar);
}
}
}
catch (Exception ex)
{
LoggerService.Error("Error processing StaminaBar.ChangeBar: " + ex.Message, "AfflictionUIService", <configService>P.Current.VerboseLogging);
}
}
public void HandleBarStart(StaminaBar staminaBar)
{
if (staminaBar?.afflictions != null && staminaBar.afflictions.Length != 0 && _texts.Count != 0)
{
LoggerService.Info("StaminaBar.Start detected, resetting affliction UI cache", "AfflictionUIService", <configService>P.Current.VerboseLogging);
Cleanup();
}
}
public void Cleanup()
{
if (_texts.Count > 0)
{
LoggerService.Info($"Cleaning up {_texts.Count} affliction text elements", "AfflictionUIService", <configService>P.Current.VerboseLogging);
}
foreach (TextMeshProUGUI item in _texts.Values.Where((TextMeshProUGUI text) => (Object)(object)text != (Object)null && (Object)(object)((Component)text).gameObject != (Object)null))
{
Object.Destroy((Object)(object)((Component)item).gameObject);
}
_texts.Clear();
if ((Object)(object)_canvas != (Object)null)
{
Object.Destroy((Object)(object)((Component)_canvas).gameObject);
}
_canvas = null;
}
private TextMeshProUGUI EnsureText(STATUSTYPE type, BarAffliction affliction)
{
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_0039: Unknown result type (might be due to invalid IL or missing references)
//IL_0044: Unknown result type (might be due to invalid IL or missing references)
//IL_0049: Unknown result type (might be due to invalid IL or missing references)
//IL_0060: Unknown result type (might be due to invalid IL or missing references)
//IL_0067: Unknown result type (might be due to invalid IL or missing references)
//IL_0078: Unknown result type (might be due to invalid IL or missing references)
//IL_008d: Unknown result type (might be due to invalid IL or missing references)
//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
if (_texts.TryGetValue(type, out var value) && (Object)(object)value != (Object)null)
{
UpdateTracker(value, affliction);
return value;
}
<fontService>P.EnsureResolved();
EnsureCanvas();
GameObject val = new GameObject($"{type}Text");
val.transform.SetParent(((Component)_canvas).transform, false);
value = val.AddComponent<TextMeshProUGUI>();
RectTransform component = val.GetComponent<RectTransform>();
component.anchorMin = new Vector2(0.5f, 0.5f);
component.anchorMax = new Vector2(0.5f, 0.5f);
component.pivot = new Vector2(0.5f, 0.5f);
component.sizeDelta = new Vector2(GetWidth(affliction), 24f);
SetupTextStyle(value);
((Graphic)value).raycastTarget = false;
val.AddComponent<AfflictionTextTracker>().Initialize(affliction, -22f);
_texts[type] = value;
LoggerService.Info($"Created affliction text element for {type}", "AfflictionUIService", <configService>P.Current.VerboseLogging);
return value;
}
private void UpdateTracker(TextMeshProUGUI text, BarAffliction affliction)
{
AfflictionTextTracker component = ((Component)text).GetComponent<AfflictionTextTracker>();
if (!((Object)(object)component == (Object)null))
{
component.UpdateWidth(GetWidth(affliction));
component.UpdateAffliction(affliction);
}
}
private void EnsureCanvas()
{
//IL_0014: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Expected O, but got Unknown
//IL_0053: Unknown result type (might be due to invalid IL or missing references)
if (!((Object)(object)_canvas != (Object)null))
{
GameObject val = new GameObject("AfflictionTextCanvas");
Object.DontDestroyOnLoad((Object)(object)val);
_canvas = val.AddComponent<Canvas>();
_canvas.renderMode = (RenderMode)0;
_canvas.sortingOrder = 100;
CanvasScaler obj = val.AddComponent<CanvasScaler>();
obj.uiScaleMode = (ScaleMode)1;
obj.referenceResolution = Constants.Ui.Affliction.CanvasReferenceResolution;
obj.screenMatchMode = (ScreenMatchMode)0;
val.AddComponent<GraphicRaycaster>();
val.layer = LayerMask.NameToLayer("UI");
LoggerService.Info("Created top-level affliction canvas", "AfflictionUIService", <configService>P.Current.VerboseLogging);
}
}
private void SetupTextStyle(TextMeshProUGUI text)
{
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
//IL_0047: Unknown result type (might be due to invalid IL or missing references)
//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
((TMP_Text)text).alignment = (TextAlignmentOptions)514;
((TMP_Text)text).textWrappingMode = (TextWrappingModes)0;
((TMP_Text)text).overflowMode = (TextOverflowModes)0;
((Graphic)text).color = Color.white;
((TMP_Text)text).enableAutoSizing = false;
((TMP_Text)text).fontStyle = (FontStyles)1;
((TMP_Text)text).faceColor = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue);
if ((Object)(object)<fontService>P.Font != (Object)null)
{
((TMP_Text)text).font = <fontService>P.Font;
}
if ((Object)(object)<fontService>P.Material != (Object)null)
{
((Graphic)text).material = <fontService>P.Material;
}
Shadow obj = ((Component)text).gameObject.AddComponent<Shadow>();
obj.effectColor = new Color(0f, 0f, 0f, 0.95f);
obj.effectDistance = new Vector2(2f, -2f);
}
private static float GetWidth(BarAffliction affliction)
{
if (!((Object)(object)affliction == (Object)null))
{
return Mathf.Max(40f, affliction.width * 0.8f);
}
return 40f;
}
private static Color ResolveAfflictionColor(STATUSTYPE type, BarAffliction affliction)
{
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_000f: Unknown result type (might be due to invalid IL or missing references)
//IL_0028: Unknown result type (might be due to invalid IL or missing references)
//IL_0052: Unknown result type (might be due to invalid IL or missing references)
//IL_0021: Unknown result type (might be due to invalid IL or missing references)
if (Colors.AfflictionByStatus.TryGetValue(type, out var value))
{
return value;
}
Image componentInChildren = ((Component)affliction).GetComponentInChildren<Image>();
if ((Object)(object)componentInChildren == (Object)null)
{
return Color.white;
}
float num = default(float);
float num2 = default(float);
float num3 = default(float);
Color.RGBToHSV(((Graphic)componentInChildren).color, ref num, ref num2, ref num3);
return Color.HSVToRGB(num, Mathf.Clamp01(num2 * 1.2f), Mathf.Clamp01(num3 * 1.3f));
}
}
public sealed class FontService
{
private bool _hasResolved;
public TMP_FontAsset Font { get; private set; }
public Material Material { get; private set; }
public void EnsureResolved()
{
if (_hasResolved)
{
return;
}
_hasResolved = true;
TextMeshProUGUI[] array = Resources.FindObjectsOfTypeAll<TextMeshProUGUI>();
foreach (TextMeshProUGUI val in array)
{
if (!(((Object)val).name != "InteractNameText") || !(((Object)val).name != "InteractPromptText") || !(((Object)val).name != "ItemPromptMain"))
{
Font = ((TMP_Text)val).font;
Material = ((Graphic)val).material;
return;
}
}
TMP_FontAsset[] array2 = Resources.FindObjectsOfTypeAll<TMP_FontAsset>();
if (array2.Length != 0)
{
Font = array2[0];
}
}
}
public sealed class StaminaUIService
{
[CompilerGenerated]
private ConfigService <configService>P;
[CompilerGenerated]
private FontService <fontService>P;
private const TextAlignmentOptions StaminaAlignment = 516;
private const TextAlignmentOptions ExtraAlignment = 513;
private TextMeshProUGUI _extraStaminaText;
private TextMeshProUGUI _staminaText;
private GameObject _container;
public StaminaUIService(ConfigService configService, FontService fontService)
{
<configService>P = configService;
<fontService>P = fontService;
base..ctor();
}
public void Update(StaminaBar staminaBar)
{
//IL_006a: Unknown result type (might be due to invalid IL or missing references)
if (!CharacterService.TryGetStaminaModel(staminaBar, out var model))
{
return;
}
EnsureElements(staminaBar);
if ((Object)(object)_staminaText == (Object)null)
{
return;
}
PluginConfig current = <configService>P.Current;
ApplyTextStyle(_staminaText, current.StaminaFontSize, current.TextOutlineThickness);
((TMP_Text)_staminaText).text = FormatValue(model.CurrentPercent, current.ShowPercentageSign);
((Graphic)_staminaText).color = ResolveStaminaColor(staminaBar, in model);
if ((Object)(object)_extraStaminaText != (Object)null)
{
ApplyTextStyle(_extraStaminaText, current.StaminaFontSize, current.TextOutlineThickness);
if (model.ExtraPercent > 0f)
{
((Component)_extraStaminaText).gameObject.SetActive(true);
((TMP_Text)_extraStaminaText).text = (current.ShowPercentageSign ? $" +{model.ExtraPercent:F0}%" : $" +{model.ExtraPercent:F0}");
}
else if (((Component)_extraStaminaText).gameObject.activeSelf)
{
((Component)_extraStaminaText).gameObject.SetActive(false);
}
}
if ((Object)(object)_container != (Object)null && _container.activeSelf != model.IsVisible)
{
_container.SetActive(model.IsVisible);
}
}
public void HandleBarStart(StaminaBar staminaBar)
{
if (!((Object)(object)_container == (Object)null))
{
if ((Object)(object)staminaBar == (Object)null)
{
Cleanup();
}
else if ((Object)(object)_container.transform.parent != (Object)(object)((Component)staminaBar).transform)
{
Cleanup();
}
}
}
private void EnsureElements(StaminaBar staminaBar)
{
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
//IL_004d: Expected O, but got Unknown
//IL_0076: Unknown result type (might be due to invalid IL or missing references)
//IL_007b: Unknown result type (might be due to invalid IL or missing references)
//IL_008a: Unknown result type (might be due to invalid IL or missing references)
//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
//IL_00de: Unknown result type (might be due to invalid IL or missing references)
if (!((Object)(object)_container != (Object)null) || !((Object)(object)_container.transform.parent == (Object)(object)((Component)staminaBar).transform))
{
Cleanup();
<fontService>P.EnsureResolved();
_container = new GameObject("StaminaTextContainer");
_container.transform.SetParent(((Component)staminaBar).transform, false);
_staminaText = CreateTextElement(_container.transform, "StaminaText", Constants.Ui.Stamina.MainTextSize, Constants.Ui.Stamina.MainTextAnchoredPosition, new Vector2(1f, 0f), (TextAlignmentOptions)516);
_extraStaminaText = CreateTextElement(_container.transform, "ExtraStaminaText", Constants.Ui.Stamina.ExtraTextSize, Constants.Ui.Stamina.ExtraTextAnchoredPosition, new Vector2(0f, 0f), (TextAlignmentOptions)513);
((Graphic)_extraStaminaText).color = Colors.ExtraStamina;
}
}
private TextMeshProUGUI CreateTextElement(Transform parent, string name, Vector2 size, Vector2 anchoredPosition, Vector2 pivot, TextAlignmentOptions alignment)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_0013: Unknown result type (might be due to invalid IL or missing references)
//IL_002a: Unknown result type (might be due to invalid IL or missing references)
//IL_003f: Unknown result type (might be due to invalid IL or missing references)
//IL_004a: Unknown result type (might be due to invalid IL or missing references)
//IL_0052: Unknown result type (might be due to invalid IL or missing references)
//IL_0058: Unknown result type (might be due to invalid IL or missing references)
//IL_0061: Unknown result type (might be due to invalid IL or missing references)
GameObject val = new GameObject(name);
val.transform.SetParent(parent, false);
TextMeshProUGUI val2 = val.AddComponent<TextMeshProUGUI>();
RectTransform component = val.GetComponent<RectTransform>();
component.anchorMin = new Vector2(0.5f, 0.5f);
component.anchorMax = new Vector2(0.5f, 0.5f);
component.pivot = pivot;
component.sizeDelta = size;
component.anchoredPosition = anchoredPosition;
SetupBaseTextStyle(val2, alignment);
return val2;
}
private void SetupBaseTextStyle(TextMeshProUGUI text, TextAlignmentOptions alignment)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0016: Unknown result type (might be due to invalid IL or missing references)
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
((TMP_Text)text).alignment = alignment;
((TMP_Text)text).textWrappingMode = (TextWrappingModes)0;
((TMP_Text)text).overflowMode = (TextOverflowModes)0;
((Graphic)text).color = Color.white;
((TMP_Text)text).enableAutoSizing = false;
((TMP_Text)text).fontStyle = (FontStyles)1;
((TMP_Text)text).faceColor = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue);
if ((Object)(object)<fontService>P.Font != (Object)null)
{
((TMP_Text)text).font = <fontService>P.Font;
}
if ((Object)(object)<fontService>P.Material != (Object)null)
{
((Graphic)text).material = <fontService>P.Material;
}
Shadow obj = ((Component)text).gameObject.AddComponent<Shadow>();
obj.effectColor = new Color(0f, 0f, 0f, 0.95f);
obj.effectDistance = new Vector2(2f, -2f);
}
private static void ApplyTextStyle(TextMeshProUGUI text, int fontSize, float outlineThickness)
{
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
((TMP_Text)text).fontSize = fontSize;
((TMP_Text)text).outlineWidth = outlineThickness;
((TMP_Text)text).outlineColor = Color32.op_Implicit(Color.black);
}
private static string FormatValue(float value, bool withPercent)
{
if (!withPercent)
{
return $"{value:F0}";
}
return $"{value:F0}%";
}
private static Color ResolveStaminaColor(StaminaBar staminaBar, in StaminaModel model)
{
//IL_0016: Unknown result type (might be due to invalid IL or missing references)
//IL_0040: Unknown result type (might be due to invalid IL or missing references)
//IL_007a: Unknown result type (might be due to invalid IL or missing references)
//IL_007f: Unknown result type (might be due to invalid IL or missing references)
//IL_0093: Unknown result type (might be due to invalid IL or missing references)
//IL_0083: Unknown result type (might be due to invalid IL or missing references)
//IL_0088: Unknown result type (might be due to invalid IL or missing references)
//IL_008c: Unknown result type (might be due to invalid IL or missing references)
//IL_0091: Unknown result type (might be due to invalid IL or missing references)
Image component = ((Component)staminaBar.staminaBar).GetComponent<Image>();
if ((Object)(object)component != (Object)null)
{
float num = default(float);
float num2 = default(float);
float num3 = default(float);
Color.RGBToHSV(((Graphic)component).color, ref num, ref num2, ref num3);
return Color.HSVToRGB(num, Mathf.Clamp01(num2 * 1.2f), Mathf.Clamp01(num3 * 1.3f));
}
float num4 = ((model.MaxPercent <= 0f) ? 0f : (model.CurrentPercent / model.MaxPercent));
if (!(num4 < 0.25f))
{
if (num4 < 0.5f)
{
return Colors.MediumStamina;
}
return Colors.HighStamina;
}
return Colors.LowStamina;
}
public void Cleanup()
{
if ((Object)(object)_container != (Object)null)
{
Object.Destroy((Object)(object)_container);
}
_container = null;
_staminaText = null;
_extraStaminaText = null;
}
}
}
namespace BetterInfoUI.Services.Infrastructure
{
public class LoggerService
{
private static readonly string PluginPath = Directory.GetCurrentDirectory();
private static readonly string BepInExConfigsPath = Path.Combine(PluginPath, "BepInEx", "config");
private static readonly string ConfigsPath = Path.Combine(BepInExConfigsPath, "BetterInfoUI".ToLower());
private const string DefaultLogType = "BetterInfoUI";
private static readonly string LogsPath = Path.Combine(ConfigsPath, "Logs");
private static readonly string ArchiveLogsPath = Path.Combine(LogsPath, "ArchiveLogs");
private const int MaxLogFiles = 5;
private bool _isInitialized;
private bool _splitLogMode;
private readonly Dictionary<string, string> _registeredLogTypes = new Dictionary<string, string>();
private readonly List<LogItem> _logBuffer = new List<LogItem>();
private static LoggerService _instance;
private static string CurrentTime => GetFormatTime();
public LoggerService()
{
_instance = this;
RegisterLogType("Info", "BetterInfoUI");
RegisterLogType("Warning", "DEBUG");
RegisterLogType("Debug", "DEBUG");
RegisterLogType("Error", "BetterInfoUI");
RegisterLogType("Fatal", "BetterInfoUI");
}
public static LoggerService Create()
{
return _instance ?? new LoggerService();
}
public LoggerService Initialize(bool splitLogMode = false)
{
if (_isInitialized)
{
Core.Logger.LogError((object)"LoggerService is already initialized. Skipping initialization...");
throw new InvalidOperationException("LoggerService is already initialized.");
}
_splitLogMode = splitLogMode;
try
{
if (!Directory.Exists(LogsPath))
{
Directory.CreateDirectory(ArchiveLogsPath);
}
else
{
ArchiveOldLogsFiles();
}
InitializeLogFiles();
TaskRunner.AddUniqueTask((Action)WriteLogBuffer, "LoggerService::WriteBuffer", 0, 5f, highPriority: true, loop: true, runNow: false);
_isInitialized = true;
return this;
}
catch (Exception ex)
{
Core.Logger.LogError((object)("Failed to initialize LoggerService: " + ex.Message + "\n" + ex.StackTrace));
throw new InvalidOperationException("LoggerService initialization failed.", ex);
}
}
public static void Info(string content, string moduleName = null, bool printToConsole = false)
{
_instance.Log("Info", content, moduleName, printToConsole);
}
public static void Warning(string content, string moduleName = null, bool printToConsole = false)
{
_instance.Log("Warning", content, moduleName, printToConsole);
}
public static void Debug(string content, string moduleName = null, bool printToConsole = false)
{
_instance.Log("Debug", content, moduleName, printToConsole);
}
public static void Error(string content, string moduleName = null, bool printToConsole = false)
{
_instance.Log("Error", content, moduleName, printToConsole);
}
public static void Fatal(string content, string moduleName = null, bool printToConsole = false)
{
_instance.Log("Fatal", content, moduleName, printToConsole);
}
public static Action<string, bool> CreateCustomTypeLogger(string type)
{
return CreateCustomTypeLogger(type, null);
}
public static Action<string, bool> CreateCustomTypeLogger(string type, string moduleName)
{
if (string.IsNullOrWhiteSpace(type))
{
throw new ArgumentException("Log type cannot be null or empty.", "type");
}
if (!_instance._registeredLogTypes.ContainsKey(type))
{
throw new InvalidOperationException("Log type '" + type + "' is not registered. Use RegisterLogType to register it first.");
}
return delegate(string content, bool printToConsole)
{
Custom(type, content, moduleName, printToConsole);
};
}
public static void Custom(string type, string content, string moduleName = null, bool printToConsole = false)
{
if (string.IsNullOrWhiteSpace(type))
{
Core.Logger.LogError((object)"Log type cannot be null or empty.");
}
else if (!_instance._registeredLogTypes.ContainsKey(type))
{
Core.Logger.LogError((object)("Log type '" + type + "' is not registered. Use RegisterLogType to register it first."));
}
else
{
_instance.Log(type, content, moduleName, printToConsole);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Log(string type, string content, string moduleName = null, bool printToConsole = false)
{
if (!_isInitialized)
{
Core.Logger.LogError((object)"LoggerService is not initialized. Cannot log messages.");
return;
}
LogItem logItem = new LogItem(content, type, moduleName ?? string.Empty);
_logBuffer.Add(logItem);
if (printToConsole)
{
LogToConsole(type, logItem.GetLine());
}
if (_logBuffer.Count >= 50)
{
WriteLogBuffer();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public LoggerService RegisterLogType(string name, string category)
{
if (_registeredLogTypes.ContainsKey(name))
{
Core.Logger.LogError((object)("Log type '" + name + "' is already registered. Skipping registration..."));
return this;
}
_registeredLogTypes[name] = (_splitLogMode ? category : "BetterInfoUI");
return this;
}
private void InitializeLogFiles()
{
foreach (string item in _registeredLogTypes.Values.Distinct())
{
if (!File.Exists(GetLogFilePath(item)))
{
InitializeLogFile(item);
}
}
}
private void WriteLogBuffer()
{
if (_logBuffer.Count == 0)
{
return;
}
Dictionary<string, List<LogItem>> dictionary = (from log in _logBuffer
group log by (!_registeredLogTypes.ContainsKey(log.Type)) ? "BetterInfoUI" : _registeredLogTypes[log.Type]).ToDictionary((IGrouping<string, LogItem> group) => group.Key, (IGrouping<string, LogItem> group) => group.ToList());
_logBuffer.Clear();
foreach (string key in dictionary.Keys)
{
string logFilePath = GetLogFilePath(key);
if (!File.Exists(logFilePath))
{
InitializeLogFile(key);
}
IEnumerable<string> contents = dictionary[key].Select((LogItem log) => log.GetLine());
File.AppendAllLines(logFilePath, contents);
}
}
public static void Shutdown()
{
if (!_instance._isInitialized)
{
return;
}
try
{
if (_instance._logBuffer.Count > 0)
{
_instance.WriteLogBuffer();
}
ManualLogSource logger = Core.Logger;
if (logger != null)
{
logger.LogInfo((object)"LoggerService shutdown completed successfully.");
}
_instance._isInitialized = false;
}
catch (Exception ex)
{
ManualLogSource logger2 = Core.Logger;
if (logger2 != null)
{
logger2.LogError((object)("LoggerService shutdown error: " + ex.Message + "\n" + ex.StackTrace));
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string GetLogFilePath(string category)
{
string path = category + ".log";
return Path.Combine(LogsPath, path);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InitializeLogFile(string category)
{
string text = CurrentTime + " [BetterInfoUI] [LoggerService]";
text += Environment.NewLine;
text += "-----------------------------------------------";
text = text + Environment.NewLine + Environment.NewLine;
File.WriteAllText(GetLogFilePath(category), text);
}
private static string GetFormatTime()
{
DateTime dateTime = DateTime.Now.ToLocalTime();
string text = $"{GetTwoDigit(dateTime.Day)}.{GetTwoDigit(dateTime.Month)}.{dateTime.Year}";
string text2 = GetTwoDigit(dateTime.Hour) + ":" + GetTwoDigit(dateTime.Minute) + ":" + GetTwoDigit(dateTime.Second);
return "[" + text + "|" + text2 + "]";
static string GetTwoDigit(int value)
{
return value.ToString("D2");
}
}
private static void ArchiveOldLogsFiles()
{
if (!Directory.Exists(ArchiveLogsPath))
{
Directory.CreateDirectory(ArchiveLogsPath);
}
string text = Path.Combine(ArchiveLogsPath, DateTime.Now.ToString("dd-MM-yyyy_HH-mm-ss"));
string[] files = Directory.GetFiles(LogsPath, "*.log");
if (!Directory.Exists(text))
{
Directory.CreateDirectory(text);
}
string[] array = files;
foreach (string obj in array)
{
string fileName = Path.GetFileName(obj);
string destFileName = Path.Combine(text, fileName);
File.Move(obj, destFileName);
}
}
private static void DeleteOldLogFiles()
{
string[] files = Directory.GetFiles(LogsPath, "*.log");
if (files.Length <= 5)
{
return;
}
foreach (FileInfo item in (from file in files
select new FileInfo(file) into fileInfo
orderby fileInfo.LastWriteTime descending
select fileInfo).Skip(5).ToList())
{
try
{
item.Delete();
}
catch (Exception ex)
{
Core.Logger.LogError((object)("Failed to delete old log file '" + item.FullName + "': " + ex.Message));
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void LogToConsole(string type, string content)
{
switch (char.ToUpper(type[0]) + type.Substring(1, type.Length - 1).ToLower())
{
case "Warning":
Core.Logger.LogWarning((object)content);
break;
case "Debug":
Core.Logger.LogDebug((object)content);
break;
case "Error":
Core.Logger.LogError((object)content);
break;
case "Fatal":
Core.Logger.LogFatal((object)content);
break;
default:
Core.Logger.LogInfo((object)content);
break;
}
}
}
}
namespace BetterInfoUI.Services.Game
{
public sealed class AfflictionService
{
public static bool TryGetModel(BarAffliction affliction, out AfflictionModel model)
{
//IL_0020: Unknown result type (might be due to invalid IL or missing references)
//IL_0025: Unknown result type (might be due to invalid IL or missing references)
//IL_0035: Unknown result type (might be due to invalid IL or missing references)
//IL_003d: Unknown result type (might be due to invalid IL or missing references)
model = default(AfflictionModel);
if ((Object)(object)affliction == (Object)null || (Object)(object)Character.observedCharacter == (Object)null)
{
return false;
}
STATUSTYPE afflictionType = affliction.afflictionType;
float currentStatus = Character.observedCharacter.refs.afflictions.GetCurrentStatus(afflictionType);
model = new AfflictionModel(afflictionType, currentStatus, affliction.width, ((Component)affliction).gameObject.activeSelf);
return true;
}
public static bool TryGetValue(STATUSTYPE type, out float value)
{
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
value = 0f;
if ((Object)(object)Character.observedCharacter == (Object)null)
{
return false;
}
value = Character.observedCharacter.refs.afflictions.GetCurrentStatus(type);
return true;
}
public static bool TryFindAffliction(StaminaBar staminaBar, STATUSTYPE type, out BarAffliction affliction)
{
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
//IL_002b: Unknown result type (might be due to invalid IL or missing references)
affliction = null;
if ((Object)(object)staminaBar == (Object)null || staminaBar.afflictions == null)
{
return false;
}
BarAffliction[] afflictions = staminaBar.afflictions;
foreach (BarAffliction val in afflictions)
{
if (val.afflictionType == type)
{
affliction = val;
return true;
}
}
return false;
}
}
public sealed class CharacterService
{
private const int DeadHeightThreshold = 2000;
public static bool TryGetStaminaModel(StaminaBar staminaBar, out StaminaModel model)
{
model = default(StaminaModel);
if ((Object)(object)Character.observedCharacter == (Object)null || (Object)(object)staminaBar == (Object)null || (Object)(object)staminaBar.staminaBar == (Object)null)
{
return false;
}
Character observedCharacter = Character.observedCharacter;
CharacterData data = observedCharacter.data;
bool isVisible = ((Component)staminaBar.staminaBar).gameObject.activeSelf && !data.fullyPassedOut && !IsDead(observedCharacter);
model = new StaminaModel(data.currentStamina, data.extraStamina, observedCharacter.GetMaxStamina(), isVisible);
return true;
}
private static bool IsDead(Character character)
{
return Mathf.FloorToInt(character.refs.stats.heightInMeters) > 2000;
}
}
public sealed class RunLifecycleService
{
[CompilerGenerated]
private ConfigService <configService>P;
[CompilerGenerated]
private AfflictionUIService <afflictionUiService>P;
private bool _isSubscribed;
public RunLifecycleService(ConfigService configService, AfflictionUIService afflictionUiService)
{
<configService>P = configService;
<afflictionUiService>P = afflictionUiService;
base..ctor();
}
public RunLifecycleService Initialize()
{
//IL_0011: Unknown result type (might be due to invalid IL or missing references)
//IL_001b: Expected O, but got Unknown
if (_isSubscribed)
{
return this;
}
Application.logMessageReceived += new LogCallback(OnLogMessageReceived);
_isSubscribed = true;
LoggerService.Info("Subscribed to Application.logMessageReceived", "RunLifecycleService", <configService>P.Current.VerboseLogging);
return this;
}
public void Dispose()
{
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Expected O, but got Unknown
if (_isSubscribed)
{
Application.logMessageReceived -= new LogCallback(OnLogMessageReceived);
_isSubscribed = false;
LoggerService.Info("Unsubscribed from Application.logMessageReceived", "RunLifecycleService", <configService>P.Current.VerboseLogging);
}
}
private void OnLogMessageReceived(string logString, string stackTrace, LogType logType)
{
if (string.IsNullOrEmpty(logString) || !logString.Contains("RUN STARTED", StringComparison.Ordinal))
{
return;
}
try
{
LoggerService.Info("RUN STARTED detected, resetting affliction UI state", "RunLifecycleService", <configService>P.Current.VerboseLogging);
<afflictionUiService>P.Cleanup();
}
catch (Exception ex)
{
LoggerService.Error("Failed to reset affliction UI: " + ex.Message, "RunLifecycleService", printToConsole: true);
}
}
}
}
namespace BetterInfoUI.Models
{
public readonly struct AfflictionModel
{
public STATUSTYPE Type { get; }
public float Value { get; }
public float Width { get; }
public bool IsActive { get; }
public float ValuePercent => Value * 100f;
public AfflictionModel(STATUSTYPE type, float value, float width, bool isActive)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
Type = type;
Value = value;
Width = width;
IsActive = isActive;
}
}
public readonly struct StaminaModel
{
public float Current { get; }
public float Extra { get; }
public float Max { get; }
public bool IsVisible { get; }
public float CurrentPercent => Current * 100f;
public float ExtraPercent => Extra * 100f;
public float MaxPercent => Max * 100f;
public StaminaModel(float current, float extra, float max, bool isVisible)
{
Current = current;
Extra = extra;
Max = max;
IsVisible = isVisible;
}
}
}
namespace BetterInfoUI.Hooks
{
[HarmonyPatch(typeof(BarAffliction), "UpdateAffliction")]
public static class BarAfflictionUpdatePatch
{
private static AfflictionUIService _afflictionUiService;
public static void Initialize()
{
_afflictionUiService = GameContext.Get<AfflictionUIService>();
}
[HarmonyPostfix]
public static void Postfix(BarAffliction __instance, StaminaBar bar)
{
_afflictionUiService?.HandleAfflictionUpdate(__instance, bar);
}
}
[HarmonyPatch(typeof(StaminaBar), "ChangeBar")]
public static class StaminaBarChangeBarPatch
{
private static AfflictionUIService _afflictionUiService;
public static void Initialize()
{
_afflictionUiService = GameContext.Get<AfflictionUIService>();
}
[HarmonyPostfix]
public static void Postfix(StaminaBar __instance)
{
_afflictionUiService?.HandleBarChanged(__instance);
}
}
[HarmonyPatch(typeof(StaminaBar), "Start")]
public static class StaminaBarStartPatch
{
private static StaminaUIService _staminaUiService;
private static AfflictionUIService _afflictionUiService;
public static void Initialize()
{
_staminaUiService = GameContext.Get<StaminaUIService>();
_afflictionUiService = GameContext.Get<AfflictionUIService>();
}
[HarmonyPostfix]
public static void Postfix(StaminaBar __instance)
{
_staminaUiService?.HandleBarStart(__instance);
_afflictionUiService?.HandleBarStart(__instance);
}
}
[HarmonyPatch(typeof(StaminaBar), "Update")]
public static class StaminaBarUpdatePatch
{
private static StaminaUIService _staminaUiService;
public static void Initialize()
{
_staminaUiService = GameContext.Get<StaminaUIService>();
}
[HarmonyPostfix]
public static void Postfix(StaminaBar __instance)
{
_staminaUiService?.Update(__instance);
}
}
}
namespace BetterInfoUI.Framework
{
public static class GameContext
{
private static readonly Dictionary<Type, object> Services = new Dictionary<Type, object>();
private static bool _isInitialized;
public static void Initialize()
{
if (!_isInitialized)
{
_isInitialized = true;
}
}
public static void RegisterSingleton<TService>(TService instance) where TService : class
{
Services[typeof(TService)] = instance ?? throw new ArgumentNullException("instance");
}
public static TService Get<TService>() where TService : class
{
if (Services.TryGetValue(typeof(TService), out var value))
{
return (TService)value;
}
throw new InvalidOperationException("Service not registered: " + typeof(TService).Name);
}
public static bool TryGet<TService>(out TService service) where TService : class
{
if (Services.TryGetValue(typeof(TService), out var value))
{
service = (TService)value;
return true;
}
service = null;
return false;
}
public static void Clear()
{
Services.Clear();
_isInitialized = false;
}
}
public static class TaskRunner
{
public struct TaskRunnerStats
{
public int ActiveTasks { get; set; }
public int HighPriorityQueueSize { get; set; }
public int NormalPriorityQueueSize { get; set; }
public int PooledTasksAvailable { get; set; }
public string CurrentFrameLevel { get; set; }
public string FrameCounterDetails { get; set; }
public int ResetCount { get; set; }
public double CycleProgress { get; set; }
public bool IsInitialized { get; set; }
public bool IsServerMonitoringEnabled { get; set; }
public FramePerformanceData LastFramePerformance { get; set; }
public ServerPerformanceStats ServerPerformanceStats { get; set; }
public int MaxHighPriorityBatchSize { get; set; }
public int MaxNormalPriorityBatchSize { get; set; }
public int CurrentHighPriorityBatchCount { get; set; }
public int CurrentNormalPriorityBatchCount { get; set; }
}
private static readonly ObjectPool<TaskItem> TaskPool;
private static readonly ConcurrentDictionary<string, TaskItem> NamedTasks;
private static readonly PriorityTaskQueue HighPriorityQueue;
private static readonly PriorityTaskQueue NormalPriorityQueue;
private static long _cachedTimeMs;
private static readonly Timer TimeCacheUpdater;
private static readonly ThreadLocal<List<TaskItem>> HighPriorityBatch;
private static readonly ThreadLocal<List<TaskItem>> NormalPriorityBatch;
private static volatile bool _isInitialized;
private static int _processingLock;
public static int ActiveTaskCount => NamedTasks.Count;
static TaskRunner()
{
TaskPool = new ObjectPool<TaskItem>(200);
NamedTasks = new ConcurrentDictionary<string, TaskItem>();
HighPriorityQueue = new PriorityTaskQueue();
NormalPriorityQueue = new PriorityTaskQueue();
HighPriorityBatch = new ThreadLocal<List<TaskItem>>(() => new List<TaskItem>(64));
NormalPriorityBatch = new ThreadLocal<List<TaskItem>>(() => new List<TaskItem>(128));
TimeCacheUpdater = new Timer(UpdateTimeCache, null, 0, 16);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void UpdateTimeCache(object _)
{
Interlocked.Exchange(ref _cachedTimeMs, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
}
public static void Initialize()
{
if (!_isInitialized)
{
GameFrame.OnUpdate += ProcessAllTasks;
_isInitialized = true;
LoggerService.Info("Initialized successfully", "TaskRunner", printToConsole: true);
}
}
public static void Shutdown()
{
if (_isInitialized)
{
_isInitialized = false;
GameFrame.OnUpdate -= ProcessAllTasks;
ClearAllTasks();
TimeCacheUpdater?.Dispose();
HighPriorityBatch.Dispose();
NormalPriorityBatch.Dispose();
LoggerService.Info("Shutdown completed", "TaskRunner", printToConsole: true);
}
}
public static bool HasTask(string name)
{
if (!string.IsNullOrEmpty(name))
{
return NamedTasks.ContainsKey(name);
}
return false;
}
public static TaskRunnerStats GetDetailedStats()
{
TaskRunnerStats result = default(TaskRunnerStats);
result.ActiveTasks = NamedTasks.Count;
result.HighPriorityQueueSize = HighPriorityQueue.Count;
result.NormalPriorityQueueSize = NormalPriorityQueue.Count;
result.PooledTasksAvailable = TaskPool.AvailableCount;
result.IsInitialized = _isInitialized;
result.IsServerMonitoringEnabled = Core.IsFrameMonitoringEnabled;
result.LastFramePerformance = Core.LastFramePerformance;
result.ServerPerformanceStats = (Core.IsFrameMonitoringEnabled ? Core.FrameMonitor.GetPerformanceStats() : default(ServerPerformanceStats));
result.MaxHighPriorityBatchSize = HighPriorityBatch.Value?.Capacity ?? 0;
result.MaxNormalPriorityBatchSize = NormalPriorityBatch.Value?.Capacity ?? 0;
result.CurrentHighPriorityBatchCount = HighPriorityBatch.Value?.Count ?? 0;
result.CurrentNormalPriorityBatchCount = NormalPriorityBatch.Value?.Count ?? 0;
return result;
}
private static string GetTaskRunnerBlock()
{
TaskRunnerStats detailedStats = GetDetailedStats();
string text = (detailedStats.IsInitialized ? "True" : "False");
string text2 = detailedStats.ActiveTasks.ToString();
string text3 = detailedStats.HighPriorityQueueSize.ToString();
string text4 = detailedStats.NormalPriorityQueueSize.ToString();
string text5 = detailedStats.PooledTasksAvailable.ToString();
string text6 = detailedStats.MaxHighPriorityBatchSize.ToString();
string text7 = detailedStats.MaxNormalPriorityBatchSize.ToString();
int currentHighPriorityBatchCount = detailedStats.CurrentHighPriorityBatchCount;
int currentNormalPriorityBatchCount = detailedStats.CurrentNormalPriorityBatchCount;
return "[TaskRunner| Init: " + text + "]:\n- Tasks: " + text2 + " | HPQueue: " + text3 + ", NQueue: " + text4 + " | Pool: " + text5 + "\n" + $"- HPBatch Size: {text6} ({currentHighPriorityBatchCount}) | NBatch Size: {text7} ({currentNormalPriorityBatchCount})";
}
public static string PrintHpTasksList()
{
List<string> tasksInfo = HighPriorityQueue.GetTasksInfo();
return string.Join("\n", tasksInfo);
}
public static string PrintNTasksList()
{
List<string> tasksInfo = NormalPriorityQueue.GetTasksInfo();
return string.Join("\n", tasksInfo);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Action CreateParameterizedAction<T>(Action<T> action, T parameter)
{
return delegate
{
action(parameter);
};
}
public static void AddTask(Action action, int frameDelay = 0, float timeDelay = 0f, bool highPriority = false, bool loop = false, bool runNow = false, string name = null)
{
AddTaskToSystem(CreateAndConfigureTask(action, frameDelay, timeDelay, highPriority, loop, name), runNow, uniqueCheck: false);
}
public static void AddTask(Func<Task> asyncAction, int frameDelay = 0, float timeDelay = 0f, bool highPriority = false, bool loop = false, bool runNow = false, string name = null)
{
AddTaskToSystem(CreateAndConfigureTask(asyncAction, frameDelay, timeDelay, highPriority, loop, name), runNow, uniqueCheck: false);
}
public static void AddTask<T>(Action<T> action, T parameter, int frameDelay = 0, float timeDelay = 0f, bool highPriority = false, bool loop = false, bool runNow = false, string name = null)
{
AddTaskToSystem(CreateAndConfigureTask(CreateParameterizedAction(action, parameter), frameDelay, timeDelay, highPriority, loop, name), runNow, uniqueCheck: false);
}
public static void AddUniqueTask(Action action, string name, int frameDelay = 0, float timeDelay = 0f, bool highPriority = false, bool loop = false, bool runNow = false)
{
ValidateUniqueTaskName(name);
AddTaskToSystem(CreateAndConfigureTask(action, frameDelay, timeDelay, highPriority, loop, name), runNow, uniqueCheck: true);
}
public static void AddUniqueTask(Func<Task> asyncAction, string name, int frameDelay = 0, float timeDelay = 0f, bool highPriority = false, bool loop = false, bool runNow = false)
{
ValidateUniqueTaskName(name);
AddTaskToSystem(CreateAndConfigureTask(asyncAction, frameDelay, timeDelay, highPriority, loop, name), runNow, uniqueCheck: true);
}
public static void AddUniqueTask<T>(Action<T> action, T parameter, string name, int frameDelay = 0, float timeDelay = 0f, bool highPriority = false, bool loop = false, bool runNow = false)
{
ValidateUniqueTaskName(name);
AddTaskToSystem(CreateAndConfigureTask(CreateParameterizedAction(action, parameter), frameDelay, timeDelay, highPriority, loop, name), runNow, uniqueCheck: true);
}
public static bool RemoveTask(string name)
{
if (string.IsNullOrEmpty(name))
{
return false;
}
if (!NamedTasks.TryRemove(name, out var value))
{
return false;
}
CleanupTask(value);
return true;
}
public static void ClearAllTasks()
{
NamedTasks.Clear();
ClearQueue(HighPriorityQueue);
ClearQueue(NormalPriorityQueue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ProcessAllTasks()
{
if (Interlocked.CompareExchange(ref _processingLock, 1, 0) != 0)
{
return;
}
try
{
long cachedTimeMs = _cachedTimeMs;
ProcessTaskQueue(HighPriorityQueue, HighPriorityBatch.Value, cachedTimeMs);
ProcessTaskQueue(NormalPriorityQueue, NormalPriorityBatch.Value, cachedTimeMs);
}
finally
{
Interlocked.Exchange(ref _processingLock, 0);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ProcessTaskQueue(PriorityTaskQueue queue, List<TaskItem> batch, long currentTime)
{
CollectReadyTasks(queue, batch, currentTime);
if (batch.Count != 0)
{
ExecuteTaskBatch(batch);
batch.Clear();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CollectReadyTasks(PriorityTaskQueue queue, List<TaskItem> batch, long currentTime)
{
int num = 0;
int num2 = 0;
TaskItem task;
while (num < 100 && num2 < 50 && queue.TryPeek(out task))
{
if (!queue.TryDequeue(out task))
{
continue;
}
if (task.IsRemoved)
{
CleanupTask(task);
num2++;
continue;
}
if (!task.IsReadyToExecute(currentTime))
{
queue.Enqueue(task);
break;
}
batch.Add(task);
num++;
}
}
private static void ExecuteTaskBatch(IEnumerable<TaskItem> batch)
{
foreach (TaskItem item in batch.Where((TaskItem task) => !task.IsRemoved))
{
try
{
item.Execute();
HandleTaskCompletion(item);
}
catch (Exception ex)
{
LoggerService.Error("Task execution failed: " + ex.Message, "TaskRunner");
CleanupTask(item);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void HandleTaskCompletion(TaskItem task)
{
if (task.IsRemoved || !task.ShouldLoop)
{
CleanupTask(task);
return;
}
task.PrepareForNextLoop(_cachedTimeMs);
(task.IsHighPriority ? HighPriorityQueue : NormalPriorityQueue).Enqueue(task);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ValidateUniqueTaskName(string name)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException("Unique tasks must have a name", "name");
}
}
private static TaskItem CreateAndConfigureTask(Action action, int frameDelay, float timeDelay, bool highPriority, bool loop, string name)
{
if (action == null)
{
throw new ArgumentNullException("action");
}
TaskItem taskItem = TaskPool.Get();
taskItem.Initialize(action, frameDelay, timeDelay, highPriority, loop, name, _cachedTimeMs);
return taskItem;
}
private static TaskItem CreateAndConfigureTask(Func<Task> asyncAction, int frameDelay, float timeDelay, bool highPriority, bool loop, string name)
{
if (asyncAction == null)
{
throw new ArgumentNullException("asyncAction");
}
TaskItem taskItem = TaskPool.Get();
taskItem.Initialize(asyncAction, frameDelay, timeDelay, highPriority, loop, name, _cachedTimeMs);
return taskItem;
}
private static void AddTaskToSystem(TaskItem taskItem, bool runNow, bool uniqueCheck)
{
if (uniqueCheck && HasTask(taskItem.Name))
{
RemoveTask(taskItem.Name);
}
if (runNow)
{
ExecuteTaskImmediately(taskItem);
}
RegisterNamedTask(taskItem);
(taskItem.IsHighPriority ? HighPriorityQueue : NormalPriorityQueue).Enqueue(taskItem);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ExecuteTaskImmediately(TaskItem taskItem)
{
try
{
taskItem.Execute();
}
catch (Exception ex)
{
LoggerService.Error("Immediate task execution failed: " + ex.Message, "TaskRunner");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void RegisterNamedTask(TaskItem taskItem)
{
if (!string.IsNullOrEmpty(taskItem.Name))
{
NamedTasks.TryAdd(taskItem.Name, taskItem);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CleanupTask(TaskItem task)
{
if (!string.IsNullOrEmpty(task.Name))
{
NamedTasks.TryRemove(task.Name, out var _);
}
(task.IsHighPriority ? HighPriorityQueue : NormalPriorityQueue)?.Remove(task);
TaskPool.Return(task.Reset());
}
private static void ClearQueue(PriorityTaskQueue queue)
{
TaskItem task;
while (queue.TryDequeue(out task))
{
CleanupTask(task);
}
}
}
}
namespace BetterInfoUI.Configuration
{
public sealed class ConfigService
{
private static readonly string PluginPath = Directory.GetCurrentDirectory();
private static readonly string BepInExConfigsPath = Path.Combine(PluginPath, "BepInEx", "config");
private static readonly string ConfigsPath = Path.Combine(BepInExConfigsPath, "BetterInfoUI".ToLowerInvariant());
private static readonly string ConfigFilePath = Path.Combine(ConfigsPath, "config.json");
public PluginConfig Current { get; private set; } = PluginConfig.CreateDefault();
public ConfigService Initialize()
{
if (!Directory.Exists(ConfigsPath))
{
Directory.CreateDirectory(ConfigsPath);
}
if (!File.Exists(ConfigFilePath))
{
Current = PluginConfig.CreateDefault();
Save();
return this;
}
try
{
PluginConfig pluginConfig = JsonConvert.DeserializeObject<PluginConfig>(File.ReadAllText(ConfigFilePath));
Current = pluginConfig ?? PluginConfig.CreateDefault();
Normalize(Current);
}
catch
{
Current = PluginConfig.CreateDefault();
Save();
}
return this;
}
private void Save()
{
Normalize(Current);
string contents = JsonConvert.SerializeObject((object)Current, (Formatting)1);
File.WriteAllText(ConfigFilePath, contents);
}
private static void Normalize(PluginConfig config)
{
config.StaminaFontSize = Clamp(config.StaminaFontSize, 10, 32);
config.AfflictionFontSize = Clamp(config.AfflictionFontSize, 10, 32);
float textOutlineThickness = config.TextOutlineThickness;
float textOutlineThickness2 = ((textOutlineThickness < 0.1f) ? 0.1f : ((!(textOutlineThickness > 0.3f)) ? config.TextOutlineThickness : 0.3f));
config.TextOutlineThickness = textOutlineThickness2;
if (config.ConfigVersion <= 0)
{
config.ConfigVersion = 1;
}
}
private static int Clamp(int value, int min, int max)
{
if (value < min)
{
return min;
}
if (value <= max)
{
return value;
}
return max;
}
}
public sealed class PluginConfig
{
public int ConfigVersion { get; set; } = 1;
public bool ShowPercentageSign { get; set; }
public int StaminaFontSize { get; set; } = 18;
public int AfflictionFontSize { get; set; } = 18;
public float TextOutlineThickness { get; set; } = 0.1f;
public bool VerboseLogging { get; set; }
public static PluginConfig CreateDefault()
{
return new PluginConfig();
}
}
}
namespace BetterInfoUI.Common
{
public class CircularBuffer<T>
{
private readonly T[] _buffer = new T[capacity];
private int _head;
public int Count { get; private set; }
public int Capacity => _buffer.Length;
public T this[int index]
{
get
{
if (index < 0 || index >= Count)
{
throw new IndexOutOfRangeException();
}
int num = (_head - Count + index + _buffer.Length) % _buffer.Length;
return _buffer[num];
}
}
public CircularBuffer(int capacity)
{
}
public void Add(T item)
{
_buffer[_head] = item;
_head = (_head + 1) % _buffer.Length;
if (Count < _buffer.Length)
{
Count++;
}
}
public void Clear()
{
_head = 0;
Count = 0;
Array.Clear(_buffer, 0, _buffer.Length);
}
}
public static class Colors
{
public static readonly Color LowStamina = new Color(1f, 0.3f, 0.3f);
public static readonly Color MediumStamina = new Color(1f, 0.92f, 0.016f);
public static readonly Color HighStamina = new Color(0.5f, 1f, 0.5f);
public static readonly Color ExtraStamina = new Color(1f, 0.92f, 0.016f);
public static readonly IReadOnlyDictionary<STATUSTYPE, Color> AfflictionByStatus = new Dictionary<STATUSTYPE, Color>
{
[(STATUSTYPE)0] = new Color(1f, 0.3f, 0.3f),
[(STATUSTYPE)1] = new Color(0.9f, 0.6f, 0.1f),
[(STATUSTYPE)2] = new Color(0.2f, 0.6f, 0.9f),
[(STATUSTYPE)3] = new Color(0.6f, 0.1f, 0.6f),
[(STATUSTYPE)4] = new Color(0.9f, 0.4f, 0.4f),
[(STATUSTYPE)5] = new Color(0.5f, 0.1f, 0.5f),
[(STATUSTYPE)6] = new Color(1f, 0.4f, 0.8f),
[(STATUSTYPE)7] = new Color(0.6f, 0.4f, 0.2f),
[(STATUSTYPE)8] = new Color(1f, 0.3f, 0.1f)
};
}
public static class Constants
{
public static class Config
{
public const string FileName = "config.json";
public const int Version = 1;
public const int MinFontSize = 10;
public const int MaxFontSize = 32;
public const float MinOutlineThickness = 0.1f;
public const float MaxOutlineThickness = 0.3f;
}
public static class Ui
{
public static class Stamina
{
public const string ContainerName = "StaminaTextContainer";
public const string MainTextName = "StaminaText";
public const string ExtraTextName = "ExtraStaminaText";
public static readonly Vector2 MainTextSize = new Vector2(60f, 30f);
public static readonly Vector2 ExtraTextSize = new Vector2(60f, 30f);
public static readonly Vector2 MainTextAnchoredPosition = new Vector2(20f, 22f);
public static readonly Vector2 ExtraTextAnchoredPosition = new Vector2(24f, 22f);
}
public static class Affliction
{
public const string CanvasName = "AfflictionTextCanvas";
public static readonly Vector2 CanvasReferenceResolution = new Vector2(1920f, 1080f);
public const float MinTextWidth = 40f;
public const float TextWidthFactor = 0.8f;
public const float VerticalOffset = -22f;
public const float TextHeight = 24f;
}
}
}
public struct FrameMonitor
{
private readonly long _minExpectedFrameTimeMs;
private long _lastFrameTimeMs;
private long _frameStartTimeMs;
private long _totalDelayMs;
private int _delayedFrameCount;
private long _maxDelayMs;
private long _recentDelaySum;
private int _recentFrameCount;
private readonly long _lagThresholdMs;
private readonly long _criticalLagThresholdMs;
private readonly CircularBuffer<long> _frameTimings;
public FrameMonitor(long lagThresholdMs = 75L, long criticalLagThresholdMs = 150L, float minExpectedFPS = 20f)
{
_minExpectedFrameTimeMs = (long)(1000.0 / (double)minExpectedFPS);
_lagThresholdMs = lagThresholdMs;
_criticalLagThresholdMs = criticalLagThresholdMs;
_frameTimings = new CircularBuffer<long>(100);
_lastFrameTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
_frameStartTimeMs = _lastFrameTimeMs;
_totalDelayMs = 0L;
_delayedFrameCount = 0;
_maxDelayMs = 0L;
_recentDelaySum = 0L;
_recentFrameCount = 0;
}
public void StartFrame()
{
_frameStartTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
}
public FramePerformanceData EndFrame()
{
long num = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
long num2 = num - _frameStartTimeMs;
long num3 = num - _lastFrameTimeMs;
long minExpectedFrameTimeMs = _minExpectedFrameTimeMs;
long num4 = num3 - minExpectedFrameTimeMs;
FrameType frameType = FrameType.Normal;
if (num4 > _criticalLagThresholdMs)
{
frameType = FrameType.CriticalLag;
}
else if (num4 > _lagThresholdMs)
{
frameType = FrameType.Lag;
}
UpdateStatistics(num4, frameType != FrameType.Normal);
_frameTimings.Add(num2);
FramePerformanceData result = new FramePerformanceData(num2, num3, minExpectedFrameTimeMs, Math.Max(0L, num4), frameType, CalculateAverageFrameTime(), CalculateRecentAverageDelay());
_lastFrameTimeMs = num;
return result;
}
private void UpdateStatistics(long delayDifference, bool isDelayed)
{
if (isDelayed)
{
_delayedFrameCount++;
_totalDelayMs += delayDifference;
_maxDelayMs = Math.Max(_maxDelayMs, delayDifference);
}
_recentDelaySum += Math.Max(0L, delayDifference);
_recentFrameCount++;
if (_recentFrameCount > 60)
{
_recentDelaySum /= 2L;
_recentFrameCount = 30;
}
}
private readonly double CalculateAverageFrameTime()
{
if (_frameTimings.Count == 0)
{
return _minExpectedFrameTimeMs;
}
long num = 0L;
for (int i = 0; i < _frameTimings.Count; i++)
{
num += _frameTimings[i];
}
return (double)num / (double)_frameTimings.Count;
}
private readonly double CalculateRecentAverageDelay()
{
if (_recentFrameCount <= 0)
{
return 0.0;
}
return (double)_recentDelaySum / (double)_recentFrameCount;
}
public readonly ServerPerformanceStats GetPerformanceStats()
{
ServerPerformanceStats result = default(ServerPerformanceStats);
result.TotalDelayMs = _totalDelayMs;
result.DelayedFrameCount = _delayedFrameCount;
result.MaxDelayMs = _maxDelayMs;
result.AverageDelayMs = ((_delayedFrameCount > 0) ? ((double)_totalDelayMs / (double)_delayedFrameCount) : 0.0);
result.RecentAverageDelayMs = CalculateRecentAverageDelay();
result.AverageFrameTimeMs = CalculateAverageFrameTime();
result.MinExpectedFrameTimeMs = _minExpectedFrameTimeMs;
result.LagThresholdMs = _lagThresholdMs;
result.CriticalLagThresholdMs = _criticalLagThresholdMs;
result.FrameTimingsCount = _frameTimings.Count;
return result;
}
}
public readonly struct FramePerformanceData
{
public long FrameTime { get; }
public long TimeSinceLastFrame { get; }
public long MinExpectedFrameTime { get; }
public long DelayMs { get; }
public FrameType Type { get; }
public double AverageFrameTime { get; }
public double RecentAverageDelay { get; }
public FramePerformanceData(long frameTime, long timeSinceLastFrame, long minExpectedFrameTime, long delayMs, FrameType type, double averageFrameTime, double recentAverageDelay)
{
FrameTime = frameTime;
TimeSinceLastFrame = timeSinceLastFrame;
MinExpectedFrameTime = minExpectedFrameTime;
DelayMs = delayMs;
Type = type;
AverageFrameTime = averageFrameTime;
RecentAverageDelay = recentAverageDelay;
}
}
public enum FrameType
{
Normal,
Lag,
CriticalLag
}
public struct ServerPerformanceStats
{
public long TotalDelayMs { get; set; }
public int DelayedFrameCount { get; set; }
public long MaxDelayMs { get; set; }
public double AverageDelayMs { get; set; }
public double RecentAverageDelayMs { get; set; }
public double AverageFrameTimeMs { get; set; }
public long MinExpectedFrameTimeMs { get; set; }
public long LagThresholdMs { get; set; }
public long CriticalLagThresholdMs { get; set; }
public int FrameTimingsCount { get; set; }
public override string ToString()
{
return $"Delays: {DelayedFrameCount} frames, Avg: {AverageDelayMs:F2}ms, " + $"Recent: {RecentAverageDelayMs:F2}ms, Max: {MaxDelayMs}ms, " + $"Frame Time: {AverageFrameTimeMs:F2}ms (MinExpectedFrameTime: {MinExpectedFrameTimeMs}ms)";
}
}
public class LogItem
{
private string Content { get; }
public string Type { get; }
private string ModuleName { get; }
private DateTime Timestamp { get; }
public LogItem(string content, string type, string moduleName)
{
Content = content;
Type = type;
ModuleName = moduleName;
Timestamp = DateTime.Now;
base..ctor();
}
public string GetLine()
{
string text = Timestamp.ToString("dd.MM.yyyy|HH:mm:ss", CultureInfo.InvariantCulture);
string text2 = (string.Empty.Equals(ModuleName) ? "" : ("[" + ModuleName + "]: "));
return "[" + text + "][" + Type + "]" + text2 + Content;
}
}
internal sealed class TaskItem
{
private Action _syncAction;
private Func<Task> _asyncAction;
private TaskExecutionMode _executionMode;
private float _originalTimeDelay;
private int _originalFrameDelay;
private volatile bool _isRemoved;
public string Name { get; private set; }
public bool IsHighPriority { get; private set; }
public bool ShouldLoop { get; private set; }
public long Priority { get; private set; }
public bool IsRemoved => _isRemoved;
public void Initialize(Action action, int frameDelay, float timeDelay, bool highPriority, bool loop, string name, long currentTime)
{
_syncAction = action;
_asyncAction = null;
InitializeCommon(frameDelay, timeDelay, highPriority, loop, name, currentTime);
}
public void Initialize(Func<Task> asyncAction, int frameDelay, float timeDelay, bool highPriority, bool loop, string name, long currentTime)
{
_syncAction = null;
_asyncAction = asyncAction;
InitializeCommon(frameDelay, timeDelay, highPriority, loop, name, currentTime);
}
private void InitializeCommon(int frameDelay, float timeDelay, bool highPriority, bool loop, string name, long currentTime)
{
if (frameDelay <= 0 && timeDelay <= 0f)
{
throw new ArgumentException("Either frameDelay or timeDelay must be greater than 0");
}
_executionMode = TaskExecutionMode.Time;
IsHighPriority = highPriority;
ShouldLoop = loop;
Name = name;
_isRemoved = false;
_originalFrameDelay = frameDelay;
_originalTimeDelay = timeDelay;
CalculateExecutionTime(currentTime);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CalculateExecutionTime(long currentTime)
{
if (_executionMode == TaskExecutionMode.Time)
{
Priority = currentTime + (long)(_originalTimeDelay * 1000f);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsReadyToExecute(long currentTime)
{
if (_isRemoved)
{
return false;
}
if (_executionMode == TaskExecutionMode.Time)
{
return currentTime >= Priority;
}
return false;
}
public async void Execute()
{
try
{
if (!_isRemoved)
{
if (_syncAction != null)
{
_syncAction();
}
else if (_asyncAction != null)
{
await _asyncAction().ConfigureAwait(continueOnCapturedContext: false);
}
}
}
catch (Exception ex)
{
LoggerService.Error("Task '" + (Name ?? "unnamed") + "' execution failed: " + ex.Message, "TaskItem");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PrepareForNextLoop(long currentTime)
{
if (ShouldLoop)
{
CalculateExecutionTime(currentTime);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MarkAsRemoved()
{
_isRemoved = true;
}
public TaskItem Reset()
{
_syncAction = null;
_asyncAction = null;
_executionMode = TaskExecutionMode.Time;
Priority = 0L;
_originalTimeDelay = 0f;
_originalFrameDelay = 0;
Name = null;
IsHighPriority = false;
ShouldLoop = false;
_isRemoved = false;
return this;
}
}
internal sealed class ObjectPool<T> where T : class, new()
{
[CompilerGenerated]
private int <maxPoolSize>P;
[CompilerGenerated]
private Action<T> <resetAction>P;
private readonly ConcurrentQueue<T> _objects;
private int _currentCount;
public int AvailableCount => _currentCount;
public ObjectPool(int maxPoolSize = 100, Action<T> resetAction = null)
{
<maxPoolSize>P = maxPoolSize;
<resetAction>P = resetAction;
_objects = new ConcurrentQueue<T>();
base..ctor();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Get()
{
if (!_objects.TryDequeue(out var result))
{
return new T();
}
Interlocked.Decrement(ref _currentCount);
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Return(T item)
{
if (item != null && _currentCount < <maxPoolSize>P)
{
<resetAction>P?.Invoke(item);
_objects.Enqueue(item);
Interlocked.Increment(ref _currentCount);
}
}
}
internal sealed class PriorityTaskQueue
{
private readonly SortedDictionary<long, Queue<TaskItem>> _priorityQueues = new SortedDictionary<long, Queue<TaskItem>>();
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
public int Count
{
get
{
_lock.EnterReadLock();
try
{
return _priorityQueues.Count;
}
finally
{
_lock.ExitReadLock();
}
}
}
public void Remove(TaskItem task)
{
_lock.EnterWriteLock();
try
{
if (_priorityQueues.TryGetValue(task.Priority, out var value) && value.Contains(task))
{
if (!task.IsRemoved)
{
task.MarkAsRemoved();
}
value = new Queue<TaskItem>(value.Where((TaskItem t) => !t.IsRemoved));
_priorityQueues[task.Priority] = value;
if (value.Count == 0)
{
_priorityQueues.Remove(task.Priority);
}
}
}
finally
{
_lock.ExitWriteLock();
}
}
public List<string> GetTasksInfo()
{
_lock.EnterReadLock();
try
{
return _priorityQueues.SelectMany((KeyValuePair<long, Queue<TaskItem>> pair) => pair.Value.Select((TaskItem task) => $"{task.Name}: {!task.IsRemoved}")).ToList();
}
finally
{
_lock.ExitReadLock();
}
}
public void Enqueue(TaskItem task)
{
long priority = task.Priority;
_lock.EnterWriteLock();
try
{
if (!_priorityQueues.TryGetValue(priority, out var value))
{
value = new Queue<TaskItem>();
_priorityQueues[priority] = value;
}
value.Enqueue(task);
}
finally
{
_lock.ExitWriteLock();
}
}
public bool TryDequeue(out TaskItem task)
{
_lock.EnterWriteLock();
try
{
if (_priorityQueues.Count == 0)
{
task = null;
return false;
}
_priorityQueues.First().Deconstruct(out var key, out var value);
long key2 = key;
Queue<TaskItem> queue = value;
task = queue.Dequeue();
if (queue.Count == 0)
{
_priorityQueues.Remove(key2);
}
return true;
}
finally
{
_lock.ExitWriteLock();
}
}
public bool TryPeek(out TaskItem task)
{
_lock.EnterReadLock();
try
{
if (_priorityQueues.Count == 0)
{
task = null;
return false;
}
Queue<TaskItem> value = _priorityQueues.First().Value;
task = value.Peek();
return true;
}
finally
{
_lock.ExitReadLock();
}
}
}
internal enum TaskExecutionMode
{
Time
}
}
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
internal sealed class IgnoresAccessChecksToAttribute : Attribute
{
public IgnoresAccessChecksToAttribute(string assemblyName)
{
}
}
internal sealed class IsExternalInit
{
}
}