Decompiled source of ButtCactus v1.1.0
plugins/ButtCactus.dll
Decompiled 5 days 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.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ButtCactus.PanelKit; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8.1", FrameworkDisplayName = ".NET Framework 4.8.1")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ButtCactus { internal static class BodypartFinder { public static bool TryParse(string typeName, out BodypartType type) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected I4, but got Unknown type = (BodypartType)0; if (string.IsNullOrWhiteSpace(typeName)) { return false; } typeName = typeName.Trim(); if (Enum.TryParse<BodypartType>(typeName, ignoreCase: true, out BodypartType result)) { type = (BodypartType)(int)result; return true; } return false; } public static Bodypart Find(Character character, string typeName) { //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)character == (Object)null || character.refs == null || (Object)(object)character.refs.ragdoll == (Object)null) { return null; } if (!TryParse(typeName, out var type)) { ModLog.Warn("无法识别的身体部位名称:「" + typeName + "」。可选部位:" + Describe(character)); return null; } List<Bodypart> partList = character.refs.ragdoll.partList; if (partList == null) { ModLog.Warn("角色 partList 为空,骨架尚未就绪。"); return null; } for (int i = 0; i < partList.Count; i++) { Bodypart val = partList[i]; if ((Object)(object)val != (Object)null && val.partType == type) { return val; } } ModLog.Warn("角色骨架上没有部位「" + typeName + "」。可选部位:" + Describe(character)); return null; } public static bool TryFind(Character character, BodypartType type, out Bodypart part) { //IL_0055: 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) part = null; if ((Object)(object)character == (Object)null || character.refs == null || (Object)(object)character.refs.ragdoll == (Object)null) { return false; } List<Bodypart> partList = character.refs.ragdoll.partList; if (partList == null) { return false; } for (int i = 0; i < partList.Count; i++) { Bodypart val = partList[i]; if ((Object)(object)val != (Object)null && val.partType == type) { part = val; return true; } } return false; } public static string Describe(Character character) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)character == (Object)null || character.refs == null || (Object)(object)character.refs.ragdoll == (Object)null) { return "(角色骨架未就绪)"; } List<Bodypart> partList = character.refs.ragdoll.partList; if (partList == null) { return "(partList 为空)"; } StringBuilder stringBuilder = new StringBuilder(); foreach (Bodypart item in partList) { if ((Object)(object)item != (Object)null) { stringBuilder.Append(item.partType).Append(", "); } } return stringBuilder.ToString(); } public static void DumpAll(Character character) { if ((Object)(object)character == (Object)null || character.refs == null || (Object)(object)character.refs.ragdoll == (Object)null) { ModLog.Warn("角色骨架尚未就绪,无法打印身体部位列表。"); } else { ModLog.Info($"角色身体部位共 {character.refs.ragdoll.partList?.Count ?? 0} 个:{Describe(character)}"); } } } internal sealed class ButtCactusPanel : ModPanelBase { protected override int WindowId => 61720; protected override string Title => L.T("屁股仙人球 v", "Butt Cactus v") + "1.1.0"; protected override string TitleNote => L.T("真实粘附 / 屁股仙人球", "real stick / butt cactus"); protected override string[] TabLabels => new string[4] { L.T("真实粘附", "Real Stick"), L.T("屁股仙人球", "Butt Cactus"), L.T("效果", "Effects"), L.T("设置", "Settings") }; protected override float LoadScale() { if (ModConfig.PanelScale == null) { return 1f; } return ModConfig.PanelScale.Value; } protected override void SaveScale(float value) { if (ModConfig.PanelScale != null) { ModConfig.PanelScale.Value = value; } } protected override string Loc(string chinese, string english) { return L.T(chinese, english); } protected override void DrawTab(int index) { switch (index) { case 0: DrawRealTab(); break; case 1: DrawButtTab(); break; case 2: DrawEffectTab(); break; default: DrawSettingsTab(); break; } } private void DrawRealTab() { //IL_00c6: 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) PanelWidgets.BeginCard(L.T("模式一:真实粘附(上腿)", "Mode 1: Real stick (legs)")); ModConfig.TestLegAttach.Value = PanelWidgets.ToggleRow(ModConfig.TestLegAttach.Value, L.T("启用真实粘附", "Enable real stick"), L.T("把两个原版仙人球直接粘在左右上腿骨上,不做物理模拟,靠腿部摆动产生晃动。", "Attach two vanilla cactus balls directly to the upper leg bones; wobble comes from leg motion.")); PanelWidgets.EndCard(); PanelWidgets.BeginCard(L.T("粘附部位", "Body parts")); PartRow(L.T("左上腿部位", "Left part"), ModConfig.TestLegLeftPart); PartRow(L.T("右上腿部位", "Right part"), ModConfig.TestLegRightPart); PanelWidgets.Hint(L.T("部位名填错时会写进日志,可在「设置」页开启打印全部部位。", "A wrong part name is logged; enable the body-part dump on the Settings tab.")); PanelWidgets.EndCard(); OffsetCard(L.T("左侧位置偏移", "Left offset"), "realL", PlacementDefaults.LegLeft, ModConfig.TestLeftOffsetRight, ModConfig.TestLeftOffsetUp, ModConfig.TestLeftOffsetForward); OffsetCard(L.T("右侧位置偏移", "Right offset"), "realR", PlacementDefaults.LegRight, ModConfig.TestRightOffsetRight, ModConfig.TestRightOffsetUp, ModConfig.TestRightOffsetForward); } private void DrawButtTab() { //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) PanelWidgets.BeginCard(L.T("模式二:屁股仙人球(骨盆锁定)", "Mode 2: Butt cactus (hip lock)")); ModConfig.Enabled.Value = PanelWidgets.ToggleRow(ModConfig.Enabled.Value, L.T("启用屁股仙人球", "Enable butt cactus"), L.T("每帧按当前朝向把球锁在骨盆两侧,并可叠加惯性晃动。", "Locks the balls to the hip each frame and can add inertia wobble.")); PanelWidgets.EndCard(); PanelWidgets.BeginCard(L.T("自动定位", "Auto placement")); ModConfig.AnchorToLegs.Value = PanelWidgets.ToggleRow(ModConfig.AnchorToLegs.Value, L.T("以双腿根部为基准高度", "Anchor height to leg roots")); NumRow(L.T("上下偏移(米)", "Up (m)"), ModConfig.VerticalOffset, -1f, 1f, "buttV"); NumRow(L.T("左右间距(米)", "Spacing (m)"), ModConfig.LateralSpacing, 0f, 2f, "buttLat"); NumRow(L.T("贴背距离(米)", "Back (m)"), ModConfig.BackwardOffset, 0f, 2f, "buttBack"); NumRow(L.T("半径修正", "Radius x"), ModConfig.RadiusScale, 0.1f, 3f, "buttRad"); PartRow(L.T("基准部位", "Base part"), ModConfig.BasePart); PanelWidgets.Hint(L.T("间距/贴背填 0 表示按球半径自动,使两球刚好相切。", "0 means auto by ball radius so the two balls just touch.")); PanelWidgets.EndCard(); OffsetCard(L.T("左侧位置偏移", "Left offset"), "buttL", PlacementDefaults.ButtLeft, ModConfig.LeftOffsetRight, ModConfig.LeftOffsetUp, ModConfig.LeftOffsetForward); OffsetCard(L.T("右侧位置偏移", "Right offset"), "buttR", PlacementDefaults.ButtRight, ModConfig.RightOffsetRight, ModConfig.RightOffsetUp, ModConfig.RightOffsetForward); PanelWidgets.BeginCard(L.T("外观与晃动", "Look & wobble")); NumRow(L.T("缩放", "Scale"), ModConfig.Scale, 0.1f, 5f, "buttScale"); NumRow(L.T("旋转X", "Rot X"), ModConfig.RotX, -180f, 180f, "buttRx"); NumRow(L.T("旋转Y", "Rot Y"), ModConfig.RotY, -180f, 180f, "buttRy"); NumRow(L.T("旋转Z", "Rot Z"), ModConfig.RotZ, -180f, 180f, "buttRz"); NumRow(L.T("晃动强度", "Wobble"), ModConfig.WobbleStrength, 0f, 3f, "wobble"); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); if (PanelWidgets.Btn(L.T("立即重建", "Rebuild now"), 120f, PanelTheme.ButtonPrimary)) { ButtCactusService.RequestRebuild(); Notify(L.T("已请求重建。", "Rebuild requested.")); } GUILayout.EndHorizontal(); PanelWidgets.Hint(L.T("晃动强度 0 为不晃动,越大越明显;修改参数后若没立刻生效,可点「立即重建」。", "Wobble 0 disables it; if a change does not apply immediately, press Rebuild now.")); PanelWidgets.EndCard(); } private void DrawEffectTab() { PanelWidgets.BeginCard(L.T("效果", "Effects")); ModConfig.ImmuneToCactus.Value = PanelWidgets.ToggleRow(ModConfig.ImmuneToCactus.Value, L.T("免疫仙人球效果(仅本机)", "Immune to cactus (local)"), L.T("任何仙人球粘到身上都不掉血、不扎刺、不影响体力条;他人的球仍正常显示以保持视角一致。", "No damage, thorns or stamina drain from any cactus; others' balls still show for a consistent view.")); ModConfig.DisableInteraction.Value = PanelWidgets.ToggleRow(ModConfig.DisableInteraction.Value, L.T("禁用交互", "Disable interaction"), L.T("仙人球不会被自己或他人捡起。", "Cactus balls cannot be picked up.")); ModConfig.NotifyWhenTaken.Value = PanelWidgets.ToggleRow(ModConfig.NotifyWhenTaken.Value, L.T("被取走时屏幕提示", "Taken notification"), L.T("别人取走你身上的仙人球时在屏幕上方提示。", "Show a notice when someone takes a ball off you.")); ModConfig.AddWeight.Value = PanelWidgets.ToggleRow(ModConfig.AddWeight.Value, L.T("增加负重", "Add weight"), L.T("给角色增加负重。开启「免疫」时在本机无效。", "Adds weight; ignored locally when immunity is on.")); ModConfig.AddThorns.Value = PanelWidgets.ToggleRow(ModConfig.AddThorns.Value, L.T("造成扎刺伤害", "Thorns damage"), L.T("持续扎刺伤害。开启「免疫」时在本机无效。", "Thorns damage; ignored locally when immunity is on.")); PanelWidgets.EndCard(); } private void DrawSettingsTab() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) PanelWidgets.BeginCard(L.T("面板", "Panel")); KeyBindRow(L.T("面板快捷键", "Panel key"), ModConfig.HotkeyPanel.Value, delegate(KeyCode k) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) ModConfig.HotkeyPanel.Value = k; }, ModConfig.HotkeyPanel); PanelWidgets.EndCard(); PanelWidgets.BeginCard(L.T("高级", "Advanced")); PartRow(L.T("仙人球预制体名", "Prefab name"), ModConfig.PrefabName); ModConfig.DebugDumpBodyparts.Value = PanelWidgets.ToggleRow(ModConfig.DebugDumpBodyparts.Value, L.T("打印所有身体部位", "Dump body parts")); NumRow(L.T("远程同步间隔(秒)", "Remote sync (s)"), ModConfig.RemoteSyncInterval, 0f, 2f, "sync"); PanelWidgets.EndCard(); PanelWidgets.BeginCard(L.T("日志", "Log")); ModConfig.VerboseLog.Value = PanelWidgets.ToggleRow(ModConfig.VerboseLog.Value, L.T("详细日志", "Verbose log")); NumRow(L.T("快照间隔(秒)", "Snapshot (s)"), ModConfig.SnapshotInterval, 0f, 60f, "snap"); PanelWidgets.EndCard(); PanelWidgets.BeginCard(L.T("保存", "Save")); if (PanelWidgets.BtnWide(L.T("保存到配置文件", "Save to config file"), PanelTheme.ButtonPrimary)) { SaveAll(); } PanelWidgets.Hint(L.T("数字框按回车或点击别处生效,再点保存写入文件。", "Press Enter (or click away) to apply a number, then save to write it to disk.")); PanelWidgets.EndCard(); } protected override void DrawStatusStrip() { GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); StateLabel(L.T("真实粘附:", "Real: "), ModConfig.TestLegAttach.Value); StateLabel(L.T("屁股仙人球:", "Butt: "), ModConfig.Enabled.Value); GUILayout.Label(L.T("语言:", "Lang: ") + (L.English ? "EN" : "中文"), PanelTheme.Hint, Array.Empty<GUILayoutOption>()); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); } protected override void DrawFooterHint() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) string text = ((ModConfig.HotkeyPanel != null) ? ((object)ModConfig.HotkeyPanel.Value/*cast due to .constrained prefix*/).ToString() : "F8"); GUILayout.Label(L.T("按 " + text + " 开关本面板。", text + " toggles this panel."), PanelTheme.Hint, Array.Empty<GUILayoutOption>()); } protected override void DrawFooterExtras() { GUILayout.Space(8f); if (GUILayout.Button(L.T("English", "中文"), PanelTheme.Mini, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(58f), GUILayout.Height(18f) })) { L.Toggle(); Notify(L.T("已切换为中文。", "Switched to English.")); } if (GUILayout.Button(L.T("保存", "Save"), PanelTheme.Mini, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(44f), GUILayout.Height(18f) })) { SaveAll(); } } protected override void DrawNavFooter() { GUILayout.Label(L.T("字体:", "Font: ") + PanelTheme.FontName, PanelTheme.Hint, Array.Empty<GUILayoutOption>()); } private void SaveAll() { ModConfig.Save(); Notify(L.T("已保存到配置文件。", "Saved to config file.")); } private void StateLabel(string label, bool on) { GUILayout.Label(label + L.OnOff(on), on ? PanelTheme.Value : PanelTheme.Hint, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(120f) }); } private void PartRow(string label, ConfigEntry<string> entry) { GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label(label, PanelTheme.Sub, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(118f) }); entry.Value = PanelWidgets.TextBox(entry.Value, 132f); GUILayout.EndHorizontal(); } private static void NumRow(string label, ConfigEntry<float> entry, float min, float max, string key) { PanelWidgets.NumberFieldRow(label, entry.Value, min, max, key, delegate(float v) { entry.Value = v; }); } private void OffsetCard(string title, string keyPrefix, Vector3 origin, ConfigEntry<float> right, ConfigEntry<float> up, ConfigEntry<float> forward) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) PanelWidgets.BeginCard(title); OffsetRow(L.T("左右(米)", "Side (m)"), right, origin.x, keyPrefix + "R"); OffsetRow(L.T("上下(米)", "Up (m)"), up, origin.y, keyPrefix + "U"); OffsetRow(L.T("前后(米)", "Forward (m)"), forward, origin.z, keyPrefix + "F"); PanelWidgets.Hint(L.T("0 为你当前配置的位置(即坐标原点),输入 ± 值在此基础上移动。", "0 is your configured position (the origin); enter +/- to move from there.")); PanelWidgets.EndCard(); } private static void OffsetRow(string label, ConfigEntry<float> entry, float origin, string key) { float value = entry.Value - origin; PanelWidgets.NumberFieldRow(label, value, -1f - origin, 1f - origin, key, delegate(float v) { entry.Value = Mathf.Clamp(origin + v, -1f, 1f); }); } } internal class ButtCactusService : MonoBehaviour { private class Slot { public bool IsLeft; public GameObject Go; public StickyItemComponent Sticky; public Bodypart Part; public bool Attaching; public int LastViewId = -1; public string LastPartName = "无"; public bool PreviouslyStuck; public float Lateral; public float Backward; public float Vertical; public Vector3 LastSentOffset; public float NextRemoteSync; public bool WobbleInit; public Vector3 PrevBase; public Vector3 PrevBaseVel; public Vector3 WobbleOffset; public Vector3 WobbleVel; } private const float WobbleStiffness = 180f; private const float WobbleDamping = 9f; private const float WobbleMaxOffset = 0.12f; private const float WobbleMaxAccel = 80f; private readonly Slot[] _slots = new Slot[2] { new Slot { IsLeft = true }, new Slot { IsLeft = false } }; private static float _ballRadius = -1f; private bool _running; private float _nextAttemptTime; private float _nextSnapshotTime; private bool _dumped; private bool _hadCharacter; private bool _rebuildRequested; public static ButtCactusService Instance { get; private set; } private IEnumerator AttachRoutine(Slot slot, Character me) { string side = (slot.IsLeft ? "左" : "右"); slot.Attaching = true; try { Bodypart part = FindBase(me); if ((Object)(object)part == (Object)null) { ModLog.Warn("[" + side + "臀] 找不到基准部位「" + ModConfig.BasePart.Value + "」。"); yield break; } string text = CactusPrefabFinder.Resolve(); if (string.IsNullOrEmpty(text)) { ModLog.Error("[" + side + "臀] 找不到仙人球预制体。"); yield break; } Quaternion spawnRot = part.transform.rotation * Quaternion.Euler(ModConfig.RotX.Value, ModConfig.RotY.Value, ModConfig.RotZ.Value); GameObject go; try { go = PhotonNetwork.Instantiate(text, part.transform.position, spawnRot, (byte)0, (object[])null); } catch (Exception ex) { ModLog.Error("[" + side + "臀] 生成仙人球失败:" + ex.Message); yield break; } if ((Object)(object)go == (Object)null) { ModLog.Error("[" + side + "臀] 生成仙人球返回空对象。"); yield break; } CactusBall cactus = go.GetComponent<CactusBall>(); if ((Object)(object)cactus == (Object)null) { ModLog.Warn("[" + side + "臀] 预制体上没有 CactusBall 组件(" + text + ")。"); SafeDestroy(go); yield break; } if (ModConfig.Scale.Value != 1f) { Transform transform = go.transform; transform.localScale *= ModConfig.Scale.Value; } ((StickyItemComponent)cactus).addWeightToStuckPlayer = (ModConfig.AddWeight.Value ? 1 : 0); ((StickyItemComponent)cactus).addThornsToStuckPlayer = (ModConfig.AddThorns.Value ? 1 : 0); if ((Object)(object)((ItemComponent)cactus).item != (Object)null) { ((ItemComponent)cactus).item.SetKinematic(true); } if ((Object)(object)((StickyItemComponent)cactus).physicalCollider != (Object)null) { ((StickyItemComponent)cactus).physicalCollider.isTrigger = true; } slot.Go = go; slot.Sticky = (StickyItemComponent)(object)cactus; PhotonView component = go.GetComponent<PhotonView>(); slot.LastViewId = (((Object)(object)component != (Object)null) ? component.ViewID : (-1)); CactusTracker cactusTracker = go.AddComponent<CactusTracker>(); cactusTracker.IsLeft = slot.IsLeft; cactusTracker.ViewId = slot.LastViewId; cactusTracker.Sticky = (StickyItemComponent)(object)cactus; yield return null; yield return null; if ((Object)(object)go == (Object)null || (Object)(object)cactus == (Object)null) { slot.Go = null; slot.Sticky = null; yield break; } if ((Object)(object)((ItemComponent)cactus).item != (Object)null && (int)((ItemComponent)cactus).item.itemState != 0) { ((ItemComponent)cactus).item.itemState = (ItemState)0; } string detail; float num = PlacementMath.MeasureRadius(go, out detail); if (_ballRadius <= 0f && num > 0f) { _ballRadius = num; } float num2 = ((_ballRadius > 0f) ? _ballRadius : num) * ModConfig.RadiusScale.Value; float num3 = ((ModConfig.LateralSpacing.Value > 0f) ? ModConfig.LateralSpacing.Value : num2); float num4 = ((ModConfig.BackwardOffset.Value > 0f) ? ModConfig.BackwardOffset.Value : num2); float value = ModConfig.VerticalOffset.Value; Vector3 val = PlacementMath.TryGetButtBase(me, ModConfig.AnchorToLegs.Value, out detail); Vector3 offset = PlacementMath.ComputeWorldOffset(me, slot.IsLeft, num3, num4, value); offset = PlacementMath.ApplyDelta(me, offset, GetDelta(slot.IsLeft)); Vector3 val2 = val + offset; Vector3 worldOffset = val2 - part.transform.position; slot.Part = part; slot.LastPartName = ((object)Unsafe.As<BodypartType, BodypartType>(ref part.partType)/*cast due to .constrained prefix*/).ToString(); slot.Lateral = num3; slot.Backward = num4; slot.Vertical = value; slot.WobbleInit = false; go.transform.SetPositionAndRotation(val2, spawnRot); if (StickyApi.Stick((StickyItemComponent)(object)cactus, me, part, worldOffset)) { EnforceNoInteraction(slot); ModLog.Info($"[{side}臀] 仙人球已粘到 {part.partType}。"); yield break; } ModLog.Warn($"[{side}臀] 粘附 {part.partType} 失败,稍后重试。"); SafeDestroy(go); slot.Go = null; slot.Sticky = null; } finally { slot.Attaching = false; } } private void Awake() { Instance = this; } public static void RequestRebuild() { if ((Object)(object)Instance != (Object)null) { Instance._rebuildRequested = true; } } public string GetSlotStatus(bool isLeft) { Slot slot = (isLeft ? _slots[0] : _slots[1]); if (slot.Attaching) { return "附着中"; } if ((Object)(object)slot.Go == (Object)null) { return "未生成"; } if ((Object)(object)slot.Sticky != (Object)null && (Object)(object)slot.Sticky.stuckToCharacter == (Object)(object)Character.localCharacter) { return "已粘附"; } return "未粘附"; } private void Update() { if (!ModConfig.Enabled.Value) { if (_running) { _running = false; DetachAll(); } return; } _running = true; if (_rebuildRequested) { _rebuildRequested = false; ModLog.Info("收到重建请求,重新生成仙人球。"); DetachAll(); _nextAttemptTime = 0f; } Character localCharacter = Character.localCharacter; if ((Object)(object)localCharacter == (Object)null || !PhotonNetwork.InRoom) { if (_hadCharacter) { _hadCharacter = false; } Slot[] slots = _slots; for (int i = 0; i < slots.Length; i++) { slots[i].PreviouslyStuck = false; } _dumped = false; return; } if (!_hadCharacter) { _hadCharacter = true; ModLog.Info("开始维持屁股仙人球:角色「" + ((Object)localCharacter).name + "」。"); } if (ModConfig.DebugDumpBodyparts.Value && !_dumped) { BodypartFinder.DumpAll(localCharacter); _dumped = true; } MaintainSlots(localCharacter); LockPlacement(localCharacter); float value = ModConfig.SnapshotInterval.Value; if (value > 0f && Time.time >= _nextSnapshotTime) { _nextSnapshotTime = Time.time + value; LogSnapshot(localCharacter); } } private void MaintainSlots(Character me) { bool flag = true; Slot[] slots = _slots; foreach (Slot slot in slots) { if (slot.Attaching) { flag = false; continue; } if ((Object)(object)slot.Go != (Object)null && (Object)(object)slot.Sticky != (Object)null && (Object)(object)slot.Sticky.stuckToCharacter == (Object)(object)me) { slot.PreviouslyStuck = true; continue; } if (slot.PreviouslyStuck && (Object)(object)slot.Go == (Object)null && ModConfig.NotifyWhenTaken.Value) { CactusNotify.Show("你身上的" + (slot.IsLeft ? "左" : "右") + "臀仙人球被别人取走了!"); } slot.PreviouslyStuck = false; if ((Object)(object)slot.Go != (Object)null || slot.LastViewId != -1) { ModLog.Info(string.Format("{0}臀仙人球失效,重新生成(ViewID={1},部位={2})。", slot.IsLeft ? "左" : "右", slot.LastViewId, slot.LastPartName)); } SafeDestroy(slot.Go); slot.Go = null; slot.Sticky = null; slot.Part = null; slot.LastViewId = -1; flag = false; } if (flag || Time.time < _nextAttemptTime) { return; } _nextAttemptTime = Time.time + 1f; slots = _slots; foreach (Slot slot2 in slots) { if (!slot2.Attaching && (Object)(object)slot2.Go == (Object)null) { ((MonoBehaviour)this).StartCoroutine(AttachRoutine(slot2, me)); } } } private void DetachAll() { //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_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) Slot[] slots = _slots; foreach (Slot slot in slots) { if ((Object)(object)slot.Go != (Object)null) { StickyApi.Unstick(slot.Sticky); SafeDestroy(slot.Go); } slot.Go = null; slot.Sticky = null; slot.Part = null; slot.NextRemoteSync = 0f; slot.LastSentOffset = Vector3.zero; slot.WobbleInit = false; slot.WobbleOffset = Vector3.zero; slot.WobbleVel = Vector3.zero; slot.PreviouslyStuck = false; } } private static void SafeDestroy(GameObject go) { if ((Object)(object)go == (Object)null) { return; } try { if (PhotonNetwork.InRoom) { PhotonNetwork.Destroy(go); } else { Object.Destroy((Object)(object)go); } } catch (Exception ex) { ModLog.Warn("销毁仙人球失败,改为本地销毁:" + ex.Message); Object.Destroy((Object)(object)go); } } private void OnDestroy() { DetachAll(); } private static Bodypart FindBase(Character me) { Bodypart val = BodypartFinder.Find(me, ModConfig.BasePart.Value); if ((Object)(object)val == (Object)null && (Object)(object)me != (Object)null && me.refs != null && (Object)(object)me.refs.hip != (Object)null) { ModLog.Warn("基准部位「" + ModConfig.BasePart.Value + "」不可用,回退到 Hip。"); val = me.refs.hip; } return val; } private static Vector3 GetDelta(bool isLeft) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (!isLeft) { return new Vector3(ModConfig.RightOffsetRight.Value, ModConfig.RightOffsetUp.Value, ModConfig.RightOffsetForward.Value); } return new Vector3(ModConfig.LeftOffsetRight.Value, ModConfig.LeftOffsetUp.Value, ModConfig.LeftOffsetForward.Value); } private void LogSnapshot(Character me) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("诊断快照:"); stringBuilder.Append("在房间=").Append(PhotonNetwork.InRoom); stringBuilder.Append(",房主=").Append(PhotonNetwork.IsMasterClient); stringBuilder.Append(",角色=").Append(((Object)(object)me == (Object)null) ? "无" : ((Object)me).name); stringBuilder.Append(",基准部位=").Append(ModConfig.BasePart.Value); stringBuilder.Append(",粘附列表总数=").Append(StickyItemComponent.ALL_STUCK_ITEMS?.Count ?? 0); if ((Object)(object)me != (Object)null) { PlacementMath.TryGetFrame(me, out var forward, out var right, out var _); stringBuilder.Append(",当前前方=").Append(((Vector3)(ref forward)).ToString("F2")); stringBuilder.Append(",当前右方=").Append(((Vector3)(ref right)).ToString("F2")); } ModLog.Info(stringBuilder.ToString()); Slot[] slots = _slots; foreach (Slot slot in slots) { ModLog.Info(DescribeSlot(slot, me)); } } private string DescribeSlot(Slot slot, Character me) { //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Unknown result type (might be due to invalid IL or missing references) string text = (slot.IsLeft ? "左臀" : "右臀"); Bodypart val = FindBase(me); string text2 = (((Object)(object)slot.Go == (Object)null) ? "无" : ((Object)slot.Go).name); string text3 = "无"; if ((Object)(object)slot.Go != (Object)null) { PhotonView component = slot.Go.GetComponent<PhotonView>(); if ((Object)(object)component != (Object)null) { text3 = component.ViewID.ToString(); } } string text4 = (((Object)(object)slot.Sticky == (Object)null || (Object)(object)slot.Sticky.stuckToCharacter == (Object)null) ? "无" : ((Object)slot.Sticky.stuckToCharacter).name); bool flag = (Object)(object)slot.Sticky != (Object)null && (Object)(object)slot.Sticky.stuckToCharacter == (Object)(object)me; string text5 = (((Object)(object)slot.Sticky == (Object)null) ? "无" : ((object)Unsafe.As<BodypartType, BodypartType>(ref slot.Sticky.stuckToBodypart)/*cast due to .constrained prefix*/).ToString()); string text6 = (((Object)(object)slot.Sticky != (Object)null && (Object)(object)((ItemComponent)slot.Sticky).item != (Object)null) ? ((object)((ItemComponent)slot.Sticky).item.itemState/*cast due to .constrained prefix*/).ToString() : "无"); string text7 = (((Object)(object)val == (Object)null) ? "无" : ((object)val.transform.position/*cast due to .constrained prefix*/).ToString()); string text8 = "无"; string text9 = "无"; if ((Object)(object)slot.Go != (Object)null) { text8 = ((object)slot.Go.transform.position/*cast due to .constrained prefix*/).ToString(); if ((Object)(object)val != (Object)null) { text9 = Vector3.Distance(slot.Go.transform.position, val.transform.position).ToString("F3") + "m"; } } string text10 = "无"; if ((Object)(object)me != (Object)null) { text10 = ((object)PlacementMath.TryGetButtBase(me, ModConfig.AnchorToLegs.Value, out var _)/*cast due to .constrained prefix*/).ToString(); } return " [" + text + "] 部位=" + (((object)Unsafe.As<BodypartType, BodypartType>(ref val?.partType)/*cast due to .constrained prefix*/).ToString() ?? "无") + " 部位位置=" + text7 + " 屁股基准=" + text10 + " " + $"仙人球={text2} ViewID={text3} 已粘角色={text4} 已粘到本人={flag} " + "粘附部位=" + text5 + " 物品状态=" + text6 + " 仙人球位置=" + text8 + " 距部位=" + text9; } private void LockPlacement(Character me) { //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: 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_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: 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_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_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_013a: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) float value = ModConfig.RemoteSyncInterval.Value; Slot[] slots = _slots; foreach (Slot slot in slots) { if (slot.Attaching || (Object)(object)slot.Go == (Object)null || (Object)(object)slot.Sticky == (Object)null || (Object)(object)slot.Part == (Object)null || (Object)(object)slot.Sticky.stuckToCharacter != (Object)(object)me) { continue; } string detail; Vector3 val = PlacementMath.TryGetButtBase(me, ModConfig.AnchorToLegs.Value, out detail); Vector3 offset = PlacementMath.ComputeWorldOffset(me, slot.IsLeft, slot.Lateral, slot.Backward, slot.Vertical); offset = PlacementMath.ApplyDelta(me, offset, GetDelta(slot.IsLeft)); Vector3 val2 = val + offset + ComputeWobble(slot, val) - slot.Part.transform.position; slot.Sticky.stuckLocalOffset = slot.Part.transform.InverseTransformVector(val2); EnforceNoInteraction(slot); if (!(value <= 0f) && !(Time.time < slot.NextRemoteSync)) { slot.NextRemoteSync = Time.time + value; Vector3 val3 = val2 - slot.LastSentOffset; if (((Vector3)(ref val3)).sqrMagnitude > 0.0001f) { slot.LastSentOffset = val2; SendRemoteOffset(slot, me, val2); } } } } private Vector3 ComputeWobble(Slot slot, Vector3 basePos) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006a: 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_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: 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) //IL_0082: 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_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0030: 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_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: 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_0057: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: 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_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0160: 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_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) float value = ModConfig.WobbleStrength.Value; float num = Mathf.Clamp(Time.deltaTime, 0.001f, 0.05f); if (!slot.WobbleInit) { slot.WobbleInit = true; slot.PrevBase = basePos; slot.PrevBaseVel = Vector3.zero; slot.WobbleOffset = Vector3.zero; slot.WobbleVel = Vector3.zero; return Vector3.zero; } Vector3 val = (basePos - slot.PrevBase) / num; Vector3 val2 = (val - slot.PrevBaseVel) / num; slot.PrevBase = basePos; slot.PrevBaseVel = val; if (value <= 0f) { slot.WobbleOffset = Vector3.zero; slot.WobbleVel = Vector3.zero; return Vector3.zero; } if (((Vector3)(ref val2)).sqrMagnitude > 6400f) { val2 = ((Vector3)(ref val2)).normalized * 80f; } Vector3 val3 = -180f * slot.WobbleOffset - 9f * slot.WobbleVel - val2; slot.WobbleVel += val3 * num; slot.WobbleOffset += slot.WobbleVel * num; if (((Vector3)(ref slot.WobbleOffset)).sqrMagnitude > 0.0144f) { slot.WobbleOffset = ((Vector3)(ref slot.WobbleOffset)).normalized * 0.12f; slot.WobbleVel *= 0.5f; } return slot.WobbleOffset * value; } private static void EnforceNoInteraction(Slot slot) { if (ModConfig.DisableInteraction.Value && !((Object)(object)slot.Sticky == (Object)null)) { if ((Object)(object)((ItemComponent)slot.Sticky).item != (Object)null) { ((ItemComponent)slot.Sticky).item.blockInteraction = true; } List<StickyItemComponent> aLL_STUCK_ITEMS = StickyItemComponent.ALL_STUCK_ITEMS; if (aLL_STUCK_ITEMS != null && aLL_STUCK_ITEMS.Contains(slot.Sticky)) { aLL_STUCK_ITEMS.Remove(slot.Sticky); } } } private static void SendRemoteOffset(Slot slot, Character me, Vector3 offsetFromPart) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected I4, but got Unknown //IL_0087: Unknown result type (might be due to invalid IL or missing references) PhotonView val = (((Object)(object)me != (Object)null) ? ((Component)me).GetComponent<PhotonView>() : null); if ((Object)(object)val == (Object)null || (Object)(object)slot.Sticky == (Object)null || (Object)(object)((MonoBehaviourPun)slot.Sticky).photonView == (Object)null || (Object)(object)slot.Part == (Object)null) { return; } try { ((MonoBehaviourPun)slot.Sticky).photonView.RPC("RPC_StickToCharacterRemote", (RpcTarget)1, new object[3] { val.ViewID, (int)slot.Part.partType, offsetFromPart }); } catch (Exception ex) { ModLog.Warn("同步球位置失败:" + ex.Message); } } } internal class CactusNotify : MonoBehaviour { private class Entry { public string Text; public float EndTime; } private const int MaxEntries = 3; private const float DefaultDuration = 4f; private readonly List<Entry> _entries = new List<Entry>(); private GUIStyle _box; private GUIStyle _text; public static void Show(string text, float duration = 4f) { CactusNotify cactusNotify = Object.FindAnyObjectByType<CactusNotify>(); if (!((Object)(object)cactusNotify == (Object)null)) { cactusNotify.Enqueue(text, duration); } } private void Enqueue(string text, float duration) { if (!string.IsNullOrEmpty(text)) { while (_entries.Count >= 3) { _entries.RemoveAt(0); } _entries.Add(new Entry { Text = text, EndTime = Time.time + Mathf.Max(0.5f, duration) }); ModLog.Info("[提示] " + text); } } private void Update() { for (int num = _entries.Count - 1; num >= 0; num--) { if (Time.time >= _entries[num].EndTime) { _entries.RemoveAt(num); } } } private void EnsureStyle() { //IL_0045: 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_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0020: 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_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected O, but got Unknown //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Expected O, but got Unknown PanelTheme.EnsureBuilt(); if (_text == null) { _text = new GUIStyle(PanelTheme.Value) { fontSize = 18, fontStyle = (FontStyle)1, alignment = (TextAnchor)4, wordWrap = false }; } _text.normal.textColor = PanelTheme.TxtWarn; _text.hover.textColor = PanelTheme.TxtWarn; if (_box == null) { _box = new GUIStyle(PanelTheme.Card); _box.padding = new RectOffset(14, 14, 9, 9); } Font toastFont = PanelTheme.ToastFont; if ((Object)(object)toastFont != (Object)null) { _box.font = toastFont; _text.font = toastFont; } } private void OnGUI() { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: 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) try { if (_entries.Count == 0) { return; } EnsureStyle(); float num = (float)Screen.height * 0.1f; Rect val2 = default(Rect); for (int i = 0; i < _entries.Count; i++) { Entry entry = _entries[i]; float num2 = entry.EndTime - Time.time; if (!(num2 <= 0f)) { float num3 = Mathf.Clamp01(num2 / 0.6f); Vector2 val = _text.CalcSize(new GUIContent(entry.Text)); float num4 = Mathf.Min(val.x + 28f, (float)Screen.width - 40f); ((Rect)(ref val2))..ctor(((float)Screen.width - num4) * 0.5f, num, num4, val.y + 18f); Color color = GUI.color; GUI.color = new Color(1f, 1f, 1f, num3); GUI.Box(val2, GUIContent.none, _box); GUI.Label(val2, entry.Text, _text); GUI.color = color; num += ((Rect)(ref val2)).height + 6f; } } } catch (Exception ex) { ModLog.Warn("绘制提示失败:" + ex.Message); } } } internal static class CactusPrefabFinder { private const string DirectPath = "0_Items/CactusBall"; private static string _resolved; private static bool _searched; public static string Resolve() { string text = ModConfig.PrefabName?.Value; if (!string.IsNullOrWhiteSpace(text)) { text = text.Trim(); string text2 = (text.Contains("/") ? text : ("0_Items/" + text)); ModLog.Detail("使用配置指定的仙人球预制体路径:" + text2); return text2; } if (!string.IsNullOrEmpty(_resolved)) { return _resolved; } if (_searched) { return null; } _searched = true; ModLog.Detail("尝试直连读取预制体:Resources.Load<GameObject>(\"0_Items/CactusBall\")"); GameObject val = Resources.Load<GameObject>("0_Items/CactusBall"); if ((Object)(object)val != (Object)null && (Object)(object)val.GetComponent<CactusBall>() != (Object)null) { _resolved = "0_Items/CactusBall"; ModLog.Info("已定位仙人球预制体(直连):" + _resolved); return _resolved; } ModLog.Detail("直连失败,扫描 Resources/0_Items 全部预制体,查找带 CactusBall 组件的对象……"); GameObject[] array = Resources.LoadAll<GameObject>("0_Items"); ModLog.Detail($"0_Items 下共加载 {((array != null) ? array.Length : 0)} 个预制体。"); GameObject[] array2 = array; foreach (GameObject val2 in array2) { if ((Object)(object)val2 != (Object)null && (Object)(object)val2.GetComponent<CactusBall>() != (Object)null) { _resolved = "0_Items/" + ((Object)val2).name; ModLog.Info("已自动定位仙人球预制体(扫描):" + _resolved); return _resolved; } } ModLog.Error("未能自动定位仙人球预制体,请在配置文件中手动填写「3-高级 / 仙人球预制体名」。"); return null; } public static void Reset() { _resolved = null; _searched = false; } } internal class CactusTracker : MonoBehaviour { public bool IsLeft; public int ViewId = -1; public StickyItemComponent Sticky; private string Side { get { if (!IsLeft) { return "右臀"; } return "左臀"; } } private void OnDisable() { try { ModLog.Info($"[{Side}] 仙人球对象被禁用(ViewID={ViewId},对象名={((Object)this).name})。"); } catch (Exception ex) { ModLog.Warn("[" + Side + "] 记录禁用事件失败:" + ex.Message); } } private void OnDestroy() { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) try { string text = "无"; string text2 = "无"; string text3 = "未知"; if ((Object)(object)Sticky != (Object)null) { text = (((Object)(object)Sticky.stuckToCharacter == (Object)null) ? "空" : ((Object)Sticky.stuckToCharacter).name); text2 = (((Object)(object)((ItemComponent)Sticky).item == (Object)null) ? "无" : ((object)((ItemComponent)Sticky).item.itemState/*cast due to .constrained prefix*/).ToString()); text3 = (StickyItemComponent.ALL_STUCK_ITEMS != null && StickyItemComponent.ALL_STUCK_ITEMS.Contains(Sticky)).ToString(); } else { text = "组件已销毁"; } string arg = "未知"; PhotonView component = ((Component)this).GetComponent<PhotonView>(); if ((Object)(object)component != (Object)null) { arg = ((component.Owner == null) ? "无主" : component.Owner.NickName); } ModLog.Info($"[{Side}] 仙人球对象被销毁(ViewID={ViewId},Owner={arg}," + "粘附角色=" + text + ",物品状态=" + text2 + ",在粘附列表=" + text3 + ")。"); } catch (Exception ex) { ModLog.Warn("[" + Side + "] 记录销毁事件失败:" + ex.Message); } } } internal static class L { public static bool English { get { if (ModConfig.UseEnglish != null) { return ModConfig.UseEnglish.Value; } return false; } } public static string T(string chinese, string english) { if (!English) { return chinese; } return english; } public static void Toggle() { if (ModConfig.UseEnglish != null) { ModConfig.UseEnglish.Value = !ModConfig.UseEnglish.Value; ModConfig.Save(); } } public static string OnOff(bool on) { if (!on) { return T("关", "off"); } return T("开", "on"); } } internal static class CactusInteractGuard { public static void Enforce(StickyItemComponent sticky) { if (!((Object)(object)sticky == (Object)null)) { if ((Object)(object)((ItemComponent)sticky).item != (Object)null) { ((ItemComponent)sticky).item.blockInteraction = true; } List<StickyItemComponent> aLL_STUCK_ITEMS = StickyItemComponent.ALL_STUCK_ITEMS; if (aLL_STUCK_ITEMS != null && aLL_STUCK_ITEMS.Contains(sticky)) { aLL_STUCK_ITEMS.Remove(sticky); } } } } internal class LegAttachTest : MonoBehaviour { private class Slot { public bool IsLeft; public GameObject Go; public StickyItemComponent Sticky; public Bodypart Part; public bool Attaching; public bool AppliedInit; public float AppliedLateral; public float AppliedBackward; public float AppliedVertical; public Vector3 AppliedDelta; public int LastViewId = -1; public bool PreviouslyStuck; } private readonly Slot[] _slots = new Slot[2] { new Slot { IsLeft = true }, new Slot { IsLeft = false } }; private static float _ballRadius = -1f; private bool _active; private float _nextAttemptTime; private bool _hadCharacter; private void Update() { if (!ModConfig.TestLegAttach.Value) { if (_active) { _active = false; ModLog.Info("真实粘附:已关闭,正在清理测试仙人球。"); DetachAll(); } return; } _active = true; Character localCharacter = Character.localCharacter; if ((Object)(object)localCharacter == (Object)null || !PhotonNetwork.InRoom) { _hadCharacter = false; Slot[] slots = _slots; for (int i = 0; i < slots.Length; i++) { slots[i].PreviouslyStuck = false; } return; } if (!_hadCharacter) { _hadCharacter = true; ModLog.Info("真实粘附:开始把真实仙人球粘到上腿(角色「" + ((Object)localCharacter).name + "」)。"); } MaintainSlots(localCharacter); ApplyLiveTuning(localCharacter); } private void MaintainSlots(Character me) { //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) bool flag = true; Slot[] slots = _slots; foreach (Slot slot in slots) { if (slot.Attaching) { flag = false; continue; } if ((Object)(object)slot.Go != (Object)null && (Object)(object)slot.Sticky != (Object)null && (Object)(object)slot.Sticky.stuckToCharacter == (Object)(object)me) { slot.PreviouslyStuck = true; CactusInteractGuard.Enforce(slot.Sticky); continue; } bool flag2 = (Object)(object)slot.Go == (Object)null; if (slot.PreviouslyStuck && flag2 && ModConfig.NotifyWhenTaken.Value) { CactusNotify.Show("你的测试仙人球(" + (slot.IsLeft ? "左" : "右") + "腿)被别人取走了!"); } slot.PreviouslyStuck = false; if ((Object)(object)slot.Go != (Object)null || slot.LastViewId != -1) { string text = (((Object)(object)slot.Sticky == (Object)null || (Object)(object)slot.Sticky.stuckToCharacter == (Object)null) ? "空" : ((Object)slot.Sticky.stuckToCharacter).name); string text2 = (((Object)(object)slot.Sticky == (Object)null || (Object)(object)((ItemComponent)slot.Sticky).item == (Object)null) ? "无" : ((object)((ItemComponent)slot.Sticky).item.itemState/*cast due to .constrained prefix*/).ToString()); ModLog.Info("真实粘附:" + (slot.IsLeft ? "左" : "右") + "侧已失效并重建" + string.Format("(ViewID={0},对象={1},", slot.LastViewId, ((Object)(object)slot.Go == (Object)null) ? "已销毁" : ((Object)slot.Go).name) + "粘附角色=" + text + ",物品状态=" + text2 + ")。"); } SafeDestroy(slot.Go); slot.Go = null; slot.Sticky = null; slot.Part = null; slot.LastViewId = -1; flag = false; } if (flag || Time.time < _nextAttemptTime) { return; } _nextAttemptTime = Time.time + 1f; slots = _slots; foreach (Slot slot2 in slots) { if (!slot2.Attaching && (Object)(object)slot2.Go == (Object)null) { ((MonoBehaviour)this).StartCoroutine(AttachRoutine(slot2, me)); } } } private IEnumerator AttachRoutine(Slot slot, Character me) { string side = (slot.IsLeft ? "左" : "右"); string text = (slot.IsLeft ? ModConfig.TestLegLeftPart.Value : ModConfig.TestLegRightPart.Value); slot.Attaching = true; try { Bodypart part = BodypartFinder.Find(me, text); if ((Object)(object)part == (Object)null) { ModLog.Warn("真实粘附:找不到" + side + "上腿部位「" + text + "」,请检查配置或开启「调试_打印所有身体部位」。"); yield break; } string text2 = CactusPrefabFinder.Resolve(); if (string.IsNullOrEmpty(text2)) { ModLog.Error("真实粘附:未能解析仙人球预制体路径,放弃本次附着。"); yield break; } Quaternion spawnRot = part.transform.rotation * Quaternion.Euler(ModConfig.RotX.Value, ModConfig.RotY.Value, ModConfig.RotZ.Value); GameObject go; try { go = PhotonNetwork.Instantiate(text2, part.transform.position, spawnRot, (byte)0, (object[])null); } catch (Exception arg) { ModLog.Error($"真实粘附:Instantiate 生成仙人球失败:{arg}"); yield break; } CactusBall cactus = (((Object)(object)go != (Object)null) ? go.GetComponent<CactusBall>() : null); if ((Object)(object)cactus == (Object)null) { ModLog.Warn("真实粘附:生成的预制体上没有 CactusBall 组件,已销毁。"); if ((Object)(object)go != (Object)null) { SafeDestroy(go); } yield break; } if (ModConfig.Scale.Value != 1f) { Transform transform = go.transform; transform.localScale *= ModConfig.Scale.Value; } ((StickyItemComponent)cactus).addWeightToStuckPlayer = 0; ((StickyItemComponent)cactus).addThornsToStuckPlayer = 0; if ((Object)(object)((ItemComponent)cactus).item != (Object)null) { ((ItemComponent)cactus).item.SetKinematic(true); } if ((Object)(object)((StickyItemComponent)cactus).physicalCollider != (Object)null) { ((StickyItemComponent)cactus).physicalCollider.isTrigger = true; } slot.Go = go; slot.Sticky = (StickyItemComponent)(object)cactus; PhotonView component = go.GetComponent<PhotonView>(); slot.LastViewId = (((Object)(object)component != (Object)null) ? component.ViewID : (-1)); CactusTracker cactusTracker = go.AddComponent<CactusTracker>(); cactusTracker.IsLeft = slot.IsLeft; cactusTracker.ViewId = slot.LastViewId; cactusTracker.Sticky = (StickyItemComponent)(object)cactus; yield return null; yield return null; if ((Object)(object)go == (Object)null || (Object)(object)cactus == (Object)null) { slot.Go = null; slot.Sticky = null; yield break; } if ((Object)(object)((ItemComponent)cactus).item != (Object)null && (int)((ItemComponent)cactus).item.itemState != 0) { ((ItemComponent)cactus).item.itemState = (ItemState)0; } string detail; float num = PlacementMath.MeasureRadius(go, out detail); if (_ballRadius <= 0f && num > 0f) { _ballRadius = num; } float num2 = ((_ballRadius > 0f) ? _ballRadius : num) * ModConfig.RadiusScale.Value; float num3 = ((ModConfig.LateralSpacing.Value > 0f) ? ModConfig.LateralSpacing.Value : num2); float num4 = ((ModConfig.BackwardOffset.Value > 0f) ? ModConfig.BackwardOffset.Value : num2); float value = ModConfig.VerticalOffset.Value; Vector3 delta = GetDelta(slot.IsLeft); Vector3 val = PlacementMath.ApplyDelta(me, PlacementMath.ComputeWorldOffset(me, slot.IsLeft, num3, num4, value), delta); Vector3 val2 = part.transform.position + val; slot.Part = part; slot.AppliedLateral = num3; slot.AppliedBackward = num4; slot.AppliedVertical = value; slot.AppliedDelta = delta; slot.AppliedInit = true; go.transform.SetPositionAndRotation(val2, spawnRot); if (StickyApi.Stick((StickyItemComponent)(object)cactus, me, part, val)) { CactusInteractGuard.Enforce((StickyItemComponent)(object)cactus); ModLog.Info($"真实粘附:{side}球已粘到 {part.partType},球心={val2},已禁用交互。"); yield break; } ModLog.Warn($"真实粘附:{side}球粘附「{part.partType}」失败,稍后重试。"); SafeDestroy(go); slot.Go = null; slot.Sticky = null; } finally { slot.Attaching = false; } } private static Vector3 GetDelta(bool isLeft) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (!isLeft) { return new Vector3(ModConfig.TestRightOffsetRight.Value, ModConfig.TestRightOffsetUp.Value, ModConfig.TestRightOffsetForward.Value); } return new Vector3(ModConfig.TestLeftOffsetRight.Value, ModConfig.TestLeftOffsetUp.Value, ModConfig.TestLeftOffsetForward.Value); } private void ApplyLiveTuning(Character me) { //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_016a: 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_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: 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_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: 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_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0141: 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_022a: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) float num = ((_ballRadius > 0f) ? _ballRadius : 0.4f) * ModConfig.RadiusScale.Value; float num2 = ((ModConfig.LateralSpacing.Value > 0f) ? ModConfig.LateralSpacing.Value : num); float num3 = ((ModConfig.BackwardOffset.Value > 0f) ? ModConfig.BackwardOffset.Value : num); float value = ModConfig.VerticalOffset.Value; Slot[] slots = _slots; foreach (Slot slot in slots) { if (slot.Attaching || (Object)(object)slot.Go == (Object)null || (Object)(object)slot.Sticky == (Object)null || (Object)(object)slot.Part == (Object)null || (Object)(object)slot.Sticky.stuckToCharacter != (Object)(object)me) { continue; } Vector3 delta = GetDelta(slot.IsLeft); if (slot.AppliedInit && !(Mathf.Abs(num2 - slot.AppliedLateral) > 1E-05f) && !(Mathf.Abs(num3 - slot.AppliedBackward) > 1E-05f) && !(Mathf.Abs(value - slot.AppliedVertical) > 1E-05f)) { Vector3 val = delta - slot.AppliedDelta; if (!(((Vector3)(ref val)).sqrMagnitude > 1E-08f)) { continue; } } Vector3 val2 = PlacementMath.ApplyDelta(me, PlacementMath.ComputeWorldOffset(me, slot.IsLeft, num2, num3, value), delta); slot.Sticky.stuckLocalOffset = slot.Part.transform.InverseTransformVector(val2); slot.Go.transform.SetPositionAndRotation(slot.Part.transform.position + val2, slot.Go.transform.rotation); slot.AppliedLateral = num2; slot.AppliedBackward = num3; slot.AppliedVertical = value; slot.AppliedDelta = delta; slot.AppliedInit = true; SendRemoteOffset(slot, me, val2); ModLog.Detail(string.Format("真实粘附:{0}侧定位参数变化,已重算并应用,球心={1}。", slot.IsLeft ? "左" : "右", slot.Part.transform.position + val2)); } } private static void SendRemoteOffset(Slot slot, Character me, Vector3 worldOffset) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected I4, but got Unknown //IL_0087: Unknown result type (might be due to invalid IL or missing references) PhotonView val = (((Object)(object)me != (Object)null) ? ((Component)me).GetComponent<PhotonView>() : null); if ((Object)(object)val == (Object)null || (Object)(object)slot.Sticky == (Object)null || (Object)(object)((MonoBehaviourPun)slot.Sticky).photonView == (Object)null || (Object)(object)slot.Part == (Object)null) { return; } try { ((MonoBehaviourPun)slot.Sticky).photonView.RPC("RPC_StickToCharacterRemote", (RpcTarget)1, new object[3] { val.ViewID, (int)slot.Part.partType, worldOffset }); } catch (Exception ex) { ModLog.Warn("真实粘附:同步位置给其他玩家失败:" + ex.Message); } } private void DetachAll() { Slot[] slots = _slots; foreach (Slot slot in slots) { if ((Object)(object)slot.Go != (Object)null) { StickyApi.Unstick(slot.Sticky); SafeDestroy(slot.Go); } slot.Go = null; slot.Sticky = null; slot.Part = null; slot.AppliedInit = false; slot.PreviouslyStuck = false; } } private static void SafeDestroy(GameObject go) { if ((Object)(object)go == (Object)null) { return; } try { if (PhotonNetwork.InRoom) { PhotonNetwork.Destroy(go); } else { Object.Destroy((Object)(object)go); } } catch (Exception ex) { ModLog.Warn("真实粘附:销毁失败,改为本地销毁:" + ex.Message); Object.Destroy((Object)(object)go); } } private void OnDestroy() { DetachAll(); } } internal static class ModConfig { private static ConfigFile _config; public static ConfigEntry<bool> Enabled; public static ConfigEntry<bool> TestLegAttach; public static ConfigEntry<string> TestLegLeftPart; public static ConfigEntry<string> TestLegRightPart; public static ConfigEntry<float> TestLeftOffsetRight; public static ConfigEntry<float> TestLeftOffsetUp; public static ConfigEntry<float> TestLeftOffsetForward; public static ConfigEntry<float> TestRightOffsetRight; public static ConfigEntry<float> TestRightOffsetUp; public static ConfigEntry<float> TestRightOffsetForward; public static ConfigEntry<string> BasePart; public static ConfigEntry<bool> AnchorToLegs; public static ConfigEntry<float> LateralSpacing; public static ConfigEntry<float> BackwardOffset; public static ConfigEntry<float> VerticalOffset; public static ConfigEntry<float> RadiusScale; public static ConfigEntry<float> LeftOffsetRight; public static ConfigEntry<float> LeftOffsetUp; public static ConfigEntry<float> LeftOffsetForward; public static ConfigEntry<float> RightOffsetRight; public static ConfigEntry<float> RightOffsetUp; public static ConfigEntry<float> RightOffsetForward; public static ConfigEntry<float> Scale; public static ConfigEntry<float> RotX; public static ConfigEntry<float> RotY; public static ConfigEntry<float> RotZ; public static ConfigEntry<bool> AddWeight; public static ConfigEntry<bool> AddThorns; public static ConfigEntry<bool> NotifyWhenTaken; public static ConfigEntry<bool> ImmuneToCactus; public static ConfigEntry<bool> DisableInteraction; public static ConfigEntry<float> WobbleStrength; public static ConfigEntry<string> PrefabName; public static ConfigEntry<bool> DebugDumpBodyparts; public static ConfigEntry<float> RemoteSyncInterval; public static ConfigEntry<bool> VerboseLog; public static ConfigEntry<float> SnapshotInterval; public static ConfigEntry<KeyCode> HotkeyPanel; public static ConfigEntry<float> PanelScale; public static ConfigEntry<bool> UseEnglish; public static void Bind(ConfigFile config) { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Expected O, but got Unknown //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Expected O, but got Unknown //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Expected O, but got Unknown //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Expected O, but got Unknown //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Expected O, but got Unknown //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Expected O, but got Unknown //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Expected O, but got Unknown //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_026f: Expected O, but got Unknown //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_02a7: Expected O, but got Unknown //IL_02d5: Unknown result type (might be due to invalid IL or missing references) //IL_02df: Expected O, but got Unknown //IL_030d: Unknown result type (might be due to invalid IL or missing references) //IL_0317: Expected O, but got Unknown //IL_0345: Unknown result type (might be due to invalid IL or missing references) //IL_034f: Expected O, but got Unknown //IL_037d: Unknown result type (might be due to invalid IL or missing references) //IL_0387: Expected O, but got Unknown //IL_03b5: Unknown result type (might be due to invalid IL or missing references) //IL_03bf: Expected O, but got Unknown //IL_03ed: Unknown result type (might be due to invalid IL or missing references) //IL_03f7: Expected O, but got Unknown //IL_0425: Unknown result type (might be due to invalid IL or missing references) //IL_042f: Expected O, but got Unknown //IL_045d: Unknown result type (might be due to invalid IL or missing references) //IL_0467: Expected O, but got Unknown //IL_0495: Unknown result type (might be due to invalid IL or missing references) //IL_049f: Expected O, but got Unknown //IL_04cd: Unknown result type (might be due to invalid IL or missing references) //IL_04d7: Expected O, but got Unknown //IL_0505: Unknown result type (might be due to invalid IL or missing references) //IL_050f: Expected O, but got Unknown //IL_053d: Unknown result type (might be due to invalid IL or missing references) //IL_0547: Expected O, but got Unknown //IL_0636: Unknown result type (might be due to invalid IL or missing references) //IL_0640: Expected O, but got Unknown //IL_0689: Unknown result type (might be due to invalid IL or missing references) //IL_0693: Expected O, but got Unknown //IL_06e0: Unknown result type (might be due to invalid IL or missing references) //IL_06ea: Expected O, but got Unknown _config = config; Enabled = config.Bind<bool>("0-总开关", "启用屁股仙人球", false, "关闭后不再生成,并会清理已生成的仙人球。可在面板里直接开关。"); TestLegAttach = config.Bind<bool>("1-真实粘附(上腿)", "启用真实粘附", false, "开启后把两个原版仙人球直接粘在左右上腿骨上,不做任何物理模拟,完全靠腿部摆动产生晃动。"); TestLegLeftPart = config.Bind<string>("1-真实粘附(上腿)", "左上腿部位", "Leg_L", "左侧仙人球粘附的身体部位名称,默认 Leg_L(左大腿)。"); TestLegRightPart = config.Bind<string>("1-真实粘附(上腿)", "右上腿部位", "Leg_R", "右侧仙人球粘附的身体部位名称,默认 Leg_R(右大腿)。"); TestLeftOffsetRight = config.Bind<float>("1-真实粘附(上腿)", "左侧_左右(米)", 0.06f, new ConfigDescription("相对左大腿骨的左右偏移(米),正数向右。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1f, 1f), Array.Empty<object>())); TestLeftOffsetUp = config.Bind<float>("1-真实粘附(上腿)", "左侧_上下(米)", 0.74f, new ConfigDescription("相对左大腿骨的上下偏移(米),正数向上。调小可让球往下贴。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1f, 1f), Array.Empty<object>())); TestLeftOffsetForward = config.Bind<float>("1-真实粘附(上腿)", "左侧_前后(米)", 0f, new ConfigDescription("相对左大腿骨的前后偏移(米),正数向前。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1f, 1f), Array.Empty<object>())); TestRightOffsetRight = config.Bind<float>("1-真实粘附(上腿)", "右侧_左右(米)", -0.269f, new ConfigDescription("相对右大腿骨的左右偏移(米),正数向右。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1f, 1f), Array.Empty<object>())); TestRightOffsetUp = config.Bind<float>("1-真实粘附(上腿)", "右侧_上下(米)", 0.7f, new ConfigDescription("相对右大腿骨的上下偏移(米),正数向上。调小可让球往下贴。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1f, 1f), Array.Empty<object>())); TestRightOffsetForward = config.Bind<float>("1-真实粘附(上腿)", "右侧_前后(米)", 0f, new ConfigDescription("相对右大腿骨的前后偏移(米),正数向前。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1f, 1f), Array.Empty<object>())); BasePart = config.Bind<string>("2-屁股仙人球", "基准部位", "Hip", "作为两半屁股中点的身体部位名称。默认 Hip(骨盆中心)。"); AnchorToLegs = config.Bind<bool>("2-屁股仙人球", "以双腿根部为基准高度", true, "用左右大腿骨根部的连线中点作为屁股中心高度,更贴合真实屁股;关闭则完全以「基准部位」为准。"); LateralSpacing = config.Bind<float>("2-屁股仙人球", "左右间距(米)", 0f, new ConfigDescription("每个球球心到中线的距离。0=按球半径自动,使两球刚好相切。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>())); BackwardOffset = config.Bind<float>("2-屁股仙人球", "贴背距离(米)", 0f, new ConfigDescription("沿角色背后方向偏移的距离。0=按球半径自动。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>())); VerticalOffset = config.Bind<float>("2-屁股仙人球", "上下偏移(米)", -1f, new ConfigDescription("相对基准位置的上下偏移(米),正数向上。太靠上就调小。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1f, 1f), Array.Empty<object>())); RadiusScale = config.Bind<float>("2-屁股仙人球", "半径修正", 1f, new ConfigDescription("自动半径的倍率。两球仍重叠就调大,缝隙太大就调小。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 3f), Array.Empty<object>())); LeftOffsetRight = config.Bind<float>("2-屁股仙人球", "左侧_左右(米)", 0.07f, new ConfigDescription("左侧球沿角色「右」方向的偏移(米)。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1f, 1f), Array.Empty<object>())); LeftOffsetUp = config.Bind<float>("2-屁股仙人球", "左侧_上下(米)", 0.8f, new ConfigDescription("左侧球沿角色「上」方向的偏移(米)。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1f, 1f), Array.Empty<object>())); LeftOffsetForward = config.Bind<float>("2-屁股仙人球", "左侧_前后(米)", 0f, new ConfigDescription("左侧球沿角色「前」方向的偏移(米)。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1f, 1f), Array.Empty<object>())); RightOffsetRight = config.Bind<float>("2-屁股仙人球", "右侧_左右(米)", 0.06f, new ConfigDescription("右侧球沿角色「右」方向的偏移(米)。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1f, 1f), Array.Empty<object>())); RightOffsetUp = config.Bind<float>("2-屁股仙人球", "右侧_上下(米)", 0.8f, new ConfigDescription("右侧球沿角色「上」方向的偏移(米)。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1f, 1f), Array.Empty<object>())); RightOffsetForward = config.Bind<float>("2-屁股仙人球", "右侧_前后(米)", 0f, new ConfigDescription("右侧球沿角色「前」方向的偏移(米)。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1f, 1f), Array.Empty<object>())); Scale = config.Bind<float>("2-屁股仙人球", "缩放", 1f, new ConfigDescription("仙人球的缩放倍数。缩放仅本机可见。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 5f), Array.Empty<object>())); RotX = config.Bind<float>("2-屁股仙人球", "旋转X", 0f, new ConfigDescription("仙人球自转角度(度)。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-180f, 180f), Array.Empty<object>())); RotY = config.Bind<float>("2-屁股仙人球", "旋转Y", 0f, new ConfigDescription("仙人球自转角度(度)。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-180f, 180f), Array.Empty<object>())); RotZ = config.Bind<float>("2-屁股仙人球", "旋转Z", 0f, new ConfigDescription("仙人球自转角度(度)。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-180f, 180f), Array.Empty<object>())); WobbleStrength = config.Bind<float>("2-屁股仙人球", "晃动强度", 3f, new ConfigDescription("模式二的惯性晃动幅度。0=不晃动,越大越明显。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>())); AddWeight = config.Bind<bool>("3-效果", "增加负重", false, "粘在身上的仙人球给角色增加负重(与原版一致)。开启「免疫仙人球效果」时在本机无效。"); AddThorns = config.Bind<bool>("3-效果", "造成扎刺伤害", false, "粘在身上的仙人球持续造成扎刺伤害。开启「免疫仙人球效果」时在本机无效。"); NotifyWhenTaken = config.Bind<bool>("3-效果", "被取走时屏幕提示", true, "身上的仙人球被他人取走时,在屏幕上方弹出一行提示。"); ImmuneToCactus = config.Bind<bool>("3-效果", "免疫仙人球效果(仅本机)", true, "任何仙人球粘到身上都不掉血、不扎刺、不影响体力条。他人的仙人球仍正常显示以保持视角一致。仅本机生效。"); DisableInteraction = config.Bind<bool>("3-效果", "禁用交互", true, "仙人球不会被自己或他人捡起,也不会出现在低头拾取提示里。"); PrefabName = config.Bind<string>("4-高级", "仙人球预制体名", "", "留空自动查找。可填 \"CactusBall\" 或完整路径 \"0_Items/xxx\"。"); DebugDumpBodyparts = config.Bind<bool>("4-高级", "调试_打印所有身体部位", false, "把当前角色的所有身体部位名称与坐标打印到日志。"); RemoteSyncInterval = config.Bind<float>("4-高级", "远程同步间隔(秒)", 0.25f, new ConfigDescription("每隔多少秒把当前偏移重发给其他玩家。0=只在粘附瞬间同步一次。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>())); VerboseLog = config.Bind<bool>("5-日志", "详细日志", false, "输出粘附流程的每一步细节,排错时开启。"); SnapshotInterval = config.Bind<float>("5-日志", "诊断快照间隔(秒)", 2f, new ConfigDescription("每隔多少秒打印一次状态快照。0=关闭。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 60f), Array.Empty<object>())); HotkeyPanel = config.Bind<KeyCode>("6-面板", "面板快捷键", (KeyCode)289, "按此键打开/关闭本面板。"); PanelScale = config.Bind<float>("6-面板", "面板缩放", 1f, new ConfigDescription("面板整体缩放倍率(含文字)。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.6f, 2.5f), Array.Empty<object>())); UseEnglish = config.Bind<bool>("6-面板", "英文界面", false, "开启后菜单文案切换为英文。也可在面板底栏一键切换。"); } public static void Save() { try { ConfigFile config = _config; if (config != null) { config.Save(); } ModLog.Info("配置已保存。"); } catch (Exception ex) { ModLog.Error("保存配置失败:" + ex.Message); } } } internal static class ModLog { public static bool Verbose { get { if (ModConfig.VerboseLog != null) { return ModConfig.VerboseLog.Value; } return true; } } public static void Info(string message) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)message); } } public static void Detail(string message) { if (Verbose) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("[详细] " + message)); } } } public static void Warn(string message) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)message); } } public static void Error(string message) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogError((object)message); } } } [HarmonyPatch(typeof(CharacterAfflictions), "UpdateWeight")] internal static class CactusImmunityPatch { private static void Prefix(CharacterAfflictions __instance) { try { if (!ModConfig.Enabled.Value || !ModConfig.ImmuneToCactus.Value) { return; } Character localCharacter = Character.localCharacter; if ((Object)(object)localCharacter == (Object)null || localCharacter.refs == null || (Object)(object)localCharacter.refs.afflictions != (Object)(object)__instance) { return; } if ((Object)(object)localCharacter.data != (Object)null && (Object)(object)localCharacter.data.currentStickyItem != (Object)null) { localCharacter.data.currentStickyItem.addWeightToStuckPlayer = 0; localCharacter.data.currentStickyItem.addThornsToStuckPlayer = 0; } List<StickyItemComponent> aLL_STUCK_ITEMS = StickyItemComponent.ALL_STUCK_ITEMS; if (aLL_STUCK_ITEMS == null) { return; } for (int i = 0; i < aLL_STUCK_ITEMS.Count; i++) { StickyItemComponent val = aLL_STUCK_ITEMS[i]; if (!((Object)(object)val == (Object)null) && !((Object)(object)val.stuckToCharacter != (Object)(object)localCharacter)) { val.addWeightToStuckPlayer = 0; val.addThornsToStuckPlayer = 0; } } } catch (Exception ex) { ModLog.Warn("免疫仙人球效果时出错:" + ex.Message); } } } internal static class PlacementDefaults { public static readonly Vector3 LegLeft = new Vector3(0.06f, 0.74f, 0f); public static readonly Vector3 LegRight = new Vector3(-0.269f, 0.7f, 0f); public static readonly Vector3 ButtLeft = new Vector3(0.07f, 0.8f, 0f); public static readonly Vector3 ButtRight = new Vector3(0.06f, 0.8f, 0f); } internal static class PlacementMath { public static bool TryGetFrame(Character character, out Vector3 forward, out Vector3 right, out Vector3 up) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: 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_0047: 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_004c: Unknown result type (might be due to invalid IL or missing references) //IL_006a: 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_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: 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_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0093: 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_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) forward = Vector3.forward; right = Vector3.right; up = Vector3.up; if ((Object)(object)character == (Object)null) { return false; } Vector3 val = (((Object)(object)character.data != (Object)null) ? character.data.lookDirection_Flat : Vector3.zero); val.y = 0f; Vector3 val2; if (((Vector3)(ref val)).sqrMagnitude > 1E-06f) { forward = ((Vector3)(ref val)).normalized; val2 = Vector3.Cross(Vector3.up, forward); right = ((Vector3)(ref val2)).normalized; up = Vector3.up; return true; } Bodypart val3 = ((character.refs != null) ? character.refs.hip : null); if ((Object)(object)val3 != (Object)null) { Vector3 val4 = Vector3.ProjectOnPlane(val3.transform.forward, Vector3.up); if (((Vector3)(ref val4)).sqrMagnitude > 1E-06f) { forward = ((Vector3)(ref val4)).normalized; val2 = Vector3.Cross(Vector3.up, forward); right = ((Vector3)(ref val2)).normalized; up = Vector3.up; return true; } } return false; } public static Vector3 TryGetButtBase(Character character, bool anchorToLegs, out string detail) { //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //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) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_008d: 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_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: 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_00ab: 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) Bodypart val = null; Bodypart part = null; Bodypart part2 = null; if ((Object)(object)character != (Object)null && character.refs != null && (Object)(object)character.refs.hip != (Object)null) { val = character.refs.hip; } if (anchorToLegs) { BodypartFinder.TryFind(character, (BodypartType)11, out part); BodypartFinder.TryFind(character, (BodypartType)14, out part2); } if ((Object)(object)part != (Object)null && (Object)(object)part2 != (Object)null) { Vector3 val2 = (part.transform.position + part2.transform.position) * 0.5f; Vector3 val3 = val2; if ((Object)(object)val != (Object)null) { ((Vector3)(ref val2))..ctor(val.transform.position.x, val3.y, val3.z); } detail = $"双腿根部中点(髋关节高度),Leg_L={part.transform.position},Leg_R={part2.transform.position}"; return val2; } if ((Object)(object)val != (Object)null) { detail = "双腿部位不可用,回退到骨盆 Hip"; return val.transform.position; } detail = "无法取得基准位置"; if (!((Object)(object)character != (Object)null)) { return Vector3.zero; } return ((Component)character).transform.position; } public static Vector3 ComputeWorldOffset(Character character, bool isLeft, float lateralSpacing, float backwardOffset, float verticalOffset) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: 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_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) TryGetFrame(character, out var forward, out var right, out var up); return right * (isLeft ? (0f - lateralSpacing) : lateralSpacing) - forward * backwardOffset + up * verticalOffset; } public static Vector3 ApplyDelta(Character character, Vector3 offset, Vector3 delta) { //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_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001e: 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_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_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) if (delta == Vector3.zero) { return offset; } TryGetFrame(character, out var forward, out var right, out var up); return offset + right * delta.x + up * delta.y + forward * delta.z; } public static float MeasureRadius(GameObject go, out string detail) { //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0089: 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_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: 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_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_0189: 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_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) float num = 0f; string text = "无"; Collider[] componentsInChildren = go.GetComponentsInChildren<Collider>(true); foreach (Collider val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } SphereCollider val2 = (SphereCollider)(object)((val is SphereCollider) ? val : null); float num2; string text2; if (val2 != null) { num2 = val2.radius * MaxAbsScale(((Component)val2).transform); text2 = "SphereCollider"; } else { CapsuleCollider val3 = (CapsuleCollider)(object)((val is CapsuleCollider) ? val : null); if (val3 != null) { num2 = val3.radius * MaxAbsScale(((Component)val3).transform); text2 = "CapsuleCollider"; } else { Bounds bounds = val.bounds; Vector3 extents = ((Bounds)(ref bounds)).extents; num2 = Mathf.Min(new float[3] { extents.x, extents.y, extents.z }); text2 = ((object)val).GetType().Name; } } if (num2 > num) { num = num2; text = text2; } } float num3 = 0f; Renderer[] componentsInChildren2 = go.GetComponentsInChildren<Renderer>(true); bool flag = false; Bounds bounds2 = default(Bounds); ((Bounds)(ref bounds2))..ctor(go.transform.position, Vector3.zero); foreach (Renderer val4 in componentsInChildren2) { if (!((Object)(object)val4 == (Object)null) && (val4 is MeshRenderer || val4 is SkinnedMeshRenderer)) { if (!flag) { bounds2 = val4.bounds; flag = true; } else { ((Bounds)(ref bounds2)).Encapsulate(val4.bounds); } } } if (flag) { num3 = Mathf.Max(new float[3] { ((Bounds)(ref bounds2)).extents.x, ((Bounds)(ref bounds2)).extents.y, ((Bounds)(ref bounds2)).extents.z }); } float num4 = ((num > 0f) ? num : num3); detail = $"碰撞体半径={num:F3}({text}),渲染半径={num3:F3},采用={num4:F3}"; return num4; } private static float MaxAbsScale(Transform t) { //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_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) Vector3 lossyScale = t.lossyScale; return Mathf.Max(new float[3] { Mathf.Abs(lossyScale.x), Mathf.Abs(lossyScale.y), Mathf.Abs(lossyScale.z) }); } } [BepInPlugin("peak.buttcactus", "屁股仙人球", "1.1.0")] public class Plugin : BaseUnityPlugin { internal static Plugin Instance; internal static ManualLogSource Log; private GameObject _host; private GameObject _panelHost; private void Awake() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Expected O, but got Unknown //IL_0163: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; ModConfig.Bind(((BaseUnityPlugin)this).Config); try { new Harmony("peak.buttcactus").PatchAll(typeof(Plugin).Assembly); } catch (Exception ex) { Log.LogError((object)("应用免疫补丁失败:" + ex)); } _host = new GameObject("ButtCactus_Host"); _host.AddComponent<ButtCactusService>(); _host.AddComponent<LegAttachTest>(); _host.AddComponent<CactusNotify>(); Object.DontDestroyOnLoad((Object)(object)_host); PanelTheme.Log = delegate(string msg) { Log.LogInfo((object)msg); }; _panelHost = new GameObject("ButtCactus_PanelHost"); ((Object)_panelHost).hideFlags = (HideFlags)61; PanelHost panelHost = _panelHost.AddComponent<PanelHost>(); panelHost.Panel = new ButtCactusPanel(); panelHost.ToggleKey = () => ModConfig.HotkeyPanel.Value; panelHost.LogError = delegate(string msg) { Log.LogError((object)msg); }; Object.DontDestroyOnLoad((Object)(object)_panelHost); Log.LogInfo((object)string.Format("{0} v{1} 已加载,按 {2} 打开面板。", "屁股仙人球", "1.1.0", ModConfig.HotkeyPanel.Value)); } } internal static class PluginInfo { public const string Guid = "peak.buttcactus"; public const string Name = "屁股仙人球"; public const string Version = "1.1.0"; } internal static class StickyApi { private static MethodInfo _stickMethod; private static MethodInfo _unstickMethod; private static MethodInfo StickMethod { get { if (_stickMethod == null) { _stickMethod = typeof(StickyItemComponent).GetMethod("StickToCharacterLocal", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (_stickMethod == null) { ModLog.Error("未能反射到 StickyItemComponent.StickToCharacterLocal,粘附功能不可用。"); } else { ModLog.Detail("已反射到 StickyItemComponent.StickToCharacterLocal。"); } } return _stickMethod; } } private static MethodInfo UnstickMethod { get { if (_unstickMethod == null) { _unstickMethod = typeof(StickyItemComponent).GetMethod("Unstick", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (_unstickMethod == null) { ModLog.Warn("未能反射到 StickyItemComponent.Unstick,清理功能将退化为直接销毁。"); } else { ModLog.Detail("已反射到 StickyItemComponent.Unstick。"); } } return _unstickMethod; } } public static bool Stick(StickyItemComponent sticky, Character character, Bodypart bodypart, Vector3 worldOffset) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)sticky == (Object)null || (Object)(object)character == (Object)null || (Object)(object)bodypart == (Object)null) { return false; } MethodInfo stickMethod = StickMetho