using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; using System.IO; 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 BepInEx.Logging; using HarmonyLib; using Jotunn.Extensions; 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.8.1", FrameworkDisplayName = ".NET Framework 4.8.1")] [assembly: AssemblyCompany("CarveAndPlunder")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+18ead6bcef193915788d7f793f0df01e83929a67")] [assembly: AssemblyProduct("CarveAndPlunder")] [assembly: AssemblyTitle("CarveAndPlunder")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.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 CarveAndPlunder { public enum CorpseKind { Passthrough, Animal, Humanoid } internal static class CorpseClassifier { private static HashSet _humanoids; private static HashSet _excluded; private static string _humanoidsRaw; private static string _excludedRaw; public static CorpseKind Classify(string rawName) { if (ModConfig.Enabled == null || !ModConfig.Enabled.Value) { return CorpseKind.Passthrough; } string item = Normalize(rawName); RefreshIfNeeded(); if (_excluded.Contains(item)) { return CorpseKind.Passthrough; } if (_humanoids.Contains(item)) { return CorpseKind.Humanoid; } return CorpseKind.Animal; } public static string Normalize(string rawName) { if (string.IsNullOrEmpty(rawName)) { return string.Empty; } string text = rawName.Replace("(Clone)", "").Trim(); int num = text.IndexOf("_ragdoll", StringComparison.OrdinalIgnoreCase); if (num >= 0) { text = text.Substring(0, num); } return text.Trim(); } private static void RefreshIfNeeded() { string text = ModConfig.HumanoidCreatures?.Value ?? string.Empty; string text2 = ModConfig.ExcludedCreatures?.Value ?? string.Empty; if (_humanoids == null || !(text == _humanoidsRaw) || !(text2 == _excludedRaw)) { _humanoids = Parse(text); _excluded = Parse(text2); _humanoidsRaw = text; _excludedRaw = text2; } } private static HashSet Parse(string csv) { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); string[] array = csv.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0) { hashSet.Add(text); } } return hashSet; } } public class CorpseInteraction : MonoBehaviour, Hoverable, Interactable { private GameObject _proxy; public Ragdoll Ragdoll { get; private set; } public ZNetView Nview { get; private set; } public CorpseKind Kind { get; private set; } public bool SpawnAllowed { get; private set; } private void Awake() { Ragdoll = ((Component)this).GetComponent(); Nview = ((Component)this).GetComponent(); Kind = CorpseClassifier.Classify(((Object)((Component)this).gameObject).name); if (Kind == CorpseKind.Passthrough) { SpawnAllowed = true; return; } CreateProxy(); ExtendLifetime(); } private void ExtendLifetime() { if (!((Object)(object)Ragdoll == (Object)null)) { float num = ((Kind == CorpseKind.Humanoid) ? ModConfig.HumanoidCorpseLifetime.Value : ModConfig.AnimalCorpseLifetime.Value); float num2 = Mathf.Max(1f, num); ((MonoBehaviour)Ragdoll).CancelInvoke("DestroyNow"); ((MonoBehaviour)Ragdoll).InvokeRepeating("DestroyNow", num2, 1f); ModLog.Diag($"lifetime set to {num2}s for '{((Object)((Component)this).gameObject).name}' (kind={Kind})"); } } public void SuspendDecay() { if ((Object)(object)Ragdoll != (Object)null) { ((MonoBehaviour)Ragdoll).CancelInvoke("DestroyNow"); } } public void ResumeDecay() { ExtendLifetime(); } public void ApplyExpiryLoot() { if ((Object)(object)Nview == (Object)null || !Nview.IsValid() || !Nview.IsOwner()) { return; } float num = Mathf.Clamp01(ModConfig.ExpiryLootFraction.Value); if (num <= 0f) { ModLog.Diag("expiry loot for '" + ((Object)((Component)this).gameObject).name + "': frac=0, dropping nothing"); return; } ZDO zDO = Nview.GetZDO(); if (zDO != null && num < 1f) { int num2 = zDO.GetInt(ZDOVars.s_drops, 0); for (int i = 0; i < num2; i++) { int num3 = zDO.GetInt("drop_amount" + i, 0); if (num3 > 0) { int num4 = Mathf.Max(1, Mathf.RoundToInt((float)num3 * num)); zDO.Set("drop_amount" + i, num4); } } } SpawnAllowed = true; ModLog.Diag($"expiry loot for '{((Object)((Component)this).gameObject).name}' kind={Kind} frac={num:0.00}"); } private void CreateProxy() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_0031: 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) _proxy = new GameObject("CarvePlunderInteract"); _proxy.layer = LayerMask.NameToLayer("piece_nonsolid"); _proxy.transform.position = BodyPosition(); _proxy.transform.SetParent(((Component)this).transform, true); SphereCollider obj = _proxy.AddComponent(); obj.radius = 0.8f; ((Collider)obj).isTrigger = false; _proxy.AddComponent().Target = this; ModLog.Diag($"proxy created for '{((Object)((Component)this).gameObject).name}' kind={Kind} at {BodyPosition()}"); } private void Update() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_proxy != (Object)null && !SpawnAllowed) { _proxy.transform.position = BodyPosition(); } } private Vector3 BodyPosition() { //IL_0020: 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) if (!((Object)(object)Ragdoll != (Object)null)) { return ((Component)this).transform.position; } return Ragdoll.GetAverageBodyPosition(); } public void AllowSpawnNow() { SpawnAllowed = true; } public string GetHoverName() { return Kind switch { CorpseKind.Humanoid => "Corpse", CorpseKind.Animal => "Carcass", _ => "", }; } public string GetHoverText() { if (Kind == CorpseKind.Passthrough || SpawnAllowed) { return ""; } string text = ((Kind != CorpseKind.Humanoid) ? (KnifeUtil.HasKnife(Player.m_localPlayer) ? "Skin" : "Dismantle") : "Loot"); string text2 = (ModConfig.HoldToWork.Value ? " (hold)" : ""); return Localization.instance.Localize("[$KEY_Use] " + text + text2); } public bool Interact(Humanoid user, bool hold, bool alt) { if (hold) { return false; } if (Kind == CorpseKind.Passthrough || SpawnAllowed) { return false; } Player val = (Player)(object)((user is Player) ? user : null); if (val == null || (Object)(object)val != (Object)(object)Player.m_localPlayer) { return false; } ModLog.Diag($"Interact on '{((Object)((Component)this).gameObject).name}' kind={Kind} -> begin session"); CorpseWorkSession.Begin(this, val); return true; } public bool UseItem(Humanoid user, ItemData item) { return false; } } public class CorpseInteractionProxy : MonoBehaviour, Hoverable, Interactable { public CorpseInteraction Target; public string GetHoverName() { if (!((Object)(object)Target != (Object)null)) { return ""; } return Target.GetHoverName(); } public string GetHoverText() { if (!((Object)(object)Target != (Object)null)) { return ""; } return Target.GetHoverText(); } public bool Interact(Humanoid user, bool hold, bool alt) { if ((Object)(object)Target != (Object)null) { return Target.Interact(user, hold, alt); } return false; } public bool UseItem(Humanoid user, ItemData item) { return false; } } [HarmonyPatch(typeof(Ragdoll), "Awake")] internal static class Ragdoll_Awake_Patch { private static void Postfix(Ragdoll __instance) { if ((Object)(object)((Component)__instance).GetComponent() == (Object)null) { CorpseInteraction corpseInteraction = ((Component)__instance).gameObject.AddComponent(); ModLog.Diag("Ragdoll.Awake -> attached to '" + ((Object)((Component)__instance).gameObject).name + "' " + $"kind={corpseInteraction.Kind} layer={LayerMask.LayerToName(((Component)__instance).gameObject.layer)}"); } } } [HarmonyPatch(typeof(Ragdoll), "DestroyNow")] internal static class Ragdoll_DestroyNow_Patch { private static void Prefix(Ragdoll __instance) { CorpseInteraction component = ((Component)__instance).GetComponent(); if (!((Object)(object)component == (Object)null) && component.Kind != CorpseKind.Passthrough && !component.SpawnAllowed) { component.ApplyExpiryLoot(); } } } [HarmonyPatch(typeof(Ragdoll), "SpawnLoot")] internal static class Ragdoll_SpawnLoot_Patch { private static bool Prefix(Ragdoll __instance) { CorpseInteraction component = ((Component)__instance).GetComponent(); if ((Object)(object)component == (Object)null) { ModLog.Diag("SpawnLoot '" + ((Object)((Component)__instance).gameObject).name + "': no CI -> vanilla"); return true; } if (component.Kind == CorpseKind.Passthrough) { ModLog.Diag("SpawnLoot '" + ((Object)((Component)__instance).gameObject).name + "': passthrough -> vanilla"); return true; } ModLog.Diag($"SpawnLoot '{((Object)((Component)__instance).gameObject).name}': kind={component.Kind} allowed={component.SpawnAllowed}"); return component.SpawnAllowed; } } internal static class CorpseWorkSession { private static bool _active; private static CorpseInteraction _corpse; private static Player _player; private static float _elapsed; private static float _duration; private static float _bonusExtra; private static bool _armedForPressCancel; private static bool _suppressRestart; public static bool Active => _active; public static void Begin(CorpseInteraction corpse, Player player) { if (!_active && !_suppressRestart && !((Object)(object)corpse == (Object)null) && !((Object)(object)player == (Object)null)) { ComputePlan(corpse, player, out _duration, out _bonusExtra); _corpse = corpse; _player = player; _elapsed = 0f; _armedForPressCancel = false; _active = true; corpse.SuspendDecay(); StartWorkPose(player); WorkProgressUI.Show(WorkLabel(corpse, player)); ModLog.Info($"[CarveAndPlunder] Begin {corpse.Kind} on '{((Object)((Component)corpse).gameObject).name}' " + $"dur={_duration:0.00}s extra={_bonusExtra:0.0}"); } } public static void Tick(float dt) { if (_suppressRestart && !ZInput.GetButton("Use")) { _suppressRestart = false; } if (!_active) { return; } if (!StillValid()) { Cancel(); return; } _elapsed += dt; WorkProgressUI.SetProgress(Mathf.Clamp01(_elapsed / _duration)); if (_elapsed >= _duration) { Complete(); } } private static bool StillValid() { //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_corpse == (Object)null || (Object)(object)_player == (Object)null) { return false; } if ((Object)(object)_corpse.Nview == (Object)null || !_corpse.Nview.IsValid()) { return false; } if ((Object)(object)_player != (Object)(object)Player.m_localPlayer || ((Character)_player).IsDead()) { return false; } if (ModConfig.HoldToWork.Value) { if (!ZInput.GetButton("Use")) { return false; } } else if (!_armedForPressCancel) { if (!ZInput.GetButton("Use")) { _armedForPressCancel = true; } } else if (ZInput.GetButtonDown("Use")) { _suppressRestart = true; return false; } if (((Character)_player).InAttack()) { return false; } if (Vector3.Distance(((Component)_player).transform.position, _corpse.Ragdoll.GetAverageBodyPosition()) > ModConfig.MaxWorkDistance.Value) { return false; } return true; } private static void Complete() { //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_0047: Unknown result type (might be due to invalid IL or missing references) CorpseInteraction corpse = _corpse; Player player = _player; float bonusExtra = _bonusExtra; CorpseKind kind = corpse.Kind; EndSession(); corpse.Nview.ClaimOwnership(); AddStoredDrops(corpse, bonusExtra); corpse.AllowSpawnNow(); Vector3 averageBodyPosition = corpse.Ragdoll.GetAverageBodyPosition(); corpse.Ragdoll.SpawnLoot(averageBodyPosition); if ((Object)(object)ZNetScene.instance != (Object)null) { ZNetScene.instance.Destroy(((Component)corpse).gameObject); } if (kind == CorpseKind.Humanoid) { LootingSkill.Raise(player, ModConfig.LootingXpPerLoot.Value); ((Character)player).Message((MessageType)1, "Looted the corpse", 0, (Sprite)null); } else { ((Character)player).RaiseSkill((SkillType)2, ModConfig.KnivesXpPerSkin.Value); ((Character)player).Message((MessageType)1, "Skinned the carcass", 0, (Sprite)null); } } private static void Cancel() { if ((Object)(object)_corpse != (Object)null && (Object)(object)_corpse.Nview != (Object)null && _corpse.Nview.IsValid()) { _corpse.ResumeDecay(); } EndSession(); } private static void EndSession() { if ((Object)(object)_player != (Object)null) { StopWorkPose(_player); } WorkProgressUI.Hide(); _active = false; _corpse = null; _player = null; _elapsed = 0f; _armedForPressCancel = false; } private static string WorkLabel(CorpseInteraction corpse, Player player) { if (corpse.Kind == CorpseKind.Humanoid) { return "Looting"; } if (!KnifeUtil.HasKnife(player)) { return "Dismantling"; } return "Skinning"; } private static void ComputePlan(CorpseInteraction corpse, Player player, out float duration, out float bonusExtra) { if (corpse.Kind == CorpseKind.Humanoid) { float factor = LootingSkill.GetFactor(player); duration = ModConfig.LootTimeBase.Value - factor * ModConfig.LootTimeReduction.Value; bonusExtra = factor * (float)ModConfig.LootExtraMax.Value; } else { float num = KnifeUtil.BestKnifeSharpness(player); if (num > 0f) { float skillFactor = ((Character)player).GetSkillFactor((SkillType)2); duration = ModConfig.SkinTimeBase.Value - skillFactor * ModConfig.SkinTimeReduction.Value; float num2 = Mathf.Clamp01(num / ModConfig.KnifeBonusReferenceDamage.Value); bonusExtra = num2 * (float)ModConfig.SkinExtraMax.Value; } else { duration = ModConfig.DismantleTime.Value; bonusExtra = 0f; } } duration = Mathf.Max(0.25f, duration); } private static void AddStoredDrops(CorpseInteraction corpse, float extra) { int num = Mathf.RoundToInt(extra); if (num <= 0) { return; } ZDO zDO = corpse.Nview.GetZDO(); if (zDO == null) { return; } int num2 = zDO.GetInt(ZDOVars.s_drops, 0); for (int i = 0; i < num2; i++) { int num3 = zDO.GetInt("drop_amount" + i, 0); if (num3 > 0) { zDO.Set("drop_amount" + i, num3 + num); } } } private static void StartWorkPose(Player player) { player.StartEmote("kneel", false); } private static void StopWorkPose(Player player) { if (((Character)player).InEmote()) { ((Character)player).StopEmote(); } } } internal static class KnifeUtil { public static bool HasKnife(Player player) { return BestKnifeSharpness(player) > 0f; } public static float BestKnifeSharpness(Player player) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Invalid comparison between Unknown and I4 //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return 0f; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null) { return 0f; } float num = 0f; foreach (ItemData allItem in inventory.GetAllItems()) { if (allItem?.m_shared != null && (int)allItem.m_shared.m_skillType == 2) { DamageTypes damage = allItem.GetDamage(); float num2 = damage.m_slash + damage.m_pierce + damage.m_blunt; if (num2 > num) { num = num2; } } } return num; } } internal static class LootingSkill { public const string InternalName = "CarvePlunder_Looting"; public const string DisplayName = "Looting"; private const string IconResourceSuffix = "Looting.png"; public static readonly SkillType SkillType = (SkillType)StringExtensionMethods.GetStableHashCode("CarvePlunder_Looting"); public static string LocalizationKey => "skill_" + ((object)SkillType/*cast due to .constrained prefix*/).ToString().ToLower(); public static SkillDef Def { get; private set; } public static void Init() { //IL_0008: 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_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown if (Def == null) { Def = new SkillDef { m_skill = SkillType, m_description = BuildDescription(), m_increseStep = 1f, m_icon = LoadEmbeddedIcon() }; } } private static string BuildDescription() { int value = ModConfig.LootExtraMax.Value; return "Looting humanoid corpses faster and for greater reward.\n" + $"At level 100, you recover up to {value} extra item(s) per drop."; } public static void RegisterLocalization(Localization loc) { if (loc != null) { loc.AddWord(LocalizationKey, "Looting"); } } public static void Raise(Player player, float amount) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)player == (Object)null) && !(amount <= 0f)) { ((Character)player).RaiseSkill(SkillType, amount); } } public static float GetFactor(Player player) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return 0f; } return ((Character)player).GetSkillFactor(SkillType); } private static Sprite LoadEmbeddedIcon() { //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) try { Assembly executingAssembly = Assembly.GetExecutingAssembly(); string text = null; string[] manifestResourceNames = executingAssembly.GetManifestResourceNames(); foreach (string text2 in manifestResourceNames) { if (text2.EndsWith("Looting.png", StringComparison.OrdinalIgnoreCase)) { text = text2; break; } } if (text == null) { ModLog.Diag("Looting icon resource not found; using fallback."); return MakeFallbackIcon(); } byte[] pngBytes; using (Stream stream = executingAssembly.GetManifestResourceStream(text)) { using MemoryStream memoryStream = new MemoryStream(); if (stream == null) { return MakeFallbackIcon(); } stream.CopyTo(memoryStream); pngBytes = memoryStream.ToArray(); } Texture2D val = DecodePngToTexture(pngBytes); if ((Object)(object)val == (Object)null) { ModLog.Diag("Looting icon failed to decode; using fallback."); return MakeFallbackIcon(); } ((Texture)val).wrapMode = (TextureWrapMode)1; ((Texture)val).filterMode = (FilterMode)1; return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f); } catch (Exception ex) { ModLog.Diag("Looting icon load error: " + ex.Message + "; using fallback."); return MakeFallbackIcon(); } } private static Texture2D DecodePngToTexture(byte[] pngBytes) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) using MemoryStream memoryStream = new MemoryStream(pngBytes); Bitmap val = new Bitmap((Stream)memoryStream); try { int width = ((Image)val).Width; int height = ((Image)val).Height; Texture2D val2 = new Texture2D(width, height, (TextureFormat)4, false); Color32[] array = (Color32[])(object)new Color32[width * height]; Rectangle rectangle = new Rectangle(0, 0, width, height); BitmapData val3 = val.LockBits(rectangle, (ImageLockMode)1, (PixelFormat)2498570); try { int stride = val3.Stride; byte[] array2 = new byte[stride]; for (int i = 0; i < height; i++) { Marshal.Copy(new IntPtr(val3.Scan0.ToInt64() + (long)i * (long)stride), array2, 0, stride); int num = (height - 1 - i) * width; for (int j = 0; j < width; j++) { int num2 = j * 4; array[num + j] = new Color32(array2[num2 + 2], array2[num2 + 1], array2[num2], array2[num2 + 3]); } } } finally { val.UnlockBits(val3); } val2.SetPixels32(array); val2.Apply(); return val2; } finally { ((IDisposable)val)?.Dispose(); } } private static Sprite MakeFallbackIcon() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_003d: 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_0056: Expected O, but got Unknown Texture2D val = new Texture2D(1, 1); val.SetPixel(0, 0, new Color(0.78f, 0.66f, 0.39f)); val.Apply(); return Sprite.Create(val, new Rect(0f, 0f, 1f, 1f), new Vector2(0.5f, 0.5f)); } } internal static class ModConfig { public static ConfigEntry Enabled; public static ConfigEntry EnableLogs; public static ConfigEntry HoldToWork; public static ConfigEntry MaxWorkDistance; public static ConfigEntry HumanoidCorpseLifetime; public static ConfigEntry AnimalCorpseLifetime; public static ConfigEntry ExpiryLootFraction; public static ConfigEntry SkinTimeBase; public static ConfigEntry SkinTimeReduction; public static ConfigEntry DismantleTime; public static ConfigEntry KnifeBonusReferenceDamage; public static ConfigEntry SkinExtraMax; public static ConfigEntry KnivesXpPerSkin; public static ConfigEntry LootTimeBase; public static ConfigEntry LootTimeReduction; public static ConfigEntry LootExtraMax; public static ConfigEntry LootingXpPerLoot; public static ConfigEntry HumanoidCreatures; public static ConfigEntry ExcludedCreatures; public static void Bind(ConfigFile cfg) { Enabled = ConfigFileExtensions.BindConfig(cfg, "General", "Enabled", true, "Master switch. When off, corpses drop loot the vanilla way.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); EnableLogs = ConfigFileExtensions.BindConfig(cfg, "General", "Enable Logs", false, "Print info/warning diagnostics to the BepInEx console and Player.log. Client-only.", false, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); HoldToWork = ConfigFileExtensions.BindConfig(cfg, "Interaction", "Hold To Work", true, "When on, hold the Use key to work a corpse (releasing cancels). When off, a single Use press starts the work and it continues on its own — press Use again, attack, or walk away to cancel. Client-only input preference.", false, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); MaxWorkDistance = ConfigFileExtensions.BindConfig(cfg, "Interaction", "Max Work Distance", 3f, "If you move further than this from the corpse while working, the action cancels.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); HumanoidCorpseLifetime = ConfigFileExtensions.BindConfig(cfg, "Interaction", "Humanoid Corpse Lifetime", 300f, "Seconds a humanoid corpse lingers before it rots away (un-worked loot is reduced to Expiry Loot Fraction). Replaces the short vanilla ragdoll despawn timer.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); AnimalCorpseLifetime = ConfigFileExtensions.BindConfig(cfg, "Interaction", "Animal Corpse Lifetime", 60f, "Seconds an animal carcass lingers before it rots away (un-worked loot is reduced to Expiry Loot Fraction). Replaces the short vanilla ragdoll despawn timer.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); ExpiryLootFraction = ConfigFileExtensions.BindConfig(cfg, "Interaction", "Expiry Loot Fraction", 0.5f, "Fraction of its loot a corpse still drops if it rots away un-worked (stacks are scaled by this, minimum 1 of each item). 0 = drop nothing, 1 = full loot.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); SkinTimeBase = ConfigFileExtensions.BindConfig(cfg, "Skinning", "Skin Time Base", 2f, "Seconds to skin an animal at Knives level 0 (with a knife).", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); SkinTimeReduction = ConfigFileExtensions.BindConfig(cfg, "Skinning", "Skin Time Reduction", 1f, "Seconds removed from skin time at Knives level 100 (linear).", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); DismantleTime = ConfigFileExtensions.BindConfig(cfg, "Skinning", "Dismantle Time", 4f, "Seconds to dismantle an animal bare-handed (no knife). Knives skill does not help.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); KnifeBonusReferenceDamage = ConfigFileExtensions.BindConfig(cfg, "Skinning", "Knife Bonus Reference Damage", 50f, "Knife physical damage (slash+pierce+blunt, incl. upgrades) that maps to the full skin bonus.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); SkinExtraMax = ConfigFileExtensions.BindConfig(cfg, "Skinning", "Skin Extra Max", 2, "Extra items added per drop stack when skinning with a knife at/above the reference damage. 0 = no bonus.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); KnivesXpPerSkin = ConfigFileExtensions.BindConfig(cfg, "Skinning", "Knives XP Per Skin", 1f, "Knives skill XP granted per successful skinning.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); LootTimeBase = ConfigFileExtensions.BindConfig(cfg, "Looting", "Loot Time Base", 2.5f, "Seconds to loot a humanoid corpse at Looting level 0.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); LootTimeReduction = ConfigFileExtensions.BindConfig(cfg, "Looting", "Loot Time Reduction", 1.5f, "Seconds removed from loot time at Looting level 100 (linear).", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); LootExtraMax = ConfigFileExtensions.BindConfig(cfg, "Looting", "Loot Extra Max", 2, "Extra items added per drop stack at Looting level 100. 0 = no bonus.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); LootingXpPerLoot = ConfigFileExtensions.BindConfig(cfg, "Looting", "Looting XP Per Loot", 1f, "Looting skill XP granted per successful loot.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); HumanoidCreatures = ConfigFileExtensions.BindConfig(cfg, "Classification", "Humanoid Creatures", "Greydwarf,Greydwarf_Elite,Greydwarf_Shaman,Skeleton,Skeleton_Poison,Draugr,Draugr_Elite,Draugr_Ranged,Goblin,GoblinBrute,GoblinShaman,Fuling,Cultist,Dverger", "Comma-separated creature base names treated as humanoid (Loot + Looting skill). Use the name without the (Clone)/_ragdoll suffix. Everything else with a ragdoll is an animal (Skin).", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); ExcludedCreatures = ConfigFileExtensions.BindConfig(cfg, "Classification", "Excluded Creatures", "Eikthyr,gd_king,Bonemass,Dragon,GoblinKing,SeekerQueen,Fader", "Comma-separated creatures that keep vanilla instant-drop (bosses by default).", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); } } internal static class ModLog { private static ManualLogSource _log; private static bool Verbose { get { if (ModConfig.EnableLogs != null) { return ModConfig.EnableLogs.Value; } return false; } } public static void Init(ManualLogSource log) { _log = log; } public static void Info(string msg) { if (Verbose) { ManualLogSource log = _log; if (log != null) { log.LogInfo((object)msg); } } } public static void Warn(string msg) { if (Verbose) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)msg); } } } public static void Error(string msg) { ManualLogSource log = _log; if (log != null) { log.LogError((object)msg); } } public static void Diag(string msg) { ManualLogSource log = _log; if (log != null) { log.LogInfo((object)("[diag] " + msg)); } } } [BepInPlugin("com.virtualbjorn.carveandplunder", "CarveAndPlunder", "0.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string PluginGUID = "com.virtualbjorn.carveandplunder"; public const string PluginName = "CarveAndPlunder"; public const string PluginVersion = "0.1.0"; private Harmony _harmony; public static Plugin Instance { get; private set; } private void Awake() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown Instance = this; ModConfig.Bind(((BaseUnityPlugin)this).Config); ModLog.Init(((BaseUnityPlugin)this).Logger); LootingSkill.Init(); _harmony = new Harmony("com.virtualbjorn.carveandplunder"); _harmony.PatchAll(typeof(Ragdoll_Awake_Patch)); _harmony.PatchAll(typeof(Ragdoll_SpawnLoot_Patch)); _harmony.PatchAll(typeof(Ragdoll_DestroyNow_Patch)); _harmony.PatchAll(typeof(Skills_GetSkillDef_Patch)); _harmony.PatchAll(typeof(Localization_SetupLanguage_Patch)); ((Component)this).gameObject.AddComponent(); int num = 0; foreach (MethodBase patchedMethod in _harmony.GetPatchedMethods()) { num++; ModLog.Diag("patched: " + patchedMethod.DeclaringType?.Name + "." + patchedMethod.Name); } ModLog.Diag($"total patched methods: {num}"); ((BaseUnityPlugin)this).Logger.LogInfo((object)"CarveAndPlunder v0.1.0 loaded."); } private void Update() { CorpseWorkSession.Tick(Time.deltaTime); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } [HarmonyPatch(typeof(Skills), "GetSkillDef")] internal static class Skills_GetSkillDef_Patch { private static void Postfix(SkillType type, ref SkillDef __result) { //IL_0004: 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) if (__result == null && type == LootingSkill.SkillType) { __result = LootingSkill.Def; } } } [HarmonyPatch(typeof(Localization), "SetupLanguage")] internal static class Localization_SetupLanguage_Patch { private static void Postfix(Localization __instance) { LootingSkill.RegisterLocalization(__instance); } } public class WorkProgressUI : MonoBehaviour { private static WorkProgressUI _instance; private bool _visible; private float _progress; private string _label = ""; private GameObject _root; private RectTransform _fillRt; private TMP_Text _text; private const float BarWidth = 360f; private const float BarHeight = 22f; private const float BottomOffset = 220f; public static void Show(string label) { if (!((Object)(object)_instance == (Object)null)) { _instance._label = label; _instance._progress = 0f; _instance._visible = true; _instance.Apply(); } } public static void SetProgress(float p) { if (!((Object)(object)_instance == (Object)null)) { _instance._progress = Mathf.Clamp01(p); _instance.Apply(); } } public static void Hide() { if (!((Object)(object)_instance == (Object)null)) { _instance._visible = false; _instance.Apply(); } } private void Awake() { _instance = this; } private void OnDestroy() { if ((Object)(object)_instance == (Object)(object)this) { _instance = null; } } private void Apply() { //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_004a: Unknown result type (might be due to invalid IL or missing references) EnsureBuilt(); if ((Object)(object)_root == (Object)null) { return; } if (_visible) { if ((Object)(object)_fillRt != (Object)null) { Vector2 anchorMax = _fillRt.anchorMax; anchorMax.x = _progress; _fillRt.anchorMax = anchorMax; } if ((Object)(object)_text != (Object)null) { _text.text = _label; } } _root.SetActive(_visible); } private void EnsureBuilt() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: 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_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0130: 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_015a: 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_017a: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Unknown result type (might be due to invalid IL or missing references) //IL_024d: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02c2: Unknown result type (might be due to invalid IL or missing references) //IL_02dc: Unknown result type (might be due to invalid IL or missing references) //IL_02ec: Unknown result type (might be due to invalid IL or missing references) //IL_02fc: Unknown result type (might be due to invalid IL or missing references) //IL_032b: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null)) { _root = new GameObject("CarveAndPlunder_WorkBar", new Type[2] { typeof(Canvas), typeof(CanvasScaler) }); _root.transform.SetParent(((Component)this).transform, false); Canvas component = _root.GetComponent(); component.renderMode = (RenderMode)0; component.sortingOrder = 5000; CanvasScaler component2 = _root.GetComponent(); component2.uiScaleMode = (ScaleMode)1; component2.referenceResolution = new Vector2(1920f, 1080f); component2.matchWidthOrHeight = 1f; RectTransform val = NewRect("Panel", _root.transform); Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(0.5f, 0f); val.anchorMax = val2; val.anchorMin = val2; val.pivot = new Vector2(0.5f, 0f); val.anchoredPosition = new Vector2(0f, 220f); val.sizeDelta = new Vector2(360f, 50f); RectTransform val3 = NewRect("Label", (Transform)(object)val); val3.anchorMin = new Vector2(0f, 1f); val3.anchorMax = new Vector2(1f, 1f); val3.pivot = new Vector2(0.5f, 1f); val3.anchoredPosition = Vector2.zero; val3.sizeDelta = new Vector2(0f, 26f); _text = (TMP_Text)(object)((Component)val3).gameObject.AddComponent(); _text.alignment = (TextAlignmentOptions)514; _text.fontSize = 20f; ((Graphic)_text).color = Color.white; ((Graphic)_text).raycastTarget = false; TMP_FontAsset val4 = FindFont(); if ((Object)(object)val4 != (Object)null) { _text.font = val4; } RectTransform val5 = NewRect("Track", (Transform)(object)val); val5.anchorMin = new Vector2(0f, 0f); val5.anchorMax = new Vector2(1f, 0f); val5.pivot = new Vector2(0.5f, 0f); val5.anchoredPosition = Vector2.zero; val5.sizeDelta = new Vector2(0f, 22f); Image obj = ((Component)val5).gameObject.AddComponent(); ((Graphic)obj).color = new Color(0f, 0f, 0f, 0.7f); ((Graphic)obj).raycastTarget = false; _fillRt = NewRect("Fill", (Transform)(object)val5); _fillRt.anchorMin = new Vector2(0f, 0f); _fillRt.anchorMax = new Vector2(0f, 1f); _fillRt.pivot = new Vector2(0f, 0.5f); _fillRt.offsetMin = Vector2.zero; _fillRt.offsetMax = Vector2.zero; Image obj2 = ((Component)_fillRt).gameObject.AddComponent(); ((Graphic)obj2).color = new Color(0.85f, 0.7f, 0.2f, 1f); ((Graphic)obj2).raycastTarget = false; _root.SetActive(false); } } private static RectTransform NewRect(string name, Transform parent) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) RectTransform component = new GameObject(name, new Type[1] { typeof(RectTransform) }).GetComponent(); ((Transform)component).SetParent(parent, false); return component; } private static TMP_FontAsset FindFont() { if ((Object)(object)TMP_Settings.defaultFontAsset != (Object)null) { return TMP_Settings.defaultFontAsset; } TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll(); if (array == null || array.Length == 0) { return null; } return array[0]; } } }