using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Jotunn.Utils; using UnityEngine; [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: CompilationRelaxations(8)] [assembly: AssemblyVersion("0.0.0.0")] namespace Razaotium.ClientSide; [SynchronizationMode(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("razaotium.valheim.client_side", "Razaotium Client Side", "1.0.2")] public class RazaotiumClientSidePlugin : BaseUnityPlugin { private enum Toggle { Off, On } [HarmonyPatch(typeof(CharacterDrop), "GenerateDropList")] private static class CharacterDropGenerateDropListPatch { private static bool Prefix(CharacterDrop __instance, ref List> __result) { if (ShouldBlock(__instance, out var prefab, out var requiredTier, out var playerTier)) { __result = new List>(); if (_logBlockedDrops.Value) { ((BaseUnityPlugin)_instance).Logger.LogInfo((object)$"Blocked drops from {prefab}: creature tier {requiredTier}, player tier {playerTier}."); } return false; } return true; } } public const string PluginGuid = "razaotium.valheim.client_side"; public const string PluginName = "Razaotium Client Side"; public const string PluginVersion = "1.0.2"; private const string DefaultCreatureTiers = "Greydwarf=1,Greydwarf_Elite=1,Greydwarf_Shaman=1,Troll=1,Ghost=1,Skeleton=1,Skeleton_Poison=1,RancidRemains=1,Blob=2,BlobElite=2,Draugr=2,Draugr_Elite=2,Leech=2,Surtling=2,Wraith=2,Abomination=2,Wolf=3,Fenring=3,Fenring_Cultist=3,Ulv=3,Bat=3,Hatchling=3,StoneGolem=3,Deathsquito=4,Goblin=4,GoblinArcher=4,GoblinBrute=4,GoblinShaman=4,Lox=4,Growth=4,Seeker=5,SeekerBrute=5,SeekerBrood=5,Gjall=5,Tick=5,Dverger=5,DvergerMage=5,DvergerMageFire=5,DvergerMageIce=5,DvergerMageSupport=5,DvergerRogue=5,Charred_Archer=6,Charred_Melee=6,Charred_Mage=6,Charred_Twitcher=6,Morgen=6,FallenValkyrie=6,Volture=6,Asksvin=6,BlobLava=6,BonemawSerpent=6,gd_king=2,Bonemass=3,Dragon=4,GoblinKing=5,SeekerQueen=6,Fader=6"; private static ConfigEntry _lockConfiguration; private static ConfigEntry _enabled; private static ConfigEntry _useKillerProgress; private static ConfigEntry _closestPlayerFallbackRange; private static ConfigEntry _allowUnknownCreatures; private static ConfigEntry _includeBossDrops; private static ConfigEntry _logBlockedDrops; private static ConfigEntry _tierKeys; private static ConfigEntry _creatureTiers; private static readonly FieldInfo LastHitField = AccessTools.Field(typeof(Character), "m_lastHit"); private static readonly Dictionary ParsedCreatureTiers = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary ParsedTierKeys = new Dictionary(); private static RazaotiumClientSidePlugin _instance; private Harmony _harmony; private void Awake() { //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Expected O, but got Unknown _instance = this; _lockConfiguration = BindSynced("General", "Lock Configuration", Toggle.On, "If on, the configuration is locked and can be changed by server admins only."); _enabled = BindSynced("General", "Enabled", defaultValue: true, "Enable biome-based drop blocking."); _useKillerProgress = BindSynced("General", "UseKillerProgress", defaultValue: true, "Use the player who landed the killing hit. If false, use the closest player."); _closestPlayerFallbackRange = BindSynced("General", "ClosestPlayerFallbackRange", 80f, "Range used to find a nearby player when the killer is not a player or cannot be found."); _allowUnknownCreatures = BindSynced("General", "AllowUnknownCreatures", defaultValue: true, "If true, creatures not listed in CreatureTiers keep their drops."); _includeBossDrops = BindSynced("General", "IncludeBossDrops", defaultValue: false, "If true, listed boss prefabs are also blocked by progression."); _logBlockedDrops = BindSynced("General", "LogBlockedDrops", defaultValue: true, "Log when a creature drop is blocked."); _tierKeys = BindSynced("Progression", "TierKeys", "1=defeated_eikthyr,2=defeated_gdking,3=defeated_bonemass,4=defeated_dragon,5=defeated_goblinking,6=defeated_queen", "Tier unlock keys. Tier 0 is always unlocked. Defaults: 1 Black Forest, 2 Swamp, 3 Mountains, 4 Plains, 5 Mistlands, 6 Ashlands."); _creatureTiers = BindSynced("Progression", "CreatureTiers", "Greydwarf=1,Greydwarf_Elite=1,Greydwarf_Shaman=1,Troll=1,Ghost=1,Skeleton=1,Skeleton_Poison=1,RancidRemains=1,Blob=2,BlobElite=2,Draugr=2,Draugr_Elite=2,Leech=2,Surtling=2,Wraith=2,Abomination=2,Wolf=3,Fenring=3,Fenring_Cultist=3,Ulv=3,Bat=3,Hatchling=3,StoneGolem=3,Deathsquito=4,Goblin=4,GoblinArcher=4,GoblinBrute=4,GoblinShaman=4,Lox=4,Growth=4,Seeker=5,SeekerBrute=5,SeekerBrood=5,Gjall=5,Tick=5,Dverger=5,DvergerMage=5,DvergerMageFire=5,DvergerMageIce=5,DvergerMageSupport=5,DvergerRogue=5,Charred_Archer=6,Charred_Melee=6,Charred_Mage=6,Charred_Twitcher=6,Morgen=6,FallenValkyrie=6,Volture=6,Asksvin=6,BlobLava=6,BonemawSerpent=6,gd_king=2,Bonemass=3,Dragon=4,GoblinKing=5,SeekerQueen=6,Fader=6", "Creature prefab to tier mapping. Format: Prefab=Tier,Prefab=Tier. Creatures whose tier is above the player's unlocked tier drop nothing."); ReloadConfig(); ((BaseUnityPlugin)this).Config.SettingChanged += delegate { ReloadConfig(); }; _harmony = new Harmony("razaotium.valheim.client_side"); _harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} loaded with {2} configured creatures.", "Razaotium Client Side", "1.0.2", ParsedCreatureTiers.Count)); } private ConfigEntry BindSynced(string section, string key, T defaultValue, string description) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown ConfigFile config = ((BaseUnityPlugin)this).Config; string text = description + " [Synced with Server]"; object[] array = new object[1]; ConfigurationManagerAttributes val = new ConfigurationManagerAttributes(); val.IsAdminOnly = true; array[0] = val; return config.Bind(section, key, defaultValue, new ConfigDescription(text, (AcceptableValueBase)null, array)); } private void OnDestroy() { if (_harmony != null) { _harmony.UnpatchSelf(); } } private static void ReloadConfig() { ParsedTierKeys.Clear(); int result; foreach (KeyValuePair item in SplitPairs(_tierKeys.Value)) { if (int.TryParse(item.Key, out result) && !string.IsNullOrWhiteSpace(item.Value)) { ParsedTierKeys[result] = item.Value.Trim(); } } ParsedCreatureTiers.Clear(); foreach (KeyValuePair item2 in SplitPairs(_creatureTiers.Value)) { if (int.TryParse(item2.Value, out result) && !string.IsNullOrWhiteSpace(item2.Key)) { ParsedCreatureTiers[item2.Key.Trim()] = result; } } } private static IEnumerable> SplitPairs(string value) { if (string.IsNullOrWhiteSpace(value)) { yield break; } try { string[] array = value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries); foreach (string raw in array) { string[] parts = raw.Split(new char[1] { '=' }, 2); if (parts.Length == 2) { yield return new KeyValuePair(parts[0].Trim(), parts[1].Trim()); } } } finally { } } private static bool ShouldBlock(CharacterDrop dropper, out string prefab, out int requiredTier, out int playerTier) { prefab = GetPrefabName(dropper); requiredTier = 0; playerTier = 0; if (!_enabled.Value || string.IsNullOrWhiteSpace(prefab)) { return false; } if (!ParsedCreatureTiers.TryGetValue(prefab, out requiredTier)) { return !_allowUnknownCreatures.Value; } if (!_includeBossDrops.Value && IsBossPrefab(prefab)) { return false; } Player player = FindProgressPlayer(dropper); playerTier = GetUnlockedTier(player); return requiredTier > playerTier; } private static Player FindProgressPlayer(CharacterDrop dropper) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) Character component = ((Component)dropper).GetComponent(); if (_useKillerProgress.Value && (Object)(object)component != (Object)null && LastHitField != null) { object? value = LastHitField.GetValue(component); HitData val = (HitData)((value is HitData) ? value : null); Character val2 = ((val != null && val.HaveAttacker()) ? val.GetAttacker() : null); Player val3 = (Player)(object)((val2 is Player) ? val2 : null); if ((Object)(object)val3 != (Object)null) { return val3; } } return Player.GetClosestPlayer(((Component)dropper).transform.position, _closestPlayerFallbackRange.Value); } private static int GetUnlockedTier(Player player) { int num = 0; foreach (KeyValuePair item in ParsedTierKeys.OrderBy((KeyValuePair entry) => entry.Key)) { if (HasKey(player, item.Value)) { num = Math.Max(num, item.Key); } } return num; } private static bool HasKey(Player player, string key) { if (string.IsNullOrWhiteSpace(key)) { return true; } if ((Object)(object)player != (Object)null && ((Humanoid)player).HaveUniqueKey(key)) { return true; } return (Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey(key); } private static string GetPrefabName(CharacterDrop dropper) { string name = ((Object)((Component)dropper).gameObject).name; return (name == null) ? string.Empty : name.Replace("(Clone)", string.Empty).Trim(); } private static bool IsBossPrefab(string prefab) { return prefab.Equals("Eikthyr", StringComparison.OrdinalIgnoreCase) || prefab.Equals("gd_king", StringComparison.OrdinalIgnoreCase) || prefab.Equals("Bonemass", StringComparison.OrdinalIgnoreCase) || prefab.Equals("Dragon", StringComparison.OrdinalIgnoreCase) || prefab.Equals("GoblinKing", StringComparison.OrdinalIgnoreCase) || prefab.Equals("SeekerQueen", StringComparison.OrdinalIgnoreCase) || prefab.Equals("Fader", StringComparison.OrdinalIgnoreCase); } }