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 v0.2.0
Skuld.dll
Decompiled 18 hours agousing 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<bool> EnableMod { get; private set; } internal static ConfigEntry<float> DebtPaydownShare { get; private set; } internal static ConfigEntry<int> MaxDebtPerSkill { get; private set; } internal static ConfigEntry<bool> DebugLogging { get; private set; } internal static ConfigEntry<bool> 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<bool>("General", "EnableMod", true, "Enable Skuld debt mechanics."); DebtPaydownShare = config.Bind<float>("General", "DebtPaydownShare", 0.5f, new ConfigDescription("Fraction of earned XP redirected to debt paydown while debt remains.", (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.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 5), Array.Empty<object>())); DebugLogging = config.Bind<bool>("General", "DebugLogging", false, "Enable verbose debt conversion/paydown logs."); EnableDevCommands = config.Bind<bool>("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<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>__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 <skill|all>]"); 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 <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); } } 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 <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; } 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<string> 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<string> list = new List<string> { "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<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(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<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_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<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); 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<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); } 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); } } 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); } 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<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)"; } } } 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<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) { 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>(); 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<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, 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<TMP_Text>(); } GameObject val2 = Object.Instantiate<GameObject>(((Component)bonusTransform).gameObject, bonusTransform.parent); ((Object)val2).name = "skuld_debttext"; UITooltip component = val2.GetComponent<UITooltip>(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } TMP_Text component2 = val2.GetComponent<TMP_Text>(); 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<CanvasGroup>(); if ((Object)(object)val3 == (Object)null) { val3 = val2.AddComponent<CanvasGroup>(); } 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; } } }