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 Dismemberment v0.3.0
Dismemberment.dll
Decompiled 7 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Unity.Collections; using UnityEngine; using UnityEngine.Rendering; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("0.0.0.0")] namespace Dismemberment; public static class DismembermentApi { public const int ApiVersion = 2; public static bool OwnsWoundsFor(Character target, HitData hit) { try { if (DmConfig.Enabled == null || !DmConfig.Enabled.Value || !DmConfig.EnableWounds.Value) { return false; } if ((Object)(object)target == (Object)null || hit == null) { return false; } if (target.IsPlayer() || target.IsBoss()) { return false; } PrefabInfo info = CreatureTiers.InfoFor(target); if (CreatureTiers.TierOf(target, info) == CreatureTier.None) { return false; } return WoundGate.Classify(hit, target) != GateResult.None; } catch (Exception) { return false; } } public static bool IsLocalPlayerCarrying() { try { return LimbCarry.IsCarrying; } catch (Exception) { return false; } } public static bool WasSevered(Character target) { try { return (Object)(object)target != (Object)null && SeverPlanner.WasSevered(target); } catch (Exception) { return false; } } } internal static class CarryPose { private const float GripInset = 0.08f; private const float HangFollow = 10f; private const float VelocityFollow = 12f; private const float Trail = 0.05f; private const float MaxTrail = 0.6f; private const float Forward = 0.12f; internal static Transform RightHand(Player player) { VisEquipment val = (((Object)(object)player != (Object)null) ? ((Humanoid)player).GetVisEquipment() : null); if (!((Object)(object)val != (Object)null)) { return null; } return val.m_rightHand; } internal static bool Attach(SeveredLimb limb, Player player) { Transform val = RightHand(player); if ((Object)(object)limb == (Object)null || (Object)(object)val == (Object)null) { return false; } ((Component)limb).transform.SetParent(val, true); limb.PoseStarted = false; return true; } internal static void Apply(SeveredLimb limb, Player player, bool twoHanded, float dt) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0058: 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_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0108: 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_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008e: 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) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: 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) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: 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) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0189: 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_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: 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_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_01fc: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Unknown result type (might be due to invalid IL or missing references) //IL_0231: 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_023b: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_0246: 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_0255: Unknown result type (might be due to invalid IL or missing references) //IL_025a: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Unknown result type (might be due to invalid IL or missing references) VisEquipment visEquipment = ((Humanoid)player).GetVisEquipment(); if ((Object)(object)visEquipment == (Object)null || (Object)(object)visEquipment.m_rightHand == (Object)null) { return; } Transform transform = ((Component)limb).transform; Transform transform2 = ((Component)player).transform; float worldLength = limb.WorldLength; Vector3 val = ((((Vector3)(ref limb.Axis)).sqrMagnitude > 1E-06f) ? ((Vector3)(ref limb.Axis)).normalized : Vector3.up); if (twoHanded && (Object)(object)visEquipment.m_leftHand != (Object)null) { Vector3 val2 = (visEquipment.m_rightHand.position + visEquipment.m_leftHand.position) * 0.5f + transform2.forward * 0.1f; Quaternion val3 = (transform.rotation = Orient(val, transform2.right, Vector3.up)); transform.position = val2 - val3 * val * (worldLength * 0.5f); return; } Vector3 position = visEquipment.m_rightHand.position; if (!limb.PoseStarted) { limb.PoseStarted = true; limb.LastGrip = position; limb.GripVelocity = Vector3.zero; limb.HangDir = Vector3.down; } else if (dt > 0.0001f) { Vector3 val5 = Vector3.ClampMagnitude((position - limb.LastGrip) / dt, 20f); limb.GripVelocity = Vector3.Lerp(limb.GripVelocity, val5, 1f - Mathf.Exp(-12f * dt)); limb.LastGrip = position; } Vector3 val6 = Vector3.down - Vector3.ClampMagnitude(limb.GripVelocity * 0.05f, 0.6f) + transform2.forward * 0.12f; if (((Vector3)(ref val6)).sqrMagnitude < 0.0001f) { val6 = Vector3.down; } Vector3 val7 = Vector3.Slerp(limb.HangDir, ((Vector3)(ref val6)).normalized, 1f - Mathf.Exp(-10f * Mathf.Max(dt, 0f))); limb.HangDir = ((Vector3)(ref val7)).normalized; if (((Vector3)(ref limb.HangDir)).sqrMagnitude < 0.0001f) { limb.HangDir = Vector3.down; } Quaternion rotation = Orient(-val, limb.HangDir, transform2.forward); transform.rotation = rotation; transform.position = position + limb.HangDir * (worldLength * 0.92f); } private static Quaternion Orient(Vector3 localDir, Vector3 worldDir, Vector3 worldHint) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_001e: 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_0023: 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_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_002b: 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_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: 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_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0062: 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_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0069: 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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005a: 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) Vector3 val = ((Mathf.Abs(Vector3.Dot(localDir, Vector3.up)) < 0.9f) ? Vector3.up : Vector3.forward); Vector3 val2 = Vector3.ProjectOnPlane(worldHint, worldDir); if (((Vector3)(ref val2)).sqrMagnitude < 1E-06f) { val2 = Vector3.ProjectOnPlane(Vector3.forward, worldDir); } if (((Vector3)(ref val2)).sqrMagnitude < 1E-06f) { val2 = Vector3.ProjectOnPlane(Vector3.right, worldDir); } return Quaternion.LookRotation(worldDir, val2) * Quaternion.Inverse(Quaternion.LookRotation(localDir, val)); } } internal static class CarrySync { private sealed class Carrier { internal Player Player; internal bool Seen; internal uint Revision; internal string Hold = ""; internal int ThrowSeq; internal SeveredLimb Copy; internal bool HasCopy; internal LimbIdentity Identity; internal int Rebuilds; internal bool Present; } private struct ReleaseEvent { internal int Seq; internal long Time; internal string Identity; internal Vector3 Position; internal Quaternion Rotation; internal Vector3 Velocity; internal Vector3 Spin; } internal static readonly int KeyHold = StringExtensionMethods.GetStableHashCode("dm_hold"); internal static readonly int KeyThrow = StringExtensionMethods.GetStableHashCode("dm_thr"); private const byte ThrowFormat = 1; private const float PollSeconds = 0.25f; private const double FreshMilliseconds = 3000.0; private const int MaxRebuilds = 2; private static readonly List<Carrier> Carriers = new List<Carrier>(); private static float nextPoll; private static int throwSeq; internal static int CopyCount { get { int num = 0; for (int i = 0; i < Carriers.Count; i++) { if ((Object)(object)Carriers[i].Copy != (Object)null) { num++; } } return num; } } internal static void WriteHold(Player player, string identity) { try { ZDO val = OwnZdo(player); if (val != null) { val.Set(KeyHold, identity ?? ""); } } catch (Exception ex) { Plugin.LogOnce("carry-write-hold", "Dismemberment: writing dm_hold threw (" + ex.Message + "); other players may not see the carried limb."); } } internal static void WriteThrow(Player player, string identity, Vector3 position, Quaternion rotation, Vector3 velocity, Vector3 spin) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown //IL_004b: 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_0059: 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) try { ZDO val = OwnZdo(player); if (val != null) { ZPackage val2 = new ZPackage(); val2.Write((byte)1); val2.Write(++throwSeq); val2.Write(NowMilliseconds()); val2.Write(identity ?? ""); val2.Write(position); val2.Write(rotation); val2.Write(velocity); val2.Write(spin); val.Set(KeyThrow, val2.GetArray()); } } catch (Exception ex) { Plugin.LogOnce("carry-write-throw", "Dismemberment: writing dm_thr threw (" + ex.Message + "); other players may not see the throw."); } } private static ZDO OwnZdo(Player player) { ZNetView val = Reflect.NView((Character)(object)player); if (!((Object)(object)val != (Object)null) || !val.IsValid() || !val.IsOwner()) { return null; } return val.GetZDO(); } internal static void Tick() { if (Time.time < nextPoll) { return; } nextPoll = Time.time + 0.25f; try { if (!LimbCarry.Enabled) { if (Carriers.Count > 0) { Reset(); } return; } for (int i = 0; i < Carriers.Count; i++) { Carriers[i].Present = false; } List<Player> allPlayers = Player.GetAllPlayers(); Player localPlayer = Player.m_localPlayer; for (int j = 0; j < allPlayers.Count; j++) { Player val = allPlayers[j]; if (!((Object)(object)val == (Object)null) && val != localPlayer) { Carrier carrier = Find(val); if (carrier == null) { carrier = new Carrier { Player = val }; Carriers.Add(carrier); } carrier.Present = true; Poll(carrier); } } for (int num = Carriers.Count - 1; num >= 0; num--) { if (!Carriers[num].Present) { RemoveCopy(Carriers[num], "player gone"); Carriers.RemoveAt(num); } } } catch (Exception ex) { Plugin.LogOnce("carry-sync", "Dismemberment: reading other players' carried limbs threw (" + ex.GetType().Name + ": " + ex.Message + ").\n" + ex.StackTrace); } } internal static void LateTick() { if (Carriers.Count == 0) { return; } float deltaTime = Time.deltaTime; for (int i = 0; i < Carriers.Count; i++) { Carrier carrier = Carriers[i]; if (!((Object)(object)carrier.Copy == (Object)null) && !((Object)(object)carrier.Player == (Object)null) && carrier.Identity != null) { try { CarryPose.Apply(carrier.Copy, carrier.Player, carrier.Identity.TwoHanded, deltaTime); } catch (Exception ex) { Plugin.LogOnce("carry-pose-remote", "Dismemberment: posing another player's limb threw (" + ex.Message + ")."); } } } } private static Carrier Find(Player player) { for (int i = 0; i < Carriers.Count; i++) { if (Carriers[i].Player == player) { return Carriers[i]; } } return null; } private static void Poll(Carrier carrier) { ZNetView val = Reflect.NView((Character)(object)carrier.Player); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); if (val2 == null) { return; } if (carrier.HasCopy && (Object)(object)carrier.Copy == (Object)null) { Recover(carrier); } if (carrier.Seen && val2.DataRevision == carrier.Revision) { return; } carrier.Revision = val2.DataRevision; string text = val2.GetString(KeyHold, ""); ReleaseEvent release; bool flag = TryRead(val2.GetByteArray(KeyThrow, (byte[])null), out release); if (!carrier.Seen) { carrier.Seen = true; carrier.ThrowSeq = (flag ? release.Seq : 0); } else if (flag && release.Seq != carrier.ThrowSeq) { carrier.ThrowSeq = release.Seq; if (Fresh(release.Time)) { Launch(carrier, release); } } if (!string.Equals(text, carrier.Hold, StringComparison.Ordinal)) { carrier.Hold = text; ApplyHold(carrier); } } private static void Launch(Carrier carrier, ReleaseEvent release) { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) LimbIdentity limbIdentity = LimbIdentity.Parse(release.Identity); if (limbIdentity == null) { return; } SeveredLimb severedLimb; string text; if ((Object)(object)carrier.Copy != (Object)null && carrier.Identity != null && carrier.Identity.Key == limbIdentity.Key) { severedLimb = carrier.Copy; carrier.Copy = null; carrier.HasCopy = false; carrier.Identity = null; text = "hand"; } else { severedLimb = TakeOwnCopy(limbIdentity); text = (((Object)(object)severedLimb != (Object)null) ? "own" : "rebuilt"); if ((Object)(object)severedLimb == (Object)null) { severedLimb = LimbProps.Rebuild(limbIdentity); } if ((Object)(object)severedLimb == (Object)null) { return; } } LimbProps.Give(severedLimb, release.Position, release.Rotation, release.Velocity, release.Spin); if (Plugin.LogEvents) { Plugin.Event("remote-release player=" + carrier.Player.GetPlayerName() + " " + severedLimb.DisplayName + " key=" + limbIdentity.Key + " copy=" + text + " speed=" + ((Vector3)(ref release.Velocity)).magnitude.ToString("0.0", CultureInfo.InvariantCulture)); } } private static void ApplyHold(Carrier carrier) { LimbIdentity limbIdentity = ((carrier.Hold.Length > 0) ? LimbIdentity.Parse(carrier.Hold) : null); if (carrier.HasCopy && (limbIdentity == null || carrier.Identity == null || carrier.Identity.Key != limbIdentity.Key)) { RemoveCopy(carrier, "hands empty"); } if (limbIdentity == null || carrier.HasCopy) { return; } SeveredLimb severedLimb = TakeOwnCopy(limbIdentity); string text = (((Object)(object)severedLimb != (Object)null) ? "own" : "rebuilt"); if ((Object)(object)severedLimb == (Object)null) { severedLimb = LimbProps.Rebuild(limbIdentity); } if ((Object)(object)severedLimb == (Object)null) { return; } if (!Hold(carrier, severedLimb, limbIdentity)) { Object.Destroy((Object)(object)((Component)severedLimb).gameObject); return; } carrier.Rebuilds = 0; if (Plugin.LogEvents) { Plugin.Event("remote-hold player=" + carrier.Player.GetPlayerName() + " " + severedLimb.DisplayName + " key=" + limbIdentity.Key + " copy=" + text + " hands=" + ((!limbIdentity.TwoHanded) ? 1 : 2)); } } private static bool Hold(Carrier carrier, SeveredLimb limb, LimbIdentity id) { LimbProps.PrepareForHand(limb); if (!CarryPose.Attach(limb, carrier.Player)) { return false; } carrier.Copy = limb; carrier.HasCopy = true; carrier.Identity = id; return true; } private static SeveredLimb TakeOwnCopy(LimbIdentity id) { if (!id.Matchable) { return null; } SeveredLimb severedLimb = LimbProps.FindWorldLimb(id.Key); if ((Object)(object)severedLimb == (Object)null) { return null; } LimbProps.Detach(((Component)severedLimb).gameObject, out var tied); if (tied && !Compat.UntieFromDeadRemain(((Component)severedLimb).gameObject)) { SeveredLimb result = LimbProps.Rebuild(id); Object.Destroy((Object)(object)((Component)severedLimb).gameObject); return result; } return severedLimb; } private static void Recover(Carrier carrier) { carrier.HasCopy = false; LimbIdentity identity = carrier.Identity; carrier.Identity = null; if (identity != null && carrier.Rebuilds < 2) { carrier.Rebuilds++; SeveredLimb severedLimb = LimbProps.Rebuild(identity); if (!((Object)(object)severedLimb == (Object)null) && !Hold(carrier, severedLimb, identity)) { Object.Destroy((Object)(object)((Component)severedLimb).gameObject); } } } private static void RemoveCopy(Carrier carrier, string reason) { bool num = (Object)(object)carrier.Copy != (Object)null; if (num) { Object.Destroy((Object)(object)((Component)carrier.Copy).gameObject); } carrier.Copy = null; carrier.HasCopy = false; carrier.Identity = null; if (num && Plugin.LogEvents) { Plugin.Event("remote-clear reason=" + reason); } } private static bool TryRead(byte[] data, out ReleaseEvent release) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0057: 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_0063: 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_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) release = default(ReleaseEvent); if (data == null || data.Length == 0) { return false; } try { ZPackage val = new ZPackage(data); if (val.ReadByte() != 1) { return false; } release.Seq = val.ReadInt(); release.Time = val.ReadLong(); release.Identity = val.ReadString(); release.Position = val.ReadVector3(); release.Rotation = val.ReadQuaternion(); release.Velocity = val.ReadVector3(); release.Spin = val.ReadVector3(); return true; } catch (Exception) { return false; } } private static long NowMilliseconds() { if (!((Object)(object)ZNet.instance != (Object)null)) { return 0L; } return (long)(ZNet.instance.GetTimeSeconds() * 1000.0); } private static bool Fresh(long time) { if ((Object)(object)ZNet.instance == (Object)null || time <= 0) { return false; } double num = ZNet.instance.GetTimeSeconds() * 1000.0 - (double)time; if (num < 3000.0) { return num > -3000.0; } return false; } internal static void Reset() { for (int i = 0; i < Carriers.Count; i++) { if ((Object)(object)Carriers[i].Copy != (Object)null) { Object.Destroy((Object)(object)((Component)Carriers[i].Copy).gameObject); } } Carriers.Clear(); nextPoll = 0f; } } internal static class LimbCarry { private enum Phase { None, Reaching, Holding, Throwing } private const float AttachDelay = 0.2f; private const float ReleaseDelay = 0.51f; internal const float TwoHandedLength = 1.1f; private const float TwoHandedThrow = 0.6f; private const int TorchState = 5; private const int FeasterState = 16; private static Phase phase; private static Player owner; private static ZSyncAnimation ownerAnim; private static SeveredLimb held; private static LimbIdentity heldIdentity; private static string heldText = ""; private static bool twoHanded; private static float phaseStart; private static bool waitAttackRelease; private static bool recovered; private static bool keysBroken; private static bool bound; private static MethodInfo setupAnimationState; private static MethodInfo doInteractAnimation; private static MethodInfo takeInput; private static readonly object[] OneArg = new object[1]; internal static int ForcedState = -1; internal static string LastRefusal = ""; internal static bool Enabled { get { if (DmConfig.Enabled != null && DmConfig.Enabled.Value && DmConfig.EnableCarrying != null) { return DmConfig.EnableCarrying.Value; } return false; } } internal static bool IsCarrying => phase != Phase.None; internal static bool BlocksAttacks { get { if (phase == Phase.None) { return waitAttackRelease; } return true; } } internal static bool IsCarrierAnimator(ZSyncAnimation anim) { return anim == ownerAnim; } internal static bool IsCarrier(Humanoid humanoid) { if (phase != Phase.None) { return (object)humanoid == owner; } return false; } internal static string Describe() { if (phase == Phase.None || (Object)(object)held == (Object)null) { return "nothing"; } return held.DisplayName + " (" + phase.ToString().ToLowerInvariant() + (twoHanded ? ", two hands" : ", right hand") + ", key " + held.Key + ")"; } internal static bool PickUp(Player player, SeveredLimb limb, bool playReach, string via) { //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) LastRefusal = ""; if (!Enabled) { return Refuse("carrying is off (8 - Carrying, EnableCarrying)"); } if ((Object)(object)player == (Object)null || (Object)(object)limb == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer) { return Refuse("not the local player"); } if (phase == Phase.Reaching || phase == Phase.Throwing) { return Refuse("busy " + phase.ToString().ToLowerInvariant()); } if (!LimbProps.IsWorldLimb(((Component)limb).gameObject)) { return Refuse("that limb is not lying in the world"); } string text = Guard(player); if (text == null && (((Character)player).InAttack() || ((Character)player).InDodge())) { text = "attacking"; } if (text == null && (((Character)player).InMinorAction() || Reflect.UseItemTime((Humanoid)(object)player) > 0f)) { text = "eating or busy"; } if (text != null) { return Refuse(text); } if (Compat.IsLocalPlayerDragging()) { ((Character)player).Message((MessageType)2, "Let go of the body first", 0, (Sprite)null, false); return Refuse("dragging a carcass (The Dead Remain)"); } if (!LimbProps.Detach(((Component)limb).gameObject, out var tied)) { return Refuse("that limb is not lying in the world"); } bool flag = false; if (tied && !Compat.UntieFromDeadRemain(((Component)limb).gameObject)) { SeveredLimb severedLimb = LimbProps.Rebuild(LimbIdentity.Of(limb, twoHanded: false)); if ((Object)(object)severedLimb != (Object)null) { ((Component)severedLimb).transform.SetPositionAndRotation(((Component)limb).transform.position, ((Component)limb).transform.rotation); Object.Destroy((Object)(object)((Component)limb).gameObject); limb = severedLimb; flag = true; } } if (phase == Phase.Holding) { Release(DropVelocity(player), Vector3.zero, "swap"); } LimbProps.PrepareForHand(limb); owner = player; ownerAnim = ((Component)player).GetComponent<ZSyncAnimation>(); held = limb; twoHanded = DmConfig.CarryHeavyLimbsTwoHanded.Value && limb.WorldLength >= 1.1f; heldIdentity = LimbIdentity.Of(limb, twoHanded); heldText = heldIdentity.Format(); phase = Phase.Reaching; phaseStart = Time.time; recovered = false; SetStance(twoHanded ? 16 : 5); ((Humanoid)player).HideHandItems(false, false); CarrySync.WriteHold(player, heldText); if (playReach) { PlayReach(player, ((Component)limb).gameObject); } if (Plugin.LogEvents) { Plugin.Event("pickup " + limb.DisplayName + " key=" + limb.Key + " via=" + via + " hands=" + ((!twoHanded) ? 1 : 2) + " length=" + limb.WorldLength.ToString("0.00", CultureInfo.InvariantCulture) + " mass=" + limb.Mass.ToString("0.0", CultureInfo.InvariantCulture) + " tied=" + tied + (flag ? " rebuilt" : "")); } return true; } private static bool Refuse(string why) { LastRefusal = why; if (Plugin.LogEvents) { Plugin.Event("pickup-refused " + why); } return false; } private static string Guard(Player player) { if (((Character)player).IsDead()) { return "dead"; } if (((Character)player).IsSwimming()) { return "swimming"; } if (((Character)player).IsAttached()) { return "attached"; } if (((Character)player).InPlaceMode()) { return "building"; } if (((Character)player).IsTeleporting()) { return "teleporting"; } return null; } internal static void Tick() { try { Player localPlayer = Player.m_localPlayer; if (phase != Phase.None && ((Object)(object)localPlayer == (Object)null || (Object)(object)localPlayer != (Object)(object)owner)) { Abandon("player gone"); return; } if (waitAttackRelease && !ZInput.GetButton("Attack") && !ZInput.GetButton("JoyAttack")) { waitAttackRelease = false; } if (!((Object)(object)localPlayer == (Object)null)) { if (phase != Phase.None) { TickCarry(localPlayer); } if (!keysBroken) { HandleKeys(localPlayer); } } } catch (Exception ex) { Plugin.LogOnce("carry-tick", "Dismemberment: carrying threw (" + ex.GetType().Name + ": " + ex.Message + "); the limb is dropped.\n" + ex.StackTrace); SafeAbandon(); } } private static void TickCarry(Player player) { if (!Enabled) { Drop("disabled"); return; } if ((Object)(object)held == (Object)null && !Recover(player)) { End("limb lost"); return; } string text = Guard(player); if (text != null) { Drop(text); return; } if ((Object)(object)ownerAnim != (Object)null && ForcedState >= 0) { ownerAnim.SetFloat(CarryHooks.StatefHash, (float)ForcedState); ownerAnim.SetInt(CarryHooks.StateiHash, ForcedState); } float num = Time.time - phaseStart; if (phase == Phase.Reaching && num >= 0.2f) { Attach(player); } else if (phase == Phase.Throwing && num >= 0.51f) { ReleaseThrow(player); } } internal static void LateTick() { if ((phase != Phase.Holding && phase != Phase.Throwing) || (Object)(object)held == (Object)null || (Object)(object)owner == (Object)null) { return; } try { CarryPose.Apply(held, owner, twoHanded, Time.deltaTime); } catch (Exception ex) { Plugin.LogOnce("carry-pose", "Dismemberment: posing the carried limb threw (" + ex.Message + ")."); } } private static void HandleKeys(Player player) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) if (!Enabled) { return; } bool num = phase == Phase.Holding && (ZInput.GetButtonDown("Use") || ZInput.GetButtonDown("JoyUse")); bool flag = KeyDown(DmConfig.PickUpKey.Value); if ((!num && !flag) || !TakesInput(player) || Hud.InRadial()) { return; } GameObject hoverObject = ((Humanoid)player).GetHoverObject(); if (flag) { SeveredLimb severedLimb = (((Object)(object)hoverObject != (Object)null) ? hoverObject.GetComponentInParent<SeveredLimb>() : null); if ((Object)(object)severedLimb != (Object)null) { PickUp(player, severedLimb, playReach: true, "key"); } else if (phase == Phase.Holding) { Drop("key"); } } else if ((Object)(object)hoverObject == (Object)null || hoverObject.GetComponentInParent<Interactable>() == null) { Drop("use"); } } private static bool KeyDown(KeyboardShortcut shortcut) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) try { KeyCode mainKey = ((KeyboardShortcut)(ref shortcut)).MainKey; if ((int)mainKey == 0) { return false; } if (((KeyboardShortcut)(ref shortcut)).IsDown()) { return true; } if (!Input.GetKeyDown(mainKey)) { return false; } foreach (KeyCode modifier in ((KeyboardShortcut)(ref shortcut)).Modifiers) { if (!Input.GetKey(modifier)) { return false; } } return true; } catch (Exception ex) { keysBroken = true; Plugin.LogOnce("carry-key", "Dismemberment: reading PickUpKey threw (" + ex.Message + "); only E picks up limbs now."); return false; } } private static void Attach(Player player) { if (!CarryPose.Attach(held, player)) { Drop("no hand"); return; } phase = Phase.Holding; CarryPose.Apply(held, player, twoHanded, 0f); if (Plugin.LogEvents) { Plugin.Event("carry " + held.DisplayName + " key=" + held.Key + " hands=" + ((!twoHanded) ? 1 : 2)); } } private static bool Recover(Player player) { if (recovered || heldIdentity == null) { return false; } recovered = true; SeveredLimb severedLimb = LimbProps.Rebuild(heldIdentity); if ((Object)(object)severedLimb == (Object)null) { return false; } LimbProps.PrepareForHand(severedLimb); held = severedLimb; if (!CarryPose.Attach(severedLimb, player)) { Object.Destroy((Object)(object)((Component)severedLimb).gameObject); held = null; return false; } if (phase == Phase.Reaching) { phase = Phase.Holding; } if (Plugin.LogEvents) { Plugin.Event("carry-recovered " + severedLimb.DisplayName + " key=" + severedLimb.Key); } return true; } internal static bool OnAttack(Humanoid attacker, bool secondary) { if (secondary || phase != Phase.Holding) { return true; } if (((Character)attacker).InAttack() || ((Character)attacker).InDodge() || !((Character)attacker).CanMove() || ((Character)attacker).IsKnockedBack() || ((Character)attacker).IsStaggering() || ((Character)attacker).InMinorAction()) { return false; } StartThrow(); return true; } internal static bool StartThrow() { if (phase != Phase.Holding || (Object)(object)owner == (Object)null || (Object)(object)ownerAnim == (Object)null || (Object)(object)held == (Object)null) { return false; } ownerAnim.SetTrigger("throw_bomb"); phase = Phase.Throwing; phaseStart = Time.time; waitAttackRelease = true; if (Plugin.LogEvents) { Plugin.Event("throw-start " + held.DisplayName + " key=" + held.Key); } return true; } private static void ReleaseThrow(Player player) { //IL_0022: 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_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: 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) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0091: 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_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) Camera mainCamera = Utils.GetMainCamera(); Vector3 val = (((Object)(object)mainCamera != (Object)null) ? ((Component)mainCamera).transform.forward : ((Component)player).transform.forward) + Vector3.up * 0.15f; Vector3 normalized = ((Vector3)(ref val)).normalized; float num = Mathf.Sqrt(Mathf.Max(1f, held.Mass)); float num2 = DmConfig.ThrowSpeed.Value / num * (twoHanded ? 0.6f : 1f); Release(spin: ((Component)player).transform.right * (10f / num), velocity: normalized * num2 + ((Character)player).GetVelocity(), reason: "throw"); } internal static void Drop(string reason) { //IL_0021: 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_0026: Unknown result type (might be due to invalid IL or missing references) if (phase != Phase.None) { Release(((Object)(object)owner != (Object)null) ? DropVelocity(owner) : Vector3.zero, Vector3.zero, reason); } } private static Vector3 DropVelocity(Player player) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //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_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0029: 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_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0069: 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_0079: Unknown result type (might be due to invalid IL or missing references) //IL_005b: 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_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) Vector3 val = ((Component)player).transform.forward * 0.4f; Vector3 val2 = (((Object)(object)held != (Object)null) ? ((Component)held).transform.position : (((Component)player).transform.position + Vector3.up)); Rigidbody val3 = LimbProps.VehicleUnder(val2); if ((Object)(object)val3 != (Object)null) { return val3.GetPointVelocity(val2) + val; } return ((Character)player).GetVelocity() * 0.5f + val; } private static void Release(Vector3 velocity, Vector3 spin, string reason) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: 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_0039: 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_003d: 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) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0051: 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_0054: 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) Player val = owner; SeveredLimb severedLimb = held; string identity = heldText; ClearCarry(val); if (!((Object)(object)severedLimb == (Object)null)) { Vector3 position = ((Component)severedLimb).transform.position; Quaternion rotation = ((Component)severedLimb).transform.rotation; LimbProps.Give(severedLimb, position, rotation, velocity, spin); if ((Object)(object)val != (Object)null) { CarrySync.WriteThrow(val, identity, position, rotation, velocity, spin); } if (Plugin.LogEvents) { bool flag = reason == "throw"; Plugin.Event((flag ? "throw " : "drop ") + severedLimb.DisplayName + " key=" + severedLimb.Key + (flag ? "" : (" reason=" + reason)) + " speed=" + ((Vector3)(ref velocity)).magnitude.ToString("0.0", CultureInfo.InvariantCulture)); } } } private static void End(string reason) { ClearCarry(owner); if (Plugin.LogEvents) { Plugin.Event("carry-end reason=" + reason); } } internal static void Discard(string reason) { if (phase != Phase.None) { SeveredLimb severedLimb = held; End(reason); if ((Object)(object)severedLimb != (Object)null) { Object.Destroy((Object)(object)((Component)severedLimb).gameObject); } } } private static void ClearCarry(Player player) { phase = Phase.None; held = null; heldIdentity = null; heldText = ""; twoHanded = false; SetStance(-1); if ((Object)(object)player != (Object)null) { CarrySync.WriteHold(player, ""); } owner = null; ownerAnim = null; } private static void Abandon(string reason) { SeveredLimb severedLimb = held; phase = Phase.None; held = null; heldIdentity = null; heldText = ""; twoHanded = false; ForcedState = -1; owner = null; ownerAnim = null; waitAttackRelease = false; if ((Object)(object)severedLimb != (Object)null) { Object.Destroy((Object)(object)((Component)severedLimb).gameObject); } if (Plugin.LogEvents) { Plugin.Event("carry-end reason=" + reason); } } private static void SafeAbandon() { try { if (phase != Phase.None) { Drop("error"); } } catch (Exception) { Abandon("error"); } } internal static void Reset() { Abandon("world reset"); } private static void SetStance(int state) { int forcedState = ForcedState; ForcedState = state; if (state >= 0) { if (!((Object)(object)ownerAnim == (Object)null)) { ownerAnim.SetFloat(CarryHooks.StatefHash, (float)state); ownerAnim.SetInt(CarryHooks.StateiHash, state); } } else if (forcedState >= 0 && (Object)(object)owner != (Object)null) { Bind(); Invoke(setupAnimationState, owner, null, "SetupAnimationState"); } } private static void PlayReach(Player player, GameObject target) { Bind(); OneArg[0] = target; Invoke(doInteractAnimation, player, OneArg, "DoInteractAnimation"); OneArg[0] = null; } private static bool TakesInput(Player player) { Bind(); if (takeInput == null) { return !Console.IsVisible(); } try { object obj = takeInput.Invoke(player, null); return obj is bool && (bool)obj; } catch (Exception ex) { Plugin.LogOnce("carry-takeinput", "Dismemberment: Player.TakeInput threw (" + ex.Message + ")."); takeInput = null; return !Console.IsVisible(); } } private static void Invoke(MethodInfo method, object instance, object[] args, string name) { if (method == null) { return; } try { method.Invoke(instance, args); } catch (Exception ex) { Exception ex2 = ex.InnerException ?? ex; Plugin.LogOnce("carry-invoke-" + name, "Dismemberment: " + name + " threw (" + ex2.Message + ")."); } } private static void Bind() { if (!bound) { bound = true; setupAnimationState = AccessTools.Method(typeof(Humanoid), "SetupAnimationState", Type.EmptyTypes, (Type[])null); doInteractAnimation = AccessTools.Method(typeof(Humanoid), "DoInteractAnimation", new Type[1] { typeof(GameObject) }, (Type[])null); takeInput = AccessTools.Method(typeof(Player), "TakeInput", Type.EmptyTypes, (Type[])null); if (setupAnimationState == null || doInteractAnimation == null || takeInput == null) { Plugin.LogOnce("carry-bind", "Dismemberment: a Humanoid/Player method carrying uses is missing in this Valheim build (stance restore, reach gesture or input check); carrying still works without it."); } } } } internal sealed class LimbIdentity { private const string Version = "1"; internal const int FlagTwoHanded = 1; internal string SourceKey = ""; internal string Bone = ""; internal string RagdollPrefab = ""; internal string CreaturePrefab = ""; internal float Scale = 1f; internal int Flags; internal string Key => KeyOf(SourceKey, Bone); internal bool Matchable => SourceKey.Length > 0; internal bool TwoHanded => (Flags & 1) != 0; internal static string SourceKeyOf(ZDOID id) { if (!((ZDOID)(ref id)).IsNone()) { return ((ZDOID)(ref id)).UserID.ToString(CultureInfo.InvariantCulture) + ":" + ((ZDOID)(ref id)).ID.ToString(CultureInfo.InvariantCulture); } return ""; } internal static string KeyOf(string sourceKey, string bone) { return sourceKey + "|" + bone; } internal static LimbIdentity Of(SeveredLimb limb, bool twoHanded) { return new LimbIdentity { SourceKey = (limb.SourceKey ?? ""), Bone = (limb.Bone ?? ""), RagdollPrefab = (limb.RagdollPrefab ?? ""), CreaturePrefab = (limb.CreaturePrefab ?? ""), Scale = limb.Scale, Flags = (twoHanded ? 1 : 0) }; } internal string Format() { return "1|" + SourceKey + "|" + Bone + "|" + RagdollPrefab + "|" + CreaturePrefab + "|" + Scale.ToString("0.###", CultureInfo.InvariantCulture) + "|" + Flags.ToString(CultureInfo.InvariantCulture); } internal static LimbIdentity Parse(string text) { if (string.IsNullOrEmpty(text)) { return null; } string[] array = text.Split(new char[1] { '|' }); if (array.Length < 7 || array[0] != "1" || array[2].Length == 0) { return null; } LimbIdentity limbIdentity = new LimbIdentity { SourceKey = array[1], Bone = array[2], RagdollPrefab = array[3], CreaturePrefab = array[4] }; if (float.TryParse(array[5], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && result > 0.01f && result < 100f) { limbIdentity.Scale = result; } if (int.TryParse(array[6], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2)) { limbIdentity.Flags = result2; } return limbIdentity; } internal static bool TryParseSource(string sourceKey, out ZDOID id) { //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_005b: 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) id = ZDOID.None; if (string.IsNullOrEmpty(sourceKey)) { return false; } int num = sourceKey.IndexOf(':'); if (num <= 0) { return false; } if (!long.TryParse(sourceKey.Substring(0, num), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return false; } if (!uint.TryParse(sourceKey.Substring(num + 1), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2)) { return false; } try { id = new ZDOID(result, result2); return true; } catch (Exception) { return false; } } } internal sealed class SeveredLimb : MonoBehaviour, Hoverable, Interactable { internal string SourceKey = ""; internal string Bone = ""; internal string RagdollPrefab = ""; internal string CreaturePrefab = ""; internal float Scale = 1f; internal float Mass = 1f; internal Vector3 Axis = Vector3.up; internal float Length = 0.3f; internal Collider Collider; internal Rigidbody Body; internal bool BoundsSettled; internal bool Rebuilt; internal bool PoseStarted; internal Vector3 HangDir = Vector3.down; internal Vector3 LastGrip; internal Vector3 GripVelocity; private const float HoverReachOffset = -1.5f; private string key; private string displayName; private static readonly Dictionary<string, string> CreatureNames = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); private static readonly string[] Quadrupeds = new string[11] { "Wolf", "Boar", "Deer", "Lox", "Asksvin", "Bjorn", "Unbjorn", "Hare", "Moose", "Neck", "Seeker" }; internal string Key { get { if (key == null) { key = LimbIdentity.KeyOf(SourceKey, Bone); } return key; } } internal bool Matchable => SourceKey.Length > 0; internal float RootScale { get { if (!Rebuilt) { return 1f; } return Scale; } } internal float WorldLength => Length * RootScale; internal string DisplayName { get { if (displayName == null) { string text = CreatureName(CreaturePrefab); displayName = "Severed " + LimbWord(Bone, CreaturePrefab) + ((text.Length > 0) ? (" of " + text) : ""); } return displayName; } } internal void SetIdentity(LimbIdentity id) { SourceKey = id.SourceKey; Bone = id.Bone; RagdollPrefab = id.RagdollPrefab; CreaturePrefab = id.CreaturePrefab; Scale = id.Scale; key = null; displayName = null; } public string GetHoverText() { try { if (!LimbCarry.Enabled) { return ""; } return Localize(DisplayName + "\n[<color=yellow><b>$KEY_Use</b></color>] Pick up"); } catch (Exception ex) { Plugin.LogOnce("limb-hover", "Dismemberment: limb hover text threw (" + ex.Message + ")."); return ""; } } public string GetHoverName() { try { return DisplayName; } catch (Exception) { return "Severed limb"; } } public float GetHoverOffset() { return -1.5f; } public bool Interact(Humanoid user, bool hold, bool alt) { if (hold) { return false; } try { Player val = (Player)(object)((user is Player) ? user : null); if ((Object)(object)val == (Object)null || (Object)(object)val != (Object)(object)Player.m_localPlayer) { return false; } return LimbCarry.PickUp(val, this, playReach: false, "use"); } catch (Exception ex) { Plugin.LogOnce("limb-interact", "Dismemberment: picking up a limb threw (" + ex.GetType().Name + ": " + ex.Message + ").\n" + ex.StackTrace); return false; } } public bool UseItem(Humanoid user, ItemData item) { return false; } private static string Localize(string text) { if (Localization.instance == null) { return text; } return Localization.instance.Localize(text); } private static string CreatureName(string prefab) { if (string.IsNullOrEmpty(prefab)) { return ""; } if (CreatureNames.TryGetValue(prefab, out var value)) { return value; } value = prefab; GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(prefab) : null); Character val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<Character>() : null); if ((Object)(object)val2 != (Object)null && !string.IsNullOrEmpty(val2.m_name)) { value = Localize(val2.m_name); } CreatureNames[prefab] = value; return value; } private static string LimbWord(string bone, string creature) { string text = (bone ?? "").ToLowerInvariant(); if (text.Contains("head") || text.Contains("neck")) { return "head"; } if (text.Contains("wing")) { return "wing"; } if (text.Contains("flipper")) { return "flipper"; } if (text.Contains("tail")) { return "tail"; } bool flag = text.Contains("arm") || text.Contains("hand") || text.Contains("shoulder") || text.Contains("clavicle"); bool flag2 = text.Contains("leg") || text.Contains("thigh") || text.Contains("calf") || text.Contains("foot") || text.Contains("knee"); if (!flag && !flag2) { return "limb"; } if (text.Contains("front")) { return "foreleg"; } if (text.Contains("back") || text.Contains("hind")) { return "hind leg"; } if (IsQuadruped(creature)) { if (!flag) { return "hind leg"; } return "foreleg"; } if (!flag) { return "leg"; } return "arm"; } private static bool IsQuadruped(string creature) { if (string.IsNullOrEmpty(creature)) { return false; } for (int i = 0; i < Quadrupeds.Length; i++) { if (creature.StartsWith(Quadrupeds[i], StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } } internal static class Compat { public const string CrimsonBloodGuid = "marc.valheim.crimsonblood"; public const string DeadRemainGuid = "marc.valheim.thedeadremain"; private static MethodInfo tieToCarcassOf; private static readonly object[] TieArgs = new object[2]; private static MethodInfo untie; private static MethodInfo isLocalPlayerDragging; private static readonly object[] UntieArgs = new object[1]; public static bool CrimsonBloodLoaded { get; private set; } public static bool DeadRemainLoaded { get; private set; } internal static bool CanUntie => untie != null; public static void Detect() { try { CrimsonBloodLoaded = Chainloader.PluginInfos.ContainsKey("marc.valheim.crimsonblood"); DeadRemainLoaded = Chainloader.PluginInfos.ContainsKey("marc.valheim.thedeadremain"); } catch (Exception ex) { Plugin.LogOnce("compat", "Dismemberment: could not list loaded plugins (" + ex.Message + ")."); } if (CrimsonBloodLoaded) { Plugin.Log.LogInfo((object)"Dismemberment: Crimson Blood found; it draws hit blood and kill pools, this mod draws wounds and severs."); } if (!DeadRemainLoaded) { return; } Type type = AccessTools.TypeByName("TheDeadRemain.DeadRemainApi"); if (type != null) { tieToCarcassOf = AccessTools.Method(type, "TieToCarcassOf", new Type[2] { typeof(GameObject), typeof(GameObject) }, (Type[])null); } if (tieToCarcassOf != null) { Plugin.Log.LogInfo((object)"Dismemberment: The Dead Remain found; severed limbs stay with their carcass until it is down to bones."); } else { Plugin.Log.LogWarning((object)"Dismemberment: The Dead Remain found but its TieToCarcassOf API is missing (an older build); severed limbs keep Dismemberment's own lifetime (6 - Severed Limbs)."); } if (type != null) { untie = type.GetMethod("Untie", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(GameObject) }, null); isLocalPlayerDragging = type.GetMethod("IsLocalPlayerDragging", BindingFlags.Static | BindingFlags.Public, null, Type.EmptyTypes, null); if (untie != null && untie.ReturnType != typeof(bool)) { untie = null; } if (isLocalPlayerDragging != null && isLocalPlayerDragging.ReturnType != typeof(bool)) { isLocalPlayerDragging = null; } } if (tieToCarcassOf != null && untie == null) { Plugin.Log.LogInfo((object)"Dismemberment: The Dead Remain has no Untie API (an older build); a limb picked up off a carcass is carried as an untied copy."); } } internal static bool UntieFromDeadRemain(GameObject limb) { if (untie == null || (Object)(object)limb == (Object)null) { return false; } try { UntieArgs[0] = limb; object obj = untie.Invoke(null, UntieArgs); return obj is bool && (bool)obj; } catch (Exception ex) { Exception ex2 = ex.InnerException ?? ex; Plugin.LogOnce("untie", "Dismemberment: The Dead Remain's Untie threw (" + ex2.Message + "); limbs taken off carcasses are carried as untied copies."); untie = null; return false; } finally { UntieArgs[0] = null; } } internal static bool IsLocalPlayerDragging() { if (isLocalPlayerDragging == null) { return false; } try { object obj = isLocalPlayerDragging.Invoke(null, null); return obj is bool && (bool)obj; } catch (Exception ex) { Exception ex2 = ex.InnerException ?? ex; Plugin.LogOnce("is-dragging", "Dismemberment: The Dead Remain's IsLocalPlayerDragging threw (" + ex2.Message + "); ignored from now on."); isLocalPlayerDragging = null; return false; } } internal static bool TryHandOffLimbToDeadRemain(GameObject limb, GameObject body) { if (tieToCarcassOf == null || (Object)(object)limb == (Object)null || (Object)(object)body == (Object)null) { return false; } try { TieArgs[0] = limb; TieArgs[1] = body; object obj = tieToCarcassOf.Invoke(null, TieArgs); return obj is bool && (bool)obj; } catch (Exception ex) { Exception ex2 = ex.InnerException ?? ex; Plugin.LogOnce("tie-carcass", "Dismemberment: handing a limb to The Dead Remain threw (" + ex2.Message + "); limbs keep their own lifetime."); tieToCarcassOf = null; return false; } finally { TieArgs[0] = null; TieArgs[1] = null; } } } internal static class BloodFxBundle { internal sealed class Entry { internal string Kind; internal string Name; internal readonly Dictionary<string, string> Values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); internal float GetFloat(string key, float fallback) { if (Values.TryGetValue(key, out var value) && float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return result; } return fallback; } internal int GetInt(string key, int fallback) { if (Values.TryGetValue(key, out var value) && int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return result; } return fallback; } internal string GetString(string key, string fallback) { if (!Values.TryGetValue(key, out var value)) { return fallback; } return value; } } internal const string BundleName = "marc_bloodfx_v1"; internal const string ManifestAsset = "mbf_manifest"; private static AssetBundle bundle; private static bool attempted; private static List<Entry> entries; private static readonly Dictionary<string, Object> Cache = new Dictionary<string, Object>(StringComparer.OrdinalIgnoreCase); internal static string Status = "not loaded yet"; internal static bool LoadedByThisMod; internal static bool Available => (Object)(object)bundle != (Object)null; internal static AssetBundle Get(ManualLogSource log) { if ((Object)(object)bundle != (Object)null) { return bundle; } if (attempted) { return null; } attempted = true; bundle = FindLoaded(); if ((Object)(object)bundle != (Object)null) { Status = "shared: 'marc_bloodfx_v1' was already loaded by another mod"; log.LogInfo((object)("Blood FX: " + Status)); return bundle; } string text = ResolvePath(); if (text == null) { Status = "built-in blood effects (no blood bundle installed)"; log.LogInfo((object)("Blood FX: " + Status)); return null; } try { bundle = AssetBundle.LoadFromFile(text); } catch (Exception ex) { bundle = null; log.LogWarning((object)("Blood FX: loading " + text + " threw: " + ex.Message)); } if ((Object)(object)bundle == (Object)null) { bundle = FindLoaded(); if ((Object)(object)bundle != (Object)null) { Status = "shared: another copy of 'marc_bloodfx_v1' is loaded"; log.LogInfo((object)("Blood FX: " + Status)); return bundle; } Status = "failed to load " + text + " (built with an incompatible Unity version, or damaged)"; log.LogWarning((object)("Blood FX: " + Status)); return null; } LoadedByThisMod = true; Status = "loaded " + text; log.LogInfo((object)("Blood FX: " + Status)); return bundle; } internal static T Load<T>(string assetName, ManualLogSource log) where T : Object { if (string.IsNullOrEmpty(assetName)) { return default(T); } string key = typeof(T).Name + ":" + assetName; if (Cache.TryGetValue(key, out var value)) { return (T)(object)((value is T) ? value : null); } T val = default(T); AssetBundle val2 = Get(log); if ((Object)(object)val2 != (Object)null) { try { val = val2.LoadAsset<T>(assetName); } catch (Exception ex) { log.LogWarning((object)("Blood FX: could not load '" + assetName + "': " + ex.Message)); } if ((Object)(object)val == (Object)null) { log.LogWarning((object)("Blood FX: no " + typeof(T).Name + " named '" + assetName + "' in marc_bloodfx_v1; that effect uses its fallback.")); } } Cache[key] = (Object)(object)val; return val; } internal static List<Entry> Entries(string kind, ManualLogSource log) { List<Entry> list = new List<Entry>(); List<Entry> list2 = AllEntries(log); for (int i = 0; i < list2.Count; i++) { if (string.Equals(list2[i].Kind, kind, StringComparison.OrdinalIgnoreCase)) { list.Add(list2[i]); } } return list; } private static List<Entry> AllEntries(ManualLogSource log) { if (entries != null) { return entries; } entries = new List<Entry>(); TextAsset val = BloodFxBundle.Load<TextAsset>("mbf_manifest", log); if ((Object)(object)val == (Object)null) { return entries; } string[] array = val.text.Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); bool flag = false; for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0 || text[0] == '#') { continue; } string[] array2 = text.Split(new char[1] { '|' }); if (array2.Length < 2) { continue; } if (array2[0] == "bundle") { flag = true; if (!string.Equals(array2[1], "marc_bloodfx_v1", StringComparison.OrdinalIgnoreCase)) { log.LogWarning((object)("Blood FX: manifest names bundle '" + array2[1] + "', expected 'marc_bloodfx_v1'.")); } continue; } Entry entry = new Entry { Kind = array2[0], Name = array2[1] }; for (int j = 2; j < array2.Length; j++) { int num = array2[j].IndexOf('='); if (num > 0) { entry.Values[array2[j].Substring(0, num)] = array2[j].Substring(num + 1); } } entries.Add(entry); } if (!flag) { log.LogWarning((object)"Blood FX: manifest has no bundle line; is this the right bundle?"); } return entries; } private static AssetBundle FindLoaded() { try { foreach (AssetBundle allLoadedAssetBundle in AssetBundle.GetAllLoadedAssetBundles()) { if ((Object)(object)allLoadedAssetBundle != (Object)null && string.Equals(((Object)allLoadedAssetBundle).name, "marc_bloodfx_v1", StringComparison.OrdinalIgnoreCase)) { return allLoadedAssetBundle; } } } catch (Exception) { } return null; } private static string ResolvePath() { List<string> list = new List<string>(); try { string directoryName = Path.GetDirectoryName(typeof(BloodFxBundle).Assembly.Location); if (!string.IsNullOrEmpty(directoryName)) { list.Add(Path.Combine(directoryName, Path.Combine("Assets", "marc_bloodfx_v1"))); list.Add(Path.Combine(directoryName, Path.Combine("assets", "marc_bloodfx_v1"))); list.Add(Path.Combine(directoryName, "marc_bloodfx_v1")); } } catch { } for (int i = 0; i < list.Count; i++) { if (File.Exists(list[i])) { return list[i]; } } try { string[] files = Directory.GetFiles(Paths.PluginPath, "marc_bloodfx_v1", SearchOption.AllDirectories); if (files.Length != 0) { return files[0]; } } catch { } return null; } } internal sealed class BoneRow { internal string Prefab; internal string LiveBone; internal float MinDamage; internal float Weight; internal string RagdollBone; } internal static class BoneTable { internal const string FileName = "marc.valheim.dismemberment.bones.txt"; private static readonly Dictionary<string, List<BoneRow>> Rows = new Dictionary<string, List<BoneRow>>(StringComparer.OrdinalIgnoreCase); private static readonly List<BoneRow> NoRows = new List<BoneRow>(); private static int rowCount; internal static int Version = 1; private static StringBuilder text; internal static string FilePath => Path.Combine(Paths.ConfigPath, "marc.valheim.dismemberment.bones.txt"); internal static List<BoneRow> RowsFor(string prefab) { if (prefab == null || !Rows.TryGetValue(prefab, out var value)) { return NoRows; } return value; } internal static string Summary() { return rowCount + " rows for " + Rows.Count + " creatures"; } internal static void Load() { Rows.Clear(); rowCount = 0; string filePath = FilePath; try { if (!File.Exists(filePath)) { File.WriteAllText(filePath, DefaultText()); Plugin.Log.LogInfo((object)("Dismemberment: wrote the default bone table to " + filePath)); } string[] array = File.ReadAllLines(filePath); for (int i = 0; i < array.Length; i++) { ParseLine(array[i], i + 1); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Dismemberment: could not read " + filePath + " (" + ex.Message + "); using the built-in defaults.")); Rows.Clear(); rowCount = 0; string[] array2 = DefaultText().Split(new char[1] { '\n' }); for (int j = 0; j < array2.Length; j++) { ParseLine(array2[j], j + 1); } } Version++; } private static void ParseLine(string raw, int lineNumber) { string text = raw.Trim(); int num = text.IndexOf('#'); if (num >= 0) { text = text.Substring(0, num).Trim(); } if (text.Length == 0) { return; } string[] array = text.Split(new char[1] { '|' }); if (array.Length < 4) { Warn(lineNumber, "needs at least 4 columns: PrefabName | BoneName | MinDamage | Weight [| RagdollBone]"); return; } string text2 = array[0].Trim(); string text3 = array[1].Trim(); if (text2.Length == 0 || text3.Length == 0) { Warn(lineNumber, "prefab and bone names must not be empty"); return; } if (!float.TryParse(array[2].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || result < 0f) { Warn(lineNumber, "MinDamage '" + array[2].Trim() + "' is not a number >= 0"); return; } if (!float.TryParse(array[3].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result2) || result2 <= 0f) { Warn(lineNumber, "Weight '" + array[3].Trim() + "' is not a number > 0"); return; } string text4 = ((array.Length > 4) ? array[4].Trim() : ""); if (text4.Length == 0) { text4 = text3; } if (!Rows.TryGetValue(text2, out var value)) { value = new List<BoneRow>(); Rows[text2] = value; } value.Add(new BoneRow { Prefab = text2, LiveBone = text3, MinDamage = result, Weight = result2, RagdollBone = text4 }); rowCount++; } private static void Warn(int lineNumber, string problem) { Plugin.Log.LogWarning((object)("Dismemberment: marc.valheim.dismemberment.bones.txt line " + lineNumber + ": " + problem + " (skipped).")); } private static string DefaultText() { text = new StringBuilder(16384); text.Append("# Dismemberment severable-bone table.\n"); text.Append("# One row per line: PrefabName | BoneName | MinDamage | Weight | RagdollBone\n"); text.Append("# PrefabName = creature prefab (as dm_bones prints it).\n"); text.Append("# BoneName = bone on the LIVING creature (the hit picks the nearest listed bone).\n"); text.Append("# MinDamage = minimum slash damage of the hit to sever this bone (0 = any).\n"); text.Append("# Weight = > 0; higher is preferred when two bones are equally near the hit.\n"); text.Append("# RagdollBone = optional; the bone collapsed on the ragdoll. Defaults to BoneName.\n"); text.Append("# Lines starting with # are comments. This file is never overwritten: delete it to get the defaults back.\n"); text.Append("# Console: dm_bones dumps the bones of the creature you look at; dm_reload re-reads this file.\n"); string[] array = new string[4] { "Greydwarf", "Greydwarf_Elite", "Greyling", "Greydwarf_Frozen" }; Section("Greydwarf family (live rig and ragdoll rig differ)"); for (int i = 0; i < array.Length; i++) { Row(array[i], "l_arm1", 1f, "mixamorig:LeftArm"); Row(array[i], "r_arm1", 1f, "mixamorig:RightArm"); Row(array[i], "l_leg1", 0.8f, "mixamorig:LeftUpLeg"); Row(array[i], "r_leg1", 0.8f, "mixamorig:RightUpLeg"); Row(array[i], "head", 0.5f, "mixamorig:Neck"); } Rig("Greydwarf shamans", new string[2] { "Greydwarf_Shaman", "Greydwarf_Shaman_Frozen" }, "l_arm1:1", "r_arm1:1", "l_leg1:0.8", "r_leg1:0.8", "head:0.5"); Rig("Draugr", new string[6] { "Draugr", "Draugr_Elite", "Draugr_Ranged", "Draugr_sleeping", "Draugr_Elite_sleeping", "Draugr_Ranged_sleeping" }, "LeftArm:1", "RightArm:1", "LeftUpLeg:0.8", "RightUpLeg:0.8", "Head:0.5"); Rig("Goblins", new string[3] { "Goblin", "GoblinArcher", "GoblinDeepNorth" }, "l_upperarm:1", "r_upperarm:1", "l_leg0:0.8", "r_leg0:0.8", "head:0.5"); Rig("Goblin shamans", new string[2] { "GoblinShaman", "GoblinShaman_Hildir" }, "L.Uarm:1", "R.Uarm:1", "L.Uleg:0.8", "R.Uleg:0.8", "Head:0.5"); Rig("Goblin brutes (uppoerleg is the rig's own spelling)", new string[2] { "GoblinBrute", "GoblinBrute_Hildir" }, "Upperarm.l:1", "Upperarm.r:1", "uppoerleg.l:0.8", "uppoerleg.r:0.8", "Head:0.5"); Rig("Wolf, boar, deer (Boar thighs have no Rigidbody; collapsing them is still the right seam)", new string[6] { "Wolf", "Wolf_cub", "Boar", "Boar_piggy", "Deer", "Deer_White" }, "L UpperArm:1", "R UpperArm:1", "L Thigh:0.8", "R Thigh:0.8", "Head:0.5"); Rig("Neck, Fenrings, Ulv, Troll, Jotun", new string[8] { "Neck", "Fenring", "Fenring_Cultist", "Fenring_Cultist_Hildir", "Ulv", "Troll", "JotunWarrior", "JotunWarriorDualWield" }, "LeftArm:1", "RightArm:1", "LeftUpLeg:0.8", "RightUpLeg:0.8", "Head:0.5"); Rig("Lox", new string[2] { "Lox", "Lox_Calf" }, "Frontupperleg.l:1", "Frontupperleg.r:1", "Backupperleg.l:0.8", "Backupperleg.r:0.8", "Head:0.5"); Rig("Asksvin", new string[2] { "Asksvin", "Asksvin_hatchling" }, "L.FrontUpperLeg2:1", "R.FrontUpperLeg2:1", "L.BackUpperLeg:0.8", "R.BackUpperLeg:0.8", "Head:0.5"); Rig("Bears", new string[2] { "Bjorn", "Unbjorn" }, "Front_UpperLeg.L:1", "Front_UpperLeg.R:1", "Back_UpperLeg.L:0.8", "Back_UpperLeg.R:0.8", "Head:0.5"); Rig("Dvergr", new string[7] { "Dverger", "DvergerMage", "DvergerMageFire", "DvergerMageIce", "DvergerMageSupport", "DvergerAshlands", "DvergerDeepNorth" }, "l.upperarm:1", "r.upperarm:1", "l.upperleg:0.8", "r.upperleg:0.8", "head:0.5"); Rig("Hare", new string[1] { "Hare" }, "FrontUpperLeg.l:1", "FrontUpperLeg.r:1", "BackUpperLeg.l:0.8", "BackUpperLeg.r:0.8", "Head:0.5"); Rig("Abomination", new string[1] { "Abomination" }, "l.upperarm:1", "r.upperarm:1", "l.upperleg:0.8", "r.upperleg:0.8", "head:0.5"); Rig("Volture (wings as arms)", new string[1] { "Volture" }, "l.wing1:1", "r.wing1:1", "l.uleg:0.8", "r.uleg:0.8", "head:0.5"); Rig("Moose (Rigify: first leg Rigidbodies; DEF-neck.004 is the head body)", new string[2] { "Moose", "Moose_calf" }, "DEF-forearm.L:1", "DEF-forearm.R:1", "DEF-lower_leg.L:0.8", "DEF-lower_leg.R:0.8", "DEF-neck.004:0.5"); Rig("Elaking", new string[2] { "Elaking", "ElakingMole" }, "l_upperarm:1", "r_upperarm:1", "l_leg0:0.8", "r_leg0:0.8", "head:0.5"); Rig("Hatchling (wings and head only)", new string[1] { "Hatchling" }, "Uwing.l:1", "Uwing.r:1", "Head:0.5"); Rig("Seal", new string[1] { "Seal" }, "FrontFlipper_02.l:1", "FrontFlipper_02.r:1", "Head:0.5"); Rig("Barka (no head row: its head bone has no body and no vertices)", new string[1] { "Barka" }, "l.upperarm:1", "r.upperarm:1", "l.upperleg:0.8", "r.upperleg:0.8"); Rig("No ragdoll: skeletons (listed for completeness; NeverAffect skips them by default)", new string[3] { "Skeleton", "Skeleton_Poison", "Skeleton_DeepNorth" }, "LeftArm:1", "RightArm:1", "LeftUpLeg:0.8", "RightUpLeg:0.8", "Head:0.5"); Rig("No ragdoll: Charred", new string[4] { "Charred_Melee", "Charred_Archer", "Charred_Twitcher", "Charred_Mage" }, "LeftArm:1", "RightArm:1", "LeftUpLeg:0.8", "RightUpLeg:0.8", "Head:0.5"); Rig("No ragdoll: Seekers", new string[2] { "Seeker", "SeekerBrute" }, "l.frontarm1:1", "r.frontarm1:1", "l.backleg1:0.8", "r.backleg1:0.8", "head:0.5"); Rig("No ragdoll: Morgen (no head bone)", new string[1] { "Morgen" }, "UpperArm.l:1", "UpperArm.r:1", "UpperLeg.l:0.8", "UpperLeg.r:0.8"); string result = text.ToString(); text = null; return result; } private static void Section(string title) { text.Append("\n# ").Append(title).Append('\n'); } private static void Rig(string title, string[] prefabs, params string[] bones) { Section(title); for (int i = 0; i < prefabs.Length; i++) { for (int j = 0; j < bones.Length; j++) { int num = bones[j].LastIndexOf(':'); string bone = bones[j].Substring(0, num); float weight = float.Parse(bones[j].Substring(num + 1), CultureInfo.InvariantCulture); Row(prefabs[i], bone, weight, null); } } } private static void Row(string prefab, string bone, float weight, string ragdollBone) { text.Append(prefab).Append(" | ").Append(bone) .Append(" | 0 | ") .Append(weight.ToString("0.0##", CultureInfo.InvariantCulture)); if (!string.IsNullOrEmpty(ragdollBone)) { text.Append(" | ").Append(ragdollBone); } text.Append('\n'); } } internal enum CreatureTier { None, CutsOnly, Full } internal sealed class PatternSet { private readonly HashSet<string> exact = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private readonly List<string> prefixes = new List<string>(); private readonly List<string> suffixes = new List<string>(); private readonly List<string> contains = new List<string>(); private readonly List<string> globs = new List<string>(); internal int Count => exact.Count + prefixes.Count + suffixes.Count + contains.Count + globs.Count; internal static PatternSet Parse(string text) { PatternSet patternSet = new PatternSet(); if (string.IsNullOrEmpty(text)) { return patternSet; } 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; } bool flag = text2[0] == '*'; bool flag2 = text2[text2.Length - 1] == '*'; string text3 = text2.Trim(new char[1] { '*' }); if (text3.Length != 0) { if (text3.IndexOf('*') >= 0) { patternSet.globs.Add(text2); } else if (flag && flag2) { patternSet.contains.Add(text3); } else if (flag2) { patternSet.prefixes.Add(text3); } else if (flag) { patternSet.suffixes.Add(text3); } else { patternSet.exact.Add(text3); } } } return patternSet; } internal bool Matches(string name) { if (string.IsNullOrEmpty(name)) { return false; } if (exact.Contains(name)) { return true; } for (int i = 0; i < prefixes.Count; i++) { if (name.StartsWith(prefixes[i], StringComparison.OrdinalIgnoreCase)) { return true; } } for (int j = 0; j < suffixes.Count; j++) { if (name.EndsWith(suffixes[j], StringComparison.OrdinalIgnoreCase)) { return true; } } for (int k = 0; k < contains.Count; k++) { if (name.IndexOf(contains[k], StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } for (int l = 0; l < globs.Count; l++) { if (GlobMatch(globs[l], name)) { return true; } } return false; } private static bool GlobMatch(string pattern, string name) { string[] array = pattern.Split(new char[1] { '*' }); int num = 0; for (int i = 0; i < array.Length; i++) { string text = array[i]; if (text.Length == 0) { continue; } if (i == 0) { if (!name.StartsWith(text, StringComparison.OrdinalIgnoreCase)) { return false; } num = text.Length; continue; } if (i == array.Length - 1) { if (name.Length - text.Length >= num) { return name.EndsWith(text, StringComparison.OrdinalIgnoreCase); } return false; } int num2 = name.IndexOf(text, num, StringComparison.OrdinalIgnoreCase); if (num2 < 0) { return false; } num = num2 + text.Length; } if (array.Length <= 1) { return num == name.Length; } return true; } } internal sealed class PrefabInfo { internal string Name; internal CreatureTier Tier; internal bool Tinted; internal Color Tint = Color.white; internal int ListsVersion = -1; internal List<BoneRow> Rows; internal int RowsVersion = -1; internal bool RagdollResolved; internal bool HasRagdoll; internal string RagdollName = ""; internal GameObject RagdollPrefab; internal float CreatureScale = 1f; internal bool CorpseRigDiffers; internal readonly Dictionary<string, float> RadiusByBone = new Dictionary<string, float>(StringComparer.Ordinal); internal Dictionary<string, Collider> RagdollColliders; } internal static class CreatureTiers { private struct InstanceEntry { internal Character Character; internal PrefabInfo Info; } private static readonly Dictionary<string, PrefabInfo> ByPrefab = new Dictionary<string, PrefabInfo>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<int, InstanceEntry> ByInstance = new Dictionary<int, InstanceEntry>(); private static readonly List<int> DeadKeys = new List<int>(); private static readonly HashSet<string> DifferentCorpseRigs = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Greydwarf", "Greydwarf_Elite", "Greyling", "Greydwarf_Frozen" }; internal static int CachedPrefabCount => ByPrefab.Count; internal static PrefabInfo InfoFor(Character character) { if ((Object)(object)character == (Object)null) { return null; } int instanceID = ((Object)character).GetInstanceID(); if (ByInstance.TryGetValue(instanceID, out var value) && value.Info != null) { Refresh(value.Info); return value.Info; } PrefabInfo prefabInfo = Info(Utils.GetPrefabName(((Component)character).gameObject)); ByInstance[instanceID] = new InstanceEntry { Character = character, Info = prefabInfo }; return prefabInfo; } internal static PrefabInfo Info(string prefabName) { if (prefabName == null) { prefabName = ""; } if (!ByPrefab.TryGetValue(prefabName, out var value)) { value = new PrefabInfo { Name = prefabName, CorpseRigDiffers = DifferentCorpseRigs.Contains(prefabName) }; ByPrefab[prefabName] = value; } Refresh(value); return value; } internal static CreatureTier TierOf(Character character, PrefabInfo info) { if ((Object)(object)character == (Object)null || info == null) { return CreatureTier.None; } if (character.IsPlayer() || character.IsBoss()) { return CreatureTier.None; } return info.Tier; } internal static CreatureTier TierFor(string prefabName) { if (DmConfig.NeverSet.Matches(prefabName)) { return CreatureTier.None; } if (DmConfig.FullSet.Matches(prefabName)) { return CreatureTier.Full; } if (DmConfig.CutsOnlySet.Matches(prefabName)) { return CreatureTier.CutsOnly; } return CreatureTier.None; } private static void Refresh(PrefabInfo info) { //IL_0034: 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_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) if (info.ListsVersion != DmConfig.ListsVersion) { info.ListsVersion = DmConfig.ListsVersion; info.Tier = TierFor(info.Name); info.Tinted = false; info.Tint = Color.white; for (int i = 0; i < DmConfig.TintRules.Count; i++) { if (DmConfig.TintRules[i].Key.Matches(info.Name)) { info.Tinted = true; info.Tint = DmConfig.TintRules[i].Value; break; } } } if (info.RowsVersion != BoneTable.Version) { info.RowsVersion = BoneTable.Version; info.Rows = BoneTable.RowsFor(info.Name); } if (!info.RagdollResolved) { ResolveRagdoll(info); } } private static void ResolveRagdoll(PrefabInfo info) { if ((Object)(object)ZNetScene.instance == (Object)null || string.IsNullOrEmpty(info.Name)) { return; } info.RagdollResolved = true; try { GameObject prefab = ZNetScene.instance.GetPrefab(info.Name); CapsuleCollider val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<CapsuleCollider>() : null); if ((Object)(object)val != (Object)null) { info.CreatureScale = Mathf.Clamp(Mathf.Max(val.height, val.radius * 2f) / 1.8f, 0.6f, 2.5f); } Character val2 = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<Character>() : null); if ((Object)(object)val2 == (Object)null || val2.m_deathEffects == null || val2.m_deathEffects.m_effectPrefabs == null) { return; } EffectData[] effectPrefabs = val2.m_deathEffects.m_effectPrefabs; foreach (EffectData val3 in effectPrefabs) { if (val3 != null && val3.m_enabled && !((Object)(object)val3.m_prefab == (Object)null) && !((Object)(object)val3.m_prefab.GetComponent<Ragdoll>() == (Object)null)) { info.HasRagdoll = true; info.RagdollPrefab = val3.m_prefab; info.RagdollName = ((Object)val3.m_prefab).name; break; } } } catch (Exception ex) { Plugin.LogOnce("ragdoll-resolve-" + info.Name, "Dismemberment: could not read the death effects of " + info.Name + ": " + ex.Message); } } internal static string PrefabNameOf(Character character) { PrefabInfo prefabInfo = InfoFor(character); if (prefabInfo == null) { return ""; } return prefabInfo.Name; } internal static void PruneInstances() { if (ByInstance.Count == 0) { return; } DeadKeys.Clear(); foreach (KeyValuePair<int, InstanceEntry> item in ByInstance) { if ((Object)(object)item.Value.Character == (Object)null) { DeadKeys.Add(item.Key); } } for (int i = 0; i < DeadKeys.Count; i++) { ByInstance.Remove(DeadKeys[i]); } } internal static void ResetInstances() { ByInstance.Clear(); foreach (PrefabInfo value in ByPrefab.Values) { value.RagdollResolved = false; value.HasRagdoll = false; value.RagdollPrefab = null; value.RagdollName = ""; value.RagdollColliders = null; value.RadiusByBone.Clear(); } } internal static void Invalidate() { foreach (PrefabInfo value in ByPrefab.Values) { value.ListsVersion = -1; value.RowsVersion = -1; } } } internal sealed class ConfigurationManagerAttributes { public int? Order; public bool? IsAdvanced; public bool? Browsable; public string DispName; public bool? ShowRangeAsPercent; public bool? ReadOnly; } internal static class DmConfig { private const string General = "1 - General"; private const string Wounds = "2 - Wounds"; private const string Dismember = "3 - Dismemberment"; private const string Creatures = "4 - Creatures"; private const string Weapons = "5 - Weapons"; private const string Limbs = "6 - Severed Limbs"; private const string Fx = "7 - Blood FX"; private const string Carrying = "8 - Carrying"; private const string Debug = "9 - Debug"; internal const string DefaultFullCreatures = "Greydwarf,Greydwarf_Shaman,Greyling,Greydwarf_Frozen,Greydwarf_Shaman_Frozen,Draugr,Draugr_Elite,Draugr_Ranged,Draugr_sleeping,Draugr_Elite_sleeping,Draugr_Ranged_sleeping,Goblin,GoblinArcher,GoblinShaman,GoblinBrute,GoblinDeepNorth,Wolf,Wolf_cub,Boar,Boar_piggy,Deer,Deer_White,Neck,Fenring,Fenring_Cultist,Ulv,Charred_Melee,Charred_Archer,Charred_Twitcher,Charred_Mage,Asksvin,Dverger,DvergerMage,DvergerMageFire,DvergerMageIce,DvergerMageSupport,DvergerAshlands,DvergerDeepNorth"; internal const string DefaultCutsOnlyCreatures = "Troll,Troll_sleeping,Seeker,SeekerBrute,Lox,Lox_Calf,Moose,Greydwarf_Elite,Bjorn,Bjorn_sleeping,Unbjorn,Morgen,Morgen_NonSleeping,JotunWarrior,JotunWarriorDualWield"; internal const string DefaultNeverAffect = "*_Hildir*,GoblinBruteBros*,Skeleton*,Ghost*,Wraith,Abomination,Blob*,Leech*,Deathsquito,Tick,Bat*,Surtling,Serpent*,Gjall,FallenValkyrie,Hare,Chicken,Hen,Volture,TrainingDummy,BogWitchKvastur,Charred_*_Fader,Charred_Melee_Dyrnwyn,Charred_Twitcher_Summoned"; private static ConfigFile file; private static int order; public static ConfigEntry<bool> Enabled; public static ConfigEntry<string> BloodTints; public static ConfigEntry<bool> EnableWounds; public static ConfigEntry<float> DeepCutThreshold; public static ConfigEntry<int> MaxWoundsPerCreature; public static ConfigEntry<float> WoundSizeMultiplier; public static ConfigEntry<bool> PreciseSkinPlacement; public static ConfigEntry<float> WoundVisibleDistance; public static ConfigEntry<bool> KeepWoundsOnCorpse; public static ConfigEntry<bool> WoundsBleed; public static ConfigEntry<float> BleedRate; public static ConfigEntry<float> BleedSeconds; public static ConfigEntry<int> MaxBleedingWounds; public static ConfigEntry<float> DripStainSeconds; public static ConfigEntry<int> MaxDripStains; public static ConfigEntry<bool> EnableDismemberment; public static ConfigEntry<float> DismemberThreshold; public static ConfigEntry<float> DismemberChance; public static ConfigEntry<bool> NeverDismemberTamed; public static ConfigEntry<float> SeverImpulse; public static ConfigEntry<int> GutsPerSever; public static ConfigEntry<float> StumpSpurtSeconds; public static ConfigEntry<bool> GroundPoolOnSever; public static ConfigEntry<bool> BodyCuts; public static ConfigEntry<float> BodyCutChance; public static ConfigEntry<float> BodyCutMaxTilt; public static ConfigEntry<float> BodyCutImpulse; public static ConfigEntry<bool> BodyCutCaps; public static ConfigEntry<int> GutsPerBodyCut; public static ConfigEntry<string> FullCreatures; public static ConfigEntry<string> CutsOnlyCreatures; public static ConfigEntry<string> NeverAffect; public static ConfigEntry<string> SlashSkills; public static ConfigEntry<string> BodyCutSkills; public static ConfigEntry<string> NeverWoundSkills; public static ConfigEntry<string> CreatureAttackersThatDismember; public static ConfigEntry<bool> CreatureSlashAttacksCut; public static ConfigEntry<float> SeveredLimbLifetimeSeconds; public static ConfigEntry<int> MaxSeveredLimbs; public static ConfigEntry<float> LimbDespawnDistance; public static ConfigEntry<float> GutLifetimeSeconds; public static ConfigEntry<int> MaxGuts; public static ConfigEntry<bool> UseBloodPack; public static ConfigEntry<float> SplashScale; public static ConfigEntry<int> MaxActiveSplashes; public static ConfigEntry<float> GroundPoolLifetimeSeconds; public static ConfigEntry<int> MaxGroundPools; public static ConfigEntry<bool> EnableCarrying; public static ConfigEntry<KeyboardShortcut> PickUpKey; public static ConfigEntry<float> ThrowSpeed; public static ConfigEntry<bool> CarryHeavyLimbsTwoHanded; public static ConfigEntry<bool> VerboseLogging; public static ConfigEntry<bool> LogSeverEvents; internal static int ListsVersion = 1; internal static PatternSet FullSet = new PatternSet(); internal static PatternSet CutsOnlySet = new PatternSet(); internal static PatternSet NeverSet = new PatternSet(); internal static PatternSet DismemberAttackerSet = new PatternSet(); internal static readonly HashSet<SkillType> SlashSkillSet = new HashSet<SkillType>(); internal static readonly HashSet<SkillType> BodyCutSkillSet = new HashSet<SkillType>(); internal static readonly HashSet<SkillType> NeverWoundSkillSet = new HashSet<SkillType>(); internal static readonly List<KeyValuePair<PatternSet, Color>> TintRules = new List<KeyValuePair<PatternSet, Color>>(); public static void Bind(ConfigFile config) { //IL_06d7: Unknown result type (might be due to invalid IL or missing references) file = config; Enabled = B("1 - General", "Enabled", value: true, "Master switch. Off = no wounds, no severs, nothing spawned.", null); BloodTints = B("1 - General", "BloodTints", "Charred*=0.30,0.16,0.10;Draugr*=0.70,0.55,0.50", "Blood colour multipliers per creature: pattern=r,g,b separated by ';'. Patterns are a prefab name, Prefix*, *Suffix or *contains* (case-insensitive). The first matching pattern wins; unlisted creatures keep the pack's red.", null); EnableWounds = B("2 - Wounds", "EnableWounds", value: true, "Cuts and deep cuts on creatures hit by slashing attacks.", null); DeepCutThreshold = B("2 - Wounds", "DeepCutThreshold", 0.5f, "Health fraction AFTER the hit. Above it a hit leaves a cut; at or below it, a deep cut.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f)); MaxWoundsPerCreature = B("2 - Wounds", "MaxWoundsPerCreature", 8, "Wounds kept on one creature (and copied to its corpse). The oldest is reused once the cap is reached.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 24)); WoundSizeMultiplier = B("2 - Wounds", "WoundSizeMultiplier", 1f, "Scales every wound mark.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 4f)); PreciseSkinPlacement = B("2 - Wounds", "PreciseSkinPlacement", value: true, "Bake readable creature meshes (at most once per creature per frame) to sit wounds exactly on the skin. Off = estimate the skin from the bone and the ragdoll collider sizes.", null); WoundVisibleDistance = B("2 - Wounds", "WoundVisibleDistance", 40f, "Metres from the camera. Hits farther away make no wound.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 200f)); KeepWoundsOnCorpse = B("2 - Wounds", "KeepWoundsOnCorpse", value: true, "Copy a creature's wounds onto its ragdoll when it dies (same bones, same places).", null); WoundsBleed = B("2 - Wounds", "WoundsBleed", value: true, "Wounds slowly drip blood that falls and settles on the ground, tapering off over BleedSeconds. Deep cuts bleed faster.", null); BleedRate = B("2 - Wounds", "BleedRate", 1f, "Drops per second multiplier. 1 = about 0.6 drops/s from a cut and 1.5 from a deep cut when fresh.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f)); BleedSeconds = B("2 - Wounds", "BleedSeconds", 30f, "How long a cut keeps dripping; deep cuts bleed twice as long. The drip slows down over this time.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 600f)); MaxBleedingWounds = B("2 - Wounds", "MaxBleedingWounds", 24, "Wounds dripping at once across the whole world. Past this the oldest stops dripping first.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 128)); DripStainSeconds = B("2 - Wounds", "DripStainSeconds", 900f, "Seconds a stain left by a landed drip stays (then it shrinks away). Drops landing on a recent stain grow it into a small pool.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 7200f)); MaxDripStains = B("2 - Wounds", "MaxDripStains", 200, "Drip stains in the world at once; past this the oldest is reused. 0 = drops leave no stain.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 2000)); EnableDismemberment = B("3 - Dismemberment", "EnableDismemberment", value: true, "Sever a limb or the head on a qualifying blow. A sever always kills the creature.", null); DismemberThreshold = B("3 - Dismemberment", "DismemberThreshold", 0.15f, "Health fraction AFTER the hit below which a qualifying blow severs. Killing blows count (they are below it).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f)); DismemberChance = B("3 - Dismemberment", "DismemberChance", 1f, "Chance (0-1) that a qualifying blow actually severs.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f)); NeverDismemberTamed = B("3 - Dismemberment", "NeverDismemberTamed", value: true, "Tamed creatures still get cuts, but never a sever (and so never a forced death).", null); SeverImpulse = B("3 - Dismemberment", "SeverImpulse", 3.5f, "Metres per second added to the severed limb along the blow.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 15f)); GutsPerSever = B("3 - Dismemberment", "GutsPerSever", 2, "Flying gut chunks per sever (a head gets one more).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 8)); StumpSpurtSeconds = B("3 - Dismemberment", "StumpSpurtSeconds", 2.5f, "Seconds the stump keeps spurting. 0 = no spurt.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f)); GroundPoolOnSever = B("3 - Dismemberment", "GroundPoolOnSever", value: true, "Spread a blood pool on the ground under a sever. Skipped automatically when Crimson Blood is loaded (it already pools every kill).", null); BodyCuts = B("3 - Dismemberment", "BodyCuts", value: true, "A severing blow from a BodyCutSkills weapon can cut the body in two along the line of the swing (across the chest, the waist, a shoulder down to the hip...) instead of taking a limb or the head at a joint. Needs a creature with a ragdoll; Charred keep joint severs.", null); BodyCutChance = B("3 - Dismemberment", "BodyCutChance", 0.7f, "Share (0-1) of severing blows that cut across the body when they can; the rest take a limb or the head at a joint.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f)); BodyCutMaxTilt = B("3 - Dismemberment", "BodyCutMaxTilt", 60f, "Steepest body cut, in degrees away from straight across the spine. Steeper swings are tilted back to this. 0 = always straight across.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 75f)); BodyCutImpulse = B("3 - Dismemberment", "BodyCutImpulse", 2.5f, "Metres per second the severed part gets along the swing (plus a little off the cut).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 15f)); BodyCutCaps = B("3 - Dismemberment", "BodyCutCaps", value: true, "Fill each cut face with flesh. Off = the halves are open shells.", null); GutsPerBodyCut = B("3 - Dismemberment", "GutsPerBodyCut", 3, "Flying gut chunks per body cut (needs the blood bundle).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 10)); FullCreatures = B("4 - Creatures", "FullCreatures", "Greydwarf,Greydwarf_Shaman,Greyling,Greydwarf_Frozen,Greydwarf_Shaman_Frozen,Draugr,Draugr_Elite,Draugr_Ranged,Draugr_sleeping,Draugr_Elite_sleeping,Draugr_Ranged_sleeping,Goblin,GoblinArcher,GoblinShaman,GoblinBrute,GoblinDeepNorth,Wolf,Wolf_cub,Boar,Boar_piggy,Deer,Deer_White,Neck,Fenring,Fenring_Cultist,Ulv,Charred_Melee,Charred_Archer,Charred_Twitcher,Charred_Mage,Asksvin,Dverger,DvergerMage,DvergerMageFire,DvergerMageIce,DvergerMageSupport,DvergerAshlands,DvergerDeepNorth", "Creatures (prefab names or patterns, comma separated) that get wounds and can be dismembered. A * matches any run of characters anywhere in a name (Troll*, *_Hildir*, Charred_*_Fader).", null); CutsOnlyCreatures = B("4 - Creatures", "CutsOnlyCreatures", "Troll,Troll_sleeping,Seeker,SeekerBrute,Lox,Lox_Calf,Moose,Greydwarf_Elite,Bjorn,Bjorn_sleeping,Unbjorn,Morgen,Morgen_NonSleeping,JotunWarrior,JotunWarriorDualWield", "Creatures that get cuts and deep cuts but are never dismembered.", null); NeverAffect = B("4 - Creatures", "NeverAffect", "*_Hildir*,GoblinBruteBros*,Skeleton*,Ghost*,Wraith,Abomination,Blob*,Leech*,Deathsquito,Tick,Bat*,Surtling,Serpent*,Gjall,FallenValkyrie,Hare,Chicken,Hen,Volture,TrainingDummy,BogWitchKvastur,Charred_*_Fader,Charred_Melee_Dyrnwyn,Charred_Twitcher_Summoned", "Creatures this mod never touches. Wins over both lists above. Players and bosses are always skipped.", null); SlashSkills = B("5 - Weapons", "SlashSkills", "Swords,Knives,Axes", "Player weapon skills (Skills.SkillType names) whose slashing hits cut and dismember.", null); BodyCutSkills = B("5 - Weapons", "BodyCutSkills", "Swords,Axes", "Weapon skills whose severing blows can cut across the body (BodyCuts). Knives only take limbs.", null); NeverWoundSkills = B("5 - Weapons", "NeverWoundSkills", "Clubs,Spears,Polearms,Bows,Crossbows,Unarmed,Pickaxes,ElementalMagic,BloodMagic", "Player weapon skills that never leave a wound. Any other skill wounds only when slash is its largest physical damage.", null); CreatureAttackersThatDismember = B("5 - Weapons", "CreatureAttackersThatDismember", "Wolf,Wolf_cub,Fenring,Fenring_Cultist,Ulv,Bjorn,Unbjorn,Asksvin", "Creatures whose natural weapons (claws, bites) can cut and dismember other creatures. Any physical damage counts.", null); CreatureSlashAttacksCut = B("5 - Weapons", "CreatureSlashAttacksCut", value: true, "A creature's hit on another creature leaves a cut (never a sever) when slash is its largest physical damage.", null); SeveredLimbLifetimeSeconds = B("6 - Severed Limbs", "SeveredLimbLifetimeSeconds", 600f, "Real seconds a severed limb lies in the world before it shrinks away.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 7200f)); MaxSeveredLimbs = B("6 - Severed Limbs", "MaxSeveredLimbs", 12, "Severed limbs kept at once; the oldest goes first.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 64)); LimbDespawnDistance = B("6 - Severed Limbs", "LimbDespawnDistance", 150f, "A severed limb farther than this from the camera is removed.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(20f, 1000f)); GutLifetimeSeconds = B("6 - Severed Limbs", "GutLifetimeSeconds", 30f, "Seconds a flying gut chunk stays before it shrinks away.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 600f)); MaxGuts = B("6 - Severed Limbs", "MaxGuts", 20, "Gut chunks kept at once; the oldest goes first.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100)); UseBloodPack = B("7 - Blood FX", "UseBloodPack", value: true, "Use the blood bundle (marc_bloodfx_v1) when it is installed. Off = plain fallback visuals even if it exists.", null); SplashScale = B("7 - Blood FX", "SplashScale", 1f, "Scales the sever blood burst and stump spurt.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 4f)); MaxActiveSplashes = B("7 - Blood FX", "MaxActiveSplashes", 12, "Bursts and spurts playing at once; the oldest is recycled.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 64)); GroundPoolLifetimeSeconds = B("7 - Blood FX", "GroundPoolLifetimeSeconds", 120f, "Seconds a ground pool stays before fading out.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 1800f)); MaxGroundPools = B("7 - Blood FX", "MaxGroundPools", 8, "Ground pools kept at once; the oldest is recycled.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 64)); EnableCarrying = B("8 - Carrying", "EnableCarrying", value: true, "A severed limb that has come to rest can be picked up (E on it, or PickUpKey), carried in the hand, thrown (attack) and dropped (E on nothing, or R). Other players with the mod see it; the server needs nothing.", null); PickUpKey = B<KeyboardShortcut>("8 - Carrying", "PickUpKey", new KeyboardShortcut((KeyCode)112, Array.Empty<KeyCode>()), "Picks up the severed limb under the crosshair, like E. With anything else under the crosshair it drops the limb you carry. The Dead Remain's own PickUpKey only acts on carcasses, so the two can share a key.", null); ThrowSpeed = B("8 - Carrying", "ThrowSpeed", 10f, "Metres per second a thrown limb of 1 kg leaves the hand (plus your own speed). Heavier limbs go slower (divided by the square root of the mass);