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 CarveAndPlunder v0.1.0
plugins/CarveAndPlunder.dll
Decompiled 2 hours agousing 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<string> _humanoids; private static HashSet<string> _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<string> Parse(string csv) { HashSet<string> hashSet = new HashSet<string>(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<Ragdoll>(); Nview = ((Component)this).GetComponent<ZNetView>(); 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<SphereCollider>(); obj.radius = 0.8f; ((Collider)obj).isTrigger = false; _proxy.AddComponent<CorpseInteractionProxy>().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 ? " <color=grey>(hold)</color>" : ""); return Localization.instance.Localize("[<color=yellow><b>$KEY_Use</b></color>] " + 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<CorpseInteraction>() == (Object)null) { CorpseInteraction corpseInteraction = ((Component)__instance).gameObject.AddComponent<CorpseInteraction>(); 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<CorpseInteraction>(); 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<CorpseInteraction>(); 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<bool> Enabled; public static ConfigEntry<bool> EnableLogs; public static ConfigEntry<bool> HoldToWork; public static ConfigEntry<float> MaxWorkDistance; public static ConfigEntry<float> HumanoidCorpseLifetime; public static ConfigEntry<float> AnimalCorpseLifetime; public static ConfigEntry<float> ExpiryLootFraction; public static ConfigEntry<float> SkinTimeBase; public static ConfigEntry<float> SkinTimeReduction; public static ConfigEntry<float> DismantleTime; public static ConfigEntry<float> KnifeBonusReferenceDamage; public static ConfigEntry<int> SkinExtraMax; public static ConfigEntry<float> KnivesXpPerSkin; public static ConfigEntry<float> LootTimeBase; public static ConfigEntry<float> LootTimeReduction; public static ConfigEntry<int> LootExtraMax; public static ConfigEntry<float> LootingXpPerLoot; public static ConfigEntry<string> HumanoidCreatures; public static ConfigEntry<string> ExcludedCreatures; public static void Bind(ConfigFile cfg) { Enabled = ConfigFileExtensions.BindConfig<bool>(cfg, "General", "Enabled", true, "Master switch. When off, corpses drop loot the vanilla way.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); EnableLogs = ConfigFileExtensions.BindConfig<bool>(cfg, "General", "Enable Logs", false, "Print info/warning diagnostics to the BepInEx console and Player.log. Client-only.", false, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); HoldToWork = ConfigFileExtensions.BindConfig<bool>(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<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); MaxWorkDistance = ConfigFileExtensions.BindConfig<float>(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<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); HumanoidCorpseLifetime = ConfigFileExtensions.BindConfig<float>(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<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); AnimalCorpseLifetime = ConfigFileExtensions.BindConfig<float>(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<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); ExpiryLootFraction = ConfigFileExtensions.BindConfig<float>(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<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); SkinTimeBase = ConfigFileExtensions.BindConfig<float>(cfg, "Skinning", "Skin Time Base", 2f, "Seconds to skin an animal at Knives level 0 (with a knife).", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); SkinTimeReduction = ConfigFileExtensions.BindConfig<float>(cfg, "Skinning", "Skin Time Reduction", 1f, "Seconds removed from skin time at Knives level 100 (linear).", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); DismantleTime = ConfigFileExtensions.BindConfig<float>(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<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); KnifeBonusReferenceDamage = ConfigFileExtensions.BindConfig<float>(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<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); SkinExtraMax = ConfigFileExtensions.BindConfig<int>(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<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); KnivesXpPerSkin = ConfigFileExtensions.BindConfig<float>(cfg, "Skinning", "Knives XP Per Skin", 1f, "Knives skill XP granted per successful skinning.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); LootTimeBase = ConfigFileExtensions.BindConfig<float>(cfg, "Looting", "Loot Time Base", 2.5f, "Seconds to loot a humanoid corpse at Looting level 0.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); LootTimeReduction = ConfigFileExtensions.BindConfig<float>(cfg, "Looting", "Loot Time Reduction", 1.5f, "Seconds removed from loot time at Looting level 100 (linear).", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); LootExtraMax = ConfigFileExtensions.BindConfig<int>(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<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); LootingXpPerLoot = ConfigFileExtensions.BindConfig<float>(cfg, "Looting", "Looting XP Per Loot", 1f, "Looting skill XP granted per successful loot.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); HumanoidCreatures = ConfigFileExtensions.BindConfig<string>(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<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); ExcludedCreatures = ConfigFileExtensions.BindConfig<string>(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<ConfigEntryBase>)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<WorkProgressUI>(); 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<Canvas>(); component.renderMode = (RenderMode)0; component.sortingOrder = 5000; CanvasScaler component2 = _root.GetComponent<CanvasScaler>(); 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<TextMeshProUGUI>(); _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<Image>(); ((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<Image>(); ((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<RectTransform>(); ((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<TMP_FontAsset>(); if (array == null || array.Length == 0) { return null; } return array[0]; } } }