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 SkillFloors v1.1.5
plugins/SkillFloors.dll
Decompiled a week agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Jotunn; using Jotunn.Utils; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("SkillFloors")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("SkillFloors")] [assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("e3243d22-4307-4008-ba36-9f326008cde5")] [assembly: AssemblyFileVersion("1.1.5")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.5.0")] [module: UnverifiableCode] namespace SkillFloors; [BepInPlugin("Armikur.mod.Valheim.SkillFloors", "SkillFloors", "1.1.5")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] internal class SkillFloors : BaseUnityPlugin { public const string PluginName = "SkillFloors"; public const string PluginGUID = "Armikur.mod.Valheim.SkillFloors"; public const string PluginVersion = "1.1.5"; private readonly Harmony HarmonyInstance = new Harmony("Armikur.mod.Valheim.SkillFloors"); internal static ConfigEntry<float> Config_Rate; internal static ConfigEntry<bool> Config_Debug; internal static bool BookIsLoaded = false; public static Dictionary<SkillType, FloorValues> Floors_Book = new Dictionary<SkillType, FloorValues>(); private void Awake() { CreateConfigValues(); SFLog.Info("Armikur's SkillFloors mod 1.1.5 has loaded!"); Assembly executingAssembly = Assembly.GetExecutingAssembly(); HarmonyInstance.PatchAll(executingAssembly); } private void CreateConfigValues() { //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_003e: Expected O, but got Unknown //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Expected O, but got Unknown Config_Rate = ((BaseUnityPlugin)this).Config.Bind<float>("Server Config", "Floor XP Gain Rate", 0.15f, new ConfigDescription("Multiplier for how much XP floor skills gain relative to regular skills (e.g., 0.15 = 15%). Values over 1 will give more XP than normal; helpful for catching up.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1.5f), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); Config_Debug = ((BaseUnityPlugin)this).Config.Bind<bool>("Client Config", "Enable Debug Logging", false, new ConfigDescription("Shows each skill floor increase in the BepInEx window.", (AcceptableValueBase)null, Array.Empty<object>())); } public static void FreshFloorsBook() { Floors_Book = new Dictionary<SkillType, FloorValues>(); if (Config_Debug.Value) { SFLog.Warn("Fresh Floors Book created"); } } public static void UpdateFloor(Skill skill, float skillXPGain) { //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_0011: 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_0121: Unknown result type (might be due to invalid IL or missing references) //IL_013a: 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_0197: Unknown result type (might be due to invalid IL or missing references) SkillType skill2 = skill.m_info.m_skill; if (!Floors_Book.TryGetValue(skill2, out var value)) { value = new FloorValues(); Floors_Book[skill2] = value; } float num = CalcFloorReqXP(value.Level); float nextLevelRequirement = skill.GetNextLevelRequirement(); float num2 = Mathf.Floor(skill.m_level); if (value.Level >= num2) { value.Level = num2; value.XP = 0f; if (Config_Debug.Value) { SFLog.Info($"{skill2} floor clamped at {value.Level} ({value.XP}/{num}) | Skill: {skill.m_level} ({skill.m_accumulator}/{nextLevelRequirement})"); } return; } value.XP += skillXPGain * Config_Rate.Value; if (value.XP >= num) { value.Level += 1f; value.XP = 0f; FloorGainNotify(skill2, value.Level); SFLog.Info($"{skill2} floor increased to {value.Level}!! | Skill: {skill.m_level} ({skill.m_accumulator}/{nextLevelRequirement} )"); } if (Config_Debug.Value) { SFLog.Info($"{skill2} Floor: {value.Level} ({value.XP}/{num}) | Skill: {skill.m_level} ({skill.m_accumulator}/{nextLevelRequirement})"); } } public static float GetFloorLevel(SkillType type) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (!Floors_Book.TryGetValue(type, out var value)) { return 0f; } return value.Level; } public static float CalcFloorReqXP(float curFloorLevel) { return Mathf.Pow(Mathf.Floor(curFloorLevel + 1f), 1.5f) * 0.5f + 0.5f; } private static void FloorGainNotify(SkillType skillType, float floorLevel) { if (!((Object)(object)MessageHud.instance == (Object)null)) { string arg = Localization.instance.Localize("$skill_" + ((object)(SkillType)(ref skillType)).ToString().ToLower()); string text = $"<color=#7D9FB8>{arg} floor increased to {floorLevel}</color>"; ((Character)Player.m_localPlayer).Message((MessageType)2, text, 0, (Sprite)null); } } } internal static class SFLog { private const string prefix = "[SkillFloors] "; public static void Info(string msg) { Logger.LogInfo((object)("[SkillFloors] " + msg)); } public static void Warn(string msg) { Logger.LogWarning((object)("[SkillFloors] " + msg)); } public static void Err(string msg) { Logger.LogError((object)("[SkillFloors] " + msg)); } } public class FloorValues { public float Level; public float XP; } public class JSONFloorValuesReference { public float SkillFloors_Floor_Level; public float SkillFloors_Floor_XPProgress; } [HarmonyPatch(typeof(Skill), "Raise")] public class Patch_SkillFloor_Raise { private static void Postfix(Skill __instance, float factor) { float increseStep = __instance.m_info.m_increseStep; float skillGainRate = Game.m_skillGainRate; float skillXPGain = increseStep * factor * skillGainRate; SkillFloors.UpdateFloor(__instance, skillXPGain); } } [HarmonyPatch(typeof(Player), "OnDeath")] public class Patch_Player_OnDeath { private static void Postfix(Player __instance) { //IL_0021: 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_0027: 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 (Skill skill2 in ((Character)__instance).GetSkills().GetSkillList()) { SkillType skill = skill2.m_info.m_skill; float floorLevel = SkillFloors.GetFloorLevel(skill); if (skill2.m_level < floorLevel) { skill2.m_level = floorLevel; SFLog.Info($"{skill} hit its SkillFloor. Holding the line (I mean floor!) at level {floorLevel}!."); } } } } [HarmonyPatch(typeof(SkillsDialog), "Setup")] public class Patch_SkillsDialog { private static void Postfix(SkillsDialog __instance, Player player) { //IL_001f: 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) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) List<Skill> skillList = ((Character)player).GetSkills().GetSkillList(); for (int i = 0; i < skillList.Count && i < __instance.m_elements.Count; i++) { SkillType skill = skillList[i].m_info.m_skill; float floorLevel = SkillFloors.GetFloorLevel(skill); GameObject val = __instance.m_elements[i]; TMP_Text component = ((Component)Utils.FindChild(val.transform, "name", (IterativeSearchType)0)).GetComponent<TMP_Text>(); if ((Object)(object)component != (Object)null && floorLevel > 0f) { string arg = Localization.instance.Localize("$skill_" + ((object)(SkillType)(ref skill)).ToString().ToLower()); component.text = $"{arg} <color=#7D9FB8><size=85%>{Mathf.FloorToInt(floorLevel)}</size></color>"; } if (!SkillFloors.Floors_Book.TryGetValue(skill, out var value)) { continue; } GuiBar component2 = ((Component)Utils.FindChild(val.transform, "currentlevel", (IterativeSearchType)0)).GetComponent<GuiBar>(); if ((Object)(object)component2 != (Object)null) { Transform obj = Utils.FindChild(val.transform, "floorlevel", (IterativeSearchType)0); GuiBar val2 = ((obj != null) ? ((Component)obj).GetComponent<GuiBar>() : null); GuiBar val3; if ((Object)(object)val2 != (Object)null) { val3 = val2; } else { GameObject obj2 = Object.Instantiate<GameObject>(((Component)component2).gameObject, ((Component)component2).transform.parent); ((Object)obj2).name = "floorlevel"; val3 = obj2.GetComponent<GuiBar>(); RectTransform component3 = obj2.GetComponent<RectTransform>(); component3.anchoredPosition -= new Vector2(0f, 2f); component3.SetSizeWithCurrentAnchors((Axis)1, 2f); } float num = SkillFloors.CalcFloorReqXP(value.Level); float value2 = Mathf.Clamp01(value.XP / num); val3.SetValue(value2); val3.SetColor(new Color(0.66f, 0.76f, 0.84f, 1f)); } } } } public static class SaveData { private const string SaveDataKey = "SkillFloors_Data"; private const string SaveDataKey_OLDJSON = "SkillFloors_SkillFloorData_JSON"; private const int SaveVersion = 1; public static bool IsMainScene() { //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) Scene activeScene = SceneManager.GetActiveScene(); return ((Scene)(ref activeScene)).name.Equals("main"); } public static void Save_Floors(Player player) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected I4, but got Unknown ZPackage val = new ZPackage(); val.Write(1); val.Write(SkillFloors.Floors_Book.Count); foreach (KeyValuePair<SkillType, FloorValues> item in SkillFloors.Floors_Book) { val.Write((int)item.Key); val.Write(item.Value.Level); val.Write(item.Value.XP); } player.m_customData["SkillFloors_Data"] = val.GetBase64(); if (!SkillFloors.Config_Debug.Value) { SFLog.Info("Floors data saved"); } Log_Book("Floors data saved"); } public static void Load_Floors(Player player) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) if (player.m_customData.TryGetValue("SkillFloors_Data", out var value)) { ZPackage val = new ZPackage(value); int num = val.ReadInt(); if (num != 1) { SFLog.Warn($"ZPackage found, unsupported save version {num}, aborting"); return; } int num2 = val.ReadInt(); for (int i = 0; i < num2; i++) { SkillType key = (SkillType)val.ReadInt(); float level = val.ReadSingle(); float xP = val.ReadSingle(); SkillFloors.Floors_Book[key] = new FloorValues { Level = level, XP = xP }; } if (!SkillFloors.Config_Debug.Value) { SFLog.Info("Floors data loaded"); } Log_Book("ZPackage found, Floors data loaded"); } else { SFLog.Warn("No ZPackage found, Trying JSON (old) system instead"); if (!Load_Floors_OLDJSON(player)) { SFLog.Warn("No Floors data found (normal for first use / new characters)."); } } } private static bool Load_Floors_OLDJSON(Player player) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown //IL_0049: Expected O, but got Unknown //IL_00c3: Unknown result type (might be due to invalid IL or missing references) if (!player.m_customData.TryGetValue("SkillFloors_SkillFloorData_JSON", out var value)) { if (SkillFloors.Config_Debug.Value) { SFLog.Warn("Tried to load from JSON data but found none."); } return false; } try { JsonSerializerSettings val = new JsonSerializerSettings { Converters = new List<JsonConverter> { (JsonConverter)new StringEnumConverter() } }; Dictionary<SkillType, JSONFloorValuesReference> dictionary = JsonConvert.DeserializeObject<Dictionary<SkillType, JSONFloorValuesReference>>(value, val); if (dictionary == null || dictionary.Count == 0) { SFLog.Warn("Found JSON data, but it was empty. Aborting JSON load."); return false; } if (SkillFloors.Config_Debug.Value) { SFLog.Warn($"JSON save has entries: {dictionary?.Count ?? (-1)}"); SFLog.Warn("JSON (raw): \n " + value); } foreach (KeyValuePair<SkillType, JSONFloorValuesReference> item in dictionary) { SkillFloors.Floors_Book[item.Key] = new FloorValues { Level = item.Value.SkillFloors_Floor_Level, XP = item.Value.SkillFloors_Floor_XPProgress }; } Save_Floors(player); player.m_customData.Remove("SkillFloors_SkillFloorData_JSON"); SFLog.Warn("JSON migrated to ZPackage. Old JSON removed."); return true; } catch (Exception arg) { SFLog.Err($"JSON migration failed:\n{arg}"); return false; } } private static void Log_Book(string headerMsg = null) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) if (!SkillFloors.Config_Debug.Value) { return; } if (!string.IsNullOrWhiteSpace(headerMsg)) { SFLog.Warn(headerMsg); } Dictionary<SkillType, FloorValues> floors_Book = SkillFloors.Floors_Book; if (floors_Book.Count == 0) { SFLog.Warn("xxxxxx Floors Book Is Empty xxxxxx"); return; } SFLog.Warn("****** Current Floors Book ******"); foreach (KeyValuePair<SkillType, FloorValues> item in floors_Book) { SFLog.Info($"** {item.Key}: Floor: {item.Value.Level}, Floor XP: {item.Value.XP}"); } } } [HarmonyPatch(typeof(Player), "Save")] public class Patch_Player_Save { private static void Prefix(Player __instance) { if ((Object)(object)__instance == (Object)null || (Object)(object)Player.m_localPlayer != (Object)(object)__instance) { if (SkillFloors.Config_Debug.Value) { SFLog.Warn("skip save: non-local or null player"); } } else if (!SaveData.IsMainScene()) { if (SkillFloors.Config_Debug.Value) { SFLog.Warn("skip save: not in-world"); } } else { SaveData.Save_Floors(__instance); } } } [HarmonyPatch(typeof(Player), "OnSpawned")] public static class Patch_Player_OnSpawned { private static void Postfix(Player __instance) { if ((Object)(object)__instance == (Object)null || (Object)(object)Player.m_localPlayer != (Object)(object)__instance) { if (SkillFloors.Config_Debug.Value) { SFLog.Warn("skip load: non-local or null player"); } return; } if (!SaveData.IsMainScene()) { if (SkillFloors.Config_Debug.Value) { SFLog.Warn("skip load: not in-world"); } return; } if (SkillFloors.BookIsLoaded) { if (SkillFloors.Config_Debug.Value) { SFLog.Warn("skip load: only load once per login"); } return; } if (SkillFloors.Config_Debug.Value) { SFLog.Warn("Refreshing Book & loading Floors data"); } SkillFloors.FreshFloorsBook(); SaveData.Load_Floors(__instance); SkillFloors.BookIsLoaded = true; } } [HarmonyPatch(typeof(Game), "Logout")] public static class Patch_Game_Logout { private static void Prefix() { SkillFloors.BookIsLoaded = false; if (SkillFloors.Config_Debug.Value) { SFLog.Warn("\"Loaded\" state reset"); } } }