Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Skuld v1.0.0
Skuld.dll
Decompiled 3 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("Skuld")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Converts death skill loss into repayable per-skill debt.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+9b5c5156c205ee9ba7217ec92501947c6fcacbb8")] [assembly: AssemblyProduct("Skuld")] [assembly: AssemblyTitle("Skuld")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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 Skuld { internal static class ModColorUtil { internal static Color Parse(string value, Color fallback) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_001b: 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_0027: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(value)) { return fallback; } string value2 = value.Trim(); if (TryParseHex(value2, out var color)) { return color; } if (TryParseRgb(value2, out var color2)) { return color2; } Plugin.Log.LogWarning((object)("Skuld: could not parse color '" + value + "'. Using default.")); return fallback; } private static bool TryParseHex(string value, out Color color) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) color = default(Color); string text = (value.StartsWith("#", StringComparison.Ordinal) ? value.Substring(1) : value); if (text.Length != 6 && text.Length != 8) { return false; } if (!uint.TryParse(text, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result)) { return false; } if (text.Length == 6) { color = new Color((float)((result >> 16) & 0xFF) / 255f, (float)((result >> 8) & 0xFF) / 255f, (float)(result & 0xFF) / 255f, 1f); return true; } color = new Color((float)((result >> 24) & 0xFF) / 255f, (float)((result >> 16) & 0xFF) / 255f, (float)((result >> 8) & 0xFF) / 255f, (float)(result & 0xFF) / 255f); return true; } private static bool TryParseRgb(string value, out Color color) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_008e: 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) color = default(Color); string[] array = value.Split(new char[1] { ',' }); if (array.Length < 3 || array.Length > 4) { return false; } if (!TryParseByte(array[0], out var result) || !TryParseByte(array[1], out var result2) || !TryParseByte(array[2], out var result3)) { return false; } float num = 1f; if (array.Length == 4) { if (!TryParseByte(array[3], out var result4)) { return false; } num = (float)(int)result4 / 255f; } color = new Color((float)(int)result / 255f, (float)(int)result2 / 255f, (float)(int)result3 / 255f, num); return true; } private static bool TryParseByte(string value, out byte result) { if (int.TryParse(value.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2)) { result = (byte)Mathf.Clamp(result2, 0, 255); return true; } result = 0; return false; } } internal static class ModConfig { private static readonly Color DefaultDebtBarColor = new Color(0.55f, 0.12f, 0.22f, 1f); private static readonly Color DefaultDebtTextColor = new Color(0.85f, 0.18f, 0.18f, 1f); private const int DefaultDebtTextSize = 18; private const string ServerSyncedNote = " This setting is server synced."; private const string ClientOnlyNote = " Client-only; not server synced."; private static bool serverOverlayActive; private static bool serverEnableMod; private static float serverDebtPaydownShare; private static int serverMaxDebtPerSkill; internal static ConfigEntry<bool> EnableMod { get; private set; } = null; internal static ConfigEntry<float> DebtPaydownShare { get; private set; } = null; internal static ConfigEntry<int> MaxDebtPerSkill { get; private set; } = null; internal static ConfigEntry<bool> DebugLogging { get; private set; } = null; internal static ConfigEntry<bool> EnableDebtClearedSound { get; private set; } = null; internal static ConfigEntry<bool> EnableDevCommands { get; private set; } = null; internal static ConfigEntry<string> DebtBarColor { get; private set; } = null; internal static ConfigEntry<string> DebtTextColor { get; private set; } = null; internal static ConfigEntry<int> DebtTextSize { get; private set; } = null; private static bool UseServerOverlay { get { if (serverOverlayActive && (Object)(object)ZNet.instance != (Object)null) { return !ZNet.instance.IsServer(); } return false; } } internal static bool IsModEnabled { get { if (!UseServerOverlay) { return EnableMod.Value; } return serverEnableMod; } } internal static void ApplyServerOverlay(bool enableMod, float paydownShare, int maxDebtPerSkill) { serverOverlayActive = true; serverEnableMod = enableMod; serverDebtPaydownShare = paydownShare; serverMaxDebtPerSkill = maxDebtPerSkill; } internal static void ClearServerOverlay() { serverOverlayActive = false; } internal static void Bind(ConfigFile config) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Expected O, but got Unknown //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Expected O, but got Unknown EnableMod = config.Bind<bool>("General", "EnableMod", true, "Enable Skuld debt mechanics. This setting is server synced."); DebtPaydownShare = config.Bind<float>("General", "DebtPaydownShare", 0.5f, new ConfigDescription("Fraction of earned XP redirected to debt paydown while debt remains. This setting is server synced.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 1f), Array.Empty<object>())); MaxDebtPerSkill = config.Bind<int>("General", "MaxDebtPerSkill", 3, new ConfigDescription("Maximum debt per skill, measured in deaths-worth of that skill's current level. 0 = uncapped. This setting is server synced.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 5), Array.Empty<object>())); DebugLogging = config.Bind<bool>("General", "DebugLogging", false, "Enable verbose debt conversion/paydown logs. Client-only; not server synced."); EnableDebtClearedSound = config.Bind<bool>("General", "EnableDebtClearedSound", true, "Client-side: red-tinted skill halo + crafting-bonus pop sound when a skill's debt is fully repaid. Client-only; not server synced."); EnableDevCommands = config.Bind<bool>("Dev", "EnableDevCommands", false, "Server/host only: enables skuld_clearcooldown and skuld_cleardebt on the world host. Not server synced; ignored on multiplayer clients. Still requires Valheim devcommands and admin rights on dedicated servers. Must remain false for Thunderstore releases."); DebtBarColor = config.Bind<string>("Visual", "DebtBarColor", "140,31,56", "Client-side color of the debt segment on the gold skill level bar. Use R,G,B (0-255) or #RRGGBB. Client-only; not server synced."); DebtTextColor = config.Bind<string>("Visual", "DebtTextColor", "217,46,46", "Client-side color of the -N debt label on skill rows. Use R,G,B (0-255) or #RRGGBB. Client-only; not server synced."); DebtTextSize = config.Bind<int>("Visual", "DebtTextSize", 18, new ConfigDescription("Client-side pixel font size for the -N debt label on all skill rows. Client-only; not server synced.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(8, 36), Array.Empty<object>())); } internal static bool IsDevCommandsEnabled() { if (!EnableDevCommands.Value) { return false; } if ((Object)(object)ZNet.instance == (Object)null) { return true; } return ZNet.instance.IsServer(); } internal static float GetPaydownShare() { return Mathf.Clamp(UseServerOverlay ? serverDebtPaydownShare : DebtPaydownShare.Value, 0.5f, 1f); } internal static int GetMaxDebtPerSkill() { return Mathf.Clamp(UseServerOverlay ? serverMaxDebtPerSkill : MaxDebtPerSkill.Value, 0, 5); } internal static Color GetDebtBarColor() { //IL_000a: 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) return ModColorUtil.Parse(DebtBarColor.Value, DefaultDebtBarColor); } internal static Color GetDebtTextColor() { //IL_000a: 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) return ModColorUtil.Parse(DebtTextColor.Value, DefaultDebtTextColor); } internal static float GetDebtTextSize() { return Mathf.Clamp(DebtTextSize.Value, 8, 36); } } internal static class ModConstants { internal const string ModGuid = "com.cdjensen99.skuld"; internal const string ModName = "Skuld"; internal const string ModVersion = "1.0.0"; internal const string DebtKeyPrefix = "Skuld_Debt_"; internal const string DebtBaselineKeyPrefix = "Skuld_DebtBaseline_"; internal const string DebtIncurredKeyPrefix = "Skuld_DebtIncurred_"; internal const string FocusPaydownAllKey = "Skuld_FocusPaydown_All"; internal const string FocusPaydownKeyPrefix = "Skuld_FocusPaydown_"; internal const float FocusPaydownShare = 1f; internal const string ConfigSyncRpc = "Skuld_ConfigSync"; } [BepInPlugin("com.cdjensen99.skuld", "Skuld", "1.0.0")] public sealed class Plugin : BaseUnityPlugin { internal static ManualLogSource Log; private Harmony harmony; internal static Plugin Instance { get; private set; } private void Awake() { Instance = this; Log = ((BaseUnityPlugin)this).Logger; ModConfig.Bind(((BaseUnityPlugin)this).Config); SkillDebtService.Initialize(); harmony = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "com.cdjensen99.skuld"); LogPatchStatus(); TryRegisterCommands("dev console", DevConsoleCommands.Register); TryRegisterCommands("player chat", PlayerChatCommands.Register); Log.LogInfo((object)"Skuld 1.0.0 loaded."); } private static void TryRegisterCommands(string label, Action register) { try { register(); } catch (Exception arg) { Log.LogError((object)$"Skuld failed to register {label} commands — patches remain active. {arg}"); } } private void OnDestroy() { Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } private static void LogPatchStatus() { LogOriginalPatchStatus(typeof(Skills), "OnDeath"); LogOriginalPatchStatus(typeof(Skills), "RaiseSkill"); LogOriginalPatchStatus(typeof(Player), "OnDeath"); LogOriginalPatchStatus(typeof(SkillsDialog), "Setup"); } private static void LogOriginalPatchStatus(Type type, string methodName) { MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null); if (methodInfo == null) { Log.LogWarning((object)("Skuld patch diagnostic: could not resolve original " + type.FullName + "." + methodName + ".")); return; } Patches patchInfo = Harmony.GetPatchInfo((MethodBase)methodInfo); if (patchInfo == null) { Log.LogWarning((object)("Skuld patch diagnostic: no Harmony patch info for " + type.FullName + "." + methodName + ".")); } else { string text = string.Join(", ", patchInfo.Owners.Distinct()); Log.LogInfo((object)("Skuld patch diagnostic: " + type.FullName + "." + methodName + " patched. Owners=[" + text + "]")); } } } internal static class DebtClearedFeedback { private const float DebounceSeconds = 0.25f; private static readonly FieldInfo HeadField = AccessTools.Field(typeof(Character), "m_head"); private static readonly Color HaloRed = new Color(0.92f, 0.1f, 0.14f, 1f); private static readonly Color HaloRedSoft = new Color(0.7f, 0.05f, 0.1f, 1f); private static float lastPlayTime = -10f; internal static void TryPlay(Player player) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0095: 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_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer) { return; } if (!ModConfig.EnableDebtClearedSound.Value) { Plugin.Log.LogInfo((object)"Skuld debt-cleared feedback skipped: EnableDebtClearedSound=false in config. Set [General] EnableDebtClearedSound=true (close Valheim first, or use Configuration Manager)."); } else if (!(Time.time - lastPlayTime < 0.25f)) { lastPlayTime = Time.time; Plugin.Log.LogInfo((object)"Skuld debt-cleared feedback: playing red halo + craft pop."); Transform headTransform = GetHeadTransform(player); Vector3 position = (((Object)(object)headTransform != (Object)null) ? headTransform.position : ((Character)player).GetHeadPoint()); Quaternion rotation = (((Object)(object)headTransform != (Object)null) ? headTransform.rotation : ((Component)player).transform.rotation); bool num = SpawnRedDebtHalo(player, position, rotation, headTransform); bool flag = PlayCraftBonusPop(position); if (!num && !flag) { Plugin.Log.LogWarning((object)"Skuld debt-cleared feedback: both halo and pop failed; trying hard fallbacks."); TryHardFallbacks(position); } } } private static Transform GetHeadTransform(Player player) { object? obj = HeadField?.GetValue(player); Transform val = (Transform)((obj is Transform) ? obj : null); if (val != null && (Object)(object)val != (Object)null) { return val; } return ((Component)player).transform; } private static bool SpawnRedDebtHalo(Player player, Vector3 position, Quaternion rotation, Transform headParent) { //IL_007a: 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_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) EffectList skillLevelupEffects = player.m_skillLevelupEffects; if (skillLevelupEffects != null && skillLevelupEffects.HasEffects()) { GameObject[] array = skillLevelupEffects.Create(position, rotation, headParent, 1f, -1, default(ZDOID)); int num = 0; if (array != null) { foreach (GameObject val in array) { if (!((Object)(object)val == (Object)null)) { if (IsAudioOnlyEffect(val)) { Object.Destroy((Object)(object)val); continue; } ApplyRedHaloTint(val); num++; } } } if (num > 0) { return true; } } return TrySpawnPrefabVisual("vfx_skilllevelup", position, rotation, headParent); } private static bool PlayCraftBonusPop(Vector3 position) { //IL_0027: 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_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: 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) EffectList val = (((Object)(object)InventoryGui.instance != (Object)null) ? InventoryGui.instance.m_craftBonusEffect : null); if (val != null && val.HasEffects()) { val.Create(position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID)); return true; } string[] array = new string[6] { "sfx_gui_craftitem", "sfx_gui_craftitem_done", "sfx_gui_craft_item", "sfx_craft_item", "sfx_gui_button", "sfx_gui_repairitem" }; for (int i = 0; i < array.Length; i++) { if (TryPlayZsfxPrefab(array[i], position)) { return true; } } return false; } private static void TryHardFallbacks(Vector3 position) { //IL_0005: 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_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) TrySpawnPrefabVisual("vfx_HealthUpgrade", position, Quaternion.identity, null); TryPlayZsfxPrefab("sfx_levelup", position); TryPlayZsfxPrefab("sfx_gui_button", position); } private static bool TrySpawnPrefabVisual(string prefabName, Vector3 position, Quaternion rotation, Transform parent) { //IL_0027: 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) if ((Object)(object)ZNetScene.instance == (Object)null) { return false; } GameObject prefab = ZNetScene.instance.GetPrefab(prefabName); if ((Object)(object)prefab == (Object)null) { return false; } GameObject val = Object.Instantiate<GameObject>(prefab, position, rotation); if ((Object)(object)parent != (Object)null) { val.transform.SetParent(parent, true); } if (IsAudioOnlyEffect(val)) { ZSFX componentInChildren = val.GetComponentInChildren<ZSFX>(true); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.m_playOnAwake = false; componentInChildren.Play(); return true; } Object.Destroy((Object)(object)val); return false; } ApplyRedHaloTint(val); return true; } private static bool IsAudioOnlyEffect(GameObject root) { if (((Object)root).name.StartsWith("sfx_", StringComparison.OrdinalIgnoreCase)) { return true; } if ((Object)(object)root.GetComponent<ZSFX>() != (Object)null) { return (Object)(object)root.GetComponentInChildren<ParticleSystem>(true) == (Object)null; } return false; } private static void ApplyRedHaloTint(GameObject root) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001b: 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_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: 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_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) ParticleSystem[] componentsInChildren = root.GetComponentsInChildren<ParticleSystem>(true); foreach (ParticleSystem obj in componentsInChildren) { MainModule main = obj.main; ((MainModule)(ref main)).startColor = MinMaxGradient.op_Implicit(HaloRed); ColorOverLifetimeModule colorOverLifetime = obj.colorOverLifetime; ((ColorOverLifetimeModule)(ref colorOverLifetime)).enabled = true; ((ColorOverLifetimeModule)(ref colorOverLifetime)).color = new MinMaxGradient(CreateRedGradient()); } ParticleSystemRenderer[] componentsInChildren2 = root.GetComponentsInChildren<ParticleSystemRenderer>(true); for (int j = 0; j < componentsInChildren2.Length; j++) { if ((Object)(object)((Renderer)componentsInChildren2[j]).material != (Object)null && ((Renderer)componentsInChildren2[j]).material.HasProperty("_Color")) { ((Renderer)componentsInChildren2[j]).material.color = HaloRed; } } TrailRenderer[] componentsInChildren3 = root.GetComponentsInChildren<TrailRenderer>(true); for (int k = 0; k < componentsInChildren3.Length; k++) { componentsInChildren3[k].startColor = HaloRed; componentsInChildren3[k].endColor = new Color(HaloRedSoft.r, HaloRedSoft.g, HaloRedSoft.b, 0f); } Light[] componentsInChildren4 = root.GetComponentsInChildren<Light>(true); for (int l = 0; l < componentsInChildren4.Length; l++) { componentsInChildren4[l].color = HaloRed; } } private static Gradient CreateRedGradient() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: 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_002f: 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_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) Gradient val = new Gradient(); val.SetKeys((GradientColorKey[])(object)new GradientColorKey[3] { new GradientColorKey(HaloRed, 0f), new GradientColorKey(HaloRedSoft, 0.55f), new GradientColorKey(new Color(0.45f, 0.02f, 0.05f), 1f) }, (GradientAlphaKey[])(object)new GradientAlphaKey[3] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(0.85f, 0.65f), new GradientAlphaKey(0f, 1f) }); return val; } private static bool TryPlayZsfxPrefab(string prefabName, Vector3 position) { //IL_0027: 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) if ((Object)(object)ZNetScene.instance == (Object)null) { return false; } GameObject prefab = ZNetScene.instance.GetPrefab(prefabName); if ((Object)(object)prefab == (Object)null) { return false; } GameObject val = Object.Instantiate<GameObject>(prefab, position, Quaternion.identity); ZSFX componentInChildren = val.GetComponentInChildren<ZSFX>(true); if ((Object)(object)componentInChildren == (Object)null) { AudioSource componentInChildren2 = val.GetComponentInChildren<AudioSource>(true); if ((Object)(object)componentInChildren2 != (Object)null && (Object)(object)componentInChildren2.clip != (Object)null) { componentInChildren2.Play(); Object.Destroy((Object)(object)val, componentInChildren2.clip.length + 0.25f); return true; } Object.Destroy((Object)(object)val); return false; } componentInChildren.m_playOnAwake = false; componentInChildren.Play(); return true; } } internal sealed class DebtTooltipBinder : MonoBehaviour { private SkillType skillType; private Skill skill; private UITooltip tooltip; private RectTransform tooltipAnchor; private Vector2 fixedPosition; private string baseDescription = string.Empty; internal void Initialize(SkillType type, Skill skillRef, UITooltip tip, string description, RectTransform anchor, Vector2 position) { //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) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) skillType = type; skill = skillRef; tooltip = tip; baseDescription = description ?? string.Empty; tooltipAnchor = anchor; fixedPosition = position; Refresh(); } private void LateUpdate() { Refresh(); } private void Refresh() { //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_0069: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)tooltip == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null)) { if (skill != null) { SkillDebtBarOverlay.Apply(((Component)this).transform, Player.m_localPlayer, skill); } if (SkillDebtService.GetDebt(Player.m_localPlayer, skillType) <= 0f) { tooltip.Set(string.Empty, baseDescription, tooltipAnchor, fixedPosition); return; } string text = SkillDebtService.FormatProgressDetail(Player.m_localPlayer, skillType); string text2 = (string.IsNullOrEmpty(baseDescription) ? text : (baseDescription + "\n\n" + text)); tooltip.Set(string.Empty, text2, tooltipAnchor, fixedPosition); } } } internal static class DevConsoleCommands { [CompilerGenerated] private static class <>O { public static ConsoleEvent <0>__ClearCooldown; public static ConsoleEvent <1>__ClearDebt; } private static bool registered; internal static void Register() { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //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_003c: Expected O, but got Unknown //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown if (registered) { Plugin.Log.LogWarning((object)"Skuld dev console commands already registered; skipping duplicate registration."); return; } object obj = <>O.<0>__ClearCooldown; if (obj == null) { ConsoleEvent val = ClearCooldown; <>O.<0>__ClearCooldown = val; obj = (object)val; } new ConsoleCommand("skuld_clearcooldown", "Skuld dev: force the next death to count as a hard death.", (ConsoleEvent)obj, true, false, true, true, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj2 = <>O.<1>__ClearDebt; if (obj2 == null) { ConsoleEvent val2 = ClearDebt; <>O.<1>__ClearDebt = val2; obj2 = (object)val2; } new ConsoleCommand("skuld_cleardebt", "Skuld dev: clear all outstanding skill debt for the local player.", (ConsoleEvent)obj2, true, false, true, true, false, false, (ConsoleOptionsFetcher)null, false, false, false); registered = true; string text = (ModConfig.IsDevCommandsEnabled() ? "enabled on server/host" : "disabled (set EnableDevCommands=true on the server/host cfg)"); Plugin.Log.LogInfo((object)("Skuld dev console commands registered (" + text + "): skuld_clearcooldown, skuld_cleardebt.")); } private static void ClearCooldown(ConsoleEventArgs args) { if (!ModConfig.IsDevCommandsEnabled()) { args.Context.AddString("Skuld dev commands are disabled. Set EnableDevCommands=true in the server/host BepInEx config."); return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { args.Context.AddString("No local player."); return; } localPlayer.ClearHardDeath(); args.Context.AddString("Skuld: hard-death cooldown cleared. Next death will generate debt."); } private static void ClearDebt(ConsoleEventArgs args) { if (!ModConfig.IsDevCommandsEnabled()) { args.Context.AddString("Skuld dev commands are disabled. Set EnableDevCommands=true in the server/host BepInEx config."); return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { args.Context.AddString("No local player."); return; } List<string> list = SkillDebtService.ClearAllDebt(localPlayer); if (list.Count == 0) { args.Context.AddString("Skuld: no debt to clear."); } else { args.Context.AddString("Skuld: cleared debt for " + string.Join(", ", list) + "."); } } } internal static class PlayerChatCommands { [CompilerGenerated] private static class <>O { public static ConsoleEvent <0>__ShowCommand; public static ConsoleOptionsFetcher <1>__GetShowTabOptions; public static ConsoleEvent <2>__FocusCommand; public static ConsoleOptionsFetcher <3>__GetFocusTabOptions; } private static bool registered; internal static void Register() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown //IL_0051: 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_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected O, but got Unknown if (!registered) { object obj = <>O.<0>__ShowCommand; if (obj == null) { ConsoleEvent val = ShowCommand; <>O.<0>__ShowCommand = val; obj = (object)val; } object obj2 = <>O.<1>__GetShowTabOptions; if (obj2 == null) { ConsoleOptionsFetcher val2 = GetShowTabOptions; <>O.<1>__GetShowTabOptions = val2; obj2 = (object)val2; } new ConsoleCommand("show", "Skuld: /show debt [...] or /show focus — show debt or focus paydown status.", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)obj2, false, false, false); object obj3 = <>O.<2>__FocusCommand; if (obj3 == null) { ConsoleEvent val3 = FocusCommand; <>O.<2>__FocusCommand = val3; obj3 = (object)val3; } object obj4 = <>O.<3>__GetFocusTabOptions; if (obj4 == null) { ConsoleOptionsFetcher val4 = GetFocusTabOptions; <>O.<3>__GetFocusTabOptions = val4; obj4 = (object)val4; } new ConsoleCommand("focus", "Skuld: /focus <skill|all|off> — 100% debt paydown until cleared.", (ConsoleEvent)obj3, false, false, false, false, false, false, (ConsoleOptionsFetcher)obj4, false, false, false); registered = true; Plugin.Log.LogInfo((object)"Skuld player chat commands registered: /show debt, /show focus, /focus"); } } private static void ShowCommand(ConsoleEventArgs args) { //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) if (args.Length < 2) { args.Context.AddString("Usage: /show debt [...] or /show focus"); return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { args.Context.AddString("No local player."); return; } if (string.Equals(args[1], "focus", StringComparison.OrdinalIgnoreCase)) { SkillDebtService.ShowFocusOverview(localPlayer, args.Context); return; } if (!string.Equals(args[1], "debt", StringComparison.OrdinalIgnoreCase)) { args.Context.AddString("Usage: /show debt [...] or /show focus"); return; } if (args.Length < 3) { args.Context.AddString("Usage: /show debt [skill|all|lifetime <skill|all>]"); return; } string[] array = args.Args.Skip(2).ToArray(); if (array.Length >= 1 && string.Equals(array[0], "lifetime", StringComparison.OrdinalIgnoreCase)) { HandleLifetimeCommand(localPlayer, args.Context, array.Skip(1).ToArray()); return; } string text = string.Join(string.Empty, array); if (string.Equals(text, "all", StringComparison.OrdinalIgnoreCase) || string.Equals(string.Join(" ", array), "all", StringComparison.OrdinalIgnoreCase)) { SkillDebtService.ShowAllDebtOverview(localPlayer, args.Context); return; } if (!SkillDebtService.TryResolveSkillType(text, out var skillType)) { text = string.Join(" ", array); if (!SkillDebtService.TryResolveSkillType(text, out skillType)) { args.Context.AddString("Unknown skill '" + text + "'."); return; } } float debt = SkillDebtService.GetDebt(localPlayer, skillType); float paidOff = SkillDebtService.GetPaidOff(localPlayer, skillType); if (debt <= 0f && paidOff <= 0f) { args.Context.AddString(SkillDebtService.FormatSkillName(skillType) + ": no debt recorded."); return; } string text2 = SkillDebtService.FormatProgressDetail(localPlayer, skillType); args.Context.AddString(SkillDebtService.FormatSkillName(skillType) + ": " + text2); MessageHud instance = MessageHud.instance; if (instance != null) { instance.ShowMessage((MessageType)1, SkillDebtService.FormatSkillName(skillType) + " " + text2, 0, (Sprite)null, false, true); } } private static void FocusCommand(ConsoleEventArgs args) { //IL_011b: Unknown result type (might be due to invalid IL or missing references) if (!ModConfig.IsModEnabled) { args.Context.AddString("Skuld is disabled."); return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { args.Context.AddString("No local player."); return; } if (args.Length < 2) { args.Context.AddString("Usage: /focus <skill|all|off>"); return; } string text = string.Join(" ", args.Args.Skip(1)); SkillType skillType; string message2; if (string.Equals(text, "off", StringComparison.OrdinalIgnoreCase)) { SkillDebtService.ClearAllFocusPaydown(localPlayer); args.Context.AddString("Focused paydown cleared."); MessageHud instance = MessageHud.instance; if (instance != null) { instance.ShowMessage((MessageType)1, "Focused paydown cleared.", 0, (Sprite)null, false, true); } } else if (string.Equals(text, "all", StringComparison.OrdinalIgnoreCase)) { if (!SkillDebtService.TryEnableFocusAll(localPlayer, out var message)) { args.Context.AddString("Could not enable focus all."); return; } args.Context.AddString(message); MessageHud instance2 = MessageHud.instance; if (instance2 != null) { instance2.ShowMessage((MessageType)1, message, 0, (Sprite)null, false, true); } } else if (!SkillDebtService.TryResolveSkillType(text, out skillType)) { args.Context.AddString("Unknown skill '" + text + "'."); } else if (!SkillDebtService.TryEnableFocusSkill(localPlayer, skillType, out message2)) { args.Context.AddString("Could not enable focused paydown."); } else { args.Context.AddString(message2); MessageHud instance3 = MessageHud.instance; if (instance3 != null) { instance3.ShowMessage((MessageType)1, message2, 0, (Sprite)null, false, true); } } } private static void HandleLifetimeCommand(Player player, Terminal context, string[] tail) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0089: 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) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) if (tail.Length == 0) { context.AddString("Usage: /show debt lifetime <skill|all>"); return; } string text = string.Join(" ", tail); if (string.Equals(text, "all", StringComparison.OrdinalIgnoreCase)) { SkillDebtService.ShowAllLifetimeOverview(player, context); return; } if (!SkillDebtService.TryResolveSkillType(text, out var skillType)) { context.AddString("Unknown skill '" + text + "'."); return; } float incurred = SkillDebtService.GetIncurred(player, skillType); float lifetimeRepaid = SkillDebtService.GetLifetimeRepaid(player, skillType); if (incurred <= 0f && lifetimeRepaid <= 0f) { context.AddString(SkillDebtService.FormatSkillName(skillType) + ": no lifetime debt recorded."); return; } string text2 = SkillDebtService.FormatLifetimeDetail(player, skillType); context.AddString(SkillDebtService.FormatSkillName(skillType) + ": " + text2); MessageHud instance = MessageHud.instance; if (instance != null) { instance.ShowMessage((MessageType)1, SkillDebtService.FormatSkillName(skillType) + ": " + text2, 0, (Sprite)null, false, true); } } private static List<string> GetShowTabOptions() { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) List<string> list = new List<string> { "debt", "focus", "all", "lifetime" }; foreach (SkillType value in Enum.GetValues(typeof(SkillType))) { if ((int)value != 0) { list.Add(((object)value/*cast due to .constrained prefix*/).ToString().ToLowerInvariant()); } } return list; } private static List<string> GetFocusTabOptions() { //IL_0039: 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_003f: Unknown result type (might be due to invalid IL or missing references) List<string> list = new List<string> { "all", "off" }; foreach (SkillType value in Enum.GetValues(typeof(SkillType))) { if ((int)value != 0) { list.Add(((object)value/*cast due to .constrained prefix*/).ToString().ToLowerInvariant()); } } return list; } } internal static class ServerConfigSync { private const byte PackageVersion = 1; private static bool rpcRegistered; internal static void Initialize() { if (!rpcRegistered && ZRoutedRpc.instance != null) { ZRoutedRpc.instance.Register<ZPackage>("Skuld_ConfigSync", (Action<long, ZPackage>)ReceiveFromServer); rpcRegistered = true; Plugin.Log.LogInfo((object)"Skuld server config sync RPC registered."); } } internal static void SendToPeer(ZNetPeer peer) { if (peer != null && ZRoutedRpc.instance != null && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { ZPackage val = WriteServerPackage(); ZRoutedRpc.instance.InvokeRoutedRPC(peer.m_uid, "Skuld_ConfigSync", new object[1] { val }); Plugin.Log.LogInfo((object)($"Skuld sent gameplay config to peer {peer.m_playerName} (uid={peer.m_uid}): " + $"EnableMod={ModConfig.EnableMod.Value}, Paydown={ModConfig.GetPaydownShare():F2}, MaxDebt={ModConfig.GetMaxDebtPerSkill()}")); } } internal static void ClearSession() { ModConfig.ClearServerOverlay(); } private static void ReceiveFromServer(long sender, ZPackage package) { if (package == null || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer()) { return; } if (!TryReadServerPackage(package, out var enableMod, out var paydownShare, out var maxDebtPerSkill)) { Plugin.Log.LogWarning((object)"Skuld server config sync: ignored malformed package."); return; } bool value = ModConfig.EnableMod.Value; ModConfig.ApplyServerOverlay(enableMod, paydownShare, maxDebtPerSkill); Plugin.Log.LogInfo((object)$"Skuld applied server gameplay config: EnableMod={enableMod}, Paydown={paydownShare:F2}, MaxDebt={maxDebtPerSkill}"); if ((Object)(object)Plugin.Instance != (Object)null) { ((MonoBehaviour)Plugin.Instance).StartCoroutine(ShowJoinMessagesAfterPlayerReady(value, enableMod, paydownShare, maxDebtPerSkill)); } } private static ZPackage WriteServerPackage() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write((byte)1); val.Write(ModConfig.EnableMod.Value); val.Write(ModConfig.GetPaydownShare()); val.Write(ModConfig.GetMaxDebtPerSkill()); return val; } private static bool TryReadServerPackage(ZPackage package, out bool enableMod, out float paydownShare, out int maxDebtPerSkill) { enableMod = true; paydownShare = 0.5f; maxDebtPerSkill = 3; if (package.Size() < 13) { return false; } if (package.ReadByte() != 1) { return false; } enableMod = package.ReadBool(); paydownShare = package.ReadSingle(); maxDebtPerSkill = package.ReadInt(); return true; } private static IEnumerator ShowJoinMessagesAfterPlayerReady(bool localEnableMod, bool serverEnableMod, float paydownShare, int maxDebtPerSkill) { for (int i = 0; i < 600; i++) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { if (localEnableMod != serverEnableMod) { string text = (serverEnableMod ? "Skuld: server has debt enabled but your local EnableMod is false. Gameplay uses server rules; keep Skuld installed for UI." : "Skuld: server has debt disabled. Your local EnableMod setting is ignored while connected."); ((Character)localPlayer).Message((MessageType)1, text, 0, (Sprite)null, false); } if (serverEnableMod) { string arg = ((maxDebtPerSkill <= 0) ? "uncapped" : $"{maxDebtPerSkill}x death debt"); int num = Mathf.RoundToInt(paydownShare * 100f); ((Character)localPlayer).Message((MessageType)1, $"Skuld server rules: {num}% paydown, max {arg}", 0, (Sprite)null, false); } break; } yield return null; } } internal static ZNetPeer FindPeerByRpc(ZRpc rpc) { if ((Object)(object)ZNet.instance == (Object)null || rpc == null) { return null; } List<ZNetPeer> peers = ZNet.instance.GetPeers(); for (int i = 0; i < peers.Count; i++) { ZNetPeer val = peers[i]; if (val?.m_rpc == rpc) { return val; } } return null; } } [HarmonyPatch(typeof(ZNet), "Start")] internal static class ZNetStartServerConfigSyncPatch { private static void Postfix() { ServerConfigSync.Initialize(); } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] internal static class ZNetPeerInfoServerConfigSyncPatch { private static void Postfix(ZNet __instance, ZRpc rpc) { if (!((Object)(object)__instance == (Object)null) && __instance.IsServer()) { ServerConfigSync.Initialize(); ZNetPeer val = ServerConfigSync.FindPeerByRpc(rpc); if (val != null) { ServerConfigSync.SendToPeer(val); } } } } [HarmonyPatch(typeof(ZNet), "Shutdown")] internal static class ZNetShutdownServerConfigSyncPatch { private static void Prefix() { ServerConfigSync.ClearSession(); } } internal static class SkillDebtBarOverlay { private const string DebtBarName = "skuld_debtbar"; private const string DebtTextName = "skuld_debttext"; private const float LevelBarScale = 100f; private const float LabelGapPixels = 4f; internal static void Apply(Transform row, Player player, Skill skill) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)row == (Object)null || (Object)(object)player == (Object)null || skill?.m_info == null) { return; } RemoveLegacyDebtBar(Utils.FindChild(row, "currentlevel", (IterativeSearchType)0)); Transform val = Utils.FindChild(row, "levelbar", (IterativeSearchType)0); if (!((Object)(object)val == (Object)null)) { GuiBar component = ((Component)val).GetComponent<GuiBar>(); if (!((Object)(object)component == (Object)null)) { float debt = SkillDebtService.GetDebt(player, skill.m_info.m_skill); float levelBarFill = GetLevelBarFill(component); float barDenominator = ResolveBarDenominator(skill.m_info.m_skill, skill.m_level, levelBarFill); ComputeDebtBarSegment(skill.m_level, debt, barDenominator, levelBarFill, out var segmentStart, out var segmentEnd); float num = segmentEnd - segmentStart; bool showDebt = debt > 0f && num > 0f; ApplyMaroonSegment(val, component, segmentStart, segmentEnd, showDebt); ApplyDebtLabel(row, segmentStart, segmentEnd, debt, showDebt); EnsureVanillaLabelsDrawAboveDebtBar(row); } } } private static void EnsureVanillaLabelsDrawAboveDebtBar(Transform row) { Transform val = Utils.FindChild(row, "leveltext", (IterativeSearchType)0); if ((Object)(object)val != (Object)null) { val.SetAsLastSibling(); } Transform val2 = Utils.FindChild(row, "bonustext", (IterativeSearchType)0); if ((Object)(object)val2 != (Object)null) { val2.SetAsLastSibling(); } } internal static void ComputeDebtBarSegment(float skillLevel, float debtLevels, float barDenominator, float levelBarFill, out float segmentStart, out float segmentEnd) { segmentStart = 0f; segmentEnd = 0f; if (!(debtLevels <= 0f) && !(skillLevel <= 0f)) { float num = ((barDenominator > 0f) ? barDenominator : 100f); float num2 = ((levelBarFill > 0f) ? Mathf.Clamp01(levelBarFill) : Mathf.Clamp01(skillLevel / num)); float num3 = Mathf.Min(debtLevels / num, num2); segmentEnd = num2; segmentStart = num2 - num3; } } private static float GetLevelBarFill(GuiBar levelBar) { if (!((Object)(object)levelBar != (Object)null)) { return 0f; } return Mathf.Clamp01(levelBar.GetSmoothValue()); } private static float ResolveBarDenominator(SkillType skillType, float skillLevel, float levelBarFill) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) if (SkillLimitExtenderCompat.TryGetUiDenominator(skillType, out var denominator)) { return denominator; } if (skillLevel > 0f && levelBarFill > 0.0001f) { return skillLevel / levelBarFill; } return 100f; } private static void ApplyMaroonSegment(Transform levelBarTransform, GuiBar levelBar, float segmentStart, float segmentEnd, bool showDebt) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_004d: 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_0063: Unknown result type (might be due to invalid IL or missing references) Image orCreateDebtImage = GetOrCreateDebtImage(levelBarTransform, levelBar); if (!((Object)(object)orCreateDebtImage == (Object)null)) { ((Component)orCreateDebtImage).gameObject.SetActive(showDebt); if (showDebt) { ((Graphic)orCreateDebtImage).color = ModConfig.GetDebtBarColor(); RectTransform rectTransform = ((Graphic)orCreateDebtImage).rectTransform; rectTransform.anchorMin = new Vector2(segmentStart, 0f); rectTransform.anchorMax = new Vector2(segmentEnd, 1f); rectTransform.offsetMin = Vector2.zero; rectTransform.offsetMax = Vector2.zero; ((Transform)rectTransform).SetAsLastSibling(); } } } private static void ApplyDebtLabel(Transform row, float maroonStart, float maroonEnd, float debt, bool showDebt) { //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) Transform val = Utils.FindChild(row, "levelbar", (IterativeSearchType)0); TMP_Text orCreateDebtText = GetOrCreateDebtText(row, val); if ((Object)(object)orCreateDebtText == (Object)null) { return; } ((Component)orCreateDebtText).gameObject.SetActive(showDebt); if (showDebt) { orCreateDebtText.text = (-Mathf.RoundToInt(debt)).ToString("0"); ApplyDebtTextStyle(orCreateDebtText, row); orCreateDebtText.ForceMeshUpdate(false, false); RectTransform rectTransform = orCreateDebtText.rectTransform; RectTransform val2 = (RectTransform)(object)((val is RectTransform) ? val : null); if (val2 != null) { PlaceDebtLabelAtBarStart(orCreateDebtText, rectTransform, val2); ((Transform)rectTransform).SetAsLastSibling(); return; } ((Transform)rectTransform).SetParent(val, false); rectTransform.anchorMin = new Vector2(maroonStart, 0f); rectTransform.anchorMax = new Vector2(maroonEnd, 1f); rectTransform.offsetMin = Vector2.zero; rectTransform.offsetMax = Vector2.zero; rectTransform.pivot = new Vector2(0.5f, 0.5f); rectTransform.anchoredPosition = Vector2.zero; rectTransform.sizeDelta = Vector2.zero; orCreateDebtText.alignment = (TextAlignmentOptions)514; ((Transform)rectTransform).SetAsLastSibling(); } } private static void PlaceDebtLabelAtBarStart(TMP_Text debtText, RectTransform debtRect, RectTransform levelBarRect) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: 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_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: 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_008e: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) Transform parent = ((Transform)levelBarRect).parent; ((Transform)debtRect).SetParent(parent, false); float num = Mathf.Max(28f, debtText.preferredWidth + 4f); Rect rect = levelBarRect.rect; float num2; if (!(((Rect)(ref rect)).height > 0f)) { num2 = 28f; } else { rect = levelBarRect.rect; num2 = ((Rect)(ref rect)).height; } float num3 = num2; rect = levelBarRect.rect; float num4 = ((Rect)(ref rect)).xMin + 4f; rect = levelBarRect.rect; Vector3 val = ((Transform)levelBarRect).TransformPoint(new Vector3(num4, ((Rect)(ref rect)).center.y, 0f)); Vector3 localPosition = parent.InverseTransformPoint(val); debtRect.anchorMin = new Vector2(0.5f, 0.5f); debtRect.anchorMax = new Vector2(0.5f, 0.5f); debtRect.pivot = new Vector2(0f, 0.5f); debtRect.sizeDelta = new Vector2(num, num3); ((Transform)debtRect).localPosition = localPosition; debtText.alignment = (TextAlignmentOptions)4097; } private static void RemoveLegacyDebtBar(Transform progressBarTransform) { if (!((Object)(object)progressBarTransform == (Object)null)) { Transform val = progressBarTransform.Find("skuld_debtbar"); if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)((Component)val).gameObject); } } } private static Image GetOrCreateDebtImage(Transform trackTransform, GuiBar levelBar) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) Transform val = trackTransform.Find("skuld_debtbar"); if ((Object)(object)val != (Object)null) { return ((Component)val).GetComponent<Image>(); } Image val2 = (((Object)(object)levelBar.m_bar != (Object)null) ? ((Component)levelBar.m_bar).GetComponent<Image>() : null); GameObject val3 = new GameObject("skuld_debtbar", new Type[2] { typeof(RectTransform), typeof(Image) }); val3.transform.SetParent(trackTransform, false); Image component = val3.GetComponent<Image>(); ((Graphic)component).raycastTarget = false; ((Graphic)component).color = ModConfig.GetDebtBarColor(); if ((Object)(object)val2 != (Object)null) { component.sprite = val2.sprite; component.type = val2.type; ((Graphic)component).material = ((Graphic)val2).material; component.pixelsPerUnitMultiplier = val2.pixelsPerUnitMultiplier; } return component; } private static TMP_Text GetOrCreateDebtText(Transform row, Transform levelBarTransform) { Transform val = Utils.FindChild(row, "skuld_debttext", (IterativeSearchType)0); if ((Object)(object)val != (Object)null && (Object)(object)val.parent == (Object)(object)levelBarTransform) { Object.Destroy((Object)(object)((Component)val).gameObject); val = null; } if ((Object)(object)val != (Object)null) { TMP_Text component = ((Component)val).GetComponent<TMP_Text>(); ApplyDebtTextStyle(component, row); return component; } TMP_Text debtTextFontSource = GetDebtTextFontSource(row); if ((Object)(object)debtTextFontSource == (Object)null || (Object)(object)levelBarTransform == (Object)null) { return null; } GameObject val2 = Object.Instantiate<GameObject>(((Component)debtTextFontSource).gameObject, debtTextFontSource.transform.parent); ((Object)val2).name = "skuld_debttext"; val2.SetActive(false); StripNonTextComponents(val2); TMP_Text component2 = val2.GetComponent<TMP_Text>(); ((Graphic)component2).raycastTarget = false; component2.text = string.Empty; ApplyDebtTextStyle(component2, row); if ((Object)(object)val2.GetComponent<CanvasGroup>() == (Object)null) { CanvasGroup obj = val2.AddComponent<CanvasGroup>(); obj.blocksRaycasts = false; obj.interactable = false; } return component2; } private static TMP_Text GetDebtTextFontSource(Transform row) { Transform obj = Utils.FindChild(row, "leveltext", (IterativeSearchType)0); object obj2 = ((obj != null) ? ((Component)obj).GetComponent<TMP_Text>() : null); if (obj2 == null) { Transform obj3 = Utils.FindChild(row, "bonustext", (IterativeSearchType)0); if (obj3 == null) { return null; } obj2 = ((Component)obj3).GetComponent<TMP_Text>(); } return (TMP_Text)obj2; } private static void ApplyDebtTextStyle(TMP_Text debtText, Transform row) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) TMP_Text debtTextFontSource = GetDebtTextFontSource(row); if (!((Object)(object)debtText == (Object)null) && !((Object)(object)debtTextFontSource == (Object)null)) { debtText.font = debtTextFontSource.font; debtText.fontSharedMaterial = debtTextFontSource.fontSharedMaterial; debtText.fontStyle = debtTextFontSource.fontStyle; debtText.characterSpacing = debtTextFontSource.characterSpacing; debtText.wordSpacing = debtTextFontSource.wordSpacing; debtText.lineSpacing = debtTextFontSource.lineSpacing; debtText.paragraphSpacing = debtTextFontSource.paragraphSpacing; debtText.textWrappingMode = debtTextFontSource.textWrappingMode; debtText.overflowMode = (TextOverflowModes)0; debtText.enableAutoSizing = false; ((Graphic)debtText).color = ModConfig.GetDebtTextColor(); debtText.fontSize = ModConfig.GetDebtTextSize(); } } private static void StripNonTextComponents(GameObject debtObject) { Component[] components = debtObject.GetComponents<Component>(); foreach (Component val in components) { if (!(val is Transform) && !(val is RectTransform) && !(val is TextMeshProUGUI) && !(val is CanvasRenderer)) { Object.Destroy((Object)(object)val); } } } } internal static class SkillDebtService { private static readonly FieldInfo SkillsDataField = AccessTools.Field(typeof(Skills), "m_skillData"); private static readonly FieldInfo DeathLowerFactorField = AccessTools.Field(typeof(Skills), "m_DeathLowerFactor"); private static readonly FieldInfo SkillsPlayerField = AccessTools.Field(typeof(Skills), "m_player"); private static readonly Type SkillType = AccessTools.Inner(typeof(Skills), "Skill"); private static readonly FieldInfo SkillLevelField = ((SkillType != null) ? AccessTools.Field(SkillType, "m_level") : null); private static readonly FieldInfo SkillInfoField = ((SkillType != null) ? AccessTools.Field(SkillType, "m_info") : null); private static readonly Type SkillDefType = AccessTools.Inner(typeof(Skills), "SkillDef"); private static readonly FieldInfo SkillIncreaseStepField = ((SkillDefType != null) ? AccessTools.Field(SkillDefType, "m_increseStep") : null); internal static bool IsReady { get { if (SkillsDataField != null && DeathLowerFactorField != null && SkillsPlayerField != null && SkillLevelField != null && SkillInfoField != null) { return SkillIncreaseStepField != null; } return false; } } internal static void Initialize() { if (SkillsDataField == null || DeathLowerFactorField == null || SkillsPlayerField == null || SkillLevelField == null || SkillInfoField == null || SkillIncreaseStepField == null) { Plugin.Log.LogError((object)"Skuld failed to bind reflection fields; debt features may not work."); } Plugin.Log.LogInfo((object)"Skuld debt storage: Player.m_customData (character save). Not written to player ZDO — other players cannot read your debt keys."); } internal static void LogHardDeathDiagnostics(Skills skills) { //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) if (!IsReady) { Plugin.Log.LogInfo((object)"Skuld hard-death diagnostic: reflection bindings not ready."); return; } float num = (float)DeathLowerFactorField.GetValue(skills); float skillReductionRate = Game.m_skillReductionRate; float num2 = num * skillReductionRate; Plugin.Log.LogInfo((object)$"Skuld hard-death diagnostic: m_DeathLowerFactor={num:F6}, Game.m_skillReductionRate={skillReductionRate:F6}, factor={num2:F6}"); if (!(SkillsDataField.GetValue(skills) is IDictionary dictionary)) { Plugin.Log.LogInfo((object)"Skuld hard-death diagnostic: m_skillData is null."); return; } foreach (DictionaryEntry item in dictionary) { try { SkillType val = (SkillType)item.Key; if (item.Value != null) { float skillLevel = GetSkillLevel(item.Value); float num3 = skillLevel * num2; Plugin.Log.LogInfo((object)$"Skuld hard-death diagnostic: skill={val}, level={skillLevel:F3}, lossAmount={num3:F6}"); } } catch (Exception arg) { Plugin.Log.LogError((object)$"Skuld hard-death diagnostic failed for skill entry {item.Key}: {arg}"); } } } internal static bool TryConvertDeathToDebt(Skills skills, out string deathSummary) { //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) deathSummary = string.Empty; object? obj = SkillsPlayerField?.GetValue(skills); Player val = (Player)((obj is Player) ? obj : null); if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning((object)"Skuld death conversion: Skills.m_player was null."); return false; } float deathFactor = GetDeathFactor(skills); if (deathFactor <= 0f) { return true; } if (!(SkillsDataField?.GetValue(skills) is IDictionary dictionary)) { return false; } int maxDebtPerSkill = ModConfig.GetMaxDebtPerSkill(); List<string> list = new List<string>(); int num = 0; Plugin.Log.LogInfo((object)$"Skuld death conversion: starting per-skill customData writes. player={DescribePlayer(val)}, MaxDebtPerSkill={maxDebtPerSkill}"); foreach (DictionaryEntry item in dictionary) { try { SkillType val2 = (SkillType)item.Key; if (item.Value == null) { continue; } float skillLevel = GetSkillLevel(item.Value); if (skillLevel <= 0f) { continue; } float num2 = skillLevel * deathFactor; if (!(num2 <= 0f)) { EnsureProgressConsistency(val, val2); float num3 = GetDebt(val, val2) + num2; float num4 = GetIncurred(val, val2) + num2; if (maxDebtPerSkill > 0) { float num5 = (float)maxDebtPerSkill * num2; num3 = Mathf.Min(num3, num5); } SetDebt(val, val2, num3); SetIncurred(val, val2, num4); SetBaseline(val, val2, num3); float debt = GetDebt(val, val2); num++; list.Add($"{FormatSkillName(val2)} +{num2:0.0}"); Plugin.Log.LogInfo((object)$"Skuld death conversion: skill={val2}, raw={num2:F4}, current={num3:F4}, baseline={num3:F4}, lifetime={num4:F4}, customDataReadback={debt:F4}, key={GetDebtKey(val2)}"); LogDebug($"Death debt added: {val2} raw +{num2:F3} (current {num3:F3}, lifetime {num4:F3})"); } } catch (Exception arg) { Plugin.Log.LogError((object)$"Skuld death conversion failed for skill entry {item.Key}: {arg}"); } } Plugin.Log.LogInfo((object)$"Skuld death conversion: complete, {num} skills debted."); if (list.Count > 0) { deathSummary = BuildDeathSummary(list); } return true; } internal static bool TryApplyRaiseSkillDebt(Skills skills, SkillType skillType, ref float value) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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_0071: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) object? obj = SkillsPlayerField?.GetValue(skills); Player val = (Player)((obj is Player) ? obj : null); if (value <= 0f || (int)skillType == 0 || (Object)(object)val == (Object)null) { return true; } EnsureProgressConsistency(val, skillType); float debt = GetDebt(val, skillType); if (debt <= 0f) { return true; } Skill skillFromMap = GetSkillFromMap(skills, skillType); if (skillFromMap == null) { return true; } float increaseStep = GetIncreaseStep(skillFromMap); if (increaseStep <= 0f) { return true; } float num = increaseStep * value * Game.m_skillGainRate; float paydownShare = GetPaydownShare(val, skillType); float num2 = 1f - paydownShare; float nextLevelRequirement = GetNextLevelRequirement(skillFromMap.m_level); float num3 = num * paydownShare; float num4 = num * num2; float num5 = ((nextLevelRequirement > 0f) ? (num3 / nextLevelRequirement) : 0f); float num6 = Mathf.Min(debt, num5); float num7 = num3 - num6 * nextLevelRequirement; float num8 = num4 + num7; float num9 = increaseStep * Game.m_skillGainRate; value = ((num9 > 0f) ? (num8 / num9) : 0f); float num10 = Mathf.Max(0f, debt - num6); SetDebt(val, skillType, num10); LogDebug($"Debt paydown: {skillType} paid {num6:F4} (intended {num5:F4}), debt {debt:F4}->{num10:F4}, " + $"rerouted {num7:F4} units to leveling, forwarded factor {value:F4}"); if (debt > 0f && num10 <= 0f) { ClearFocusPaydownForSkill(val, skillType); DebtClearedFeedback.TryPlay(val); MessageHud instance = MessageHud.instance; if (instance != null) { instance.ShowMessage((MessageType)2, $"{skillType} debt repaid", 0, (Sprite)null, false, true); } } return true; } internal static float GetDeathFactor(Skills skills) { return ((DeathLowerFactorField != null) ? ((float)DeathLowerFactorField.GetValue(skills)) : 0f) * Game.m_skillReductionRate; } private static float GetSkillLevel(object skillObj) { if (!(SkillLevelField != null)) { return 0f; } return (float)SkillLevelField.GetValue(skillObj); } private static float GetIncreaseStep(Skill skill) { object obj = SkillInfoField?.GetValue(skill); if (obj == null || !(SkillIncreaseStepField != null)) { return 0f; } return (float)SkillIncreaseStepField.GetValue(obj); } private static Skill GetSkillFromMap(Skills skills, SkillType skillType) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (!(SkillsDataField?.GetValue(skills) is IDictionary dictionary) || !dictionary.Contains(skillType)) { return null; } object? obj = dictionary[skillType]; return (Skill)((obj is Skill) ? obj : null); } private unsafe static string GetDebtKey(SkillType skillType) { return "Skuld_Debt_" + ((object)(*(SkillType*)(&skillType))/*cast due to .constrained prefix*/).ToString(); } private unsafe static string GetBaselineKey(SkillType skillType) { return "Skuld_DebtBaseline_" + ((object)(*(SkillType*)(&skillType))/*cast due to .constrained prefix*/).ToString(); } private unsafe static string GetIncurredKey(SkillType skillType) { return "Skuld_DebtIncurred_" + ((object)(*(SkillType*)(&skillType))/*cast due to .constrained prefix*/).ToString(); } internal static float GetDebt(Player player, SkillType skillType) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return GetCustomFloat(player, GetDebtKey(skillType)); } internal static float GetBaseline(Player player, SkillType skillType) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return GetCustomFloat(player, GetBaselineKey(skillType)); } internal static float GetIncurred(Player player, SkillType skillType) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return GetCustomFloat(player, GetIncurredKey(skillType)); } internal static float GetPaidOff(Player player, SkillType skillType) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) EnsureProgressConsistency(player, skillType); return Mathf.Max(0f, GetBaseline(player, skillType) - GetDebt(player, skillType)); } internal static string FormatProgressDetail(Player player, SkillType skillType) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0010: 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) EnsureProgressConsistency(player, skillType); float debt = GetDebt(player, skillType); float paidOff = GetPaidOff(player, skillType); string arg = (IsFocusPaydownActive(player, skillType) ? " [focused 100%]" : string.Empty); return $"-{debt:0.0000} ({paidOff:0.0000} paid off){arg}"; } internal static float GetPaydownShare(Player player, SkillType skillType) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) if (IsFocusPaydownActive(player, skillType)) { return 1f; } return ModConfig.GetPaydownShare(); } internal static bool IsFocusAll(Player player) { return GetCustomFlag(player, "Skuld_FocusPaydown_All"); } internal static bool IsFocusPaydownActive(Player player, SkillType skillType) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || (int)skillType == 0) { return false; } if (IsFocusAll(player)) { return GetDebt(player, skillType) > 0f; } return GetCustomFlag(player, GetFocusPaydownKey(skillType)); } internal static bool TryEnableFocusAll(Player player, out string message) { message = string.Empty; if (player?.m_customData == null) { return false; } ClearPerSkillFocusFlags(player); SetCustomFlag(player, "Skuld_FocusPaydown_All", value: true); message = "Focus all enabled — 100% paydown on skills with debt until cleared."; return true; } internal static bool TryEnableFocusSkill(Player player, SkillType skillType, out string message) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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) message = string.Empty; if (player?.m_customData == null || (int)skillType == 0) { return false; } SetCustomFlag(player, "Skuld_FocusPaydown_All", value: false); SetCustomFlag(player, GetFocusPaydownKey(skillType), value: true); message = FormatSkillName(skillType) + " focus enabled — 100% paydown until that debt is cleared."; return true; } internal static void ClearAllFocusPaydown(Player player) { if (player?.m_customData != null) { SetCustomFlag(player, "Skuld_FocusPaydown_All", value: false); ClearPerSkillFocusFlags(player); } } internal static void ShowFocusOverview(Player player, Terminal context) { if ((Object)(object)player == (Object)null) { if (context != null) { context.AddString("No local player."); } return; } PruneInactiveFocusFlags(player); if (IsFocusAll(player)) { List<string> indebtedSkillNames = GetIndebtedSkillNames(player); context.AddString("Focus all: active — 100% paydown on skills with debt."); if (indebtedSkillNames.Count > 0) { context.AddString(" Indebted: " + string.Join(", ", indebtedSkillNames)); } else { context.AddString(" No outstanding debt right now."); } return; } List<string> focusedSkillNames = GetFocusedSkillNames(player); if (focusedSkillNames.Count == 0) { context.AddString("No focused paydown skills. Use /focus <skill|all> or /focus off."); return; } context.AddString($"Focused paydown ({focusedSkillNames.Count}) — 100% until cleared:"); foreach (string item in focusedSkillNames) { context.AddString(" " + item); } } private static void ClearFocusPaydownForSkill(Player player, SkillType skillType) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) SetCustomFlag(player, GetFocusPaydownKey(skillType), value: false); if (IsFocusAll(player) && !HasAnyDebt(player)) { SetCustomFlag(player, "Skuld_FocusPaydown_All", value: false); } } private unsafe static string GetFocusPaydownKey(SkillType skillType) { return "Skuld_FocusPaydown_" + ((object)(*(SkillType*)(&skillType))/*cast due to .constrained prefix*/).ToString(); } private static void ClearPerSkillFocusFlags(Player player) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) foreach (SkillType value in Enum.GetValues(typeof(SkillType))) { if ((int)value != 0) { SetCustomFlag(player, GetFocusPaydownKey(value), value: false); } } } private static void PruneInactiveFocusFlags(Player player) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_0043: Unknown result type (might be due to invalid IL or missing references) foreach (SkillType value in Enum.GetValues(typeof(SkillType))) { if ((int)value != 0 && GetCustomFlag(player, GetFocusPaydownKey(value)) && GetDebt(player, value) <= 0f) { SetCustomFlag(player, GetFocusPaydownKey(value), value: false); } } if (IsFocusAll(player) && !HasAnyDebt(player)) { SetCustomFlag(player, "Skuld_FocusPaydown_All", value: false); } } private static bool HasAnyDebt(Player player) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) foreach (SkillType value in Enum.GetValues(typeof(SkillType))) { if ((int)value != 0 && GetDebt(player, value) > 0f) { return true; } } return false; } private static List<string> GetIndebtedSkillNames(Player player) { //IL_0023: 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_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) List<string> list = new List<string>(); foreach (SkillType value in Enum.GetValues(typeof(SkillType))) { if ((int)value != 0 && GetDebt(player, value) > 0f) { list.Add(FormatSkillName(value)); } } return list; } private static List<string> GetFocusedSkillNames(Player player) { //IL_0023: 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_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003b: 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) List<string> list = new List<string>(); foreach (SkillType value in Enum.GetValues(typeof(SkillType))) { if ((int)value != 0 && GetCustomFlag(player, GetFocusPaydownKey(value))) { string text = ((GetDebt(player, value) > 0f) ? string.Empty : " (no debt)"); list.Add(FormatSkillName(value) + text); } } return list; } private static bool GetCustomFlag(Player player, string key) { if (player?.m_customData == null) { return false; } if (player.m_customData.TryGetValue(key, out var value)) { return string.Equals(value, "1", StringComparison.Ordinal); } return false; } private static void SetCustomFlag(Player player, string key, bool value) { if (player?.m_customData != null) { if (value) { player.m_customData[key] = "1"; } else { player.m_customData.Remove(key); } } } internal static float GetLifetimeRepaid(Player player, SkillType skillType) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) return Mathf.Max(0f, GetIncurred(player, skillType) - GetDebt(player, skillType)); } internal static string FormatLifetimeDetail(Player player, SkillType skillType) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) float incurred = GetIncurred(player, skillType); float lifetimeRepaid = GetLifetimeRepaid(player, skillType); return $"lifetime debt {incurred:0.0000}, lifetime repaid {lifetimeRepaid:0.0000}"; } private static void EnsureProgressConsistency(Player player, SkillType skillType) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0011: 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_003f: Unknown result type (might be due to invalid IL or missing references) float debt = GetDebt(player, skillType); float num = GetIncurred(player, skillType); float baseline = GetBaseline(player, skillType); if (debt > 0f && num < debt) { SetIncurred(player, skillType, debt); num = debt; } if (debt > 0f && baseline <= 0f) { SetBaseline(player, skillType, Mathf.Max(debt, num)); } } private static float GetCustomFloat(Player player, string key) { if (player?.m_customData == null) { return 0f; } if (!player.m_customData.TryGetValue(key, out var value) || string.IsNullOrEmpty(value)) { return 0f; } if (!float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return 0f; } return Mathf.Max(0f, result); } private static void SetDebt(Player player, SkillType skillType, float value) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) SetCustomFloat(player, GetDebtKey(skillType), value); } private static void SetBaseline(Player player, SkillType skillType, float value) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) SetCustomFloat(player, GetBaselineKey(skillType), value); } private static void SetIncurred(Player player, SkillType skillType, float value) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) SetCustomFloat(player, GetIncurredKey(skillType), value); } private static void SetCustomFloat(Player player, string key, float value) { if (player?.m_customData != null) { float num = Mathf.Max(0f, value); if (num <= 0f) { player.m_customData.Remove(key); } else { player.m_customData[key] = num.ToString("R", CultureInfo.InvariantCulture); } } } internal static List<string> ClearAllDebt(Player player) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003c: 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_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0077: 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_008f: Unknown result type (might be due to invalid IL or missing references) //IL_009b: 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) List<string> list = new List<string>(); if (player?.m_customData == null) { return list; } foreach (SkillType value in Enum.GetValues(typeof(SkillType))) { if ((int)value == 0) { continue; } float debt = GetDebt(player, value); float incurred = GetIncurred(player, value); float baseline = GetBaseline(player, value); if (!(debt <= 0f) || !(incurred <= 0f) || !(baseline <= 0f)) { SetDebt(player, value, 0f); SetBaseline(player, value, 0f); SetIncurred(player, value, 0f); SetCustomFlag(player, GetFocusPaydownKey(value), value: false); if (debt > 0f) { list.Add($"{FormatSkillName(value)} {debt:0.0}"); } } } SetCustomFlag(player, "Skuld_FocusPaydown_All", value: false); return list; } internal static void ShowAllDebtOverview(Player player, Terminal context) { //IL_003b: 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_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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) if ((Object)(object)player == (Object)null) { if (context != null) { context.AddString("No local player."); } return; } List<string> list = new List<string>(); foreach (SkillType value in Enum.GetValues(typeof(SkillType))) { if ((int)value != 0) { EnsureProgressConsistency(player, value); float debt = GetDebt(player, value); if (!(debt <= 0f)) { list.Add($"{FormatSkillName(value)} -{Mathf.RoundToInt(debt)}"); } } } if (list.Count == 0) { if (context != null) { context.AddString("Skuld: no outstanding skill debt."); } MessageHud instance = MessageHud.instance; if (instance != null) { instance.ShowMessage((MessageType)1, "No outstanding skill debt.", 0, (Sprite)null, false, true); } return; } if (context != null) { context.AddString($"Skuld debt ({list.Count}):"); } foreach (string item in list) { if (context != null) { context.AddString(" " + item); } } if (context != null) { context.AddString("Exact progress: /show debt <skill>"); } MessageHud instance2 = MessageHud.instance; if (instance2 != null) { instance2.ShowMessage((MessageType)1, $"Debt on {list.Count} skills — list printed to chat.", 0, (Sprite)null, false, true); } } internal static void ShowAllLifetimeOverview(Player player, Terminal context) { //IL_003b: 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_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { if (context != null) { context.AddString("No local player."); } return; } List<string> list = new List<string>(); foreach (SkillType value in Enum.GetValues(typeof(SkillType))) { if ((int)value != 0) { float incurred = GetIncurred(player, value); if (!(incurred <= 0f)) { list.Add($"{FormatSkillName(value)} {incurred:0.0000}"); } } } if (list.Count == 0) { if (context != null) { context.AddString("Skuld: no lifetime debt recorded."); } MessageHud instance = MessageHud.instance; if (instance != null) { instance.ShowMessage((MessageType)1, "No lifetime debt recorded.", 0, (Sprite)null, false, true); } return; } if (context != null) { context.AddString($"Skuld lifetime debt ({list.Count}):"); } foreach (string item in list) { if (context != null) { context.AddString(" " + item); } } MessageHud instance2 = MessageHud.instance; if (instance2 != null) { instance2.ShowMessage((MessageType)1, $"Lifetime debt on {list.Count} skills — list printed to chat.", 0, (Sprite)null, false, true); } } internal static bool TryResolveSkillType(string rawName, out SkillType skillType) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Expected I4, but got Unknown //IL_0075: Unknown result type (might be due to invalid IL or missing references) skillType = (SkillType)0; if (string.IsNullOrWhiteSpace(rawName)) { return false; } string b = rawName.Replace(" ", string.Empty).Replace("_", string.Empty); foreach (SkillType value in Enum.GetValues(typeof(SkillType))) { if ((int)value != 0) { string a = ((object)value/*cast due to .constrained prefix*/).ToString(); if (string.Equals(a, rawName, StringComparison.OrdinalIgnoreCase) || string.Equals(a, b, StringComparison.OrdinalIgnoreCase) || string.Equals(FormatSkillName(value).Replace(" ", string.Empty), b, StringComparison.OrdinalIgnoreCase)) { skillType = (SkillType)(int)value; return true; } } } return false; } internal unsafe static string FormatSkillName(SkillType skillType) { string text = ((object)(*(SkillType*)(&skillType))/*cast due to .constrained prefix*/).ToString(); if (string.IsNullOrEmpty(text)) { return text; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(text[0]); for (int i = 1; i < text.Length; i++) { char c = text[i]; if (char.IsUpper(c) && !char.IsUpper(text[i - 1])) { stringBuilder.Append(' '); } stringBuilder.Append(c); } return stringBuilder.ToString(); } private static string DescribePlayer(Player player) { if ((Object)(object)player == (Object)null) { return "null"; } return $"name={player.GetPlayerName()}, id={player.GetPlayerID()}, isLocal={(Object)(object)player == (Object)(object)Player.m_localPlayer}"; } private static float GetNextLevelRequirement(float currentLevel) { return Mathf.Pow(Mathf.Floor(currentLevel + 1f), 1.5f) * 0.5f + 0.5f; } private static void LogDebug(string message) { if (ModConfig.DebugLogging.Value) { Plugin.Log.LogInfo((object)message); } } private static string BuildDeathSummary(List<string> entries) { if (entries.Count <= 6) { return "Debt: " + string.Join(", ", entries); } return $"Debt ({entries.Count} skills): " + string.Join(", ", entries.GetRange(0, 6)) + $" (+{entries.Count - 6} more; /show debt all)"; } } internal static class SkillLimitExtenderCompat { private static bool lookupAttempted; private static MethodInfo getUiDenominatorMethod; internal static bool TryGetUiDenominator(SkillType skillType, out float denominator) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) denominator = 0f; if (!TryResolveMethod()) { return false; } try { object obj = getUiDenominatorMethod.Invoke(null, new object[1] { skillType }); if (obj is float num && num > 0f) { denominator = num; return true; } if (obj is double num2 && num2 > 0.0) { denominator = (float)num2; return true; } } catch (Exception ex) { Plugin.Log.LogDebug((object)$"SkillLimitExtender UI denominator lookup failed for {skillType}: {ex.Message}"); } return false; } private static bool TryResolveMethod() { if (lookupAttempted) { return getUiDenominatorMethod != null; } lookupAttempted = true; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { if ((assembly.GetName().Name ?? string.Empty).IndexOf("SkillLimitExtender", StringComparison.OrdinalIgnoreCase) < 0) { continue; } Type type = assembly.GetType("SkillLimitExtender.SkillConfigManager") ?? assembly.GetType("SkillConfigManager"); if (!(type == null)) { getUiDenominatorMethod = type.GetMethod("GetUiDenominatorForSkillSafe", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(object) }, null); if (getUiDenominatorMethod != null) { return true; } } } return false; } } } namespace Skuld.Patches { [HarmonyPatch(typeof(Player), "OnDeath")] internal static class PlayerOnDeathDiagnosticsPatch { private static readonly FieldInfo TimeSinceDeathField = AccessTools.Field(typeof(Player), "m_timeSinceDeath"); private static readonly MethodInfo HardDeathMethod = AccessTools.Method(typeof(Player), "HardDeath", (Type[])null, (Type[])null); private static void Prefix(Player __instance) { float num = ((TimeSinceDeathField != null) ? ((float)TimeSinceDeathField.GetValue(__instance)) : (-1f)); float num2 = (((Object)(object)__instance != (Object)null) ? __instance.m_hardDeathCooldown : (-1f)); bool flag = HardDeathMethod != null && (Object)(object)__instance != (Object)null && (bool)HardDeathMethod.Invoke(__instance, null); Plugin.Log.LogInfo((object)$"Skuld diagnostic: Player.OnDeath entered. hardDeath={flag}, m_timeSinceDeath={num:F3}, m_hardDeathCooldown={num2:F3}"); } } [HarmonyPatch(typeof(Player), "OnDeath")] internal static class PlayerSoftDeathClarityPatch { private const string SoftDeathDebtMessage = "Soft death — no skill debt"; private static readonly MethodInfo HardDeathMethod = AccessTools.Method(typeof(Player), "HardDeath", (Type[])null, (Type[])null); private static bool isHardDeath; private static void Prefix(Player __instance) { isHardDeath = HardDeathMethod != null && (Object)(object)__instance != (Object)null && (bool)HardDeathMethod.Invoke(__instance, null); } private static void Postfix(Player __instance) { if (ModConfig.IsModEnabled && !isHardDeath && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { ((Character)__instance).Message((MessageType)1, "Soft death — no skill debt", 0, (Sprite)null, false); } } } [HarmonyPatch(typeof(SkillsDialog), "Setup")] internal static class SkillsDialogSetupPatch { private static readonly FieldInfo ElementsField = AccessTools.Field(typeof(SkillsDialog), "m_elements"); private static readonly FieldInfo TooltipAnchorField = AccessTools.Field(typeof(SkillsDialog), "m_tooltipAnchor"); private static void Postfix(SkillsDialog __instance, Player player) { if (!ModConfig.IsModEnabled || (Object)(object)player == (Object)null || ElementsField == null) { return; } if (!(ElementsField.GetValue(__instance) is List<GameObject> list)) { Plugin.Log.LogWarning((object)"Skuld skill UI: could not read SkillsDialog.m_elements."); return; } object? obj = TooltipAnchorField?.GetValue(__instance); RectTransform tooltipAnchor = (RectTransform)((obj is RectTransform) ? obj : null); List<Skill> skillList = ((Character)player).GetSkills().GetSkillList(); int num = Mathf.Min(skillList.Count, list.Count); for (int i = 0; i < num; i++) { Skill val = skillList[i]; GameObject val2 = list[i]; if (val?.m_info != null && !((Object)(object)val2 == (Object)null) && val2.activeSelf) { ApplyDebtDisplay(val2, player, val, tooltipAnchor); } } } private static void ApplyDebtDisplay(GameObject row, Player player, Skill skill, RectTransform tooltipAnchor) { SkillDebtBarOverlay.Apply(row.transform, player, skill); BindRowDebtTooltip(row, skill, tooltipAnchor); } private static void BindRowDebtTooltip(GameObject row, Skill skill, RectTransform tooltipAnchor) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) UITooltip componentInChildren = row.GetComponentInChildren<UITooltip>(true); if (!((Object)(object)componentInChildren == (Object)null)) { Transform transform = row.transform; RectTransform val = (RectTransform)(object)((transform is RectTransform) ? transform : null); Vector2 position = (Vector2)(((Object)(object)val != (Object)null) ? new Vector2(0f, Mathf.Min(255f, ((Transform)val).localPosition.y + 10f)) : Vector2.zero); DebtTooltipBinder debtTooltipBinder = row.GetComponent<DebtTooltipBinder>(); if ((Object)(object)debtTooltipBinder == (Object)null) { debtTooltipBinder = row.AddComponent<DebtTooltipBinder>(); } debtTooltipBinder.Initialize(skill.m_info.m_skill, skill, componentInChildren, skill.m_info.m_description, tooltipAnchor, position); } } } [HarmonyPatch(typeof(Skills), "OnDeath")] internal static class SkillsOnDeathPatch { private static bool Prefix(Skills __instance) { Plugin.Log.LogInfo((object)"Skuld patch entry: Skills.OnDeath prefix fired."); SkillDebtService.LogHardDeathDiagnostics(__instance); if (!ModConfig.IsModEnabled || !SkillDebtService.IsReady) { return true; } if (!SkillDebtService.TryConvertDeathToDebt(__instance, out var deathSummary)) { Plugin.Log.LogWarning((object)"Skuld could not convert death penalty to debt. Falling back to vanilla skill loss."); return true; } if (!string.IsNullOrEmpty(deathSummary)) { object? obj = AccessTools.Field(typeof(Skills), "m_player")?.GetValue(__instance); object? obj2 = ((obj is Player) ? obj : null); if (obj2 != null) { ((Character)obj2).Message((MessageType)1, deathSummary, 0, (Sprite)null, false); } } return false; } } [HarmonyPatch(typeof(Skills), "RaiseSkill")] internal static class SkillsRaiseSkillPatch { private static bool hasLoggedEntry; private static v