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 BossStarsAfterDefeat v2.0.3
BossStarsAfterDefeat.dll
Decompiled a day agousing System; 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 BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: AssemblyCompany("BossStarsAfterDefeat")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("BossStarsAfterDefeat")] [assembly: AssemblyTitle("BossStarsAfterDefeat")] [assembly: AssemblyVersion("1.0.0.0")] [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 BossStarsAfterDefeat { [BepInPlugin("pieter.valheim.bossstarsafterdefeat", "Boss Stars After Defeat", "2.0.3")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string Guid = "pieter.valheim.bossstarsafterdefeat"; public const string Name = "Boss Stars After Defeat"; public const string Version = "2.0.3"; private const string StarLevelSystemGuid = "MidnightsFX.StarLevelSystem"; private const string EpicLootGuid = "randyknapp.mods.epicloot"; private static readonly string[] BossKeys = new string[8] { "defeated_eikthyr", "defeated_gdking", "defeated_bonemass", "defeated_dragon", "defeated_goblinking", "defeated_queen", "defeated_fader", "defeated_frozenking_p3" }; private static readonly Dictionary<Biome, int> BiomeIndex = new Dictionary<Biome, int> { { (Biome)1, 0 }, { (Biome)8, 1 }, { (Biome)2, 2 }, { (Biome)256, 2 }, { (Biome)4, 3 }, { (Biome)16, 4 }, { (Biome)512, 5 }, { (Biome)32, 6 }, { (Biome)64, 7 } }; private static readonly SortedDictionary<int, float>[] CreatureTables = new SortedDictionary<int, float>[5] { null, Table(37.5f, 17.5f, 7.5f, 2.5f, 0f), Table(45f, 25f, 12.5f, 5f, 1.25f, 0f), Table(55f, 32.5f, 17.5f, 7.5f, 2.5f, 0f), Table(66.25f, 41.25f, 23.75f, 11.25f, 3.75f, 0f) }; private static readonly SortedDictionary<int, float>[] BossTables = new SortedDictionary<int, float>[3] { Table(35f, 15f, 5f, 0f), Table(43.75f, 23.75f, 11.25f, 3.75f, 0f), Table(52.5f, 32.5f, 17.5f, 7.5f, 2.5f, 0f) }; private static readonly string[] StageNames = new string[5] { "base (3*)", "4*", "5*", "5* boost 1", "5* boost 2" }; private static readonly float[] DamageFactors = new float[6] { 1f, 1.5f, 2f, 2.5f, 2.75f, 3f }; private static ConfigEntry<bool> _progression; private static ConfigEntry<bool> _damage; private static ConfigEntry<bool> _bossTrophies; private static ConfigEntry<bool> _logTrophyRolls; private static ConfigEntry<bool> _logStars; private static ConfigEntry<float> _bossTrophyMultiplier; private static ConfigEntry<string> _trophyChances; private static float[] _trophyChanceTable; private static ManualLogSource _log; private static readonly FieldRef<Attack, Humanoid> AttackCharacter = AccessTools.FieldRefAccess<Attack, Humanoid>("m_character"); private static ConfigEntry<float> _slsDamagePerStar; private static ConfigEntry<bool> _scaleAllLoot; [ThreadStatic] private static Character _rolling; private static int _loggedHighest = int.MinValue; [ThreadStatic] private static string _bossTableUsed; [ThreadStatic] private static string _creatureTableUsed; private static readonly Dictionary<string, int[]> CreatureRolls = new Dictionary<string, int[]>(); private static float _nextSummary; private void Awake() { //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Expected O, but got Unknown //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Expected O, but got Unknown //IL_03b1: Unknown result type (might be due to invalid IL or missing references) //IL_03b6: Unknown result type (might be due to invalid IL or missing references) //IL_03c5: Expected O, but got Unknown //IL_0374: Unknown result type (might be due to invalid IL or missing references) //IL_0379: Unknown result type (might be due to invalid IL or missing references) //IL_0388: Expected O, but got Unknown _log = ((BaseUnityPlugin)this).Logger; _progression = ((BaseUnityPlugin)this).Config.Bind<bool>("Stars", "Boss progression", true, "Bosses raise the star cap of earlier biomes (one boss behind, up to 5 stars, then two chance boosts) and of earlier bosses (two bosses behind). Off = only LevelSettings.yaml and MaxBossLevel apply."); _damage = ((BaseUnityPlugin)this).Config.Bind<bool>("Stars", "Per-star damage", true, "Normal creatures deal +50% damage per star up to 3 stars, +175% at 4 and +200% at 5 stars. Creatures in the Ocean get no extra damage from stars. Off = SLS's EnemyDamageLevelMultiplier for every star."); _logStars = ((BaseUnityPlugin)this).Config.Bind<bool>("Stars", "Log star progression", false, "Write the active star stage per biome and every boss level roll to the BepInEx log."); _trophyChances = ((BaseUnityPlugin)this).Config.Bind<string>("Trophies", "Trophy chance per star (%)", "10, 15, 20, 25, 37.5, 50", "Normal creatures: minimum trophy chance at 0, 1, 2, ... stars. Creatures with a higher vanilla chance keep theirs. Stars past the list use the last value."); _bossTrophies = ((BaseUnityPlugin)this).Config.Bind<bool>("Trophies", "Multiply boss trophies per star", true, "Starred bosses drop more trophies, on top of Epic Loot's one-per-player count."); _bossTrophyMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Trophies", "Boss trophy multiplier per star", 1.5f, new ConfigDescription("Boss trophies x this per star: 1.5 = x1, x1.5, x2.25, x3.4, x5.1, x7.6 at 0-5 stars (fractions rounded randomly).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 2f), Array.Empty<object>())); _logTrophyRolls = ((BaseUnityPlugin)this).Config.Bind<bool>("Trophies", "Log trophy rolls", false, "Write how each trophy drop was rolled to the BepInEx log."); _trophyChanceTable = ParseChances(_trophyChances.Value); _trophyChances.SettingChanged += delegate { _trophyChanceTable = ParseChances(_trophyChances.Value); }; Type type = AccessTools.TypeByName("StarLevelSystem.common.ValConfig"); _slsDamagePerStar = AccessTools.Field(type, "EnemyDamageLevelMultiplier")?.GetValue(null) as ConfigEntry<float>; _scaleAllLoot = AccessTools.Field(type, "ScaleAllLootByLevel")?.GetValue(null) as ConfigEntry<bool>; Harmony val = new Harmony("pieter.valheim.bossstarsafterdefeat"); Type type2 = AccessTools.TypeByName("StarLevelSystem.modules.LevelSystem.LevelSelection"); Type type3 = AccessTools.TypeByName("StarLevelSystem.modules.LevelSystem.ConditionalScaleSystem"); MethodInfo methodInfo = AccessTools.Method(type2, "GetMaxCreatureLevel", (Type[])null, (Type[])null); MethodInfo methodInfo2 = type2?.GetMethods(AccessTools.all).FirstOrDefault((MethodInfo m) => m.Name == "DetermineLevel" && m.GetParameters().Length != 0 && m.GetParameters()[0].ParameterType == typeof(Character)); MethodInfo methodInfo3 = type2?.GetMethods(AccessTools.all).FirstOrDefault((MethodInfo m) => m.Name == "DetermineLevelupChance" && m.GetParameters().Length == 6); MethodInfo methodInfo4 = AccessTools.Method(type3, "GetConditionalLevelupChance", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null || methodInfo3 == null || methodInfo4 == null || _slsDamagePerStar == null) { ((BaseUnityPlugin)this).Logger.LogError((object)($"Star progression disabled, StarLevelSystem internals not found (max {methodInfo != null}, " + $"roll {methodInfo2 != null}, chance {methodInfo3 != null}, conditional {methodInfo4 != null}, " + $"damage {_slsDamagePerStar != null}).")); } else { val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, Hook("MaxLevelPostfix"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)methodInfo2, Hook("DeterminePrefix"), Hook("DeterminePostfix"), (HarmonyMethod)null, Hook("DetermineFinalizer"), (HarmonyMethod)null); val.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, Hook("BossChancePostfix"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, Hook("ConditionalChancePostfix"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.Method(typeof(Attack), "GetLevelDamageFactor", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "DamageFactorPostfix", (Type[])null) { priority = 0 }, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } val.Patch((MethodBase)AccessTools.Method(typeof(CharacterDrop), "GenerateDropList", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "DropListPostfix", (Type[])null) { priority = 0 }, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Boss star progression, per-star damage and per-star trophies active."); } private static HarmonyMethod Hook(string name) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown return new HarmonyMethod(typeof(Plugin).GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic)); } private static SortedDictionary<int, float> Table(params float[] chances) { SortedDictionary<int, float> sortedDictionary = new SortedDictionary<int, float>(); for (int i = 0; i < chances.Length; i++) { sortedDictionary[i + 1] = chances[i]; } return sortedDictionary; } private static float[] ParseChances(string text) { List<float> list = new List<float>(); string[] array = (text ?? "").Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { if (float.TryParse(array[i].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { list.Add(Mathf.Clamp(result, 0f, 100f)); } } if (list.Count <= 0) { return new float[1]; } return list.ToArray(); } private static int HighestDefeated() { if ((Object)(object)ZoneSystem.instance == (Object)null) { return -1; } for (int num = BossKeys.Length - 1; num >= 0; num--) { if (ZoneSystem.instance.GetGlobalKey(BossKeys[num])) { return num; } } return -1; } private static int BiomeStage(Biome biome, int highest) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (!BiomeIndex.TryGetValue(biome, out var value)) { return 0; } return Mathf.Clamp(highest - value, 0, CreatureTables.Length - 1); } private static int BossStage(Character boss, int highest) { int num = Array.IndexOf(BossKeys, boss.m_defeatSetGlobalKey); if (num >= 0) { return Mathf.Clamp(highest - num - 1, 0, BossTables.Length - 1); } return 0; } private static void ConditionalChancePostfix(Biome biome, ref SortedDictionary<int, float> __result) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) if (_progression.Value) { int highest = HighestDefeated(); LogStages(highest); int num = BiomeStage(biome, highest); if (num > 0) { __result = CreatureTables[num]; } } } private static void MaxLevelPostfix(Character character, ref int __result) { if (__result <= 1 || (Object)(object)character == (Object)null || !character.IsBoss()) { return; } string defeatSetGlobalKey = character.m_defeatSetGlobalKey; if (!string.IsNullOrEmpty(defeatSetGlobalKey) && !((Object)(object)ZoneSystem.instance == (Object)null)) { if (!ZoneSystem.instance.GetGlobalKey(defeatSetGlobalKey)) { __result = 1; } else if (_progression.Value) { __result = BossTables[BossStage(character, HighestDefeated())].Keys.Last(); } } } private static void DeterminePrefix(Character character) { _rolling = character; _bossTableUsed = null; _creatureTableUsed = null; } private static void DeterminePostfix(Character character, ZDO cZDO, Biome biome, int leveloverride, bool allowRoll, int __result) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) if (_logStars.Value && !((Object)(object)character == (Object)null) && _progression.Value) { if (!character.IsBoss()) { RecordCreatureRoll(biome, cZDO, leveloverride, allowRoll, __result); return; } int num = BossStage(character, HighestDefeated()); ManualLogSource log = _log; string[] obj = new string[5] { $"Star roll: boss {Utils.GetPrefabName(((Component)character).gameObject)} level {__result} ({Math.Max(0, __result - 1)}*), ", null, null, null, null }; string arg = StageNames[num]; ZoneSystem instance = ZoneSystem.instance; obj[1] = $"table {arg}, defeated before: {((instance != null) ? new bool?(instance.GetGlobalKey(character.m_defeatSetGlobalKey)) : ((bool?)null))}, "; obj[2] = $"saved level {((cZDO != null) ? new int?(cZDO.GetInt(ZDOVars.s_level, 0)) : ((int?)null))}, override {leveloverride}, roll allowed {allowRoll}, "; obj[3] = "boss table used: "; obj[4] = _bossTableUsed ?? "no (no new roll)"; log.LogInfo((object)string.Concat(obj)); } } private static Exception DetermineFinalizer(Exception __exception) { _rolling = null; return __exception; } private static void BossChancePostfix(bool isBoss, ref SortedDictionary<int, float> __result) { if (!isBoss) { int num = Array.IndexOf(CreatureTables, __result); _creatureTableUsed = ((num > 0) ? StageNames[num] : ((__result != null && __result.TryGetValue(1, out var value)) ? $"default (1+ stars {value}%)" : "none")); } else if (_progression.Value && !((Object)(object)_rolling == (Object)null) && _rolling.IsBoss()) { int num2 = BossStage(_rolling, HighestDefeated()); __result = BossTables[num2]; _bossTableUsed = $"{StageNames[num2]} (1+ stars {__result[1]}%)"; } } private static void RecordCreatureRoll(Biome biome, ZDO zdo, int leveloverride, bool allowRoll, int level) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) if (allowRoll && leveloverride <= 0 && zdo != null && zdo.GetInt(ZDOVars.s_level, 0) <= 0 && _creatureTableUsed != null) { string key = $"{biome} [{_creatureTableUsed}]"; if (!CreatureRolls.TryGetValue(key, out var value)) { value = (CreatureRolls[key] = new int[7]); } value[Mathf.Clamp(level - 1, 0, 6)]++; } _creatureTableUsed = null; if (Time.time < _nextSummary || CreatureRolls.Count == 0) { return; } _nextSummary = Time.time + 60f; foreach (KeyValuePair<string, int[]> pair in CreatureRolls) { int total = pair.Value.Sum(); string arg = string.Join(", ", from i in Enumerable.Range(0, 7) where pair.Value[i] > 0 select $"{i}* {pair.Value[i]} ({100f * (float)pair.Value[i] / (float)total:0}%)"); _log.LogInfo((object)$"Star rolls: {pair.Key}: {total} new creatures: {arg}"); } CreatureRolls.Clear(); } private static void LogStages(int highest) { if (highest != _loggedHighest && _logStars.Value) { _loggedHighest = highest; Biome[] array = new Biome[9]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); IEnumerable<string> values = ((IEnumerable<Biome>)(object)array).Select((Biome b) => $"{b} {StageNames[BiomeStage(b, highest)]}"); _log.LogInfo((object)("Star stages (latest boss: " + ((highest >= 0) ? BossKeys[highest] : "none") + "): " + string.Join(", ", values))); } } private static void DamageFactorPostfix(Attack __instance, ref float __result) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Invalid comparison between Unknown and I4 if (!_damage.Value || _slsDamagePerStar == null) { return; } Humanoid val = AttackCharacter.Invoke(__instance); if ((Object)(object)val == (Object)null || ((Character)val).IsPlayer() || ((Character)val).IsBoss()) { return; } int num = ((Character)val).GetLevel() - 1; if (num > 0) { float num2 = 1f + (float)num * _slsDamagePerStar.Value; if (!(num2 <= 0f)) { float num3 = (((int)Heightmap.FindBiome(((Component)val).transform.position) == 256) ? 1f : ((num < DamageFactors.Length) ? DamageFactors[num] : (DamageFactors[DamageFactors.Length - 1] + 0.25f * (float)(num - DamageFactors.Length + 1)))); __result = __result / num2 * num3; } } } private static void DropListPostfix(CharacterDrop __instance, List<KeyValuePair<GameObject, int>> __result) { //IL_00c0: 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) //IL_00d5: Invalid comparison between Unknown and I4 if (__result == null) { return; } Character component = ((Component)__instance).GetComponent<Character>(); if ((Object)(object)component == (Object)null) { return; } int num = Mathf.Clamp(component.GetLevel() - 1, 0, 10); if (!component.IsBoss()) { ExtraTrophyRoll(__instance, __result, num); } else { if (num == 0 || !_bossTrophies.Value) { return; } float num2 = Mathf.Pow(_bossTrophyMultiplier.Value, (float)num); for (int i = 0; i < __result.Count; i++) { ItemDrop obj = (Object.op_Implicit((Object)(object)__result[i].Key) ? __result[i].Key.GetComponent<ItemDrop>() : null); if (obj != null && (int)(obj.m_itemData?.m_shared?.m_itemType).GetValueOrDefault() == 13) { float num3 = (float)__result[i].Value * num2; int num4 = Mathf.FloorToInt(num3); if (Random.value < num3 - (float)num4) { num4++; } num4 = Mathf.Max(__result[i].Value, num4); LogRoll($"boss {Utils.GetPrefabName(((Component)__instance).gameObject)} {num}*: {((Object)__result[i].Key).name} {__result[i].Value} x {num2:0.##} = {num3:0.##} -> {num4}"); __result[i] = new KeyValuePair<GameObject, int>(__result[i].Key, num4); } } } } private static void ExtraTrophyRoll(CharacterDrop drop, List<KeyValuePair<GameObject, int>> result, int stars) { //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Invalid comparison between Unknown and I4 string text = $"{Utils.GetPrefabName(((Component)drop).gameObject)} {stars}*"; float[] trophyChanceTable = _trophyChanceTable; float num = trophyChanceTable[Mathf.Min(stars, trophyChanceTable.Length - 1)] / 100f; if (drop.m_drops == null) { LogRoll(text + ": no drop table"); return; } bool flag = false; foreach (Drop entry in drop.m_drops) { ItemDrop obj = (Object.op_Implicit((Object)(object)entry?.m_prefab) ? entry.m_prefab.GetComponent<ItemDrop>() : null); if (obj == null || (int)(obj.m_itemData?.m_shared?.m_itemType).GetValueOrDefault() != 13) { continue; } flag = true; string name = ((Object)entry.m_prefab).name; string text2 = $"{text}: {name} vanilla {entry.m_chance:P1}"; int num2 = result.FindIndex((KeyValuePair<GameObject, int> r) => (Object)(object)r.Key == (Object)(object)entry.m_prefab); if (num2 >= 0) { LogRoll($"{text2} -> vanilla roll HIT, dropping {result[num2].Value}"); continue; } if (entry.m_chance <= 0f || entry.m_chance >= 1f || num <= 0f) { LogRoll($"{text2} -> vanilla roll missed, no extra roll (chance {entry.m_chance:P1}, target {num:P1})"); continue; } float num3 = Mathf.Min(1f, num); if (num3 <= entry.m_chance) { LogRoll($"{text2} -> vanilla roll missed, vanilla chance already >= target {num3:P1}"); continue; } float num4 = (num3 - entry.m_chance) / (1f - entry.m_chance); float value = Random.value; if (value >= num4) { LogRoll($"{text2} -> vanilla missed, extra roll {value:0.000} vs {num4:0.000} (total {num3:P1}) MISSED"); continue; } int num5 = Random.Range(entry.m_amountMin, entry.m_amountMax + 1); if (entry.m_levelMultiplier || (_scaleAllLoot != null && _scaleAllLoot.Value)) { num5 *= stars + 1; } num5 = Mathf.Max(1, num5); result.Add(new KeyValuePair<GameObject, int>(entry.m_prefab, num5)); LogRoll($"{text2} -> vanilla missed, extra roll {value:0.000} vs {num4:0.000} (total {num3:P1}) HIT, dropping {num5}"); } if (!flag) { LogRoll(text + ": has no trophy in its drop table"); } } private static void LogRoll(string message) { if (_logTrophyRolls.Value) { _log.LogInfo((object)("Trophy roll: " + message)); } } } }