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 CorpseHarvest v1.1.0
CorpseHarvest.dll
Decompiled 2 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; 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 Jotunn.Managers; using Jotunn.Utils; 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("CorpseHarvest")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("CorpseHarvest")] [assembly: AssemblyTitle("CorpseHarvest")] [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 CorpseHarvest { internal static class BeastBonePileFactory { private struct BeastEntry { public GameObject Prefab; public float CorrectionFactor; public string SourceName; } private const string AsksvinSourceName = "asksvin_carrion2"; private const string LoxSourceName = "lox_ribs"; private static bool _initialized; private static readonly List<BeastEntry> _entries = new List<BeastEntry>(); public static bool HasAny => _entries.Count > 0; public static void Initialize() { if (_initialized) { return; } _initialized = true; if ((Object)(object)BonePileFactory.SourcePickable == (Object)null || BonePileFactory.SourceHorizontalWidth <= 0f) { ModLog.Warn("BeastBonePileFactory: human bone pile source wasn't available/measured — animal bones pile stays disabled (nothing to size/pick-effect against)."); return; } TryRegister("asksvin_carrion2", "CorpseHarvest_BeastBonePile_Asksvin"); TryRegister("lox_ribs", "CorpseHarvest_BeastBonePile_Lox"); if (_entries.Count == 0) { ModLog.Warn("BeastBonePileFactory: neither 'asksvin_carrion2' nor 'lox_ribs' could be found/cloned — animal bones pile stays disabled, Bone Pile Beast Creatures falls back to the human remains pile."); } } private static void TryRegister(string sourceName, string clonedName) { GameObject prefab = PrefabManager.Instance.GetPrefab(sourceName); if ((Object)(object)prefab == (Object)null) { ModLog.Diag("BeastBonePileFactory: source '" + sourceName + "' not found — skipping just this one."); return; } float num = BonePileCloneUtil.MeasureHorizontalWidth(prefab); if (num <= 0f) { ModLog.Warn("BeastBonePileFactory: source '" + sourceName + "' has no measurable renderer bounds — skipping just this one."); return; } GameObject val = PrefabManager.Instance.CreateClonedPrefab(clonedName, prefab); if ((Object)(object)val == (Object)null) { ModLog.Warn("BeastBonePileFactory: Jotunn CreateClonedPrefab returned null for '" + sourceName + "' — skipping just this one."); return; } BonePileCloneUtil.StripToVisualAndCollision(val, "BeastBonePileFactory(" + sourceName + ")"); BonePileCloneUtil.EnsureZNetView(val, ((Component)BonePileFactory.SourcePickable).GetComponent<ZNetView>()); Pickable val2 = val.GetComponent<Pickable>(); if ((Object)(object)val2 == (Object)null) { val2 = val.AddComponent<Pickable>(); } BonePileCloneUtil.SetupBonePickable(val2, BonePileFactory.SourcePickable); val.AddComponent<BeastBonePileMarker>(); val.AddComponent<BonePile>(); float num2 = BonePileFactory.SourceHorizontalWidth / num; PrefabManager.Instance.AddPrefab(val); _entries.Add(new BeastEntry { Prefab = val, CorrectionFactor = num2, SourceName = sourceName }); ModLog.Diag("BeastBonePileFactory: cloned '" + sourceName + "' -> '" + clonedName + "' and registered via " + $"Jotunn AddPrefab. sourceWidth={num:0.00} humanSourceWidth=" + $"{BonePileFactory.SourceHorizontalWidth:0.00} correctionFactor={num2:0.00}, " + "Pickable item='" + Utils.GetPrefabName(val2.m_itemPrefab) + "' amount driven by Bone Pile Bone Fragments (shared config with the human pile, applied per-spawn by BonePile)."); } public static GameObject PickRandom(out float correctionFactor, out string sourceName) { correctionFactor = 1f; sourceName = null; if (_entries.Count == 0) { return null; } BeastEntry beastEntry = _entries[Random.Range(0, _entries.Count)]; correctionFactor = beastEntry.CorrectionFactor; sourceName = beastEntry.SourceName; return beastEntry.Prefab; } } internal sealed class BeastBonePileMarker : MonoBehaviour { } public class BonePile : MonoBehaviour { private ZNetView _nview; private Pickable _pickable; private bool _appliedFromZdo; private const string SpawnTimeKey = "chbp_spawntime"; private const string ScaleKey = "chbp_scale"; private const string FragmentsKey = "chbp_fragments"; private bool _expireScheduled; private void Awake() { _nview = ((Component)this).GetComponent<ZNetView>(); _pickable = ((Component)this).GetComponent<Pickable>(); } public void Setup(float scale, int fragments) { if (!((Object)(object)_nview == (Object)null) && _nview.IsValid() && _nview.IsOwner()) { ZDO zDO = _nview.GetZDO(); if (zDO != null) { zDO.Set("chbp_spawntime", (long)(((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTimeSeconds() : 0.0)); zDO.Set("chbp_scale", scale); zDO.Set("chbp_fragments", fragments); ApplyFromZdo(zDO); } } } private void Update() { if ((Object)(object)_nview == (Object)null || !_nview.IsValid()) { return; } ZDO zDO = _nview.GetZDO(); if (zDO == null) { return; } if (!_appliedFromZdo) { if (zDO.GetLong("chbp_spawntime", -1L) < 0) { return; } ApplyFromZdo(zDO); } CheckExpire(zDO); } private void ApplyFromZdo(ZDO zdo) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) float num = zdo.GetFloat("chbp_scale", 1f); ((Component)this).transform.localScale = Vector3.one * num; int num2 = Mathf.Max(0, zdo.GetInt("chbp_fragments", ModConfig.BonePileBoneFragments.Value)); if ((Object)(object)_pickable != (Object)null) { _pickable.m_dontScale = true; _pickable.m_amount = num2; _pickable.m_minAmountScaled = num2; if (num2 <= 0) { _pickable.SetEnabled(0); } } _appliedFromZdo = true; } private void CheckExpire(ZDO zdo) { long num = zdo.GetLong("chbp_spawntime", -1L); if (num >= 0) { double num2 = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTimeSeconds() : 0.0); float num3 = ModConfig.BonePileLifetime.Value - (float)(num2 - (double)num); if (num3 <= 0f) { ModLog.Diag("'" + ((Object)((Component)this).gameObject).name + "' bone pile expired -> despawn"); Despawn(); } else if (!_expireScheduled) { _expireScheduled = true; ((MonoBehaviour)this).CancelInvoke("Expire"); ((MonoBehaviour)this).Invoke("Expire", num3); } } } private void Expire() { if (!((Object)(object)this == (Object)null) && !((Object)(object)((Component)this).gameObject == (Object)null)) { ModLog.Diag("'" + ((Object)((Component)this).gameObject).name + "' bone pile expired -> despawn"); Despawn(); } } private void Despawn() { if ((Object)(object)_nview != (Object)null && _nview.IsValid() && !_nview.IsOwner()) { _nview.ClaimOwnership(); } if ((Object)(object)ZNetScene.instance != (Object)null) { ZNetScene.instance.Destroy(((Component)this).gameObject); } } } internal static class BonePileCloneUtil { private static int _pieceNonSolidLayer = -1; private static int PieceNonSolidLayer { get { if (_pieceNonSolidLayer < 0) { return _pieceNonSolidLayer = LayerMask.NameToLayer("piece_nonsolid"); } return _pieceNonSolidLayer; } } public static void StripToVisualAndCollision(GameObject go, string diagLabel) { bool flag = false; Component[] componentsInChildren = go.GetComponentsInChildren<Component>(true); foreach (Component val in componentsInChildren) { if ((Object)(object)val == (Object)null || val is Transform || val is ZNetView || val is Pickable || val is Renderer || val is MeshFilter || val is Animator || val is LODGroup) { continue; } Collider val2 = (Collider)(object)((val is Collider) ? val : null); if (val2 != null) { if (!flag) { flag = true; ModLog.Diag(diagLabel + ": original collider layer='" + LayerMask.LayerToName(((Component)val2).gameObject.layer) + "' -> forcing to 'piece_nonsolid' (aim/E-key interact still work there; see StripToVisualAndCollision comment for what is/isn't decompile-confirmed)"); } ((Component)val2).gameObject.layer = PieceNonSolidLayer; val2.isTrigger = false; } else { ModLog.Diag(diagLabel + ": stripping component '" + ((object)val).GetType().Name + "' from clone"); Object.Destroy((Object)(object)val); } } } public static void SetupBonePickable(Pickable p, Pickable referenceSource) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)p == (Object)null) && !((Object)(object)referenceSource == (Object)null)) { p.m_itemPrefab = referenceSource.m_itemPrefab; p.m_pickEffector = referenceSource.m_pickEffector; p.m_extraDrops = null; p.m_pickRaiseSkill = (SkillType)0; p.m_bonusYieldAmount = 0; p.m_respawnTimeMinutes = 0f; p.m_hideWhenPicked = null; } } public static ZNetView EnsureZNetView(GameObject go, ZNetView referenceSource) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) ZNetView val = go.GetComponent<ZNetView>(); if ((Object)(object)val == (Object)null) { val = go.AddComponent<ZNetView>(); } if ((Object)(object)referenceSource != (Object)null) { val.m_persistent = referenceSource.m_persistent; val.m_distant = referenceSource.m_distant; val.m_type = referenceSource.m_type; val.m_syncInitialScale = referenceSource.m_syncInitialScale; } return val; } public static float MeasureHorizontalWidth(GameObject source) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)source == (Object)null) { return 0f; } GameObject val = Object.Instantiate<GameObject>(source, Vector3.zero, Quaternion.identity); try { Renderer[] componentsInChildren = val.GetComponentsInChildren<Renderer>(true); if (componentsInChildren.Length == 0) { return 0f; } Bounds bounds = componentsInChildren[0].bounds; for (int i = 1; i < componentsInChildren.Length; i++) { ((Bounds)(ref bounds)).Encapsulate(componentsInChildren[i].bounds); } return Mathf.Max(((Bounds)(ref bounds)).size.x, ((Bounds)(ref bounds)).size.z); } finally { Object.Destroy((Object)(object)val); } } } internal static class BonePileFactory { public const string ClonedPrefabName = "CorpseHarvest_BonePile"; private const string BoneFragmentsItemName = "BoneFragments"; private static readonly string[] PreferredSourceNames = new string[4] { "Pickable_ForestCryptRemains01", "Pickable_ForestCryptRemains02", "Pickable_ForestCryptRemains03", "Pickable_ForestCryptRemains04" }; private static bool _initialized; private static GameObject _clonedPrefab; private static string _sourceName; public static GameObject ClonedPrefab => _clonedPrefab; public static string SourceName => _sourceName; public static float SourceHorizontalWidth { get; private set; } public static Pickable SourcePickable { get; private set; } public static void Initialize() { if (_initialized) { return; } _initialized = true; List<string> candidates; GameObject val = FindSourcePickable(out candidates); ModLog.Diag($"BonePileFactory: [OnVanillaPrefabsAvailable] {candidates.Count} candidate prefab(s) " + "with a Pickable granting exactly 'BoneFragments': " + string.Join(", ", candidates)); if ((Object)(object)val == (Object)null) { if (candidates.Count > 0) { ModLog.Warn("BonePileFactory: none of the known vanilla crypt-remains prefabs (" + string.Join(", ", PreferredSourceNames) + ") were found among the " + string.Format("{0} candidate(s) granting '{1}' ", candidates.Count, "BoneFragments") + "(" + string.Join(", ", candidates) + ") — Bone Pile feature stays disabled. Not substituting another mod's prefab."); } else { ModLog.Warn("BonePileFactory: no Pickable grants exactly 'BoneFragments' — Bone Pile feature stays disabled."); } return; } _sourceName = Utils.GetPrefabName(val); SourcePickable = val.GetComponent<Pickable>(); SourceHorizontalWidth = BonePileCloneUtil.MeasureHorizontalWidth(val); GameObject val2 = PrefabManager.Instance.CreateClonedPrefab("CorpseHarvest_BonePile", val); if ((Object)(object)val2 == (Object)null) { ModLog.Warn("BonePileFactory: Jotunn CreateClonedPrefab returned null — Bone Pile stays disabled."); return; } BonePileCloneUtil.StripToVisualAndCollision(val2, "BonePileFactory"); BonePileCloneUtil.SetupBonePickable(val2.GetComponent<Pickable>(), SourcePickable); val2.AddComponent<BonePile>(); PrefabManager.Instance.AddPrefab(val2); _clonedPrefab = val2; ModLog.Diag("BonePileFactory: [OnVanillaPrefabsAvailable] cloned '" + _sourceName + "' -> 'CorpseHarvest_BonePile' and registered via Jotunn AddPrefab (auto-reappears in " + $"ZNetScene on every world load from here on). sourceWidth={SourceHorizontalWidth:0.00}"); } private static GameObject FindSourcePickable(out List<string> candidates) { List<string> list = new List<string>(); Dictionary<string, Pickable> dictionary = new Dictionary<string, Pickable>(StringComparer.Ordinal); Dictionary<string, Object> prefabs = Cache.GetPrefabs(typeof(Pickable)); if (prefabs != null) { foreach (KeyValuePair<string, Object> item in prefabs) { Object value = item.Value; Pickable val = (Pickable)(object)((value is Pickable) ? value : null); if (val != null && !((Object)(object)val.m_itemPrefab == (Object)null) && string.Equals(Utils.GetPrefabName(val.m_itemPrefab), "BoneFragments", StringComparison.Ordinal)) { list.Add(item.Key); dictionary[item.Key] = val; } } } list.Sort(StringComparer.OrdinalIgnoreCase); candidates = list; string[] preferredSourceNames = PreferredSourceNames; foreach (string key in preferredSourceNames) { if (dictionary.TryGetValue(key, out var value2) && (Object)(object)value2 != (Object)null) { return ((Component)value2).gameObject; } } return null; } } internal static class BonePileSpawner { private static HashSet<string> _excluded; private static string _excludedRaw; private static HashSet<string> _beast; private static string _beastRaw; private const float RaycastUpOffset = 1f; private const float RaycastDownDistance = 10f; private const float TerrainFallbackMaxDelta = 3f; private const float MaxTiltDegrees = 20f; private static int _groundLayerMask = -1; private static int GroundLayerMask { get { if (_groundLayerMask < 0) { return _groundLayerMask = LayerMask.GetMask(new string[5] { "Default", "static_solid", "Default_small", "piece", "terrain" }); } return _groundLayerMask; } } public static void TrySpawn(CorpseInteraction corpse, Vector3 position, bool skipLiquidOrShip) { //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01ef: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_024e: Unknown result type (might be due to invalid IL or missing references) //IL_02c4: Unknown result type (might be due to invalid IL or missing references) //IL_02c6: Unknown result type (might be due to invalid IL or missing references) //IL_02cb: Unknown result type (might be due to invalid IL or missing references) //IL_02d0: Unknown result type (might be due to invalid IL or missing references) //IL_02d2: Unknown result type (might be due to invalid IL or missing references) //IL_02d4: Unknown result type (might be due to invalid IL or missing references) //IL_02d8: Unknown result type (might be due to invalid IL or missing references) //IL_02dd: Unknown result type (might be due to invalid IL or missing references) //IL_02e2: Unknown result type (might be due to invalid IL or missing references) //IL_02eb: Unknown result type (might be due to invalid IL or missing references) //IL_02ed: Unknown result type (might be due to invalid IL or missing references) //IL_025c: Unknown result type (might be due to invalid IL or missing references) //IL_027b: Unknown result type (might be due to invalid IL or missing references) //IL_0287: Unknown result type (might be due to invalid IL or missing references) //IL_03d9: Unknown result type (might be due to invalid IL or missing references) //IL_03e7: Unknown result type (might be due to invalid IL or missing references) //IL_031f: Unknown result type (might be due to invalid IL or missing references) //IL_0328: Unknown result type (might be due to invalid IL or missing references) if (ModConfig.BonePileEnabled == null || !ModConfig.BonePileEnabled.Value) { return; } if (skipLiquidOrShip) { ModLog.Diag("BonePile skipped for '" + ((Object)((Component)corpse).gameObject).name + "': corpse was in liquid or on a ship"); return; } string monsterName = corpse.MonsterName; if (IsExcluded(monsterName)) { ModLog.Diag("BonePile skipped for '" + ((Object)((Component)corpse).gameObject).name + "': '" + monsterName + "' is in Bone Pile Excluded Creatures"); return; } bool num = IsBeastCreature(monsterName) && BeastBonePileFactory.HasAny; float correctionFactor = 1f; float num2 = 1f; GameObject val; string text; string text2; if (num) { val = BeastBonePileFactory.PickRandom(out correctionFactor, out var sourceName); num2 = ((ModConfig.BeastBonePileSize != null) ? Mathf.Clamp(ModConfig.BeastBonePileSize.Value, 0.25f, 4f) : 1f); text = "beast"; text2 = sourceName; } else { val = BonePileFactory.ClonedPrefab; text = "human"; text2 = BonePileFactory.SourceName; } if ((Object)(object)val == (Object)null) { ModLog.Diag("BonePile skipped for '" + ((Object)((Component)corpse).gameObject).name + "': no cloned bone pile prefab available (kind=" + text + ", source='" + (text2 ?? "none") + "')"); } else { if (!TryFindGroundPlacement(corpse, position, out var groundPoint, out var groundNormal, out var source)) { return; } int num3 = Mathf.Max(0, ModConfig.BonePileBoneFragments.Value); float baseScale; float largeMult; float smallMult; float num4 = ComputeBonePileScale(corpse.BaseHealth, out baseScale, out largeMult, out smallMult) * correctionFactor * num2; float num5 = Vector3.Angle(Vector3.up, groundNormal); Quaternion val2 = Quaternion.FromToRotation(Vector3.up, groundNormal); Quaternion val3 = ((num5 > 20f) ? Quaternion.RotateTowards(Quaternion.identity, val2, 20f) : val2); float num6 = Mathf.Min(num5, 20f); float num7 = Random.Range(0f, 360f); Quaternion val4 = Quaternion.Euler(0f, num7, 0f); Quaternion val5 = val3 * val4; GameObject val6 = Object.Instantiate<GameObject>(val, groundPoint, val4); if ((Object)(object)val6 == (Object)null) { return; } BonePile component = val6.GetComponent<BonePile>(); if ((Object)(object)component != (Object)null) { component.Setup(num4, num3); } float num8 = 0f; Renderer[] componentsInChildren = val6.GetComponentsInChildren<Renderer>(true); if (componentsInChildren.Length != 0) { Bounds bounds = componentsInChildren[0].bounds; for (int i = 1; i < componentsInChildren.Length; i++) { ((Bounds)(ref bounds)).Encapsulate(componentsInChildren[i].bounds); } num8 = val6.transform.position.y - ((Bounds)(ref bounds)).min.y; } float num9 = ((ModConfig.BonePileHeight != null) ? Mathf.Clamp(ModConfig.BonePileHeight.Value, -1f, 1f) : 0f); float num10 = num8 * num9; Vector3 val7 = val5 * Vector3.up; Vector3 val8 = groundPoint + val7 * num10; val6.transform.SetPositionAndRotation(val8, val5); ZNetView component2 = val6.GetComponent<ZNetView>(); if ((Object)(object)component2 != (Object)null && component2.IsValid()) { ZDO zDO = component2.GetZDO(); if (zDO != null) { zDO.SetPosition(val8); zDO.SetRotation(val5); } } ModLog.Diag("BonePile spawned for '" + ((Object)((Component)corpse).gameObject).name + "': kind=" + text + " source='" + text2 + "' " + $"fragments={num3} baseScale={baseScale:0.00} largeMult={largeMult:0.00} smallMult={smallMult:0.00} " + $"beastCorrection={correctionFactor:0.00} beastExtraMult={num2:0.00} scale={num4:0.00} " + $"corpseY={position.y:0.00} groundY={groundPoint.y:0.00} method={source} depth={num8:0.00} " + $"height={num9:0.00} liftAmount={num10:0.00} tiltDeg={num6:0.0}(raw={num5:0.0}) " + $"yaw={num7:0}"); } } private static bool TryFindGroundPlacement(CorpseInteraction corpse, Vector3 corpsePosition, out Vector3 groundPoint, out Vector3 groundNormal, out string source) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //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_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0028: 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_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: 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_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) groundPoint = corpsePosition; groundNormal = Vector3.up; source = "none"; if (TryRaycastGround(corpsePosition, out var point, out var normal)) { groundPoint = point; groundNormal = normal; source = "raycast"; } else { float num = default(float); if (!((Object)(object)ZoneSystem.instance != (Object)null) || !ZoneSystem.instance.GetGroundHeight(corpsePosition, ref num)) { ModLog.Diag("BonePile skipped for '" + ((Object)((Component)corpse).gameObject).name + "': no ground found (raycast and terrain fallback both failed)"); return false; } float num2 = Mathf.Abs(num - corpsePosition.y); if (num2 > 3f) { ModLog.Diag("BonePile skipped for '" + ((Object)((Component)corpse).gameObject).name + "': raycast found no ground and " + $"terrain fallback differs too much (corpseY={corpsePosition.y:0.00} " + $"terrainY={num:0.00} delta={num2:0.00} > {3f:0.00})"); return false; } groundPoint = new Vector3(corpsePosition.x, num, corpsePosition.z); groundNormal = Vector3.up; source = "terrain-fallback"; } float liquidLevel = Floating.GetLiquidLevel(groundPoint, 1f, (LiquidType)10); if (groundPoint.y < liquidLevel) { ModLog.Diag($"BonePile skipped for '{((Object)((Component)corpse).gameObject).name}': found y={groundPoint.y:0.00} " + $"({source}) is below liquid level {liquidLevel:0.00}"); return false; } return true; } private static bool TryRaycastGround(Vector3 corpsePosition, out Vector3 point, out Vector3 normal) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) point = Vector3.zero; normal = Vector3.up; RaycastHit[] array = Physics.RaycastAll(corpsePosition + Vector3.up * 1f, Vector3.down, 10f, GroundLayerMask); if (array.Length == 0) { return false; } Array.Sort(array, (RaycastHit a, RaycastHit b) => ((RaycastHit)(ref a)).distance.CompareTo(((RaycastHit)(ref b)).distance)); RaycastHit[] array2 = array; for (int num = 0; num < array2.Length; num++) { RaycastHit val = array2[num]; if (!((Object)(object)((RaycastHit)(ref val)).collider != (Object)null) || !((Object)(object)((RaycastHit)(ref val)).collider.attachedRigidbody != (Object)null)) { point = ((RaycastHit)(ref val)).point; normal = ((RaycastHit)(ref val)).normal; return true; } } return false; } public static float ComputeBonePileScale(float baseHealth) { float baseScale; float largeMult; float smallMult; return ComputeBonePileScale(baseHealth, out baseScale, out largeMult, out smallMult); } private static float ComputeBonePileScale(float baseHealth, out float baseScale, out float largeMult, out float smallMult) { baseScale = SizeScaleFor(baseHealth); float reference = ((ModConfig.SizeReferenceHealth != null) ? Mathf.Max(1f, ModConfig.SizeReferenceHealth.Value) : 200f); largeMult = ComputeLargeCreatureMultiplier(baseHealth, reference); smallMult = ComputeSmallCreatureMultiplier(baseHealth, reference); return Mathf.Min(baseScale * largeMult * smallMult, 4f); } private static float ComputeLargeCreatureMultiplier(float baseHealth, float reference) { if (baseHealth <= 0f) { return 1f; } float num = ((ModConfig.BonePileLargeCreatureSize != null) ? Mathf.Clamp(ModConfig.BonePileLargeCreatureSize.Value, 1f, 4f) : 2f); float num2 = reference * 3f; if (baseHealth <= reference) { return 1f; } if (baseHealth >= num2) { return num; } float num3 = (baseHealth - reference) / (num2 - reference); return Mathf.Lerp(1f, num, num3); } private static float ComputeSmallCreatureMultiplier(float baseHealth, float reference) { if (baseHealth <= 0f) { return 1f; } float num = ((ModConfig.BonePileSmallCreatureSize != null) ? Mathf.Clamp(ModConfig.BonePileSmallCreatureSize.Value, 0.5f, 3f) : 1.5f); float num2 = reference * 3f; if (baseHealth <= reference) { return num; } if (baseHealth >= num2) { return 1f; } float num3 = (baseHealth - reference) / (num2 - reference); return Mathf.Lerp(num, 1f, num3); } private static float SizeScaleFor(float baseHealth) { if (baseHealth <= 0f) { return 1f; } float num = ((ModConfig.SizeReferenceHealth != null) ? Mathf.Max(1f, ModConfig.SizeReferenceHealth.Value) : 200f); return Mathf.Clamp(Mathf.Sqrt(baseHealth / num), 0.6f, 1.8f); } public static bool IsExcluded(string monsterName) { if (string.IsNullOrEmpty(monsterName)) { return false; } RefreshIfNeeded(); return _excluded.Contains(monsterName); } private static void RefreshIfNeeded() { string text = ModConfig.BonePileExcludedCreatures?.Value ?? string.Empty; if (_excluded != null && text == _excludedRaw) { return; } HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); string[] array = text.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length > 0) { hashSet.Add(text2); } } _excluded = hashSet; _excludedRaw = text; } public static bool IsBeastCreature(string monsterName) { if (string.IsNullOrEmpty(monsterName)) { return false; } RefreshBeastIfNeeded(); return _beast.Contains(monsterName); } private static void RefreshBeastIfNeeded() { string text = ModConfig.BonePileBeastCreatures?.Value ?? string.Empty; if (_beast != null && text == _beastRaw) { return; } HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); string[] array = text.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length > 0) { hashSet.Add(text2); } } _beast = hashSet; _beastRaw = text; } } internal static class CorpseClassifier { private static HashSet<string> _excluded; private static string _excludedRaw; public static bool IsNameExcluded(string monsterPrefabName) { if (string.IsNullOrEmpty(monsterPrefabName)) { return false; } RefreshIfNeeded(); return _excluded.Contains(monsterPrefabName); } private static void RefreshIfNeeded() { string text = ModConfig.ExcludedCreatures?.Value ?? string.Empty; if (_excluded == null || !(text == _excludedRaw)) { _excluded = Parse(text); _excludedRaw = text; } } 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; } } internal static class CorpseHighlight { private static CorpseInteraction _current; private static Renderer[] _currentRenderers; private static readonly int EmissionId = Shader.PropertyToID("_EmissionColor"); private static readonly int ColorId = Shader.PropertyToID("_Color"); private static readonly int TintId = Shader.PropertyToID("_TintColor"); public static void Tick() { if (ModConfig.HighlightEnabled == null || !ModConfig.HighlightEnabled.Value) { if ((Object)(object)_current != (Object)null) { Clear(); } return; } string boneName; CorpseInteraction corpseInteraction = FindHoveredHarvestable(out boneName); if (!((Object)(object)corpseInteraction == (Object)(object)_current)) { if ((Object)(object)_current != (Object)null) { ModLog.Diag("aim exit '" + ((Object)((Component)_current).gameObject).name + "'"); } Clear(); if ((Object)(object)corpseInteraction != (Object)null) { ModLog.Diag("aim enter '" + ((Object)((Component)corpseInteraction).gameObject).name + "' bone='" + boneName + "'"); Apply(corpseInteraction); } } } private static CorpseInteraction FindHoveredHarvestable(out string boneName) { boneName = null; Player localPlayer = Player.m_localPlayer; GameObject val = (((Object)(object)localPlayer != (Object)null) ? localPlayer.m_hovering : null); if ((Object)(object)val == (Object)null) { return null; } CorpseInteractionProxy component = val.GetComponent<CorpseInteractionProxy>(); CorpseInteraction corpseInteraction = (((Object)(object)component != (Object)null) ? component.Target : null); if ((Object)(object)corpseInteraction == (Object)null || corpseInteraction.State != CorpseState.Harvestable || corpseInteraction.SpawnAllowed) { return null; } boneName = ((Object)val).name; return corpseInteraction; } private static void Apply(CorpseInteraction corpse) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)corpse.Ragdoll == (Object)null) { return; } Renderer[] componentsInChildren = ((Component)corpse.Ragdoll).GetComponentsInChildren<Renderer>(); if (componentsInChildren.Length == 0) { return; } Color val = ParseColor(); MaterialPropertyBlock val2 = new MaterialPropertyBlock(); Renderer[] array = componentsInChildren; foreach (Renderer val3 in array) { if ((Object)(object)val3 == (Object)null) { continue; } Material sharedMaterial = val3.sharedMaterial; if ((Object)(object)sharedMaterial == (Object)null) { continue; } string text; if (sharedMaterial.HasProperty(EmissionId)) { val2.SetColor(EmissionId, val); text = "_EmissionColor"; } else if (sharedMaterial.HasProperty(ColorId)) { val2.SetColor(ColorId, val); text = "_Color"; } else { if (!sharedMaterial.HasProperty(TintId)) { continue; } val2.SetColor(TintId, val); text = "_TintColor"; } val3.SetPropertyBlock(val2); string[] obj = new string[9] { "highlight '", ((Object)((Component)corpse).gameObject).name, "' renderer='", ((Object)val3).name, "' shader='", null, null, null, null }; Shader shader = sharedMaterial.shader; obj[5] = ((shader != null) ? ((Object)shader).name : null); obj[6] = "' prop='"; obj[7] = text; obj[8] = "'"; ModLog.Diag(string.Concat(obj)); } _current = corpse; _currentRenderers = componentsInChildren; } private static void Clear() { if (_currentRenderers != null) { Renderer[] currentRenderers = _currentRenderers; foreach (Renderer val in currentRenderers) { if ((Object)(object)val != (Object)null) { val.SetPropertyBlock((MaterialPropertyBlock)null); } } } _current = null; _currentRenderers = null; } private static Color ParseColor() { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) string text = ModConfig.HighlightColorHex?.Value ?? "FFD24D"; Color result = default(Color); if (ColorUtility.TryParseHtmlString("#" + text, ref result)) { return result; } return new Color(1f, 0.82f, 0.3f); } } public enum CorpseState { Excluded, Pending, Harvestable } public class CorpseInteraction : MonoBehaviour, Hoverable, Interactable { private string _displayName; private GameObject _proxyRoot; private readonly List<Collider> _bodyColliders = new List<Collider>(); private readonly List<Transform> _trackTransforms = new List<Transform>(); private const float SettleEpsilon = 0.05f; private const float SettleHoldTime = 0.5f; private float _settleTimer; private bool _settled; private bool _loggedLiquidDefer; private const string DeathTimeKey = "ch_deathtime"; private const float SizeRatioTolerance = 0.1f; private const float CenterDiffTolerance = 0.05f; public Ragdoll Ragdoll { get; private set; } public ZNetView Nview { get; private set; } public CorpseState State { get; private set; } = CorpseState.Pending; public bool SpawnAllowed { get; private set; } public int TrophyHash { get { if (!((Object)(object)Nview != (Object)null) || !Nview.IsValid()) { return 0; } ZDO zDO = Nview.GetZDO(); if (zDO == null) { return 0; } return zDO.GetInt("ch_trophyhash", 0); } } public float TrophyBaseChance { get { if (!((Object)(object)Nview != (Object)null) || !Nview.IsValid()) { return 0f; } ZDO zDO = Nview.GetZDO(); if (zDO == null) { return 0f; } return zDO.GetFloat("ch_trophypct", 0f); } } public string MonsterName { get { if (!((Object)(object)Nview != (Object)null) || !Nview.IsValid()) { return null; } ZDO zDO = Nview.GetZDO(); if (zDO == null) { return null; } return zDO.GetString("ch_monstername", ""); } } public float BaseHealth { get { if (!((Object)(object)Nview != (Object)null) || !Nview.IsValid()) { return 0f; } ZDO zDO = Nview.GetZDO(); if (zDO == null) { return 0f; } return zDO.GetFloat("ch_basehealth", 0f); } } public float RequiredKnifeDamage => ToughCreatureTiers.GetRequiredDamage(BaseHealth); private void Awake() { Ragdoll = ((Component)this).GetComponent<Ragdoll>(); Nview = ((Component)this).GetComponent<ZNetView>(); if (ModConfig.Enabled == null || !ModConfig.Enabled.Value || (Object)(object)Nview == (Object)null || !Nview.IsValid()) { State = CorpseState.Excluded; SpawnAllowed = true; } else { State = CorpseState.Pending; SpawnAllowed = false; } } public void StampDeathInfo(CharacterDrop characterDrop, int level) { //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Invalid comparison between Unknown and I4 if (State != CorpseState.Pending || (Object)(object)Nview == (Object)null || !Nview.IsValid() || !Nview.IsOwner()) { return; } ZDO zDO = Nview.GetZDO(); if (zDO == null || zDO.GetBool("ch_ready", false)) { return; } Character val = (((Object)(object)characterDrop != (Object)null) ? characterDrop.m_character : null); string text = null; if ((Object)(object)val != (Object)null) { text = Utils.GetPrefabName(((Component)val).gameObject); zDO.Set("ch_name", val.m_name); zDO.Set("ch_boss", val.m_boss); zDO.Set("ch_player", val.IsPlayer()); zDO.Set("ch_hasdrop", true); zDO.Set("ch_monstername", text); float prefabHealth; bool usedOverride; bool usedPrefab; float num = CreatureHealthResolver.Resolve(text, val.m_health, out prefabHealth, out usedOverride, out usedPrefab); zDO.Set("ch_basehealth", num); ModLog.Diag($"StampDeathInfo '{((Object)((Component)this).gameObject).name}' health: prefabBase={prefabHealth:0} " + $"instanceM_health={val.m_health:0} instanceCurrentMax={val.GetMaxHealth():0} " + string.Format("resolved={0:0} source={1}", num, usedOverride ? "override" : (usedPrefab ? "prefab" : "instance-fallback"))); int num2 = 0; float num3 = 0f; foreach (Drop drop in characterDrop.m_drops) { if (!((Object)(object)drop?.m_prefab == (Object)null)) { ItemDrop component = drop.m_prefab.GetComponent<ItemDrop>(); if (component?.m_itemData?.m_shared != null && (int)component.m_itemData.m_shared.m_itemType == 13) { num2 = ZNetScene.instance.GetPrefabHash(drop.m_prefab); float num4 = (drop.m_levelMultiplier ? Mathf.Pow(2f, (float)(level - 1)) : 1f); num3 = Mathf.Clamp01(drop.m_chance * num4); break; } } } zDO.Set("ch_trophyhash", num2); zDO.Set("ch_trophypct", num3); int num5 = 0; EffectList hitEffects = val.m_hitEffects; if (hitEffects?.m_effectPrefabs != null && (Object)(object)ZNetScene.instance != (Object)null) { EffectData[] effectPrefabs = hitEffects.m_effectPrefabs; foreach (EffectData val2 in effectPrefabs) { if (!((Object)(object)val2?.m_prefab == (Object)null) && !EffectFilter.HasSoundOrShake(val2.m_prefab)) { int prefabHash = ZNetScene.instance.GetPrefabHash(val2.m_prefab); if (prefabHash != 0) { zDO.Set("ch_hitfx" + num5, prefabHash); num5++; } } } } zDO.Set("ch_hitfxcount", num5); } else { zDO.Set("ch_hasdrop", false); } zDO.Set("ch_deathtime", (long)(((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTimeSeconds() : 0.0)); zDO.Set("ch_ready", true); ModLog.Diag("StampDeathInfo '" + ((Object)((Component)this).gameObject).name + "' name=" + val?.m_name + " monster=" + text + " " + $"boss={val?.m_boss} player={((val != null) ? new bool?(val.IsPlayer()) : ((bool?)null))} hasDrop={(Object)(object)characterDrop != (Object)null} " + string.Format("hitFxCount={0}", zDO.GetInt("ch_hitfxcount", 0))); TryFinalizeClassification(); } public void TryFinalizeClassification() { if (State != CorpseState.Pending || (Object)(object)Nview == (Object)null || !Nview.IsValid()) { return; } ZDO zDO = Nview.GetZDO(); if (zDO == null || !zDO.GetBool("ch_ready", false)) { return; } bool num = zDO.GetBool("ch_boss", false); bool flag = zDO.GetBool("ch_player", false); bool flag2 = zDO.GetBool("ch_hasdrop", false); string text = zDO.GetString("ch_monstername", ""); bool flag3 = CorpseClassifier.IsNameExcluded(text); string text2 = (num ? "boss" : (flag ? "player" : ((!flag2) ? "no-characterdrop" : (flag3 ? "excluded-name" : null)))); if (text2 != null) { State = CorpseState.Excluded; SpawnAllowed = true; ModLog.Diag("classify '" + ((Object)((Component)this).gameObject).name + "': excluded (" + text2 + ") monster='" + text + "'"); return; } State = CorpseState.Harvestable; string text3 = zDO.GetString("ch_name", ""); object obj; if (!string.IsNullOrEmpty(text3)) { Localization instance = Localization.instance; obj = ((instance != null) ? instance.Localize(text3) : null); } else { obj = null; } string text4 = (string)obj; _displayName = ((string.IsNullOrEmpty(text4) || text4.StartsWith("$")) ? HarvestText.FallbackCorpseName : text4); float elapsed; double deathTime; float num2 = ComputeRemainingLifetime(out elapsed, out deathTime); ModLog.Diag($"lifetime '{((Object)((Component)this).gameObject).name}': death={deathTime:0} elapsed={elapsed:0.0}s " + $"configured={ModConfig.CorpseLifetime.Value:0.0}s remaining={num2:0.0}s"); if (num2 <= 0f) { ModLog.Diag("lifetime '" + ((Object)((Component)this).gameObject).name + "': already expired on load -> immediate decay"); if ((Object)(object)Ragdoll != (Object)null) { Ragdoll.DestroyNow(); } return; } CreateProxy(); ArmDecayTimer(num2); ModLog.Diag("classify '" + ((Object)((Component)this).gameObject).name + "': harvestable name='" + _displayName + "'"); } private float ComputeRemainingLifetime(out float elapsed, out double deathTime) { ZDO val = (((Object)(object)Nview != (Object)null && Nview.IsValid()) ? Nview.GetZDO() : null); double num = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTimeSeconds() : 0.0); long num2 = ((val != null) ? val.GetLong("ch_deathtime", -1L) : (-1)); if (num2 < 0) { num2 = (long)num; if (val != null && Nview.IsOwner()) { val.Set("ch_deathtime", num2); } ModLog.Diag(string.Format("lifetime '{0}': no {1} on record -> baselined to now ({2})", ((Object)((Component)this).gameObject).name, "ch_deathtime", num2)); } deathTime = num2; elapsed = Mathf.Max(0f, (float)(num - (double)num2)); return Mathf.Max(1f, ModConfig.CorpseLifetime.Value) - elapsed; } public float ComputeFreshness() { if (ModConfig.FreshnessEnabled == null || !ModConfig.FreshnessEnabled.Value) { return 1f; } float num = Mathf.Max(1f, ModConfig.CorpseLifetime.Value); ComputeRemainingLifetime(out var elapsed, out var _); float num2 = Mathf.Clamp01(ModConfig.FreshDurationFraction.Value); float num3 = num * num2; if (elapsed <= num3) { return 1f; } float num4 = Mathf.Max(0.001f, num - num3); float num5 = Mathf.Clamp01((elapsed - num3) / num4); float num6 = Mathf.Clamp01(ModConfig.FreshnessMinYield.Value); return Mathf.Lerp(1f, num6, num5); } private void ArmDecayTimer(float ttl) { if (!((Object)(object)Ragdoll == (Object)null)) { ttl = Mathf.Max(0.1f, ttl); ((MonoBehaviour)Ragdoll).CancelInvoke("DestroyNow"); ((MonoBehaviour)Ragdoll).InvokeRepeating("DestroyNow", ttl, 1f); } } public void SuspendDecay() { if ((Object)(object)Ragdoll != (Object)null) { ((MonoBehaviour)Ragdoll).CancelInvoke("DestroyNow"); } } public void ResumeDecay() { float num = ComputeRemainingLifetime(out var elapsed, out var deathTime); num = Mathf.Max(10f, num); ArmDecayTimer(num); ModLog.Diag($"lifetime '{((Object)((Component)this).gameObject).name}': resume after cancel, death={deathTime:0} " + $"elapsed={elapsed:0.0}s remaining={num:0.0}s (floor 10s)"); } public void ApplyExpiryLoot() { if ((Object)(object)Nview == (Object)null || !Nview.IsValid() || !Nview.IsOwner()) { return; } float num = Mathf.Clamp01(ModConfig.ExpiryLootFraction.Value); ZDO zDO = Nview.GetZDO(); if (zDO == null) { return; } int num2 = zDO.GetInt("ch_trophyhash", 0); int num3 = zDO.GetInt(ZDOVars.s_drops, 0); for (int i = 0; i < num3; i++) { int num4 = zDO.GetInt("drop_hash" + i, 0); int num5 = zDO.GetInt("drop_amount" + i, 0); if (num5 > 0) { int num6 = LootMath.StochasticRound((float)num5 * num); if (num2 != 0 && num4 == num2) { num6 = Mathf.Min(num6, 1); } zDO.Set("drop_amount" + i, num6); } } CapTrophyTotal(zDO, num2); SpawnAllowed = true; ModLog.Diag($"decay '{((Object)((Component)this).gameObject).name}' frac={num:0.00} -> loot scaled (no minimum guaranteed), " + "trophy capped to <=1 total"); } public static void CapTrophyTotal(ZDO zdo, int trophyHash) { if (zdo == null || trophyHash == 0) { return; } int num = zdo.GetInt(ZDOVars.s_drops, 0); bool flag = false; for (int i = 0; i < num; i++) { if (zdo.GetInt("drop_hash" + i, 0) != trophyHash) { continue; } int num2 = zdo.GetInt("drop_amount" + i, 0); if (num2 <= 0) { continue; } if (!flag) { if (num2 > 1) { zdo.Set("drop_amount" + i, 1); } flag = true; } else { zdo.Set("drop_amount" + i, 0); } } } 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_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Expected O, but got Unknown //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_0236: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_025e: Unknown result type (might be due to invalid IL or missing references) //IL_026a: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Unknown result type (might be due to invalid IL or missing references) _proxyRoot = new GameObject("CorpseHarvestInteract"); _proxyRoot.transform.SetParent(((Component)this).transform, true); int num = LayerMask.NameToLayer("piece_nonsolid"); Rigidbody[] array = (((Object)(object)Ragdoll != (Object)null) ? Ragdoll.m_bodies : null); int num2 = 0; int num3 = 0; bool flag = false; bool flag2 = false; float num4 = 0f; float num5 = 0f; float num6 = float.MaxValue; float num7 = float.MinValue; if (array != null) { Rigidbody[] array2 = array; foreach (Rigidbody val in array2) { if ((Object)(object)val == (Object)null) { continue; } Collider val2 = ((Component)val).GetComponent<Collider>(); if ((Object)(object)val2 == (Object)null) { val2 = ((Component)val).GetComponentInChildren<Collider>(); } Transform val3 = (((Object)(object)val2 != (Object)null) ? ((Component)val2).transform : ((Component)val).transform); GameObject val4 = new GameObject("CorpseHarvestBoneProxy"); val4.layer = num; val4.transform.SetParent(_proxyRoot.transform, false); val4.transform.SetPositionAndRotation(val3.position, val3.rotation); val4.transform.localScale = DivideComponents(val3.lossyScale, _proxyRoot.transform.lossyScale); Collider val5 = CloneColliderShape(val2, val4); val5.isTrigger = false; if ((Object)(object)val2 == (Object)null) { num3++; } val4.AddComponent<CorpseInteractionProxy>().Target = this; _bodyColliders.Add(val5); _trackTransforms.Add(val3); num2++; Vector3 lossyScale = val3.lossyScale; num6 = Mathf.Min(new float[4] { num6, lossyScale.x, lossyScale.y, lossyScale.z }); num7 = Mathf.Max(new float[4] { num7, lossyScale.x, lossyScale.y, lossyScale.z }); if ((Object)(object)val2 != (Object)null) { Bounds bounds = val5.bounds; Bounds bounds2 = val2.bounds; float num8 = Vector3.Distance(((Bounds)(ref bounds)).center, ((Bounds)(ref bounds2)).center); num5 = Mathf.Max(num5, num8); float num9 = Mathf.Abs(SafeRatio(((Bounds)(ref bounds)).size.x, ((Bounds)(ref bounds2)).size.x) - 1f); float num10 = Mathf.Abs(SafeRatio(((Bounds)(ref bounds)).size.y, ((Bounds)(ref bounds2)).size.y) - 1f); float num11 = Mathf.Abs(SafeRatio(((Bounds)(ref bounds)).size.z, ((Bounds)(ref bounds2)).size.z) - 1f); float num12 = Mathf.Max(num9, Mathf.Max(num10, num11)); num4 = Mathf.Max(num4, num12); if (num12 > 0.1f || num8 > 0.05f) { flag2 = true; } } else { flag2 = true; } if (!flag && (Object)(object)val2 != (Object)null) { flag = true; int layer = ((Component)val2).gameObject.layer; bool ignoreLayerCollision = Physics.GetIgnoreLayerCollision(layer, num); ModLog.Diag("layer check '" + ((Object)((Component)this).gameObject).name + "': bone layer='" + LayerMask.LayerToName(layer) + "' " + $"proxy layer='{LayerMask.LayerToName(num)}' ignoreLayerCollision={ignoreLayerCollision} " + "(proxies are standalone, not parented under any bone Rigidbody, so they never join a bone's compound collider regardless of this value)"); } } } if (num2 == 0) { flag2 = true; } if (flag2) { AddBodyCenterSafetyNet(num); } if (num2 == 0) { ModLog.Warn("'" + ((Object)((Component)this).gameObject).name + "': no ragdoll bones/colliders found — using single fallback sphere proxy"); } string arg = ((num6 <= num7) ? $"{num6:0.00}-{num7:0.00}" : "n/a"); ModLog.Diag($"body proxies created for '{((Object)((Component)this).gameObject).name}': count={num2} (shape-fallback={num3}) " + $"maxSizeRatioDeviation={num4:0.00} maxCenterDiff={num5:0.00}m " + $"boneLossyScale={arg} safetyNet={flag2}"); } private void AddBodyCenterSafetyNet(int proxyLayer) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("CorpseHarvestInteract_Fallback") { layer = proxyLayer }; val.transform.SetParent(_proxyRoot.transform, true); val.transform.position = BodyPosition(); SphereCollider val2 = val.AddComponent<SphereCollider>(); val2.radius = 0.8f; ((Collider)val2).isTrigger = false; val.AddComponent<CorpseInteractionProxy>().Target = this; _bodyColliders.Add((Collider)(object)val2); _trackTransforms.Add(null); } private static float SafeRatio(float proxySize, float srcSize) { if (srcSize <= 0.0001f) { if (!(proxySize <= 0.0001f)) { return 11f; } return 1f; } return proxySize / srcSize; } private static Vector3 DivideComponents(Vector3 a, Vector3 b) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0041: 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) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0068: 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) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) return new Vector3((Mathf.Abs(b.x) > 1E-06f) ? (a.x / b.x) : a.x, (Mathf.Abs(b.y) > 1E-06f) ? (a.y / b.y) : a.y, (Mathf.Abs(b.z) > 1E-06f) ? (a.z / b.z) : a.z); } private static Collider CloneColliderShape(Collider src, GameObject target) { //IL_0028: 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_006b: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) CapsuleCollider val = (CapsuleCollider)(object)((src is CapsuleCollider) ? src : null); if (val == null) { BoxCollider val2 = (BoxCollider)(object)((src is BoxCollider) ? src : null); if (val2 == null) { SphereCollider val3 = (SphereCollider)(object)((src is SphereCollider) ? src : null); if (val3 != null) { SphereCollider obj = target.AddComponent<SphereCollider>(); obj.center = val3.center; obj.radius = val3.radius; return (Collider)(object)obj; } SphereCollider obj2 = target.AddComponent<SphereCollider>(); obj2.radius = 0.25f; return (Collider)(object)obj2; } BoxCollider obj3 = target.AddComponent<BoxCollider>(); obj3.center = val2.center; obj3.size = val2.size; return (Collider)(object)obj3; } CapsuleCollider obj4 = target.AddComponent<CapsuleCollider>(); obj4.center = val.center; obj4.radius = val.radius; obj4.height = val.height; obj4.direction = val.direction; return (Collider)(object)obj4; } private void Update() { if (State == CorpseState.Pending) { if ((Object)(object)Nview != (Object)null && Nview.IsValid()) { ZDO zDO = Nview.GetZDO(); if (zDO != null && zDO.GetBool("ch_ready", false)) { TryFinalizeClassification(); } } } else if (State == CorpseState.Harvestable && !SpawnAllowed && !_settled) { TrackBoneProxies(); CheckSettle(); } } private void TrackBoneProxies() { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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) for (int i = 0; i < _bodyColliders.Count; i++) { Collider val = _bodyColliders[i]; if (!((Object)(object)val == (Object)null)) { Transform val2 = _trackTransforms[i]; if ((Object)(object)val2 != (Object)null) { ((Component)val).transform.SetPositionAndRotation(val2.position, val2.rotation); } else { ((Component)val).transform.position = BodyPosition(); } } } } private void CheckSettle() { //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Ragdoll == (Object)null || Ragdoll.m_bodies == null) { return; } if (Ragdoll.m_float && IsAnyBodyInLiquid()) { _settleTimer = 0f; if (!_loggedLiquidDefer) { _loggedLiquidDefer = true; ModLog.Diag("'" + ((Object)((Component)this).gameObject).name + "' settle deferred -> body still in liquid (m_float active)"); } return; } float num = 0f; Rigidbody[] bodies = Ragdoll.m_bodies; foreach (Rigidbody val in bodies) { if (!((Object)(object)val == (Object)null)) { Vector3 linearVelocity = val.linearVelocity; float sqrMagnitude = ((Vector3)(ref linearVelocity)).sqrMagnitude; if (sqrMagnitude > num) { num = sqrMagnitude; } } } if (num < 0.0025000002f) { _settleTimer += Time.deltaTime; if (_settleTimer >= 0.5f) { Settle(); } } else { _settleTimer = 0f; } } private bool IsAnyBodyInLiquid() { //IL_001e: 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_003a: 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) Rigidbody[] bodies = Ragdoll.m_bodies; foreach (Rigidbody val in bodies) { if (!((Object)(object)val == (Object)null)) { Vector3 worldCenterOfMass = val.worldCenterOfMass; worldCenterOfMass.y += Ragdoll.m_floatOffset; if (worldCenterOfMass.y < Floating.GetLiquidLevel(worldCenterOfMass, 1f, (LiquidType)10)) { return true; } } } return false; } private void Settle() { _settled = true; Ragdoll.m_float = false; Rigidbody[] bodies = Ragdoll.m_bodies; foreach (Rigidbody val in bodies) { if ((Object)(object)val != (Object)null) { val.isKinematic = true; } } TrackBoneProxies(); ModLog.Diag("'" + ((Object)((Component)this).gameObject).name + "' settled -> rigidbodies kinematic, float disabled, proxy updates stopped"); } 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 float DistanceToNearestPart(Vector3 from) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) float num = float.MaxValue; foreach (Collider bodyCollider in _bodyColliders) { if (!((Object)(object)bodyCollider == (Object)null)) { float num2 = Vector3.Distance(from, bodyCollider.ClosestPoint(from)); if (num2 < num) { num = num2; } } } if (num != float.MaxValue) { return num; } return Vector3.Distance(from, BodyPosition()); } public void AllowSpawnNow() { SpawnAllowed = true; } public int[] HitEffectHashes() { if ((Object)(object)Nview == (Object)null || !Nview.IsValid()) { return Array.Empty<int>(); } ZDO zDO = Nview.GetZDO(); if (zDO == null) { return Array.Empty<int>(); } int num = zDO.GetInt("ch_hitfxcount", 0); if (num <= 0) { return Array.Empty<int>(); } int[] array = new int[num]; for (int i = 0; i < num; i++) { array[i] = zDO.GetInt("ch_hitfx" + i, 0); } return array; } public bool IsCurrentlyInLiquid() { if ((Object)(object)Ragdoll != (Object)null) { return IsAnyBodyInLiquid(); } return false; } public string GetHoverName() { if (State != CorpseState.Harvestable || SpawnAllowed) { return ""; } return _displayName ?? HarvestText.FallbackCorpseName; } public float GetHoverOffset() { return 0f; } public string GetHoverText() { if (State != CorpseState.Harvestable || SpawnAllowed) { return ""; } string text = (ModConfig.HoldToWork.Value ? HarvestText.HoldHint : ""); string text2 = Localization.instance.Localize("[<color=yellow><b>$KEY_Use</b></color>] " + HarvestText.Verb + text); float requiredKnifeDamage = RequiredKnifeDamage; if (requiredKnifeDamage > 0f) { Player localPlayer = Player.m_localPlayer; HarvestKnifeKind kind; string bareHandReason; ItemData val = (((Object)(object)localPlayer != (Object)null) ? KnifeUtil.SelectHarvestKnife(localPlayer, out kind, out bareHandReason) : null); float currentDamage = ((val != null) ? KnifeUtil.Sharpness(val) : 0f); float num = ToughCreatureTiers.ComputeYieldMultiplier(requiredKnifeDamage, currentDamage); if (num < 1f) { text2 += HarvestText.WeakYieldHoverWarning(num * 100f, currentDamage, requiredKnifeDamage, val == null); } } if (ModConfig.FreshnessEnabled.Value) { text2 += HarvestText.FreshnessHoverLine(ComputeFreshness() * 100f); } return text2; } public bool Interact(Humanoid user, bool hold, bool alt) { if (hold) { return false; } if (State != CorpseState.Harvestable || 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 + "' -> 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 float GetHoverOffset() { if (!((Object)(object)Target != (Object)null)) { return 0f; } return Target.GetHoverOffset(); } 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 + "' " + $"state={corpseInteraction.State} layer={LayerMask.LayerToName(((Component)__instance).gameObject.layer)}"); } } } [HarmonyPatch(typeof(Ragdoll), "Setup")] internal static class Ragdoll_Setup_Patch { private static void Postfix(Ragdoll __instance, CharacterDrop characterDrop, int level) { CorpseInteraction component = ((Component)__instance).GetComponent<CorpseInteraction>(); if (!((Object)(object)component == (Object)null)) { component.StampDeathInfo(characterDrop, level); } } } [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.State == CorpseState.Harvestable && !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.State == CorpseState.Excluded) { ModLog.Diag("SpawnLoot '" + ((Object)((Component)__instance).gameObject).name + "': excluded -> vanilla"); return true; } ModLog.Diag($"SpawnLoot '{((Object)((Component)__instance).gameObject).name}': state={component.State} allowed={component.SpawnAllowed}"); return component.SpawnAllowed; } } [HarmonyPatch(typeof(ItemData), "GetTooltip", new Type[] { typeof(int) })] internal static class ItemData_GetTooltip_Patch { private static void Postfix(ItemData __instance, ref string __result) { if (HarvestKnifeTag.IsTagged(__instance)) { __result += HarvestText.KnifeTooltipLine; } } } [HarmonyPatch(typeof(Pickable), "GetHoverName")] internal static class Pickable_GetHoverName_Patch { private static void Postfix(Pickable __instance, ref string __result) { if ((Object)(object)((Component)__instance).GetComponent<BeastBonePileMarker>() != (Object)null) { __result = HarvestText.AnimalBonesName; } } } public enum HarvestPose { Kneel, NoneSwimming, NoneShip, NoneInLiquid } 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 ItemData _knife; private static float _knivesFactor; private static bool _isJackpot; private static float _jackpotMult; private static float _sizeMult; private static float _weakKnifeMult; private static bool _armedForPressCancel; private static bool _suppressRestart; private static bool _usedKneelPose; private static float _effectTimer; 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 _knife, out var knifeKind, out var bareHandReason, out _knivesFactor, out _isJackpot, out _jackpotMult, out _sizeMult, out _weakKnifeMult); _corpse = corpse; _player = player; _elapsed = 0f; _armedForPressCancel = false; _active = true; corpse.SuspendDecay(); HarvestPose harvestPose = DecideAndStartPose(player); _usedKneelPose = harvestPose == HarvestPose.Kneel; WorkProgressUI.Show(HarvestText.ProgressLabel(_knife)); string text = ((_knife != null) ? $"name='{_knife.m_shared.m_name}' sharpness={KnifeUtil.Sharpness(_knife):0.0} durability={_knife.m_durability:0.0}" : ("none reason=" + bareHandReason)); float requiredKnifeDamage = corpse.RequiredKnifeDamage; float num = ((_knife != null) ? KnifeUtil.Sharpness(_knife) : 0f); float num2 = corpse.ComputeFreshness(); ModLog.Diag($"Begin on '{((Object)((Component)corpse).gameObject).name}' pose={harvestPose} knifeKind={knifeKind} knife=[{text}] " + $"dur={_duration:0.00}s baseHealth={corpse.BaseHealth:0} sizeMult={_sizeMult:0.00} " + $"requiredDamage={requiredKnifeDamage:0} currentDamage={num:0} weakKnifeYield={_weakKnifeMult:0.00} " + $"freshness={num2:0.00} jackpotChance={ModConfig.JackpotMaxChance.Value * _knivesFactor:0.00}"); } } 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)); _effectTimer += dt; float num = Mathf.Max(0.1f, ModConfig.HarvestEffectInterval.Value); if (_effectTimer >= num) { _effectTimer = 0f; HarvestEffects.Pulse(_corpse, _knife); } if (_elapsed >= _duration) { Complete(); } } private static bool StillValid() { //IL_017f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_corpse == (Object)null || (Object)(object)_player == (Object)null) { ModLog.Diag("cancel: reason=session-state-null"); return false; } if ((Object)(object)_corpse.Nview == (Object)null || !_corpse.Nview.IsValid()) { ModLog.Diag("cancel '" + ((Object)((Component)_corpse).gameObject).name + "': reason=corpse-invalid"); return false; } if ((Object)(object)_player != (Object)(object)Player.m_localPlayer || ((Character)_player).IsDead()) { ModLog.Diag("cancel '" + ((Object)((Component)_corpse).gameObject).name + "': reason=player-invalid"); return false; } if (ModConfig.HoldToWork.Value) { if (!ZInput.GetButton("Use")) { ModLog.Diag("cancel '" + ((Object)((Component)_corpse).gameObject).name + "': reason=use-released"); return false; } } else if (!_armedForPressCancel) { if (!ZInput.GetButton("Use")) { _armedForPressCancel = true; } } else if (ZInput.GetButtonDown("Use")) { _suppressRestart = true; ModLog.Diag("cancel '" + ((Object)((Component)_corpse).gameObject).name + "': reason=press-again"); return false; } if (((Character)_player).InAttack()) { ModLog.Diag("cancel '" + ((Object)((Component)_corpse).gameObject).name + "': reason=attacking"); return false; } float num = _corpse.DistanceToNearestPart(((Component)_player).transform.position); if (num > ModConfig.MaxWorkDistance.Value) { ModLog.Diag("cancel '" + ((Object)((Component)_corpse).gameObject).name + "': reason=distance " + $"dist={num:0.00} max={ModConfig.MaxWorkDistance.Value:0.00}"); return false; } return true; } private static void Complete() { //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) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) CorpseInteraction corpse = _corpse; Player player = _player; ItemData knife = _knife; bool flag = knife != null; float knivesFactor = _knivesFactor; bool isJackpot = _isJackpot; float jackpotMult = _jackpotMult; float sizeMult = _sizeMult; float weakKnifeMult = _weakKnifeMult; float num = corpse.ComputeFreshness(); HarvestEffects.Pulse(corpse, knife); EndSession(); corpse.Nview.ClaimOwnership(); ApplyHarvestDrops(corpse, weakKnifeMult * jackpotMult * num); bool flag2 = ApplyTrophyRoll(corpse, flag, knivesFactor); int num2; object obj; if (flag && ModConfig.AutoLootEnabled.Value) { num2 = ((((Character)player).GetSkillFactor((SkillType)2) * 100f >= (float)ModConfig.AutoLootSkillLevel.Value) ? 1 : 0); if (num2 != 0) { obj = new HashSet<ItemDrop>(Object.FindObjectsByType<ItemDrop>((FindObjectsSortMode)0)); goto IL_00b2; } } else { num2 = 0; } obj = null; goto IL_00b2; IL_00b2: HashSet<ItemDrop> hashSet = (HashSet<ItemDrop>)obj; corpse.AllowSpawnNow(); Vector3 averageBodyPosition = corpse.Ragdoll.GetAverageBodyPosition(); corpse.Ragdoll.SpawnLoot(averageBodyPosition); bool flag3 = corpse.IsCurrentlyInLiquid(); bool flag4 = (Object)(object)((Character)player).GetStandingOnShip() != (Object)null; BonePileSpawner.TrySpawn(corpse, averageBodyPosition, flag3 || flag4); if ((Object)(object)ZNetScene.instance != (Object)null) { ZNetScene.instance.Destroy(((Component)corpse).gameObject); } int num3 = 0; int num4 = 0; if (num2 != 0) { ItemDrop[] array = Object.FindObjectsByType<ItemDrop>((FindObjectsSortMode)0); foreach (ItemDrop val in array) { if (!((Object)(object)val == (Object)null) && !hashSet.Contains(val)) { if (((Humanoid)player).Pickup(((Component)val).gameObject, false, false)) { num3++; } else { num4++; } } } ModLog.Diag($"Complete '{((Object)((Component)corpse).gameObject).name}': auto-loot looted={num3} " + $"leftOnGround={num4}"); } if (flag) { ((Character)player).RaiseSkill((SkillType)2, ModConfig.KnivesXpPerHarvest.Value * sizeMult); ApplyKnifeDurability(knife, player, knivesFactor); } ((Character)player).Message((MessageType)1, HarvestText.CompleteMessage(isJackpot, jackpotMult, weakKnifeMult < 1f, weakKnifeMult * 100f), 0, (Sprite)null, false); float num5 = ModConfig.JackpotMaxChance.Value * knivesFactor; ModLog.Diag($"Complete '{((Object)((Component)corpse).gameObject).name}' knife={flag} jackpotChance={num5:0.00} " + $"jackpot={isJackpot} mult={jackpotMult:0.00} sizeMult={sizeMult:0.00} " + $"weakKnifeYield={weakKnifeMult:0.00} freshness={num:0.00} trophyAdded={flag2}"); } private static void ApplyKnifeDurability(ItemData knife, Player player, float knivesFactor) { if (!knife.m_shared.m_useDurability) { return; } float value = ModConfig.KnifeDurabilityPerHarvest.Value; if (value <= 0f) { return; } float num = ModConfig.DurabilitySaveMaxChance.Value * knivesFactor; if (Random.value < num) { ModLog.Diag($"knife durability '{knife.m_shared.m_name}': saved (chance={num:0.00})"); return; } float durability = knife.m_durability; knife.m_durability = Mathf.Max(0f, knife.m_durability - value); bool flag = durability > 0f && knife.m_durability <= 0f; ModLog.Diag($"knife durability '{knife.m_shared.m_name}': {durability:0.0} -> {knife.m_durability:0.0} " + $"broke={flag} saved=false (chance={num:0.00})"); if (flag) { ((Character)player).Message((MessageType)1, HarvestText.KnifeBrokenMessage, 0, (Sprite)null, false); } } 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 && _usedKneelPose) { StopWorkPose(_player); } WorkProgressUI.Hide(); _active = false; _corpse = null; _player = null; _elapsed = 0f; _armedForPressCancel = false; _knife = null; _usedKneelPose = false; _effectTimer = 0f; } private static void ComputePlan(CorpseInteraction corpse, Player player, out float duration, out ItemData knife, out HarvestKnifeKind knifeKind, out string bareHandReason, out float knivesFactor, out bool isJackpot, out float jackpotMult, out float sizeMult, out float weakKnifeMult) { knife = KnifeUtil.SelectHarvestKnife(player, out knifeKind, out bareHandReason); bool num = knife != null; float num2 = (num ? KnifeUtil.Sharpness(knife) : 0f); if (num) { float num3 = 1f + (ModConfig.KnifeSpeedMaxMultiplier.Value - 1f) * Mathf.Min(num2 / ModConfig.KnifeReferenceDamage.Value, 1f); duration = ModConfig.BaseHarvestTime.Value / num3; knivesFactor = ((Character)player).GetSkillFactor((SkillType)2); float num4 = ModConfig.JackpotMaxChance.Value * knivesFactor; isJackpot = Random.value < num4; jackpotMult = (isJackpot ? (1f + (ModConfig.JackpotMaxMultiplier.Value - 1f) * knivesFactor) : 1f); } else { duration = ModConfig.BaseHarvestTime.Value * ModConfig.BareHandTimeMultiplier.Value; knivesFactor = 0f; isJackpot = false; jackpotMult = 1f; } weakKnifeMult = ToughCreatureTiers.ComputeYieldMultiplier(corpse.RequiredKnifeDamage, num2); sizeMult = SizeTier.ComputeMultiplier(corpse.BaseHealth); duration *= sizeMult; duration = Mathf.Max(0.25f, duration); } private static void ApplyHarvestDrops(CorpseInteraction corpse, float totalMult) { if (Mathf.Approximately(totalMult, 1f)) { return; } ZDO zDO = corpse.Nview.GetZDO(); if (zDO == null) { return; } int trophyHash = corpse.TrophyHash; int num = zDO.GetInt(ZDOVars.s_drops, 0); for (int i = 0; i < num; i++) { int num2 = zDO.GetInt("drop_hash" + i, 0); int num3 = zDO.GetInt("drop_amount" + i, 0); if (num3 > 0 && (trophyHash == 0 || num2 != trophyHash)) { zDO.Set("drop_amount" + i, LootMath.StochasticRound((float)num3 * totalMult)); } } } private static bool ApplyTrophyRoll(CorpseInteraction corpse, bool usedKnife, float knivesFactor) { int trophyHash = corpse.TrophyHash; if (trophyHash == 0) { return false; } ZDO zDO = corpse.Nview.GetZDO(); if (zDO == null) { return false; } bool num = TrophyAlreadyDropped(zDO, trophyHash); bool result = false; if (num) { ModLog.Diag("trophy '" + ((Object)((Component)corpse).gameObject).name + "': already dropped at death, no re-roll"); } else if (usedKnife) { float trophyBaseChance = corpse.TrophyBaseChance; float num2 = Mathf.Min(1f, trophyBaseChance * (1f + (ModConfig.TrophyChanceMaxMultiplier.Value - 1f) * knivesFactor)); float num3 = Mathf.Max(0.0001f, 1f - trophyBaseChance); float num4 = Mathf.Clamp01((num2 - trophyBaseChance) / num3); bool flag = Random.value < num4; ModLog.Diag($"trophy '{((Object)((Component)corpse).gameObject).name}': base={trophyBaseChance:0.000} target={num2:0.000} " + $"extraChance={num4:0.000} won={flag}"); if (flag) { int num5 = zDO.GetInt(ZDOVars.s_drops, 0); zDO.Set(ZDOVars.s_drops, num5 + 1, false); zDO.Set("drop_hash" + num5, trophyHash); zDO.Set("drop_amount" + num5, 1); result = true; } } CorpseInteraction.CapTrophyTotal(zDO, trophyHash); return result; } private static bool TrophyAlreadyDropped(ZDO zdo, int trophyHash) { int num = zdo.GetInt(ZDOVars.s_drops, 0); for (int i = 0; i < num; i++) { if (zdo.GetInt("drop_hash" + i, 0) == trophyHash && zdo.GetInt("drop_amount" + i, 0) > 0) { return true; } } return false; } private static HarvestPose DecideAndStartPose(Player player) { if (((Character)player).IsSwimming()) { return HarvestPose.NoneSwimming; } if ((Object)(object)((Character)player).GetStandingOnShip() != (Object)null) { return HarvestPose.NoneShip; } if (((Character)player).InLiquid()) { return HarvestPose.NoneInLiquid; } player.StartEmote("kneel", false); return HarvestPose.Kneel; } private static void StopWorkPose(Player player) { if (((Character)player).InEmote()) { ((Character)player).StopEmote(); } } } internal static class CreatureHealthOverrides { private static Dictionary<string, float> _map = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase); private static string _raw; public static float Get(string monsterPrefabName) { if (string.IsNullOrEmpty(monsterPrefabName)) { return 0f; } RefreshIfNeeded(); if (!_map.TryGetValue(monsterPrefabName, out var value)) { return 0f; } return value; } private static void RefreshIfNeeded() { string text = ModConfig.CreatureHealthOverrides?.Value ?? string.Empty; if (_raw != null && text == _raw) { return; } Dictionary<string, float> dictionary = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase); string[] array = text.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length == 0) { continue; } string[] array2 = text2.Split(new char[1] { ':' }); if (array2.Length == 2) { string text3 = array2[0].Trim(); if (text3.Length != 0 && float.TryParse(array2[1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && !(result <= 0f)) { dictionary[text3] = result; } } } _map = dictionary; _raw = text; } } internal static class CreatureHealthResolver { public static float Resolve(string monsterPrefabName, float instanceHealth, out float prefabHealth, out bool usedOverride, out bool usedPrefab) { prefabHealth = 0f; usedOverride = false; usedPrefab = false; float num = CreatureHealthOverrides.Get(monsterPrefabName); if (num > 0f) { usedOverride = true; return num; } Character val = ResolvePrefabCharacter(monsterPrefabName); if ((Object)(object)val != (Object)null) { prefabHealth = val.m_health; usedPrefab = true; return prefabHealth; } return instanceHealth; } public static float ResolveFromPrefab(string monsterPrefabName, Character prefabChar) { float num = CreatureHealthOverrides.Get(monsterPrefabName); if (num > 0f) { return num; } if (!((Object)(object)prefabChar != (Object)null)) { return 0f; } return prefabChar.m_health; } public static Character ResolvePrefabCharacter(string monsterPrefabName) { if (string.IsNullOrEmpty(monsterPrefabName) || (Object)(object)ZNetScene.instance == (Object)null) { return null; } GameObject prefab = ZNetScene.instance.GetPrefab(monsterPrefabName); if (!((Object)(object)prefab != (Object)null)) { return null; } return prefab.GetComponent<Character>(); } } internal static class EffectFilter { private static Type _zsfxType; private static bool _zsfxResolved; public static bool HasSoundOrShake(GameObject prefab) { if ((Object)(object)prefab == (Object)null) { return false; } if ((Object)(object)prefab.GetComponentInChildren<AudioSource>(true) != (Object)null) { return true; } if ((Object)(object)prefab.GetComponentInChildren<CamShaker>(true) != (Object)null) { return true; } if (!_zsfxResolved) { _zsfxResolved = true; _zsfxType = AccessTools.TypeByName("ZSFX"); } if (_zsfxType != null && (Object)(object)prefab.GetComponentInChildren(_zsfxType, true) != (Object)null) { return true; } return false; } public static bool IsAudioOnly(GameObject prefab) { if ((Object)(object)prefab == (Object)null) { return false; } if (!((Object)(object)prefab.GetComponentInChildren<AudioSource>(true) != (Object)null)) { return false; } return !((Object)(object)prefab.GetComponentInChildren<ParticleSystem>(true) != (Object)null) && !((Object)(object)prefab.GetComponentInChildren<Renderer>(true) != (Object)null) && !((Object)(object)prefab.GetComponentInChildren<Light>(true) != (Object)null); } } internal static class HarvestEffects { public static void Pulse(CorpseInteraction corpse, ItemData knife) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: 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_0040: Unknown result type (might be due to invalid IL or missing references) if (ModConfig.HarvestEffectsEnabled != null && ModConfig.HarvestEffectsEnabled.Value && !((Object)(object)corpse?.Ragdoll == (Object)null)) { Vector3 averageBodyPosition = corpse.Ragdoll.GetAverageBodyPosition(); PlayCreatureVisual(corpse, averageBodyPosition); if (knife != null) { PlayKnifeSound(knife, averageBodyPosition); } } } private static void PlayCreatureVisual(CorpseInteraction corpse, Vector3 pos) { //IL_003c: 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) int[] array = corpse.HitEffectHashes(); if (array.Length != 0 && !((Object)(object)ZNetScene.instance == (Object)null)) { int num = array[Random.Range(0, array.Length)]; GameObject prefab = ZNetScene.instance.GetPrefab(num); if (!((Object)(object)prefab == (Object)null)) { Object.Instantiate<GameObject>(prefab, pos, Quaternion.identity); ModLog.Diag("HarvestEffects: creature visual pulse '" + ((Object)prefab).name + "' at '" + ((Object)((Component)corpse).gameObject).name + "'"); } } } private static void PlayKnifeSound(ItemData knife, Vector3 pos) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) EffectList val = knife.m_shared?.m_hitEffect; if (val?.m_effectPrefabs == null) { return; } GameObject val2 = null; EffectData[] effectPrefabs = val.m_effectPrefabs; foreach (EffectData val3 in effectPrefabs) { if (!((Object)(object)val3?.m_prefab == (Object)null) && EffectFilter.IsAudioOnly(val3.m_prefab)) { val2 = val3.m_prefab; break; } } if (!((Object)(object)val2 == (Object)null)) { Object.Instantiate<GameObject>(val2, pos, Quaternion.identity); ModLog.Diag("HarvestEffects: knife sound '" + ((Object)val2).name + "' from '" + knife.m_shared.m_name + "'"); } } } internal static class HarvestKnifeIcon { private const string BadgeName = "CorpseHarvest_KnifeBadge"; private static Sprite _knifeSprite; private static bool _knifeSpriteResolved; private static HotkeyBar _hotkeyBar; private static bool _loggedGridSlotLayout; private static bool _loggedHotbarSlotLayout; public static void Tick() { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { if (InventoryGui.IsVisible()) { UpdateBackpack(); } UpdateHotbar(localPlayer); } } private static void UpdateBackpack() { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) InventoryGrid val = (((Object)(object)InventoryGui.instance != (Object)null) ? InventoryGui.instance.m_playerGrid : null); if ((Object)(object)val == (Object)null || (Object)(object)val.m_gridRoot == (Object)null) { return; } Inventory inventory = val.GetInventory(); if (inventory != null) { InventoryElement[] componentsInChildren = ((Component)val.m_gridRoot).GetComponentsInChildren<InventoryElement>(true); foreach (InventoryElement val2 in componentsInChildren) { ItemData itemAt = inventory.GetItemAt(val2.Position.x, val2.Position.y); SetBadge(((Component)val2).transform, itemAt != null && HarvestKnifeTag.IsTagged(itemAt), isHotbar: false); } } } private static void UpdateHotbar(Player player) { HotkeyBar val = FindHotkeyBar(); if ((Object)(object)val == (Object)null) { return; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory != null) { int childCount = ((Component)val).transform.childCount; for (int i = 0; i < childCount; i++) { Transform child = ((Component)val).transform.GetChild(i); ItemData itemAt = inventory.GetItemAt(i, 0); SetBadge(child, itemAt != null && HarvestKnifeTag.IsTagged(itemAt), isHotbar: true); } } } private static HotkeyBar FindHotkeyBar() { if ((Object)(object)_hotkeyBar != (Object)null) { return _hotkeyBar; } _hotkeyBar = Object.FindFirstObjectByType<HotkeyBar>(); return _hotkeyBar; } private static void SetBadge(Transform slot, bool show, bool isHotbar) { Transform val = slot.Find("CorpseHarvest_KnifeBadge"); if ((Object)(object)val == (Object)null) { if (!show) { return; } val = CreateBadge(slot, isHotbar); } ((Component)val).gameObject.SetActive(show); } private static Transform CreateBadge(Transform slot, bool isHotbar) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: 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_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) LogSlotLayoutOnce(slot, isHotbar); GameObject val = new GameObject("CorpseHarvest_KnifeBadge", new Type[1] { typeof(RectTransform) }); RectTransform component = val.GetComponent<RectTransform>(); ((Transform)component).SetParent(slot, false); component.anchorMin = new Vector2(0f, 0.5f); component.anchorMax = new Vector2(0f, 0.5f); component.pivot = new Vector2(0f, 0.5f); component.sizeDelta = new Vector2(16f, 16f); component.anchoredPosition = new Vector2(4f, 0f); Image obj = val.AddComponent<Image>(); Sprite val2 = (obj.sprite = GetKnifeSprite()); ((Graphic)obj).color = (((Object)(object)val2 != (Object)null) ? new Color(1f, 0.82f, 0.2f, 1f) : new Color(1f, 0.85f, 0.1f, 1f)); ((Graphic)obj).raycastTarget = false; return val.transform; } private static void LogSlotLayoutOnce(Transform slot, bool isHotbar) { if (isHotbar) { if (!_loggedHotbarSlotLayout) { _loggedHotbarSlotLayout = true; LogChildAnchors(slot, "icon", "amount", "durability", "equiped", "queued", "selected", "binding"); } } else if (!_loggedGridSlotLayout) { _loggedGridSlotLayout = true; LogChildAnchors(slot, "icon", "amount", "quality", "equiped", "queued", "noteleport", "food", "durability", "selected", "binding"); } } private static void LogChildAnchors(Transform slot, params string[] names) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) foreach (string text in names) { Transform val = slot.Find(text); if ((Object)(object)val == (Object)null) { ModLog.Diag("slot layout '" + text + "': not found"); continue; } RectTransform val2 = (RectTransform)(object)((val is RectTransform) ? val : null); if ((Object)(object)val2 == (Object)null) { ModLog.Diag("slot layout '" + text + "': no RectTransform"); } else { ModLog.Diag($"slot layout '{text}': anchorMin={val2.anchorMin} anchorMax={val2.anchorMax} " + $"anchoredPos={val2.anchoredPosition} sizeDelta={val2.sizeDelta}"); } } } private static Sprite GetKnifeSprite() { if (_knifeSpriteResolved) { return _knifeSprite; } Skills val = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Character)Player.m_localPlayer).GetSkills() : null); if ((Object)(object)val == (Object)null) { return null; } _knifeSprite = Traverse.Create((object)val).Method("GetSkillDef", new object[1] { (object)(SkillType)2 }).GetValue<SkillDef>()?.m_icon; _knifeSpriteResolved = true; ModLog.Diag(((Object)(object)_knifeSprite != (Object)null) ? ("knife badge icon resolved: '" + ((Object)_knifeSprite).name + "'") : "knife badge icon unavailable -> using plain dot fallback"); return _knifeSprite; } } internal static class HarvestKnifeInput { public static void Tick() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: 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_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) if (!InventoryGui.IsVisible()) { return; } KeyCode value = ModConfig.HarvestKnifeKey.Value; if ((int)value == 0 || !Input.GetKeyDown(value)) { return; } InventoryGrid val = (((Object)(object)InventoryGui.instance != (Object)null) ? InventoryGui.instance.m_playerGrid : null); Player localPlayer = Player.m_localPlayer; if (!((Object)(object)val == (Object)null) && !((Object)(object)localPlayer == (Object)null)) { Vector2i val2 = default(Vector2i); ((Vector2i)(ref val2))..ctor(ZInput.pointerPosition); ItemData item = val.GetItem(val2); if (item != null && KnifeUtil.IsKnife(item)) { HarvestKnifeTag.Toggle(((Humanoid)localPlayer).GetInventory(), item, localPlayer); } } } } internal static class HarvestKnifeTag { private const string Key = "LJS.CorpseHarvest.HarvestKnife"; public static bool IsTagged(ItemData item) { if (item?.m_customData != null) { return item.m_customData.ContainsKey("LJS.CorpseHarvest.HarvestKnife"); } return false; } public static void Toggle(Inventory inv, ItemData item, Player player) { if (inv == null || item == null || (Object)(object)player == (Object)null) { return; } bool flag = IsTagged(item); foreach (ItemData allItem in inv.GetAllItems()) { allItem.m_customData.Remove("LJS.CorpseHarvest.HarvestKnife"); } if (flag) { ((Character)player).Message((MessageType)1, HarvestText.KnifeTagCleared, 0, (Sprite)null, false); ModLog.Diag("knife tag cleared '" + item.m_shared.m_name + "'"); } else { item.m_customData["LJS.CorpseHarvest.HarvestKnife"] = "1"; ((Character)player).Message((MessageType)1, HarvestText.KnifeTagged(item), 0, (Sprite)null, false); ModLog.Diag("knife tag set '" + item.m_shared.m_name + "'"); } } public static ItemData FindTagged(Inventory inv) { if (inv == null) { return null; } foreach (ItemData allItem in inv.GetAllItems()) { if (IsTagged(allItem)) { return allItem; } } return null; } } internal static class HarvestText { private static bool Korean { get { if (Localization.instance != null) { return Localization.instance.GetSelectedLanguage() == "Korean"; } return false; } } public static string Verb { get { if (!Korean) { return "Harvest"; } return "해체"; } } public static string HoldHint { get { if (!Korean) { return " <color=grey>(hold)</color>"; } return " <color=grey>(길게 누르기)</color>"; } } public static string FallbackCorpseName { get { if (!Korean) { return "Corpse"; } return "시체"; } } public static string KnifeTagCleared { get { if (!Korean) { return "Harvest knife cleared"; } return "해체용 칼 지정 해제"; } } public static string KnifeBrokenMessage { g