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("0.2.0.0")] [assembly: AssemblyInformationalVersion("0.2.0+ca5a53799910255b1d65a6275d02de7881b295e8")] [assembly: AssemblyProduct("Skuld")] [assembly: AssemblyTitle("Skuld")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.2.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 ModConfig { internal static ConfigEntry EnableMod { get; private set; } internal static ConfigEntry DebtPaydownShare { get; private set; } internal static ConfigEntry MaxDebtPerSkill { get; private set; } internal static ConfigEntry DebugLogging { get; private set; } internal static ConfigEntry EnableDevCommands { get; private set; } 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 EnableMod = config.Bind("General", "EnableMod", true, "Enable Skuld debt mechanics."); DebtPaydownShare = config.Bind("General", "DebtPaydownShare", 0.5f, new ConfigDescription("Fraction of earned XP redirected to debt paydown while debt remains.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 1f), Array.Empty())); MaxDebtPerSkill = config.Bind("General", "MaxDebtPerSkill", 3, new ConfigDescription("Maximum debt per skill, measured in deaths-worth of that skill's current level. 0 = uncapped.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 5), Array.Empty())); DebugLogging = config.Bind("General", "DebugLogging", false, "Enable verbose debt conversion/paydown logs."); EnableDevCommands = config.Bind("Dev", "EnableDevCommands", false, "Enable Skuld test console commands. Must remain false for Thunderstore releases."); } internal static float GetPaydownShare() { return Mathf.Clamp(DebtPaydownShare.Value, 0.5f, 1f); } internal static int GetMaxDebtPerSkill() { return Mathf.Clamp(MaxDebtPerSkill.Value, 0, 5); } } internal static class ModConstants { internal const string ModGuid = "com.cdjensen99.skuld"; internal const string ModName = "Skuld"; internal const string ModVersion = "0.2.0"; internal const string DebtKeyPrefix = "Skuld_Debt_"; internal const string DebtBaselineKeyPrefix = "Skuld_DebtBaseline_"; internal const string DebtIncurredKeyPrefix = "Skuld_DebtIncurred_"; } [BepInPlugin("com.cdjensen99.skuld", "Skuld", "0.2.0")] public sealed class Plugin : BaseUnityPlugin { internal static ManualLogSource Log; private Harmony harmony; private void Awake() { Log = ((BaseUnityPlugin)this).Logger; ModConfig.Bind(((BaseUnityPlugin)this).Config); SkillDebtService.Initialize(); harmony = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "com.cdjensen99.skuld"); LogPatchStatus(); DevConsoleCommands.Register(); PlayerChatCommands.Register(); Log.LogInfo((object)"Skuld 0.2.0 loaded."); } private void OnDestroy() { Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } private static void LogPatchStatus() { LogOriginalPatchStatus(typeof(Skills), "OnDeath"); LogOriginalPatchStatus(typeof(Player), "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 sealed class DebtTooltipBinder : MonoBehaviour { private SkillType skillType; private UITooltip tooltip; private RectTransform tooltipAnchor; private Vector2 fixedPosition; private string baseDescription = string.Empty; internal void Initialize(SkillType type, 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_0027: 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) skillType = type; tooltip = tip; baseDescription = description ?? string.Empty; tooltipAnchor = anchor; fixedPosition = position; Refresh(); } private void LateUpdate() { Refresh(); } private void Refresh() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_005c: 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_009c: 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 (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_0045: 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_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0065: 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_0070: 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, false, false, false, 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, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); registered = true; string text = (ModConfig.EnableDevCommands.Value ? "enabled" : "disabled (set EnableDevCommands=true in com.cdjensen99.skuld.cfg)"); Plugin.Log.LogInfo((object)("Skuld dev console commands registered (" + text + "): skuld_clearcooldown, skuld_cleardebt.")); } private static void ClearCooldown(ConsoleEventArgs args) { if (!ModConfig.EnableDevCommands.Value) { args.Context.AddString("Skuld dev commands are disabled. Set EnableDevCommands=true in BepInEx/config/com.cdjensen99.skuld.cfg."); 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.EnableDevCommands.Value) { args.Context.AddString("Skuld dev commands are disabled. Set EnableDevCommands=true in BepInEx/config/com.cdjensen99.skuld.cfg."); return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { args.Context.AddString("No local player."); return; } List 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>__GetSkillTabOptions; } 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_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: 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>__GetSkillTabOptions; if (obj2 == null) { ConsoleOptionsFetcher val2 = GetSkillTabOptions; <>O.<1>__GetSkillTabOptions = val2; obj2 = (object)val2; } new ConsoleCommand("show", "Skuld: /show debt [skill|all|lifetime ...] — show debt progress.", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)obj2, false, false, false); registered = true; Plugin.Log.LogInfo((object)"Skuld player chat command registered: /show debt [skill|all|lifetime ...]"); } } private static void ShowCommand(ConsoleEventArgs args) { //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_0173: 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_014e: Unknown result type (might be due to invalid IL or missing references) if (args.Length < 2 || !string.Equals(args[1], "debt", StringComparison.OrdinalIgnoreCase)) { args.Context.AddString("Usage: /show debt [skill|all|lifetime ]"); return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { args.Context.AddString("No local player."); return; } if (args.Length < 3) { args.Context.AddString("Usage: /show debt [skill|all|lifetime ]"); 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); } } 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_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) if (tail.Length == 0) { context.AddString("Usage: /show debt lifetime "); 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; } if (SkillDebtService.GetIncurred(player, skillType) <= 0f) { context.AddString(SkillDebtService.FormatSkillName(skillType) + ": no lifetime debt recorded."); return; } string text2 = SkillDebtService.FormatLifetimeDetail(player, skillType); context.AddString(SkillDebtService.FormatSkillName(skillType) + " lifetime: " + text2); MessageHud instance = MessageHud.instance; if (instance != null) { instance.ShowMessage((MessageType)1, SkillDebtService.FormatSkillName(skillType) + " lifetime " + text2, 0, (Sprite)null, false); } } private static List GetSkillTabOptions() { //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_004a: Unknown result type (might be due to invalid IL or missing references) List list = new List { "debt", "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; } } 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 list = new List(); 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(Player player, SkillType skillType, ref float value) { //IL_0009: 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) //IL_001f: 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_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0127: 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) if (value <= 0f || (int)skillType == 0 || (Object)(object)player == (Object)null) { return true; } EnsureProgressConsistency(player, skillType); float debt = GetDebt(player, skillType); if (debt <= 0f) { return true; } Skills skills = ((Character)player).GetSkills(); Skill val = (((Object)(object)skills != (Object)null) ? GetSkillFromMap(skills, skillType) : null); if (val == null) { return true; } float increaseStep = GetIncreaseStep(val); if (increaseStep <= 0f) { return true; } float num = value; float num2 = 1f; ((Character)player).GetSEMan().ModifyRaiseSkill(skillType, ref num2); float num3 = num * num2; float num4 = increaseStep * num3 * Game.m_skillGainRate; float paydownShare = ModConfig.GetPaydownShare(); float num5 = 1f - paydownShare; float nextLevelRequirement = GetNextLevelRequirement(val.m_level); float num6 = num4 * paydownShare; float num7 = num4 * num5; float num8 = ((nextLevelRequirement > 0f) ? (num6 / nextLevelRequirement) : 0f); float num9 = Mathf.Min(debt, num8); float num10 = num6 - num9 * nextLevelRequirement; float num11 = num7 + num10; float num12 = increaseStep * num2 * Game.m_skillGainRate; value = ((num12 > 0f) ? (num11 / num12) : 0f); float num13 = Mathf.Max(0f, debt - num9); SetDebt(player, skillType, num13); LogDebug($"Debt paydown: {skillType} paid {num9:F4} (intended {num8:F4}), debt {debt:F4}->{num13:F4}, " + $"rerouted {num10:F4} units to leveling, forwarded pre-multiplier value {value:F4}"); if (debt > 0f && num13 <= 0f && (Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, $"{skillType} debt repaid", 0, (Sprite)null, false); } 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) EnsureProgressConsistency(player, skillType); float debt = GetDebt(player, skillType); float paidOff = GetPaidOff(player, skillType); return $"-{debt:0.0000} ({paidOff:0.0000} paid off)"; } internal static string FormatLifetimeDetail(Player player, SkillType skillType) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) float incurred = GetIncurred(player, skillType); return $"{incurred:0.0000} levels (lifetime)"; } 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 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_0040: 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_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0080: 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_00a5: Unknown result type (might be due to invalid IL or missing references) List list = new List(); 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); if (debt > 0f) { list.Add($"{FormatSkillName(value)} {debt:0.0}"); } } } 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 list = new List(); 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); } 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 "); } MessageHud instance2 = MessageHud.instance; if (instance2 != null) { instance2.ShowMessage((MessageType)1, $"Debt on {list.Count} skills — list printed to chat.", 0, (Sprite)null, false); } } 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 list = new List(); 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); } 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); } } 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 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)"; } } } 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), "RaiseSkill")] internal static class PlayerRaiseSkillPatch { private static bool hasLoggedEntry; private static void Prefix(Player __instance, SkillType skill, ref float value) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) if (!hasLoggedEntry) { Plugin.Log.LogInfo((object)"Skuld patch entry: Player.RaiseSkill prefix fired."); hasLoggedEntry = true; } if (ModConfig.EnableMod.Value && SkillDebtService.IsReady) { SkillDebtService.TryApplyRaiseSkillDebt(__instance, skill, ref value); } } } [HarmonyPatch(typeof(SkillsDialog), "Setup")] internal static class SkillsDialogSetupPatch { private const string DebtTextName = "skuld_debttext"; private static readonly Color DebtColor = new Color(0.85f, 0.18f, 0.18f, 1f); 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.EnableMod.Value || (Object)(object)player == (Object)null || ElementsField == null) { return; } if (!(ElementsField.GetValue(__instance) is List 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 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) { ApplyDebtLabel(val2, player, val, tooltipAnchor); } } } private static void ApplyDebtLabel(GameObject row, Player player, Skill skill, RectTransform tooltipAnchor) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0042: 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_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) SkillType skill2 = skill.m_info.m_skill; Transform val = Utils.FindChild(row.transform, "bonustext", (IterativeSearchType)0); if ((Object)(object)val == (Object)null) { return; } TMP_Text component = ((Component)val).GetComponent(); TMP_Text orCreateDebtText = GetOrCreateDebtText(val, component); if ((Object)(object)orCreateDebtText == (Object)null) { return; } float debt = SkillDebtService.GetDebt(player, skill2); bool flag = debt > 0f; ((Component)orCreateDebtText).gameObject.SetActive(flag); if (flag) { orCreateDebtText.text = (-Mathf.RoundToInt(debt)).ToString("0"); ((Graphic)orCreateDebtText).color = DebtColor; RectTransform rectTransform = orCreateDebtText.rectTransform; RectTransform val2 = (RectTransform)(object)((val is RectTransform) ? val : null); if ((Object)(object)rectTransform != (Object)null && (Object)(object)val2 != (Object)null) { float num; if (!((Component)val).gameObject.activeSelf) { num = 0f; } else { Rect rect = val2.rect; num = ((Rect)(ref rect)).width + 8f; } float num2 = num; rectTransform.anchoredPosition = val2.anchoredPosition + new Vector2(num2, 0f); } } 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_0084: Unknown result type (might be due to invalid IL or missing references) UITooltip componentInChildren = row.GetComponentInChildren(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(); if ((Object)(object)debtTooltipBinder == (Object)null) { debtTooltipBinder = row.AddComponent(); } debtTooltipBinder.Initialize(skill.m_info.m_skill, componentInChildren, skill.m_info.m_description, tooltipAnchor, position); } } private static TMP_Text GetOrCreateDebtText(Transform bonusTransform, TMP_Text bonusText) { //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_00dc: Unknown result type (might be due to invalid IL or missing references) Transform val = bonusTransform.parent.Find("skuld_debttext"); if ((Object)(object)val != (Object)null) { return ((Component)val).GetComponent(); } GameObject val2 = Object.Instantiate(((Component)bonusTransform).gameObject, bonusTransform.parent); ((Object)val2).name = "skuld_debttext"; UITooltip component = val2.GetComponent(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } TMP_Text component2 = val2.GetComponent(); if ((Object)(object)component2 == (Object)null) { Object.Destroy((Object)(object)val2); return null; } if ((Object)(object)bonusText != (Object)null) { component2.font = bonusText.font; component2.fontSize = bonusText.fontSize; component2.fontStyle = bonusText.fontStyle; component2.alignment = bonusText.alignment; component2.enableAutoSizing = bonusText.enableAutoSizing; } CanvasGroup val3 = val2.GetComponent(); if ((Object)(object)val3 == (Object)null) { val3 = val2.AddComponent(); } val3.blocksRaycasts = false; val3.interactable = false; ((Graphic)component2).color = DebtColor; val2.SetActive(false); return component2; } } [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.EnableMod.Value || !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); } } return false; } } }