Decompiled source of CarveAndPlunder v0.1.0

plugins/CarveAndPlunder.dll

Decompiled 2 hours ago
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<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];
		}
	}
}