using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Photon.Pun; using Photon.Realtime; using UnityEngine; using UnityEngine.SceneManagement; using Zorro.Core; using Zorro.Core.Serizalization; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("Reloadable_Hand_Cannons")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Reloadable_Hand_Cannons")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("0f5e42e3-bb26-4c24-9f5b-39f5ae0867ad")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.0.0")] namespace Hand_Cannon; public enum HandCannonAmmoType { Empty, Piton, NormalRope, AntiRope } [BepInPlugin("tony4twentys.Reloadable_Hand_Cannons", "Reloadable_Hand_Cannons", "1.0.0")] public class HandCannonPlugin : BaseUnityPlugin { public const string PluginGuid = "tony4twentys.Reloadable_Hand_Cannons"; public const string PluginName = "Reloadable_Hand_Cannons"; public const string PluginVersion = "1.0.0"; internal static ConfigEntry PitonSpawnChanceConfig; internal static ConfigEntry AllowBackpackReloadConfig; internal static ConfigEntry ReloadKeyConfig; private Harmony _harmony; public static ManualLogSource Log { get; private set; } internal static HandCannonPlugin Instance { get; private set; } private void Awake() { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Expected O, but got Unknown //IL_0107: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; PitonSpawnChanceConfig = ((BaseUnityPlugin)this).Config.Bind("Spawn", "Piton Spawn Chance", 0.5f, "Chance (0.0 to 1.0) for a Hand Cannon (item 63) to spawn loaded with a piton instead of normal rope."); AllowBackpackReloadConfig = ((BaseUnityPlugin)this).Config.Bind("Reload", "Allow Backpack Reload", true, "When true, pressing the reload key can consume ammo from a worn backpack."); ReloadKeyConfig = ((BaseUnityPlugin)this).Config.Bind("Reload", "Reload Key", (KeyCode)114, "Press this key while holding an empty Hand Cannon to reload from inventory."); WarnIfConflictingModsLoaded(); _harmony = new Harmony("tony4twentys.Reloadable_Hand_Cannons"); try { _harmony.PatchAll(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Reloadable Hand Cannons harmony patching failed: " + ex.Message)); } HandCannonReloadPickerUI.EnsureCreated(); HandCannonNetworkSync.EnsureProxy(); SceneManager.sceneLoaded += OnSceneLoaded; ((BaseUnityPlugin)this).Logger.LogInfo((object)($"Reloadable Hand Cannons loaded. Piton spawn chance: {PitonSpawnChanceConfig.Value:P0}. " + string.Format("Reload key: {0}. Backpack reload: {1}.", ReloadKeyConfig.Value, AllowBackpackReloadConfig.Value ? "enabled" : "disabled"))); } private void Update() { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) if (HandCannonReloadPickerUI.IsOpen) { if (Input.GetKeyDown((KeyCode)27) || Input.GetKeyDown(ReloadKeyConfig.Value)) { Log.LogInfo((object)"[Reload] picker cancelled."); HandCannonReloadPickerUI.Close(); } else { HandCannonReloadPickerUI.TryKeyboardSelect(); } } else { if (!Input.GetKeyDown(ReloadKeyConfig.Value)) { return; } Character localCharacter = Character.localCharacter; object obj; if (localCharacter == null) { obj = null; } else { CharacterData data = localCharacter.data; obj = ((data != null) ? data.currentItem : null); } if (!((Object)obj == (Object)null)) { Item currentItem = localCharacter.data.currentItem; RopeShooter component = ((Component)currentItem).GetComponent(); if (HandCannonHelper.IsHandCannon(currentItem) && !((Object)(object)component == (Object)null) && HandCannonHelper.IsEmptyForReload(currentItem, component) && !currentItem.isUsingPrimary) { HandCannonReloadExecutor.TryManualReload(localCharacter); } } } } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { HandCannonReloadPickerUI.ForceClose(); } private void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; Instance = null; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } HandCannonReloadPickerUI.DestroyInstance(); Log = null; } private static void WarnIfConflictingModsLoaded() { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { string name = assembly.GetName().Name; if (string.Equals(name, "Piton Launcher", StringComparison.Ordinal) || string.Equals(name, "Reloadable Rope Canons", StringComparison.Ordinal) || string.Equals(name, "Hand Cannon", StringComparison.Ordinal)) { Log.LogWarning((object)("Detected '" + name + "' — disable it to avoid Harmony conflicts with Reloadable Hand Cannons.")); } } } } internal static class HandCannonDataEntryKey { public static readonly DataEntryKey LoadedAmmoType = (DataEntryKey)74; } internal static class GameItemIds { public const ushort RopeCannonItemId = 63; public const ushort AntiRopeCannonItemId = 64; public const ushort AntiRopeSpoolItemId = 1; public const ushort RopeSpoolItemId = 65; public const ushort ClimbingSpikeItemId = 18; } internal static class HandCannonHelper { public static Item GetItem(RopeShooter shooter) { if ((Object)(object)shooter == (Object)null) { return null; } return ((ItemComponent)shooter).item ?? ((Component)shooter).GetComponent(); } public static bool IsHandCannon(Item item) { if ((Object)(object)item == (Object)null || (Object)(object)((Component)item).GetComponent() == (Object)null) { return false; } return item.itemID == 63 || item.itemID == 64; } public static bool CanTagItems() { return !PhotonNetwork.InRoom || PhotonNetwork.IsMasterClient; } public static Item GetActionItem(ItemActionBase action) { return (action != null) ? ((Component)action).GetComponent() : null; } public static bool IsEmptyForReload(Item item, RopeShooter shooter) { if ((Object)(object)item == (Object)null || (Object)(object)shooter == (Object)null) { return false; } if (!shooter.HasAmmo) { return true; } OptionableIntItemData data = item.GetData((DataEntryKey)2); if (data != null && data.HasData && data.Value <= 0) { return true; } return false; } } internal static class HandCannonFireHelper { public static void ConsumeFiredShot(RopeShooter shooter, Item item) { //IL_00f2: 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_00e4: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)shooter == (Object)null || (Object)(object)item == (Object)null || !HandCannonHelper.IsHandCannon(item) || !shooter.HasAmmo) { return; } IntItemData data = ((ItemComponent)shooter).GetData((DataEntryKey)12, (Func)(() => ItemDataHelper.CreateIntItemData(0))); data.Value = 0; OptionableIntItemData data2 = item.GetData((DataEntryKey)2); if (data2 != null) { data2.Value = 0; } item.SetUseRemainingPercentage(0f); shooter.cantReFire = false; ((ItemComponent)shooter).OnInstanceDataSet(); Optionable valueOrDefault = (item.holderCharacter?.refs?.items?.currentSelectedSlot).GetValueOrDefault(); object obj; if (valueOrDefault.IsSome) { Character holderCharacter = item.holderCharacter; if ((Object)(object)((holderCharacter != null) ? holderCharacter.player : null) != (Object)null) { obj = item.holderCharacter.player.GetItemSlot(valueOrDefault.Value); goto IL_0135; } } obj = null; goto IL_0135; IL_0135: ItemSlot slot = (ItemSlot)obj; HandCannonAmmoHelper.SetLoadedAmmo(item, slot, HandCannonAmmoType.Empty); HandCannonStateSync.Sync(item, shooter); HandCannonNetworkSync.PushAmmoState(item, slot, item.holderCharacter); } } internal static class HandCannonStateSync { public static void Sync(Item item, RopeShooter shooter) { HandCannonAntigravSync.Sync(item, shooter); HandCannonVisualSync.Sync(item, shooter); } } internal static class HandCannonNetworkSync { public const byte EventCode = 191; public const int ProtocolVersion = 100; private const byte SubAmmoSync = 0; private static HandCannonNetProxy _proxy; public static void EnsureProxy() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown if (!((Object)(object)_proxy != (Object)null)) { GameObject val = new GameObject("ReloadableHandCannonsNet"); Object.DontDestroyOnLoad((Object)(object)val); _proxy = val.AddComponent(); } } public static void PushAmmoState(Item item, ItemSlot slot, Character holder) { if ((Object)(object)item != (Object)null && slot != null) { HandCannonAmmoHelper.CopyAmmoToSlot(item, slot); } PushItemInstanceData(item); BroadcastAmmo(item); SyncHolderInventory(holder); } public static void PushItemInstanceData(Item item) { if (item?.data == null || !PhotonNetwork.InRoom) { return; } PhotonView val = ((MonoBehaviourPun)item).photonView ?? item.view; if ((Object)(object)val == (Object)null || (!val.IsMine && !PhotonNetwork.IsMasterClient)) { return; } try { val.RPC("SetItemInstanceDataRPC", (RpcTarget)1, new object[1] { item.data }); } catch (Exception ex) { ManualLogSource log = HandCannonPlugin.Log; if (log != null) { log.LogWarning((object)("[Sync] SetItemInstanceDataRPC failed: " + ex.Message)); } } BroadcastAmmo(item); } public static void BroadcastAmmo(Item item) { //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_00a1: 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_00b0: Expected O, but got Unknown if ((Object)(object)item == (Object)null || !PhotonNetwork.InRoom) { return; } PhotonView val = ((MonoBehaviourPun)item).photonView ?? item.view; if ((Object)(object)val == (Object)null || (!val.IsMine && !PhotonNetwork.IsMasterClient)) { return; } HandCannonAmmoType loadedAmmo = HandCannonAmmoHelper.GetLoadedAmmo(item); try { PhotonNetwork.RaiseEvent((byte)191, (object)new object[4] { 100, (byte)0, val.ViewID, (int)loadedAmmo }, new RaiseEventOptions { Receivers = (ReceiverGroup)0 }, SendOptions.SendReliable); } catch (Exception ex) { ManualLogSource log = HandCannonPlugin.Log; if (log != null) { log.LogWarning((object)("[Sync] RaiseEvent ammo failed: " + ex.Message)); } } } public static void ApplyRemoteAmmo(int itemViewId, HandCannonAmmoType ammo) { PhotonView val = PhotonView.Find(itemViewId); if (!((Object)(object)val == (Object)null)) { Item component = ((Component)val).GetComponent(); if (HandCannonHelper.IsHandCannon(component)) { HandCannonAmmoHelper.SetLoadedAmmo(component, null, ammo); RopeShooter component2 = ((Component)component).GetComponent(); HandCannonStateSync.Sync(component, component2); } } } public static void BroadcastAllHandCannons() { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) if (!PhotonNetwork.IsMasterClient) { return; } Item[] array = Resources.FindObjectsOfTypeAll(); foreach (Item val in array) { if (HandCannonHelper.IsHandCannon(val) && !((Object)(object)((Component)val).gameObject == (Object)null)) { Scene scene = ((Component)val).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { BroadcastAmmo(val); PushItemInstanceData(val); } } } } public static void SyncHolderInventory(Character character) { //IL_0085: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)((character != null) ? character.player : null) == (Object)null || !PhotonNetwork.InRoom || !character.IsLocal) { return; } PhotonView component = ((Component)character.player).GetComponent(); if ((Object)(object)component == (Object)null) { return; } try { InventorySyncData val = default(InventorySyncData); ((InventorySyncData)(ref val))..ctor(character.player.itemSlots, character.player.backpackSlot, character.player.tempFullSlot); component.RPC("SyncInventoryRPC", (RpcTarget)1, new object[2] { IBinarySerializable.ToManagedArray(val), false }); } catch (Exception ex) { ManualLogSource log = HandCannonPlugin.Log; if (log != null) { log.LogWarning((object)("[Sync] SyncInventoryRPC failed: " + ex.Message)); } } } public static void PreserveAmmoOnStash(Item item) { //IL_0060: 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) if (!HandCannonHelper.IsHandCannon(item)) { return; } Character holderCharacter = item.holderCharacter; if ((Object)(object)((holderCharacter != null) ? holderCharacter.player : null) == (Object)null) { return; } Character holderCharacter2 = item.holderCharacter; Optionable valueOrDefault = (holderCharacter2.refs?.items?.currentSelectedSlot).GetValueOrDefault(); if (valueOrDefault.IsSome) { ItemSlot itemSlot = holderCharacter2.player.GetItemSlot(valueOrDefault.Value); if (itemSlot != null && !itemSlot.IsEmpty()) { HandCannonAmmoHelper.CopyAmmoToSlot(item, itemSlot); SyncHolderInventory(holderCharacter2); } } } public static void ScheduleSpawnResync(Item item) { EnsureProxy(); if ((Object)(object)_proxy != (Object)null && (Object)(object)item != (Object)null) { ((MonoBehaviour)_proxy).StartCoroutine(SpawnResyncRoutine(item)); } } private static IEnumerator SpawnResyncRoutine(Item item) { yield return null; if ((Object)(object)item != (Object)null) { PushItemInstanceData(item); } yield return (object)new WaitForSeconds(0.35f); if ((Object)(object)item != (Object)null) { PushItemInstanceData(item); } } internal static void HandleEvent(EventData eventData) { if (eventData != null && eventData.Code == 191 && eventData.CustomData is object[] array && array.Length >= 4 && Convert.ToInt32(array[0]) == 100 && Convert.ToByte(array[1]) == 0) { int itemViewId = Convert.ToInt32(array[2]); HandCannonAmmoType ammo = (HandCannonAmmoType)Convert.ToInt32(array[3]); ApplyRemoteAmmo(itemViewId, ammo); } } } internal sealed class HandCannonNetProxy : MonoBehaviourPunCallbacks, IOnEventCallback { public override void OnEnable() { ((MonoBehaviourPunCallbacks)this).OnEnable(); PhotonNetwork.AddCallbackTarget((object)this); } public override void OnDisable() { PhotonNetwork.RemoveCallbackTarget((object)this); ((MonoBehaviourPunCallbacks)this).OnDisable(); } public void OnPlayerEnteredRoom(Player _) { if (PhotonNetwork.IsMasterClient) { ((MonoBehaviour)this).StartCoroutine(BroadcastAfterJoin()); } } public void OnEvent(EventData photonEvent) { HandCannonNetworkSync.HandleEvent(photonEvent); } private IEnumerator BroadcastAfterJoin() { yield return (object)new WaitForSeconds(0.5f); HandCannonNetworkSync.BroadcastAllHandCannons(); } } internal static class HandCannonAmmoHelper { public static HandCannonAmmoType GetLoadedAmmo(Item item) { //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_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)item == (Object)null) { return HandCannonAmmoType.Empty; } if (item.HasData(HandCannonDataEntryKey.LoadedAmmoType)) { return (HandCannonAmmoType)item.GetData(HandCannonDataEntryKey.LoadedAmmoType).Value; } Character holderCharacter = item.holderCharacter; Optionable valueOrDefault = (holderCharacter?.refs?.items?.currentSelectedSlot).GetValueOrDefault(); if (valueOrDefault.IsSome && (Object)(object)((holderCharacter != null) ? holderCharacter.player : null) != (Object)null) { ItemSlot itemSlot = holderCharacter.player.GetItemSlot(valueOrDefault.Value); if (itemSlot == null || itemSlot.IsEmpty() || itemSlot.data == null) { return HandCannonAmmoType.Empty; } return GetLoadedAmmo(itemSlot.data); } return HandCannonAmmoType.Empty; } public static HandCannonAmmoType GetLoadedAmmo(ItemInstanceData data) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) if (data == null || !data.HasData(HandCannonDataEntryKey.LoadedAmmoType)) { return HandCannonAmmoType.Empty; } IntItemData val = default(IntItemData); if (data.TryGetDataEntry(HandCannonDataEntryKey.LoadedAmmoType, ref val)) { return (HandCannonAmmoType)val.Value; } return HandCannonAmmoType.Empty; } public static void SetLoadedAmmo(Item item, ItemSlot slot, HandCannonAmmoType ammoType) { //IL_000e: 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_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown if ((Object)(object)item != (Object)null) { IntItemData data = item.GetData(HandCannonDataEntryKey.LoadedAmmoType); data.Value = (int)ammoType; } if (slot != null) { if (slot.data == null) { slot.data = new ItemInstanceData(Guid.NewGuid()); } ItemDataHelper.SetIntEntry(slot.data, HandCannonDataEntryKey.LoadedAmmoType, (int)ammoType); } } public static void CopyAmmoToSlot(Item item, ItemSlot slot) { //IL_004e: 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) if (!((Object)(object)item == (Object)null) && slot != null) { HandCannonAmmoType loadedAmmo = GetLoadedAmmo(item); if (slot.data == null) { slot.data = (ItemInstanceData)(((object)item.data) ?? ((object)new ItemInstanceData(Guid.NewGuid()))); } ItemDataHelper.SetIntEntry(slot.data, HandCannonDataEntryKey.LoadedAmmoType, (int)loadedAmmo); } } public static string GetAmmoLabel(HandCannonAmmoType ammoType) { return ammoType switch { HandCannonAmmoType.Piton => "piton", HandCannonAmmoType.NormalRope => "rope", HandCannonAmmoType.AntiRope => "anti-grav rope", _ => "empty", }; } public static string GetDisplayName(HandCannonAmmoType ammoType) { return ammoType switch { HandCannonAmmoType.Piton => "Piton Cannon", HandCannonAmmoType.NormalRope => "Rope Cannon", HandCannonAmmoType.AntiRope => "Anti-Rope Cannon", _ => "Hand Cannon", }; } public static HandCannonAmmoType ResolveLoadedAmmoForName(Item item, ItemInstanceData data) { HandCannonAmmoType loadedAmmo = GetLoadedAmmo(data); if (loadedAmmo != HandCannonAmmoType.Empty) { return loadedAmmo; } return GetLoadedAmmo(item); } } internal static class ItemDataHelper { public static void SetIntEntry(ItemInstanceData data, DataEntryKey key, int value) { //IL_0002: 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_001e: Expected O, but got Unknown //IL_002d: Unknown result type (might be due to invalid IL or missing references) IntItemData val = default(IntItemData); if (data.TryGetDataEntry(key, ref val)) { val.Value = value; return; } IntItemData val2 = new IntItemData(); ((DataEntryValue)val2).Init(); val2.Value = value; data.RegisterEntry(key, val2); } public static void SetOptionableIntEntry(ItemInstanceData data, DataEntryKey key, int value) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_003b: Unknown result type (might be due to invalid IL or missing references) OptionableIntItemData val = default(OptionableIntItemData); if (data.TryGetDataEntry(key, ref val)) { val.HasData = true; val.Value = value; return; } OptionableIntItemData val2 = new OptionableIntItemData(); ((DataEntryValue)val2).Init(); val2.HasData = true; val2.Value = value; data.RegisterEntry(key, val2); } public static void SetFloatEntry(ItemInstanceData data, DataEntryKey key, float value) { //IL_0002: 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_001e: Expected O, but got Unknown //IL_002d: Unknown result type (might be due to invalid IL or missing references) FloatItemData val = default(FloatItemData); if (data.TryGetDataEntry(key, ref val)) { val.Value = value; return; } FloatItemData val2 = new FloatItemData(); ((DataEntryValue)val2).Init(); val2.Value = value; data.RegisterEntry(key, val2); } public static IntItemData CreateIntItemData(int value) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown IntItemData val = new IntItemData(); ((DataEntryValue)val).Init(); val.Value = value; return val; } } internal static class HandCannonAntigravSync { private static float? _cachedIntensity; public static void Sync(Item item, RopeShooter shooter) { if ((Object)(object)item == (Object)null) { return; } bool flag = (Object)(object)shooter != (Object)null && shooter.HasAmmo && HandCannonAmmoHelper.GetLoadedAmmo(item) == HandCannonAmmoType.AntiRope; Antigrav val = ((Component)item).GetComponent(); if (flag) { if ((Object)(object)val == (Object)null) { val = ((Component)item).gameObject.AddComponent(); } val.intensity = GetAntigravIntensity(); } else if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } } private static float GetAntigravIntensity() { if (_cachedIntensity.HasValue) { return _cachedIntensity.Value; } Item[] array = Resources.FindObjectsOfTypeAll(); Item[] array2 = array; foreach (Item val in array2) { if (!((Object)(object)val == (Object)null) && val.itemID == 64) { Antigrav component = ((Component)val).GetComponent(); if ((Object)(object)component != (Object)null) { _cachedIntensity = component.intensity; return _cachedIntensity.Value; } } } _cachedIntensity = 1f; return _cachedIntensity.Value; } } internal static class HandCannonVisualSync { private const string PitonVisualObjectName = "HandCannonPitonVisual"; private const string SwappedRopeVisualObjectName = "HandCannonSwappedRopeVisual"; private const string PitonClimbingSpikeChildName = "ClimbingSpike"; private static readonly Vector3 PitonClimbingSpikeLocalPosition = new Vector3(-0.0304f, -27.3382f, -4.3815f); private static readonly Vector3 PitonClimbingSpikeLocalEuler = new Vector3(270f, 180f, 0f); private static readonly Vector3 PitonClimbingSpikeLocalScale = Vector3.one; private static readonly Dictionary SwappedRopeIsAntiVisual = new Dictionary(); public static void Sync(Item item, RopeShooter shooter) { if ((Object)(object)shooter?.hideOnFire == (Object)null || !HandCannonHelper.IsHandCannon(item)) { return; } Transform transform = shooter.hideOnFire.transform; HandCannonAmmoType handCannonAmmoType = HandCannonAmmoHelper.GetLoadedAmmo(item); if (handCannonAmmoType == HandCannonAmmoType.Empty && shooter.HasAmmo) { handCannonAmmoType = ((item.itemID == 64) ? HandCannonAmmoType.AntiRope : HandCannonAmmoType.NormalRope); } if (!shooter.HasAmmo || handCannonAmmoType == HandCannonAmmoType.Empty || !shooter.hideOnFire.activeSelf) { ClearRuntimeVisuals(shooter, transform); return; } Transform val = FindDirectChild(transform, "Harpoon"); Transform val2 = FindDirectChild(transform, "Rope"); Transform val3 = EnsurePitonVisual(transform, val); bool flag = handCannonAmmoType == HandCannonAmmoType.Piton; bool flag2 = handCannonAmmoType == HandCannonAmmoType.NormalRope || handCannonAmmoType == HandCannonAmmoType.AntiRope; SetActiveIfPresent(val, flag2); SetActiveIfPresent(val3, flag); if (flag2) { ApplyRopeVisual(item, shooter, transform, val2, handCannonAmmoType); } else { HideSwappedRopeVisual(shooter, transform); SetActiveIfPresent(val2, active: false); } HideUnmanagedLoadedChildren(transform, val, val2, val3, flag, flag2); } private static void ApplyRopeVisual(Item item, RopeShooter shooter, Transform hideOnFire, Transform nativeRope, HandCannonAmmoType ammo) { bool flag = item.itemID == 64; bool flag2 = ammo == HandCannonAmmoType.AntiRope; if ((!flag || flag2) && !(!flag && flag2)) { HideSwappedRopeVisual(shooter, hideOnFire); SetActiveIfPresent(nativeRope, active: true); return; } Transform referenceRopeTransform = GetReferenceRopeTransform(flag2); if ((Object)(object)referenceRopeTransform == (Object)null) { HideSwappedRopeVisual(shooter, hideOnFire); SetActiveIfPresent(nativeRope, active: true); } else { Transform transform = EnsureSwappedRopeVisual(shooter, hideOnFire, nativeRope, referenceRopeTransform, flag2); SetActiveIfPresent(nativeRope, active: false); SetActiveIfPresent(transform, active: true); } } private static Transform GetReferenceRopeTransform(bool wantsAntiVisual) { RopeShooter shooter; if (wantsAntiVisual) { AntiRopeCannonReference.TryGetShooter(out shooter); } else { NormalRopeCannonReference.TryGetShooter(out shooter); } if ((Object)(object)shooter?.hideOnFire == (Object)null) { return null; } return FindDirectChild(shooter.hideOnFire.transform, "Rope"); } private static Transform EnsureSwappedRopeVisual(RopeShooter shooter, Transform hideOnFire, Transform nativeRope, Transform referenceRope, bool wantsAntiVisual) { int instanceID = ((Object)shooter).GetInstanceID(); Transform val = hideOnFire.Find("HandCannonSwappedRopeVisual"); if ((Object)(object)val != (Object)null && SwappedRopeIsAntiVisual.TryGetValue(instanceID, out var value) && value == wantsAntiVisual) { return val; } if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)((Component)val).gameObject); SwappedRopeIsAntiVisual.Remove(instanceID); } GameObject val2 = Object.Instantiate(((Component)referenceRope).gameObject, hideOnFire); ((Object)val2).name = "HandCannonSwappedRopeVisual"; ApplyAnchorTransform(val2.transform, nativeRope ?? referenceRope); StripGameplayComponents(val2); SwappedRopeIsAntiVisual[instanceID] = wantsAntiVisual; return val2.transform; } private static Transform EnsurePitonVisual(Transform hideOnFire, Transform harpoonAnchor) { Transform val = FindDirectChild(hideOnFire, "Piton"); val = val ?? hideOnFire.Find("HandCannonPitonVisual"); if ((Object)(object)val != (Object)null) { ApplyPitonClimbingSpikeTransform(val); return val; } GameObject val2 = Resources.Load("0_Items/Piton"); if ((Object)(object)val2 == (Object)null && PitonPrefabCache.EnsureInitialized(HandCannonPlugin.Log)) { val2 = Resources.Load(PitonPrefabCache.PrefabPath); } if ((Object)(object)val2 == (Object)null) { return null; } GameObject val3 = Object.Instantiate(val2, hideOnFire); ((Object)val3).name = "HandCannonPitonVisual"; ApplyAnchorTransform(val3.transform, harpoonAnchor); StripGameplayComponents(val3); ApplyPitonClimbingSpikeTransform(val3.transform); return val3.transform; } private static void ApplyPitonClimbingSpikeTransform(Transform pitonVisualRoot) { //IL_0082: 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_0093: 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) if ((Object)(object)pitonVisualRoot == (Object)null) { return; } Transform val = pitonVisualRoot.Find("ClimbingSpike"); if ((Object)(object)val == (Object)null) { for (int i = 0; i < pitonVisualRoot.childCount; i++) { Transform child = pitonVisualRoot.GetChild(i); if (((Object)child).name.IndexOf("ClimbingSpike", StringComparison.OrdinalIgnoreCase) >= 0) { val = child; break; } } } if (!((Object)(object)val == (Object)null)) { val.localPosition = PitonClimbingSpikeLocalPosition; val.localRotation = Quaternion.Euler(PitonClimbingSpikeLocalEuler); val.localScale = PitonClimbingSpikeLocalScale; } } private static void ApplyAnchorTransform(Transform visual, Transform anchor) { //IL_0043: 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_005d: 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_0028: 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) if (!((Object)(object)visual == (Object)null)) { if ((Object)(object)anchor == (Object)null) { visual.localPosition = Vector3.zero; visual.localRotation = Quaternion.identity; visual.localScale = Vector3.one; } else { visual.localPosition = anchor.localPosition; visual.localRotation = anchor.localRotation; visual.localScale = anchor.localScale; } } } private static void HideSwappedRopeVisual(RopeShooter shooter, Transform hideOnFire) { Transform transform = hideOnFire.Find("HandCannonSwappedRopeVisual"); SetActiveIfPresent(transform, active: false); SwappedRopeIsAntiVisual.Remove(((Object)shooter).GetInstanceID()); } private static void ClearRuntimeVisuals(RopeShooter shooter, Transform hideOnFire) { HideSwappedRopeVisual(shooter, hideOnFire); Transform transform = hideOnFire.Find("HandCannonPitonVisual"); SetActiveIfPresent(transform, active: false); Transform transform2 = FindDirectChild(hideOnFire, "Piton"); SetActiveIfPresent(transform2, active: false); } private static void HideUnmanagedLoadedChildren(Transform hideOnFire, Transform harpoon, Transform nativeRope, Transform pitonVisual, bool showPiton, bool showRopeShot) { for (int i = 0; i < hideOnFire.childCount; i++) { Transform child = hideOnFire.GetChild(i); if (!((Object)(object)child == (Object)(object)harpoon) && !((Object)(object)child == (Object)(object)nativeRope) && !((Object)(object)child == (Object)(object)pitonVisual) && !(((Object)child).name == "HandCannonSwappedRopeVisual") && !(((Object)child).name == "SpawnPoint")) { bool active = (showPiton && ((Object)child).name == "Piton") || (showRopeShot && (((Object)child).name == "Harpoon" || ((Object)child).name == "Rope")); ((Component)child).gameObject.SetActive(active); } } } private static Transform FindDirectChild(Transform parent, string childName) { if ((Object)(object)parent == (Object)null) { return null; } return parent.Find(childName); } private static void SetActiveIfPresent(Transform transform, bool active) { if (transform != null) { ((Component)transform).gameObject.SetActive(active); } } private static void StripGameplayComponents(GameObject root) { if ((Object)(object)root == (Object)null) { return; } Component[] componentsInChildren = root.GetComponentsInChildren(true); foreach (Component val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && !(val is Transform) && !(val is MeshFilter) && !(val is MeshRenderer) && !(val is SkinnedMeshRenderer) && !(val is ParticleSystem) && !(val is ParticleSystemRenderer) && !(val is LineRenderer) && !(val is TrailRenderer)) { Object.Destroy((Object)(object)val); } } } } internal static class AntiRopeCannonReference { private static RopeShooter _cachedShooter; private static bool _loggedFailure; public static bool IsAvailable() { RopeShooter shooter; return TryGetShooter(out shooter); } public static bool TryGetShooter(out RopeShooter shooter) { if ((Object)(object)_cachedShooter != (Object)null) { shooter = _cachedShooter; return true; } if (TryResolveFromLoadedItems(out shooter) || TryResolveFromResources(out shooter) || TryResolveFromLoadedAssets(out shooter)) { _cachedShooter = shooter; ManualLogSource log = HandCannonPlugin.Log; if (log != null) { log.LogInfo((object)$"Resolved anti-rope reference from '{((Object)((Component)shooter).gameObject).name}' (length={shooter.length} segments)."); } return true; } if (!_loggedFailure) { _loggedFailure = true; ManualLogSource log2 = HandCannonPlugin.Log; if (log2 != null) { log2.LogWarning((object)"Could not resolve anti-rope cannon reference. Anti-grav spool reloads disabled until available."); } } shooter = null; return false; } private static bool TryResolveFromLoadedItems(out RopeShooter shooter) { shooter = null; Item[] array = Resources.FindObjectsOfTypeAll(); foreach (Item val in array) { if (!((Object)(object)val == (Object)null) && val.itemID == 64) { RopeShooter component = ((Component)val).GetComponent(); if ((Object)(object)component?.ropeAnchorWithRopePref != (Object)null) { shooter = component; return true; } } } return false; } private static bool TryResolveFromResources(out RopeShooter shooter) { shooter = null; string[] array = new string[3] { "0_Items/Anti-Rope Cannon", "0_Items/RopeShooterAnti", "0_Items/AntiRope Cannon" }; string[] array2 = array; foreach (string text in array2) { GameObject val = Resources.Load(text); shooter = ((val != null) ? val.GetComponent() : null); if ((Object)(object)shooter != (Object)null) { return true; } } GameObject[] array3 = Resources.LoadAll("0_Items/"); if (array3 == null) { return false; } GameObject[] array4 = array3; foreach (GameObject val2 in array4) { if ((Object)(object)val2 == (Object)null) { continue; } Item component = val2.GetComponent(); if (!((Object)(object)component == (Object)null) && component.itemID == 64) { shooter = val2.GetComponent(); if ((Object)(object)shooter != (Object)null) { return true; } } } return false; } private static bool TryResolveFromLoadedAssets(out RopeShooter shooter) { //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) shooter = null; RopeShooter[] array = Resources.FindObjectsOfTypeAll(); foreach (RopeShooter val in array) { if (!((Object)(object)val == (Object)null) && !((Object)(object)((Component)val).gameObject == (Object)null)) { Scene scene = ((Component)val).gameObject.scene; if (!((Scene)(ref scene)).IsValid() && ((Component)val).GetComponent()?.itemID == 64) { shooter = val; return true; } } } return false; } public static float GetShotSegments() { if (TryGetShooter(out var shooter) && shooter.length > 0f) { return shooter.length; } return 30f; } public static GameObject GetAnchorPrefab() { RopeShooter shooter; return TryGetShooter(out shooter) ? shooter.ropeAnchorWithRopePref : null; } } internal static class NormalRopeCannonReference { private static RopeShooter _cachedShooter; private static bool _loggedFailure; public static bool IsAvailable() { RopeShooter shooter; return TryGetShooter(out shooter); } public static bool TryGetShooter(out RopeShooter shooter) { if ((Object)(object)_cachedShooter != (Object)null) { shooter = _cachedShooter; return true; } if (TryResolveFromLoadedItems(out shooter) || TryResolveFromResources(out shooter) || TryResolveFromLoadedAssets(out shooter)) { _cachedShooter = shooter; ManualLogSource log = HandCannonPlugin.Log; if (log != null) { log.LogInfo((object)$"Resolved normal rope reference from '{((Object)((Component)shooter).gameObject).name}' (length={shooter.length} segments)."); } return true; } if (!_loggedFailure) { _loggedFailure = true; ManualLogSource log2 = HandCannonPlugin.Log; if (log2 != null) { log2.LogWarning((object)"Could not resolve normal rope cannon reference. Anti-grav Hand Cannon normal reloads may fire anti rope."); } } shooter = null; return false; } private static bool TryResolveFromLoadedItems(out RopeShooter shooter) { shooter = null; Item[] array = Resources.FindObjectsOfTypeAll(); foreach (Item val in array) { if (!((Object)(object)val == (Object)null) && val.itemID == 63) { RopeShooter component = ((Component)val).GetComponent(); if ((Object)(object)component?.ropeAnchorWithRopePref != (Object)null) { shooter = component; return true; } } } return false; } private static bool TryResolveFromResources(out RopeShooter shooter) { shooter = null; string[] array = new string[3] { "0_Items/Rope Cannon", "0_Items/RopeShooter", "0_Items/Rope CannonItem" }; string[] array2 = array; foreach (string text in array2) { GameObject val = Resources.Load(text); shooter = ((val != null) ? val.GetComponent() : null); if ((Object)(object)shooter?.ropeAnchorWithRopePref != (Object)null) { return true; } } GameObject[] array3 = Resources.LoadAll("0_Items/"); if (array3 == null) { return false; } GameObject[] array4 = array3; foreach (GameObject val2 in array4) { if ((Object)(object)val2 == (Object)null) { continue; } Item component = val2.GetComponent(); if (!((Object)(object)component == (Object)null) && component.itemID == 63) { shooter = val2.GetComponent(); if ((Object)(object)shooter?.ropeAnchorWithRopePref != (Object)null) { return true; } } } return false; } private static bool TryResolveFromLoadedAssets(out RopeShooter shooter) { //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) shooter = null; RopeShooter[] array = Resources.FindObjectsOfTypeAll(); foreach (RopeShooter val in array) { if (!((Object)(object)val == (Object)null) && !((Object)(object)((Component)val).gameObject == (Object)null)) { Scene scene = ((Component)val).gameObject.scene; if (!((Scene)(ref scene)).IsValid() && ((Component)val).GetComponent()?.itemID == 63) { shooter = val; return true; } } } return false; } public static float GetShotSegments() { if (TryGetShooter(out var shooter) && shooter.length > 0f) { return shooter.length; } return 30f; } public static GameObject GetAnchorPrefab() { RopeShooter shooter; return TryGetShooter(out shooter) ? shooter.ropeAnchorWithRopePref : null; } } internal static class RopeSpoolLengthHelper { public const float MetersPerSegment = 0.25f; public const float DefaultCannonShotSegments = 30f; public const float OutOfRopeSegmentThreshold = 2f; public const float DefaultSpoolStartFuel = 60f; private const float SegmentComparisonEpsilon = 0.01f; internal const float SegmentEpsilon = 0.01f; public static float SegmentsToMeters(float segments) { return segments * 0.25f; } public static float GetCannonShotSegments(RopeShooter shooter) { if ((Object)(object)shooter == (Object)null || shooter.length <= 0f) { return 30f; } return shooter.length; } public static float GetCannonShotMeters(RopeShooter shooter) { return SegmentsToMeters(GetCannonShotSegments(shooter)); } public static float GetSpoolStartFuel(ItemSlot slot) { if ((Object)(object)slot?.prefab == (Object)null) { return 60f; } RopeSpool component = ((Component)slot.prefab).GetComponent(); return ((Object)(object)component != (Object)null) ? component.ropeStartFuel : 60f; } public static float GetRemainingSegments(ItemSlot slot) { if (slot == null || slot.IsEmpty()) { return 0f; } float spoolStartFuel = GetSpoolStartFuel(slot); ItemInstanceData data = slot.data; if (data == null) { return spoolStartFuel; } FloatItemData val = default(FloatItemData); if (data.TryGetDataEntry((DataEntryKey)10, ref val)) { return Mathf.Max(0f, val.Value); } FloatItemData val2 = default(FloatItemData); if (data.TryGetDataEntry((DataEntryKey)11, ref val2)) { return Mathf.Max(0f, val2.Value * spoolStartFuel); } return spoolStartFuel; } public static int CountFullReloadsAvailable(float remainingSegments, float requiredSegments) { if (requiredSegments <= 0.01f) { return 0; } return Mathf.FloorToInt(remainingSegments / requiredSegments); } public static bool HasReloadableLength(float segments) { return segments > 2.01f; } public static bool CanFullyReload(float segments, float requiredSegments) { return segments + 0.01f >= requiredSegments; } } internal readonly struct RopeReloadPlan { public ItemSlot PartialSlot { get; } public float SegmentsToDeduct { get; } public IReadOnlyList SlotsToConsumeEntirely { get; } public bool IsAntiRopeAmmo { get; } public bool UsesPartialSlot => PartialSlot != null; public bool IsValid => UsesPartialSlot || SlotsToConsumeEntirely.Count > 0; public RopeReloadPlan(ItemSlot partialSlot, float segmentsToDeduct, IReadOnlyList slotsToConsumeEntirely, bool isAntiRopeAmmo) { PartialSlot = partialSlot; SegmentsToDeduct = segmentsToDeduct; SlotsToConsumeEntirely = slotsToConsumeEntirely ?? Array.Empty(); IsAntiRopeAmmo = isAntiRopeAmmo; } } internal sealed class HandCannonReloadPlan { public HandCannonAmmoType AmmoType { get; private set; } public ItemSlot PitonSlot { get; private set; } public RopeReloadPlan RopePlan { get; private set; } public string DisplayLabel { get; private set; } public bool IsPiton => AmmoType == HandCannonAmmoType.Piton; public bool IsValid => IsPiton ? (PitonSlot != null) : RopePlan.IsValid; public static HandCannonReloadPlan ForPiton(ItemSlot slot) { return new HandCannonReloadPlan { AmmoType = HandCannonAmmoType.Piton, PitonSlot = slot, DisplayLabel = $"Piton — slot {slot.itemSlotID}" }; } public static HandCannonReloadPlan ForRope(RopeReloadPlan ropePlan, HandCannonAmmoType ammoType, string displayLabel) { return new HandCannonReloadPlan { AmmoType = ammoType, RopePlan = ropePlan, DisplayLabel = displayLabel }; } } internal static class HandCannonInventoryHelper { private const byte TempInventorySlotId = 250; public static bool IsHandheldPitonPrefab(Item prefab, ItemInstanceData slotData = null) { if ((Object)(object)prefab == (Object)null || (Object)(object)((Component)prefab).GetComponent() != (Object)null) { return false; } if (prefab.itemID == 18 || (Object)(object)((Component)prefab).GetComponent() != (Object)null) { IntItemData val = default(IntItemData); if (slotData != null && slotData.TryGetDataEntry((DataEntryKey)1, ref val) && val.Value > 0) { return false; } return true; } string text = ((Object)((Component)prefab).gameObject).name ?? string.Empty; return text.IndexOf("ClimbingSpike", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Piton", StringComparison.OrdinalIgnoreCase) >= 0; } public static bool IsRopeSpoolPrefab(Item prefab) { if ((Object)(object)prefab == (Object)null || (Object)(object)((Component)prefab).GetComponent() != (Object)null) { return false; } if ((Object)(object)((Component)prefab).GetComponent() != (Object)null) { return true; } if (prefab.itemID == 65 || prefab.itemID == 1) { return true; } string text = ((Object)((Component)prefab).gameObject).name ?? string.Empty; return text.IndexOf("Rope Spool", StringComparison.OrdinalIgnoreCase) >= 0; } public static bool IsAntiRopeSpoolPrefab(Item prefab) { if ((Object)(object)prefab == (Object)null) { return false; } RopeSpool component = ((Component)prefab).GetComponent(); if (component != null && component.isAntiRope) { return true; } if (prefab.itemID == 1) { return true; } string text = ((Object)((Component)prefab).gameObject).name ?? string.Empty; return text.IndexOf("Anti-Rope Spool", StringComparison.OrdinalIgnoreCase) >= 0; } public static void RefreshPitonTracking(Character character) { if ((Object)(object)character?.refs?.items != (Object)null && character.player?.itemSlots != null) { character.refs.items.UpdateClimbingSpikeCount(character.player.itemSlots); } } public static List GetPitonSlotsInOrder(Character character) { List list = new List(); if ((Object)(object)character == (Object)null) { return list; } RefreshPitonTracking(character); AppendPitonSlots(character.player?.itemSlots, list); if (character.player?.tempFullSlot != null && !character.player.tempFullSlot.IsEmpty() && IsHandheldPitonPrefab(character.player.tempFullSlot.prefab, character.player.tempFullSlot.data)) { list.Add(character.player.tempFullSlot); } if (HandCannonPlugin.AllowBackpackReloadConfig.Value) { AppendBackpackPitonSlots(character, list); } return list; } private static void AppendPitonSlots(ItemSlot[] slots, List pitonSlots) { if (slots == null) { return; } foreach (ItemSlot val in slots) { if (val != null && !val.IsEmpty() && IsHandheldPitonPrefab(val.prefab, val.data)) { pitonSlots.Add(val); } } } private static void AppendBackpackPitonSlots(Character character, List pitonSlots) { if (!((Object)(object)((character != null) ? character.player : null) == (Object)null) && character.player.backpackSlot.hasBackpack) { ItemInstanceData data = ((ItemSlot)character.player.backpackSlot).data; BackpackData val = default(BackpackData); if (data != null && data.TryGetDataEntry((DataEntryKey)7, ref val)) { AppendPitonSlots(val.itemSlots, pitonSlots); } } } public static List GetRopeSpoolSlotsInOrder(Character character) { List list = new List(); if ((Object)(object)character == (Object)null) { return list; } AppendRopeSlots(character.player?.itemSlots, list); if (character.player?.tempFullSlot != null && !character.player.tempFullSlot.IsEmpty() && IsRopeSpoolPrefab(character.player.tempFullSlot.prefab)) { list.Add(character.player.tempFullSlot); } if (HandCannonPlugin.AllowBackpackReloadConfig.Value) { AppendBackpackRopeSlots(character, list); } return list; } private static void AppendRopeSlots(ItemSlot[] slots, List ropeSlots) { if (slots == null) { return; } foreach (ItemSlot val in slots) { if (val != null && !val.IsEmpty() && IsRopeSpoolPrefab(val.prefab)) { ropeSlots.Add(val); } } } private static void AppendBackpackRopeSlots(Character character, List ropeSlots) { if (!((Object)(object)((character != null) ? character.player : null) == (Object)null) && character.player.backpackSlot.hasBackpack) { ItemInstanceData data = ((ItemSlot)character.player.backpackSlot).data; BackpackData val = default(BackpackData); if (data != null && data.TryGetDataEntry((DataEntryKey)7, ref val)) { AppendRopeSlots(val.itemSlots, ropeSlots); } } } public static bool TryBuildBestRopePlan(List ropeSlots, float requiredSegments, bool isAntiRopeAmmo, out RopeReloadPlan plan) { if (HasPartialSpoolsForAmmoType(ropeSlots, requiredSegments, isAntiRopeAmmo) && TryBuildCombinedReloadPlan(ropeSlots, requiredSegments, isAntiRopeAmmo, out plan)) { return true; } if (TryBuildSingleSpoolPlan(ropeSlots, requiredSegments, isAntiRopeAmmo, out plan)) { return true; } return TryBuildCombinedReloadPlan(ropeSlots, requiredSegments, isAntiRopeAmmo, out plan); } private static bool HasPartialSpoolsForAmmoType(List ropeSlots, float requiredSegments, bool isAntiRopeAmmo) { foreach (ItemSlot ropeSlot in ropeSlots) { if (IsAntiRopeSpoolPrefab(ropeSlot.prefab) == isAntiRopeAmmo) { float remainingSegments = RopeSpoolLengthHelper.GetRemainingSegments(ropeSlot); if (RopeSpoolLengthHelper.HasReloadableLength(remainingSegments) && !RopeSpoolLengthHelper.CanFullyReload(remainingSegments, requiredSegments)) { return true; } } } return false; } private static bool TryBuildSingleSpoolPlan(List ropeSlots, float requiredSegments, bool isAntiRopeAmmo, out RopeReloadPlan plan) { plan = default(RopeReloadPlan); foreach (ItemSlot ropeSlot in ropeSlots) { if (IsAntiRopeSpoolPrefab(ropeSlot.prefab) == isAntiRopeAmmo) { float remainingSegments = RopeSpoolLengthHelper.GetRemainingSegments(ropeSlot); if (RopeSpoolLengthHelper.CanFullyReload(remainingSegments, requiredSegments)) { plan = new RopeReloadPlan(ropeSlot, requiredSegments, Array.Empty(), isAntiRopeAmmo); return true; } } } return false; } private static bool TryBuildCombinedReloadPlan(List ropeSlots, float requiredSegments, bool isAntiRopeAmmo, out RopeReloadPlan plan) { plan = default(RopeReloadPlan); float num = 0f; List list = new List(); foreach (ItemSlot ropeSlot in ropeSlots) { if (IsAntiRopeSpoolPrefab(ropeSlot.prefab) != isAntiRopeAmmo) { continue; } float remainingSegments = RopeSpoolLengthHelper.GetRemainingSegments(ropeSlot); if (RopeSpoolLengthHelper.HasReloadableLength(remainingSegments)) { list.Add(ropeSlot); num += remainingSegments; if (RopeSpoolLengthHelper.CanFullyReload(num, requiredSegments)) { plan = BuildCombinedReloadPlan(list, requiredSegments, isAntiRopeAmmo); return plan.IsValid; } } } return false; } private static RopeReloadPlan BuildCombinedReloadPlan(List spoolsInOrder, float requiredSegments, bool isAntiRopeAmmo) { float num = requiredSegments; List list = new List(); ItemSlot val = null; float segmentsToDeduct = 0f; foreach (ItemSlot item in spoolsInOrder) { float remainingSegments = RopeSpoolLengthHelper.GetRemainingSegments(item); if (!(remainingSegments <= 2f)) { if (!(num + 0.01f >= remainingSegments)) { val = item; segmentsToDeduct = num; num = 0f; break; } list.Add(item); num -= remainingSegments; } } if (num > 0.01f) { return default(RopeReloadPlan); } if (val != null) { return new RopeReloadPlan(val, segmentsToDeduct, list, isAntiRopeAmmo); } return new RopeReloadPlan(null, 0f, list, isAntiRopeAmmo); } public static string BuildRopePlanLabel(RopeReloadPlan plan, float requiredSegments, HandCannonAmmoType ammoType) { string text = ((ammoType == HandCannonAmmoType.AntiRope) ? "Anti-grav rope" : "Rope"); float num = RopeSpoolLengthHelper.SegmentsToMeters(requiredSegments); if (plan.SlotsToConsumeEntirely.Count == 0 && plan.UsesPartialSlot) { float segments = RopeSpoolLengthHelper.GetRemainingSegments(plan.PartialSlot) - plan.SegmentsToDeduct; return $"{text} — {num:0.##}m from slot {plan.PartialSlot.itemSlotID} ({RopeSpoolLengthHelper.SegmentsToMeters(segments):0.##}m left after)"; } if (plan.SlotsToConsumeEntirely.Count > 0 && !plan.UsesPartialSlot) { return $"{text} — combine {plan.SlotsToConsumeEntirely.Count} spool(s) for {num:0.##}m"; } if (plan.SlotsToConsumeEntirely.Count > 0 && plan.UsesPartialSlot) { return $"{text} — combine {plan.SlotsToConsumeEntirely.Count + 1} spool(s) for {num:0.##}m"; } return $"{text} — {num:0.##}m shot"; } public static bool TryApplyRopePlan(Character character, RopeReloadPlan plan) { if ((Object)(object)character == (Object)null || !plan.IsValid) { return false; } bool flag = false; if (plan.UsesPartialSlot) { if (!TryDeductSegmentsFromSlot(character, plan.PartialSlot, plan.SegmentsToDeduct)) { return false; } flag = IsBackpackSlot(character, plan.PartialSlot); } foreach (ItemSlot item in plan.SlotsToConsumeEntirely) { if (!TryConsumeInventorySlot(character, item, syncBackpack: false)) { return false; } flag = flag || IsBackpackSlot(character, item); } if (flag) { SyncPlayerInventory(character); } return true; } public static bool TryConsumePitonSlot(Character character, ItemSlot slot) { if ((Object)(object)character == (Object)null || slot == null) { return false; } bool result = TryConsumeInventorySlot(character, slot, syncBackpack: true); RefreshPitonTracking(character); return result; } private static bool TryDeductSegmentsFromSlot(Character character, ItemSlot slot, float segmentsToDeduct) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Expected O, but got Unknown if ((Object)(object)character == (Object)null || slot == null || slot.IsEmpty() || segmentsToDeduct <= 0f) { return false; } float spoolStartFuel = RopeSpoolLengthHelper.GetSpoolStartFuel(slot); float num = RopeSpoolLengthHelper.GetRemainingSegments(slot) - segmentsToDeduct; if (num <= 2f) { return TryConsumeInventorySlot(character, slot, syncBackpack: false); } if (slot.data == null) { slot.data = new ItemInstanceData(Guid.NewGuid()); } ItemDataHelper.SetFloatEntry(slot.data, (DataEntryKey)10, num); float value = ((spoolStartFuel > 0f) ? Mathf.Clamp01(num / spoolStartFuel) : 0f); ItemDataHelper.SetFloatEntry(slot.data, (DataEntryKey)11, value); return true; } private static bool TryConsumeInventorySlot(Character character, ItemSlot slot, bool syncBackpack) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) if (slot.itemSlotID == 250) { character.player.EmptySlot(Optionable.Some((byte)250)); } else if (IsBackpackSlot(character, slot)) { slot.EmptyOut(); if (syncBackpack) { SyncPlayerInventory(character); } } else { character.player.EmptySlot(Optionable.Some(slot.itemSlotID)); } return true; } private static void SyncPlayerInventory(Character character) { //IL_006b: Unknown result type (might be due to invalid IL or missing references) object obj; if (character == null) { obj = null; } else { Player player = character.player; obj = ((player != null) ? ((Component)player).GetComponent() : null); } PhotonView val = (PhotonView)obj; if (!PhotonNetwork.IsMasterClient || (Object)(object)val == (Object)null) { return; } try { InventorySyncData val2 = default(InventorySyncData); ((InventorySyncData)(ref val2))..ctor(character.player.itemSlots, character.player.backpackSlot, character.player.tempFullSlot); val.RPC("SyncInventoryRPC", (RpcTarget)1, new object[2] { IBinarySerializable.ToManagedArray(val2), false }); } catch (Exception ex) { HandCannonPlugin.Log.LogWarning((object)("[Reload] inventory sync skipped: " + ex.Message)); } } public static bool IsBackpackSlot(Character character, ItemSlot slot) { if ((Object)(object)((character != null) ? character.player : null) == (Object)null || slot == null || !character.player.backpackSlot.hasBackpack) { return false; } ItemInstanceData data = ((ItemSlot)character.player.backpackSlot).data; BackpackData val = default(BackpackData); if (data == null || !data.TryGetDataEntry((DataEntryKey)7, ref val)) { return false; } ItemSlot[] itemSlots = val.itemSlots; foreach (ItemSlot val2 in itemSlots) { if (val2 == slot) { return true; } } return false; } } internal static class HandCannonReloadPlanner { public static List BuildAllValidPlans(Character character, RopeShooter shooter) { List list = new List(); if ((Object)(object)character == (Object)null || (Object)(object)shooter == (Object)null) { return list; } foreach (ItemSlot item in HandCannonInventoryHelper.GetPitonSlotsInOrder(character)) { list.Add(HandCannonReloadPlan.ForPiton(item)); } List ropeSpoolSlotsInOrder = HandCannonInventoryHelper.GetRopeSpoolSlotsInOrder(character); float cannonShotSegments = RopeSpoolLengthHelper.GetCannonShotSegments(shooter); if (HandCannonInventoryHelper.TryBuildBestRopePlan(ropeSpoolSlotsInOrder, cannonShotSegments, isAntiRopeAmmo: false, out var plan)) { string displayLabel = HandCannonInventoryHelper.BuildRopePlanLabel(plan, cannonShotSegments, HandCannonAmmoType.NormalRope); list.Add(HandCannonReloadPlan.ForRope(plan, HandCannonAmmoType.NormalRope, displayLabel)); } if (CanUseAntiRopeAmmo(shooter) && HandCannonInventoryHelper.TryBuildBestRopePlan(ropeSpoolSlotsInOrder, AntiRopeCannonReference.GetShotSegments(), isAntiRopeAmmo: true, out var plan2)) { float shotSegments = AntiRopeCannonReference.GetShotSegments(); string displayLabel2 = HandCannonInventoryHelper.BuildRopePlanLabel(plan2, shotSegments, HandCannonAmmoType.AntiRope); list.Add(HandCannonReloadPlan.ForRope(plan2, HandCannonAmmoType.AntiRope, displayLabel2)); } return CollapseDistinctAmmoTypes(list); } private static List CollapseDistinctAmmoTypes(List plans) { List list = new List(); HashSet hashSet = new HashSet(); foreach (HandCannonReloadPlan plan in plans) { if (hashSet.Add(plan.AmmoType)) { list.Add(plan); } } return list; } private static bool CanUseAntiRopeAmmo(RopeShooter shooter) { if (HandCannonHelper.GetItem(shooter)?.itemID == 64) { return true; } return AntiRopeCannonReference.IsAvailable(); } } internal static class HandCannonReloadExecutor { public static bool TryManualReload(Character holder) { Character obj = holder; object obj2; if (obj == null) { obj2 = null; } else { CharacterData data = obj.data; obj2 = ((data != null) ? data.currentItem : null); } if ((Object)obj2 == (Object)null) { return false; } Item cannonItem = holder.data.currentItem; RopeShooter shooter = ((Component)cannonItem).GetComponent(); if (!HandCannonHelper.IsHandCannon(cannonItem) || (Object)(object)shooter == (Object)null || !HandCannonHelper.IsEmptyForReload(cannonItem, shooter)) { return false; } HandCannonPlugin.Log.LogInfo((object)"[Reload] manual reload requested."); List list = HandCannonReloadPlanner.BuildAllValidPlans(holder, shooter); if (list.Count == 0) { HandCannonPlugin.Log.LogInfo((object)"[Reload] no ammo available for Hand Cannon reload."); return false; } HandCannonPlugin.Log.LogInfo((object)$"[Reload] {list.Count} reload option(s) available."); if (list.Count == 1) { return ExecutePlan(holder, cannonItem, shooter, list[0]); } HandCannonReloadPickerUI.Show(list, delegate(HandCannonReloadPlan plan) { ExecutePlan(holder, cannonItem, shooter, plan); }); return true; } public static bool ExecutePlan(Character holder, Item cannonItem, RopeShooter shooter, HandCannonReloadPlan plan) { //IL_005d: 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) if ((Object)(object)holder == (Object)null || (Object)(object)cannonItem == (Object)null || (Object)(object)shooter == (Object)null || plan == null || !plan.IsValid) { return false; } HandCannonPlugin.Log.LogInfo((object)("[Reload] executing plan: " + plan.DisplayLabel)); Optionable currentSelectedSlot = holder.refs.items.currentSelectedSlot; ItemSlot val = (currentSelectedSlot.IsSome ? holder.player.GetItemSlot(currentSelectedSlot.Value) : null); if (!ApplySlotDataReload(cannonItem, shooter, plan.AmmoType, val)) { return false; } if (!shooter.HasAmmo) { HandCannonPlugin.Log.LogWarning((object)"[Reload] slot-data reload left Hand Cannon empty."); return false; } if (val != null && !val.IsEmpty()) { ResetCannonSlotForFreshShot(val, shooter, plan.AmmoType); } if (plan.IsPiton) { if (!HandCannonInventoryHelper.TryConsumePitonSlot(holder, plan.PitonSlot)) { HandCannonPlugin.Log.LogWarning((object)"[Reload] piton reload succeeded but piton could not be consumed."); return false; } } else if (!HandCannonInventoryHelper.TryApplyRopePlan(holder, plan.RopePlan)) { HandCannonPlugin.Log.LogWarning((object)"[Reload] reload succeeded but rope could not be consumed."); return false; } HandCannonStateSync.Sync(cannonItem, shooter); HandCannonNetworkSync.PushAmmoState(cannonItem, val, holder); HandCannonPlugin.Log.LogInfo((object)$"[Reload] ok: loaded {HandCannonAmmoHelper.GetAmmoLabel(plan.AmmoType)}, hasAmmo={shooter.HasAmmo}"); return true; } private static bool ApplySlotDataReload(Item cannonItem, RopeShooter shooter, HandCannonAmmoType ammoType, ItemSlot cannonSlot) { if (ammoType == HandCannonAmmoType.AntiRope && cannonItem.itemID != 64 && !AntiRopeCannonReference.IsAvailable()) { HandCannonPlugin.Log.LogWarning((object)"[Reload] anti-grav reload failed: anti reference unavailable."); return false; } int ammo = Mathf.Max(1, shooter.startAmmo); try { IntItemData data = ((ItemComponent)shooter).GetData((DataEntryKey)12, (Func)(() => ItemDataHelper.CreateIntItemData(ammo))); data.Value = ammo; OptionableIntItemData data2 = cannonItem.GetData((DataEntryKey)2); if (data2 != null) { data2.Value = ammo; } HandCannonAmmoHelper.SetLoadedAmmo(cannonItem, cannonSlot, ammoType); cannonItem.SetUseRemainingPercentage(1f); shooter.cantReFire = false; ((ItemComponent)shooter).OnInstanceDataSet(); cannonItem.finishedCast = false; cannonItem.CancelUsePrimary(); HandCannonPlugin.Log.LogInfo((object)$"[Reload] slot-data applied (ammo={HandCannonAmmoHelper.GetAmmoLabel(ammoType)}, hasAmmo={shooter.HasAmmo})"); return shooter.HasAmmo; } catch (Exception ex) { HandCannonPlugin.Log.LogError((object)("[Reload] slot-data failed: " + ex.GetType().Name + ": " + ex.Message)); return false; } } private static void ResetCannonSlotForFreshShot(ItemSlot slot, RopeShooter shooter, HandCannonAmmoType ammoType) { //IL_005c: 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_0038: Expected O, but got Unknown int value = ((!((Object)(object)shooter != (Object)null)) ? 1 : Mathf.Max(1, shooter.startAmmo)); if (slot.data == null) { slot.data = new ItemInstanceData(Guid.NewGuid()); } ItemDataHelper.SetIntEntry(slot.data, (DataEntryKey)12, value); ItemDataHelper.SetOptionableIntEntry(slot.data, (DataEntryKey)2, value); ItemDataHelper.SetIntEntry(slot.data, HandCannonDataEntryKey.LoadedAmmoType, (int)ammoType); } } internal sealed class HandCannonReloadPickerUI : MonoBehaviour { private const int WindowId = 63818; private static HandCannonReloadPickerUI _instance; private bool _visible; private List _openPlans; private Action _onSelected; private Rect _windowRect = new Rect(0f, 0f, 560f, 320f); private float _openedAt; public static bool IsOpen => (Object)(object)_instance != (Object)null && _instance._visible; public static void TryKeyboardSelect() { //IL_002f: 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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) if (!IsOpen || _instance?._openPlans == null) { return; } for (int i = 0; i < _instance._openPlans.Count && i < 9; i++) { KeyCode val = (KeyCode)(49 + i); KeyCode val2 = (KeyCode)(257 + i); if (Input.GetKeyDown(val) || Input.GetKeyDown(val2)) { _instance.OnPlanSelected(_instance._openPlans[i]); break; } } } public static void EnsureCreated() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown if (!((Object)(object)_instance != (Object)null)) { GameObject val = new GameObject("HandCannonReloadPickerUI"); Object.DontDestroyOnLoad((Object)(object)val); _instance = val.AddComponent(); } } public static void DestroyInstance() { if (!((Object)(object)_instance == (Object)null)) { Object.Destroy((Object)(object)((Component)_instance).gameObject); _instance = null; } } public static void Show(List plans, Action onSelected) { EnsureCreated(); _instance.Open(plans, onSelected); } public static void Close() { ForceClose(); } public static void ForceClose() { if (!((Object)(object)_instance == (Object)null)) { _instance.CloseInternal(); } } private void Update() { if (_visible && Time.unscaledTime - _openedAt > 120f) { HandCannonPlugin.Log.LogWarning((object)"[Reload] picker timed out, closing."); CloseInternal(); } } private void OnGUI() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Expected O, but got Unknown //IL_0047: 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) if (_visible && _openPlans != null) { Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; _windowRect = GUI.Window(63818, _windowRect, new WindowFunction(DrawWindow), "Choose Hand Cannon Ammo"); } } private void DrawWindow(int windowId) { //IL_00d3: 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_010c: Unknown result type (might be due to invalid IL or missing references) List openPlans = _openPlans; if (openPlans == null) { return; } float num = 28f; float num2 = ((Rect)(ref _windowRect)).width - 40f; for (int i = 0; i < openPlans.Count; i++) { HandCannonReloadPlan handCannonReloadPlan = openPlans[i]; if (handCannonReloadPlan != null) { string text = $"{i + 1}. {handCannonReloadPlan.DisplayLabel}"; if (GUI.Button(new Rect(20f, num, num2, 40f), text)) { OnPlanSelected(handCannonReloadPlan); return; } num += 48f; } } if (GUI.Button(new Rect(20f, ((Rect)(ref _windowRect)).height - 42f, num2, 30f), "Cancel (Esc)")) { CloseInternal(); } else { GUI.DragWindow(new Rect(0f, 0f, ((Rect)(ref _windowRect)).width, 24f)); } } private void Open(List plans, Action onSelected) { //IL_00c6: Unknown result type (might be due to invalid IL or missing references) _onSelected = onSelected; _openPlans = plans; _openedAt = Time.unscaledTime; float height = 90f + (float)plans.Count * 48f + 50f; ((Rect)(ref _windowRect)).width = 560f; ((Rect)(ref _windowRect)).height = height; ((Rect)(ref _windowRect)).x = ((float)Screen.width - ((Rect)(ref _windowRect)).width) * 0.5f; ((Rect)(ref _windowRect)).y = ((float)Screen.height - ((Rect)(ref _windowRect)).height) * 0.5f; _visible = true; HandCannonPlugin.Log.LogInfo((object)$"[Reload] picker opened with {plans.Count} option(s). Press 1-{plans.Count} to choose, click a button, or press {HandCannonPlugin.ReloadKeyConfig.Value}/Esc to cancel."); for (int i = 0; i < plans.Count; i++) { HandCannonPlugin.Log.LogInfo((object)$"[Reload] {i + 1}: {plans[i].DisplayLabel}"); } } private void OnPlanSelected(HandCannonReloadPlan plan) { Action onSelected = _onSelected; CloseInternal(); onSelected?.Invoke(plan); } private void CloseInternal() { _visible = false; _onSelected = null; _openPlans = null; Cursor.lockState = (CursorLockMode)1; Cursor.visible = false; } } internal static class HandCannonSpawnHelper { public static void InitializeSpawnedHandCannon(Item item) { if (!HandCannonHelper.IsHandCannon(item)) { return; } RopeShooter component = ((Component)item).GetComponent(); if ((Object)(object)component == (Object)null) { return; } if (HandCannonAmmoHelper.GetLoadedAmmo(item) != HandCannonAmmoType.Empty) { HandCannonStateSync.Sync(item, component); return; } if (!component.HasAmmo) { HandCannonStateSync.Sync(item, component); return; } if (!HandCannonHelper.CanTagItems()) { HandCannonStateSync.Sync(item, component); return; } HandCannonAmmoType ammoType; if (item.itemID == 64) { ammoType = HandCannonAmmoType.AntiRope; } else { float num = Random.Range(0f, 1f); ammoType = ((num < HandCannonPlugin.PitonSpawnChanceConfig.Value) ? HandCannonAmmoType.Piton : HandCannonAmmoType.NormalRope); } int ammo = Mathf.Max(1, component.startAmmo); HandCannonAmmoHelper.SetLoadedAmmo(item, null, ammoType); IntItemData data = ((ItemComponent)component).GetData((DataEntryKey)12, (Func)(() => ItemDataHelper.CreateIntItemData(ammo))); data.Value = ammo; OptionableIntItemData data2 = item.GetData((DataEntryKey)2); if (data2 != null) { data2.Value = ammo; } item.SetUseRemainingPercentage(1f); ((ItemComponent)component).OnInstanceDataSet(); HandCannonStateSync.Sync(item, component); HandCannonNetworkSync.PushItemInstanceData(item); HandCannonNetworkSync.ScheduleSpawnResync(item); HandCannonPlugin.Log.LogInfo((object)("Hand Cannon " + ((Object)item).name + ": spawned loaded with " + HandCannonAmmoHelper.GetAmmoLabel(ammoType) + ".")); } } internal static class PitonShotRegistry { private static readonly HashSet PitonAnchorViewIds = new HashSet(); private static readonly HashSet KnownAnchorViewIds = new HashSet(); public static bool MarkNextAnchorAsPiton { get; set; } public static void Register(int photonViewId) { PitonAnchorViewIds.Add(photonViewId); } public static bool TryConsume(int photonViewId) { return PitonAnchorViewIds.Remove(photonViewId); } public static void SnapshotKnownAnchors() { KnownAnchorViewIds.Clear(); RopeAnchorProjectile[] array = Resources.FindObjectsOfTypeAll(); foreach (RopeAnchorProjectile val in array) { if ((Object)(object)val?.photonView != (Object)null) { KnownAnchorViewIds.Add(val.photonView.ViewID); } } } public static bool RegisterNewAnchors() { bool result = false; RopeAnchorProjectile[] array = Resources.FindObjectsOfTypeAll(); foreach (RopeAnchorProjectile val in array) { if (!((Object)(object)val?.photonView == (Object)null)) { int viewID = val.photonView.ViewID; if (KnownAnchorViewIds.Add(viewID)) { Register(viewID); result = true; } } } return result; } public static void ClearPendingMark() { MarkNextAnchorAsPiton = false; } } internal static class PitonPrefabCache { public static string PrefabPath { get; private set; } public static bool EnsureInitialized(ManualLogSource logger) { if (!string.IsNullOrEmpty(PrefabPath)) { return true; } GameObject obj = Resources.Load("0_Items/Piton"); if (TryFromClimbingSpikeComponent((obj != null) ? obj.GetComponent() : null, logger)) { return true; } GameObject[] array = Resources.LoadAll("0_Items/"); if (array != null) { GameObject[] array2 = array; foreach (GameObject val in array2) { if (TryFromClimbingSpikeComponent(val.GetComponent(), logger)) { return true; } } } ClimbingSpikeComponent[] array3 = Resources.FindObjectsOfTypeAll(); foreach (ClimbingSpikeComponent component in array3) { if (TryFromClimbingSpikeComponent(component, logger)) { return true; } } logger.LogWarning((object)"Could not resolve piton prefab yet."); return false; } private static bool TryFromClimbingSpikeComponent(ClimbingSpikeComponent component, ManualLogSource logger) { if ((Object)(object)component?.hammeredVersionPrefab == (Object)null) { return false; } PrefabPath = "0_Items/" + ((Object)component.hammeredVersionPrefab).name; logger.LogInfo((object)("Resolved piton prefab: " + PrefabPath)); return true; } } internal static class PitonPlacementHelper { public static void GetPlacement(RopeAnchorWithRope anchor, out Vector3 point, out Vector3 normal) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0019: 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_0023: 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_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_004c: 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_005e: 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_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_0082: 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_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: 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_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: 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_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: 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_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_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_0118: 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_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) point = ((Component)anchor).transform.position; normal = -((Component)anchor).transform.up; int num = LayerMask.op_Implicit(HelperExtensions.ToLayerMask((LayerType)1)); Vector3[] array = (Vector3[])(object)new Vector3[8] { -((Component)anchor).transform.forward, ((Component)anchor).transform.forward, -((Component)anchor).transform.up, ((Component)anchor).transform.up, -((Component)anchor).transform.right, ((Component)anchor).transform.right, Vector3.down, Vector3.up }; Vector3[] array2 = array; RaycastHit val2 = default(RaycastHit); for (int i = 0; i < array2.Length; i++) { Vector3 val = array2[i]; if (!(((Vector3)(ref val)).sqrMagnitude < 0.0001f)) { Vector3 normalized = ((Vector3)(ref val)).normalized; if (Physics.Raycast(point + normalized * 0.15f, -normalized, ref val2, 0.75f, num, (QueryTriggerInteraction)0)) { point = ((RaycastHit)(ref val2)).point; normal = ((RaycastHit)(ref val2)).normal; break; } } } } } internal static class HandCannonAntiFireContext { private static readonly Dictionary ActiveFireSwaps = new Dictionary(); public static void Begin(RopeShooter shooter, HandCannonFireSwapState swapState) { if ((Object)(object)shooter != (Object)null) { ActiveFireSwaps[((Object)shooter).GetInstanceID()] = swapState; } } public static bool TryEnd(RopeShooter shooter, out HandCannonFireSwapState swapState) { swapState = default(HandCannonFireSwapState); if ((Object)(object)shooter == (Object)null) { return false; } int instanceID = ((Object)shooter).GetInstanceID(); if (!ActiveFireSwaps.TryGetValue(instanceID, out swapState)) { return false; } ActiveFireSwaps.Remove(instanceID); return true; } public static bool ShouldApplyAntiGravFallback(RopeShooter shooter) { HandCannonFireSwapState value; return (Object)(object)shooter != (Object)null && ActiveFireSwaps.TryGetValue(((Object)shooter).GetInstanceID(), out value) && value.PendingAntiGravFallback; } } internal struct HandCannonFireSwapState { public bool SwappedAntiRopePrefab; public bool PendingAntiGravFallback; public GameObject OriginalAnchorPrefab; public float OriginalLength; } internal struct HandCannonFireState { public bool IsHandCannon; public HandCannonAmmoType AmmoType; } [HarmonyPatch(typeof(RopeShooter), "OnInstanceDataSet")] internal static class RopeShooter_OnInstanceDataSet_HandCannonVisual_Patch { private static void Postfix(RopeShooter __instance) { Item item = HandCannonHelper.GetItem(__instance); if (HandCannonHelper.IsHandCannon(item)) { HandCannonStateSync.Sync(item, __instance); } } } [HarmonyPatch(typeof(RopeShooter), "Sync_Rpc")] internal static class RopeShooter_Sync_Rpc_HandCannonVisual_Patch { private static void Postfix(RopeShooter __instance) { Item item = HandCannonHelper.GetItem(__instance); if (HandCannonHelper.IsHandCannon(item)) { HandCannonStateSync.Sync(item, __instance); } } } [HarmonyPatch(typeof(Item), "SetItemInstanceDataRPC")] internal static class Item_SetItemInstanceDataRPC_HandCannon_Patch { private static void Postfix(Item __instance) { if (HandCannonHelper.IsHandCannon(__instance)) { RopeShooter component = ((Component)__instance).GetComponent(); if (!((Object)(object)component == (Object)null)) { HandCannonStateSync.Sync(__instance, component); } } } } [HarmonyPatch(typeof(Item), "OnStash")] internal static class Item_OnStash_HandCannon_Patch { private static void Prefix(Item __instance) { HandCannonNetworkSync.PreserveAmmoOnStash(__instance); } } [HarmonyPatch(typeof(RunManager), "StartRun")] internal static class RunManager_StartRun_Patch { private static void Postfix() { PitonPrefabCache.EnsureInitialized(HandCannonPlugin.Log); } } [HarmonyPatch(typeof(Item), "Start")] internal static class Item_Start_HandCannon_Patch { private static void Postfix(Item __instance) { HandCannonSpawnHelper.InitializeSpawnedHandCannon(__instance); } } [HarmonyPatch(typeof(Item), "GetItemName")] internal static class Item_GetItemName_HandCannon_Patch { private static void Postfix(Item __instance, ItemInstanceData data, ref string __result) { if (HandCannonHelper.IsHandCannon(__instance)) { HandCannonAmmoType ammoType = HandCannonAmmoHelper.ResolveLoadedAmmoForName(__instance, data); __result = HandCannonAmmoHelper.GetDisplayName(ammoType); } } } [HarmonyPatch(typeof(RopeShooter), "OnPrimaryFinishedCast")] internal static class RopeShooter_OnPrimaryFinishedCast_HandCannon_Patch { private static void CompleteFireAfterCast(RopeShooter shooter, HandCannonFireState state, string stage) { //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) Item item = HandCannonHelper.GetItem(shooter); if ((Object)(object)item == (Object)null || !state.IsHandCannon) { return; } if (state.AmmoType == HandCannonAmmoType.Piton && !PitonShotRegistry.RegisterNewAnchors()) { HandCannonPlugin.Log.LogWarning((object)("[Fire] piton shot fired but no anchor registered (" + stage + ").")); } object obj; if (!shooter.HasAmmo) { Optionable valueOrDefault = (item.holderCharacter?.refs?.items?.currentSelectedSlot).GetValueOrDefault(); if (valueOrDefault.IsSome) { Character holderCharacter = item.holderCharacter; if ((Object)(object)((holderCharacter != null) ? holderCharacter.player : null) != (Object)null) { obj = item.holderCharacter.player.GetItemSlot(valueOrDefault.Value); goto IL_0104; } } obj = null; goto IL_0104; } goto IL_0128; IL_0104: ItemSlot slot = (ItemSlot)obj; HandCannonAmmoHelper.SetLoadedAmmo(item, slot, HandCannonAmmoType.Empty); HandCannonStateSync.Sync(item, shooter); HandCannonNetworkSync.PushAmmoState(item, slot, item.holderCharacter); goto IL_0128; IL_0128: PitonShotRegistry.ClearPendingMark(); } private static void Prefix(RopeShooter __instance, ref HandCannonFireState __state) { __state = default(HandCannonFireState); Item item = HandCannonHelper.GetItem(__instance); if (!HandCannonHelper.IsHandCannon(item)) { return; } __state.IsHandCannon = true; __state.AmmoType = HandCannonAmmoHelper.GetLoadedAmmo(item); PitonShotRegistry.SnapshotKnownAnchors(); PitonShotRegistry.MarkNextAnchorAsPiton = __state.AmmoType == HandCannonAmmoType.Piton; if (__state.AmmoType == HandCannonAmmoType.Piton) { return; } HandCannonFireSwapState swapState = new HandCannonFireSwapState { OriginalAnchorPrefab = __instance.ropeAnchorWithRopePref, OriginalLength = __instance.length }; if (__state.AmmoType == HandCannonAmmoType.NormalRope) { if (item.itemID == 64) { if (!NormalRopeCannonReference.TryGetShooter(out var shooter) || (Object)(object)shooter.ropeAnchorWithRopePref == (Object)null) { HandCannonPlugin.Log.LogWarning((object)"[Fire] normal rope anchor unavailable on anti-grav Hand Cannon; shot may still be anti-grav."); return; } swapState.SwappedAntiRopePrefab = true; __instance.ropeAnchorWithRopePref = shooter.ropeAnchorWithRopePref; __instance.length = shooter.length; HandCannonAntiFireContext.Begin(__instance, swapState); HandCannonPlugin.Log.LogInfo((object)"[Fire] normal rope shot via swapped anchor on anti-grav Hand Cannon."); } } else { if (__state.AmmoType != HandCannonAmmoType.AntiRope) { return; } if (item.itemID == 64) { swapState.PendingAntiGravFallback = true; HandCannonAntiFireContext.Begin(__instance, swapState); return; } GameObject anchorPrefab = AntiRopeCannonReference.GetAnchorPrefab(); if ((Object)(object)anchorPrefab == (Object)null || !AntiRopeCannonReference.TryGetShooter(out var shooter2)) { swapState.PendingAntiGravFallback = true; HandCannonAntiFireContext.Begin(__instance, swapState); HandCannonPlugin.Log.LogWarning((object)"[Fire] anti anchor unavailable; using antigrav rope fallback."); } else { swapState.SwappedAntiRopePrefab = true; __instance.ropeAnchorWithRopePref = anchorPrefab; __instance.length = shooter2.length; HandCannonAntiFireContext.Begin(__instance, swapState); HandCannonPlugin.Log.LogInfo((object)"[Fire] anti-grav rope shot via swapped anchor prefab."); } } } private static void Postfix(RopeShooter __instance, HandCannonFireState __state) { if (__state.IsHandCannon) { Item item = HandCannonHelper.GetItem(__instance); RestoreAntiSwap(__instance, clearLoadedState: false); HandCannonFireHelper.ConsumeFiredShot(__instance, item); CompleteFireAfterCast(__instance, __state, "Postfix"); } } [HarmonyFinalizer] private static Exception Finalizer(Exception __exception, RopeShooter __instance, ref HandCannonFireState __state) { if (__exception != null) { HandCannonPlugin.Log.LogWarning((object)("[Fire] OnPrimaryFinishedCast threw: " + __exception.GetType().Name + ": " + __exception.Message)); RestoreAntiSwap(__instance, clearLoadedState: false); } if (__state.IsHandCannon) { Item item = HandCannonHelper.GetItem(__instance); HandCannonFireHelper.ConsumeFiredShot(__instance, item); } CompleteFireAfterCast(__instance, __state, "Finalizer"); return null; } private static void RestoreAntiSwap(RopeShooter shooter, bool clearLoadedState) { //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_00a4: Unknown result type (might be due to invalid IL or missing references) if (!HandCannonAntiFireContext.TryEnd(shooter, out var swapState)) { return; } if (swapState.SwappedAntiRopePrefab) { shooter.ropeAnchorWithRopePref = swapState.OriginalAnchorPrefab; shooter.length = swapState.OriginalLength; } if (!clearLoadedState) { return; } Item item = HandCannonHelper.GetItem(shooter); Optionable valueOrDefault = ((item == null) ? ((Optionable?)null) : item.holderCharacter?.refs?.items?.currentSelectedSlot).GetValueOrDefault(); object obj2; if (valueOrDefault.IsSome) { object obj; if (item == null) { obj = null; } else { Character holderCharacter = item.holderCharacter; obj = ((holderCharacter != null) ? holderCharacter.player : null); } if ((Object)obj != (Object)null) { obj2 = item.holderCharacter.player.GetItemSlot(valueOrDefault.Value); goto IL_00fb; } } obj2 = null; goto IL_00fb; IL_00fb: ItemSlot slot = (ItemSlot)obj2; HandCannonAmmoHelper.SetLoadedAmmo(item, slot, HandCannonAmmoType.Empty); HandCannonStateSync.Sync(item, shooter); } } [HarmonyPatch(typeof(RopeAnchorProjectile), "GetShot")] internal static class RopeAnchorProjectile_GetShot_HandCannon_Patch { private static void Prefix(RopeAnchorProjectile __instance) { if (PitonShotRegistry.MarkNextAnchorAsPiton && (Object)(object)__instance?.photonView != (Object)null) { PitonShotRegistry.Register(__instance.photonView.ViewID); } } private static void Postfix(RopeAnchorProjectile __instance) { RopeShooter val = FindSourceShooter(); if (!((Object)(object)val == (Object)null) && HandCannonAntiFireContext.ShouldApplyAntiGravFallback(val)) { Rope componentInChildren = ((Component)__instance).GetComponentInChildren(true); if ((Object)(object)componentInChildren == (Object)null) { HandCannonPlugin.Log.LogWarning((object)"[Fire] anti-grav fallback failed: rope not found on anchor."); } else { componentInChildren.antigrav = true; } } } private static RopeShooter FindSourceShooter() { Character localCharacter = Character.localCharacter; object obj; if (localCharacter == null) { obj = null; } else { CharacterData data = localCharacter.data; obj = ((data != null) ? data.currentItem : null); } Item val = (Item)obj; if ((Object)(object)val == (Object)null || !HandCannonHelper.IsHandCannon(val)) { return null; } return ((Component)val).GetComponent(); } } [HarmonyPatch(typeof(RopeAnchorWithRope), "SpawnRope")] internal static class RopeAnchorWithRope_SpawnRope_HandCannon_Patch { private static bool Prefix(RopeAnchorWithRope __instance, ref Rope __result) { //IL_0071: 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_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: 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_009e: Unknown result type (might be due to invalid IL or missing references) if (!((MonoBehaviourPun)__instance).photonView.IsMine || !PitonShotRegistry.TryConsume(((MonoBehaviourPun)__instance).photonView.ViewID)) { return true; } ManualLogSource log = HandCannonPlugin.Log; if (log == null || !PitonPrefabCache.EnsureInitialized(log)) { if (log != null) { log.LogWarning((object)"[Fire] piton prefab unavailable; falling back to rope."); } return true; } try { PitonPlacementHelper.GetPlacement(__instance, out var point, out var normal); PhotonNetwork.Instantiate(PitonPrefabCache.PrefabPath, point, Quaternion.LookRotation(-normal, Vector3.up), (byte)0, (object[])null); PhotonNetwork.Destroy(((Component)__instance).gameObject); log.LogInfo((object)$"[Fire] placed piton at {point}"); } catch (Exception ex) { log.LogError((object)("[Fire] piton placement failed: " + ex.Message)); return true; } __result = null; return false; } } [HarmonyPatch(typeof(Action_ReduceUses), "RunAction")] internal static class Action_ReduceUses_RunAction_HandCannon_Patch { private static bool Prefix(Action_ReduceUses __instance) { Item actionItem = HandCannonHelper.GetActionItem((ItemActionBase)(object)__instance); if (!HandCannonHelper.IsHandCannon(actionItem)) { return true; } return false; } } [HarmonyPatch(typeof(Item), "ConsumeDelayed")] internal static class Item_ConsumeDelayed_HandCannon_Patch { private static bool Prefix(Item __instance) { if (!HandCannonHelper.IsHandCannon(__instance)) { return true; } HandCannonPlugin.Log.LogInfo((object)("Blocked ConsumeDelayed for Hand Cannon " + ((Object)__instance).name)); return false; } }