using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; 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 HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using SpongePEAK.Core.Game; using SpongePEAK.Core.Logging; using SpongePEAK.Core.Patching; using SpongePEAK.Core.Reflection; using SpongePEAK.Lib.Events; using SpongePEAK.Lib.Features; using SpongePEAK.Lib.Items; using SpongePEAK.Lib.Net; using SpongePEAK.Lib.Plugin; using SpongePEAK.Lib.UI; using SpongePEAK.QOL.Features; using TMPro; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("sponge")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (c) sponge")] [assembly: AssemblyDescription("Quality-of-life improvements, each individually toggleable.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+ddcbf023fcb4c2065ad9b038910633cd4a3a93b0")] [assembly: AssemblyProduct("SpongePEAKQOL")] [assembly: AssemblyTitle("com.sponge.peakqol")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SpongePEAK.QOL { [BepInPlugin("com.sponge.peakqol", "SpongePEAKQOL", "1.0.0")] public sealed class QolPlugin : SpongePlugin { public const string Guid = "com.sponge.peakqol"; private const string RulesKey = "sponge.qolrules"; private FeatureRegistry _registry; private GameEvents _events; private HostRuleSet _rules; private PushFeature _push; private RopeCutFeature _ropeCut; private ItemInfoFeature _itemInfo; private MushroomInfoFeature _mushroomInfo; private TeammateStatsFeature _teammateStats; private CatchPlayersFeature _catchPlayers; private BackpacksForEveryoneFeature _backpacks; protected override string DisplayName => "SpongePEAKQOL"; protected override void OnLoad() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown _registry = new FeatureRegistry(((BaseUnityPlugin)this).Config, ((SpongePlugin)this).Log); _events = new GameEvents(((SpongePlugin)this).Log); _rules = new HostRuleSet("sponge.qolrules", ((SpongePlugin)this).Log); _rules.Declare("AllowRopeSwinging", true); _rules.Declare("AllowPushing", true); _rules.Declare("AllowRopeCutting", true); RopeSwingFeature.Rules = _rules; PushFeature.Rules = _rules; RopeCutFeature.Rules = _rules; LastChanceFeature lastChanceFeature = new LastChanceFeature(); lastChanceFeature.Configure(((BaseUnityPlugin)this).Config); _itemInfo = new ItemInfoFeature(); _itemInfo.Configure(((BaseUnityPlugin)this).Config); _mushroomInfo = new MushroomInfoFeature(); _mushroomInfo.Configure(((BaseUnityPlugin)this).Config); _teammateStats = new TeammateStatsFeature(); _teammateStats.Configure(((BaseUnityPlugin)this).Config); RopeSwingFeature ropeSwingFeature = new RopeSwingFeature(); ropeSwingFeature.Configure(((BaseUnityPlugin)this).Config); _push = new PushFeature(); _push.Configure(((BaseUnityPlugin)this).Config); _ropeCut = new RopeCutFeature(); _ropeCut.Configure(((BaseUnityPlugin)this).Config); _catchPlayers = new CatchPlayersFeature(); _catchPlayers.Configure(((BaseUnityPlugin)this).Config); _backpacks = new BackpacksForEveryoneFeature(); _backpacks.Configure(((BaseUnityPlugin)this).Config, (MonoBehaviour)(object)this); _registry.Register((ModFeature)(object)new DroppablePassportFeature()); _registry.Register((ModFeature)(object)lastChanceFeature); _registry.Register((ModFeature)(object)new GhostPingFeature()); _registry.Register((ModFeature)(object)_itemInfo); _registry.Register((ModFeature)(object)_mushroomInfo); _registry.Register((ModFeature)(object)_teammateStats); _registry.Register((ModFeature)(object)_catchPlayers); _registry.Register((ModFeature)(object)_backpacks); _registry.Register((ModFeature)(object)new CustomItemsFeature()); _registry.Register((ModFeature)(object)ropeSwingFeature); _registry.Register((ModFeature)(object)_push); _registry.Register((ModFeature)(object)_ropeCut); _registry.EnableAll(((SpongePlugin)this).Patches); _rules.Start(); if (((ModFeature)_backpacks).IsActive) { _events.OnCharacterSpawned((Action)delegate { _backpacks.OnRunStarted(); }); _events.OnRunEnded((Action)delegate { _backpacks.OnRunEnded(); }); } } private void Update() { if (_itemInfo != null && ((ModFeature)_itemInfo).IsActive) { _itemInfo.Tick(); } if (_mushroomInfo != null && ((ModFeature)_mushroomInfo).IsActive) { _mushroomInfo.Tick(); } if (_teammateStats != null && ((ModFeature)_teammateStats).IsActive) { _teammateStats.Tick(); } if (_catchPlayers != null && ((ModFeature)_catchPlayers).IsActive) { _catchPlayers.Tick(); } } protected override void OnUnload() { GameEvents events = _events; if (events != null) { events.UnsubscribeAll(); } _itemInfo?.Cleanup(); _mushroomInfo?.Cleanup(); _teammateStats?.Cleanup(); HostRuleSet rules = _rules; if (rules != null) { rules.Stop(); } RopeSwingFeature.Rules = null; PushFeature.Rules = null; RopeCutFeature.Rules = null; } } } namespace SpongePEAK.QOL.Features { public sealed class BackpacksForEveryoneFeature : ModFeature { private const string ItemResourcePath = "0_Items/"; internal static ConfigEntry BackpackPrefab; internal static ConfigEntry SpawnDelay; private ModLog _log; private MonoBehaviour _coroutineHost; private bool _givenThisRun; public override string Name => "BackpacksForEveryone"; public override string Description => "Gives every player a backpack when a run starts, if they do not already have one. Host only."; public override string Replaces => "Nozz-BagsForEveryone"; public override string Section => "Items"; internal void Configure(ConfigFile config, MonoBehaviour coroutineHost) { _coroutineHost = coroutineHost; BackpackPrefab = config.Bind("Items", "Backpacks_PrefabName", "Backpack", "Name of the backpack prefab to spawn, resolved under the game's 0_Items resource path."); SpawnDelay = config.Bind("Items", "Backpacks_SpawnDelaySeconds", 3f, "Seconds to wait after a run starts before handing out backpacks, so inventories are ready."); } protected override void OnEnable(PatchHost patches, ModLog log) { _log = log; if (!NetRoleInfo.HasAuthority) { ModLog log2 = _log; if (log2 != null) { log2.Info((object)"BackpacksForEveryone is host-driven: this client is not authoritative, so the host decides."); } } } internal void OnRunStarted() { if (!_givenThisRun && NetRoleInfo.HasAuthority) { _givenThisRun = true; if ((Object)(object)_coroutineHost != (Object)null) { _coroutineHost.StartCoroutine(GiveBackpacks()); } } } internal void OnRunEnded() { _givenThisRun = false; } private IEnumerator GiveBackpacks() { yield return (object)new WaitForSeconds(SpawnDelay?.Value ?? 3f); if (!PhotonNetwork.InRoom && !PhotonNetwork.OfflineMode) { yield break; } int num = 0; List allCharacters = Character.AllCharacters; if (allCharacters == null) { yield break; } for (int i = 0; i < allCharacters.Count; i++) { Character val = allCharacters[i]; if (!((Object)(object)val == (Object)null) && !val.isBot) { Player player = val.player; if (!((Object)(object)player == (Object)null) && player.backpackSlot != null && ((ItemSlot)player.backpackSlot).IsEmpty() && TrySpawnBackpackFor(val)) { num++; } } } if (num > 0) { ModLog log = _log; if (log != null) { log.Info((object)$"Handed out {num} backpack(s) at run start."); } } } private bool TrySpawnBackpackFor(Character character) { //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_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: 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) try { Vector3 val = character.Center + Vector3.up * 0.5f; GameObject val2 = PhotonNetwork.Instantiate("0_Items/" + (BackpackPrefab?.Value ?? "Backpack"), val, Quaternion.identity, (byte)0, (object[])null); if ((Object)(object)val2 == (Object)null) { return false; } Item val3 = default(Item); if (!val2.TryGetComponent(ref val3)) { return false; } PhotonView component = ((Component)character).GetComponent(); if ((Object)(object)component != (Object)null) { val3.RequestPickup(component); } return true; } catch (Exception ex) { ModLog log = _log; if (log != null) { log.Exception("Could not give a backpack to '" + character.characterName + "'", ex); } return false; } } } public sealed class CatchPlayersFeature : ModFeature { internal static ConfigEntry CatchRadius; internal static ConfigEntry MinFallTime; private ModLog _log; public override string Name => "CatchFallingPlayers"; public override string Description => "Cushions the landing of teammates who fall close to you, using the game's own catch mechanic."; public override string Replaces => "tony4twenty-Catch_Players"; public override string Section => "Survival"; internal void Configure(ConfigFile config) { CatchRadius = config.Bind("Survival", "Catch_Radius", 4f, "Metres within which a falling teammate's landing is cushioned."); MinFallTime = config.Bind("Survival", "Catch_MinFallSeconds", 0.6f, "How long someone must have been airborne before catching applies, so ordinary jumps are ignored."); } protected override void OnEnable(PatchHost patches, ModLog log) { _log = log; } private static void LimitFalling(Character character) { MethodInfo methodInfo = ReflectionCache.Method(typeof(Character), "LimitFalling"); if (methodInfo == null) { return; } try { methodInfo.Invoke(character, null); } catch { } } internal void Tick() { //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: 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) Character localCharacter = Character.localCharacter; if ((Object)(object)localCharacter == (Object)null || (Object)(object)localCharacter.data == (Object)null || localCharacter.data.dead || localCharacter.data.passedOut) { return; } List allCharacters = Character.AllCharacters; if (allCharacters == null) { return; } float num = CatchRadius?.Value ?? 4f; float num2 = num * num; float num3 = MinFallTime?.Value ?? 0.6f; for (int i = 0; i < allCharacters.Count; i++) { Character val = allCharacters[i]; if (!((Object)(object)val == (Object)null) && !((Object)(object)val == (Object)(object)localCharacter) && !((Object)(object)val.data == (Object)null) && !val.isBot && !val.data.dead && !(val.data.sinceGrounded < num3)) { Vector3 val2 = val.Center - localCharacter.Center; if (!(((Vector3)(ref val2)).sqrMagnitude > num2)) { LimitFalling(val); } } } } } public sealed class CustomItemsFeature : ModFeature { public override string Name => "ExampleCustomItems"; public override string Description => "Adds example cloned items (Grape Berrynana, Nuclear Dynamite) that spawn naturally. Demonstrates the item cloning API."; public override string Replaces => "(new behaviour)"; public override string Section => "Items"; public override bool DefaultEnabled => true; protected override void OnEnable(PatchHost patches, ModLog log) { ItemCloner.SetLogger(log); RegisterGrapeBanana(); RegisterNuclearDynamite(); RegisterBananaTorch(); RegisterImposterBanana(); if (log != null) { log.Info((object)"Registered example cloned items; they build once the item database loads."); } } private static void RegisterGrapeBanana() { //IL_000f: 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_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_00a6: 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_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: 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_00f4: 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_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0117: 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_0129: 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) ItemCloner.Register(new ItemCloneDefinition("Yellow Berrynana", "SpongeGrapeBanana", "Grape Berrynana").WithAlternateSources(new string[4] { "Berrynana Yellow", "Pink Berrynana", "Blue Berrynana", "Brown Berrynana" }).WithDescription("A berrynana that got ideas.").WithHueShift(270f, 1.15f, 1f) .WithSpawning(new SpawnRule().InPool((SpawnPool)16, (Rarity)1).InPool((SpawnPool)4096, (Rarity)2).InPool((SpawnPool)536870912, (Rarity)5)) .WithEffectsWhenCooked(0, (StatusEffect[])(object)new StatusEffect[2] { StatusEffect.FromPoints((STATUSTYPE)6, 40f), StatusEffect.FromPoints((STATUSTYPE)1, -10f) }) .WithEffectsWhenCooked(1, (StatusEffect[])(object)new StatusEffect[2] { StatusEffect.FromPoints((STATUSTYPE)6, 20f), StatusEffect.FromPoints((STATUSTYPE)1, -30f) }) .WithEffectsWhenCooked(2, (StatusEffect[])(object)new StatusEffect[1] { StatusEffect.FromPoints((STATUSTYPE)1, -50f) }) .WithEffectsWhenCooked(4, (StatusEffect[])(object)new StatusEffect[2] { StatusEffect.FromPoints((STATUSTYPE)1, -25f), StatusEffect.FromPoints((STATUSTYPE)3, 20f) }) .WithEffectsWhenCooked(5, (StatusEffect[])(object)new StatusEffect[1] { StatusEffect.FromPoints((STATUSTYPE)3, 40f) })); } private static void RegisterNuclearDynamite() { //IL_000f: 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_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_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_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0085: 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_00a0: Expected O, but got Unknown //IL_00a5: Expected O, but got Unknown //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) ItemCloner.Register(new ItemCloneDefinition("Dynamite", "SpongeNuclearDynamite", "Nuclear Dynamite").WithDescription("Light the fuse. Then run, and keep running.").WithFuse(30f, true).WithHueShift(120f, 1.3f, 1f) .WithScale(5f) .WithExplosion(new ExplosionOverride { RadiusMultiplier = 200f, DamageMultiplier = 200f, ItemForceMultiplier = 200f, KnockbackMultiplier = 200f, Cloud = new StatusCloud { Status = (STATUSTYPE)3, Amount = 0.5f, RadiusFraction = 0.5f } }) .WithSpawning(new SpawnRule { BanInSolo = true }.InPool((SpawnPool)8192, (Rarity)5).InPoolWithWeight((SpawnPool)536870912, 1))); } private static void RegisterBananaTorch() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) ItemCloner.Register(new ItemCloneDefinition("Yellow Berrynana", "SpongeBananaBaton", "Berrynana Baton").WithAlternateSources(new string[2] { "Berrynana Yellow", "Pink Berrynana" }).WithHueShift(30f, 0.7f, 1.2f).WithVariant((SpecialVariant)1) .WithScale(1.4f) .WithBehaviour(new ItemBehaviourSpec().ClearActions().Remove().Add((Action)delegate(Action_ApplyInfiniteStamina action) { action.buffTime = 6f; action.drowsyAmount = 0f; ((ItemAction)action).OnCastFinished = true; }) .ConfigureUIData((Action)delegate(Item item) { if (item.UIData != null) { item.UIData.mainInteractPrompt = "Use"; item.UIData.canDrop = true; item.UIData.canThrow = true; } }))); } private static void RegisterImposterBanana() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) ItemCloner.Register(new ItemCloneDefinition("Yellow Berrynana", "SpongeImposterBanana", "Yellow Berrynana").WithAlternateSources(new string[2] { "Berrynana Yellow", "Pink Berrynana" }).WithExplodeOnConsume("Dynamite")); } } public sealed class DroppablePassportFeature : ModFeature { private const string PassportItemName = "Passport"; public override string Name => "DroppablePassport"; public override string Description => "Lets you drop and throw the Passport instead of it occupying a slot permanently."; public override string Replaces => "Icebox-IForgorPassport"; public override string Section => "Items"; internal static string TargetItem => "Passport"; protected override void OnEnable(PatchHost patches, ModLog log) { if (!patches.PatchType(typeof(PassportDropPatch))) { ((ModFeature)this).Fail("could not apply PassportDropPatch"); } } } [HarmonyPatch(typeof(Item), "Awake")] internal static class PassportDropPatch { [HarmonyPostfix] private static void Postfix(Item __instance) { if (!((Object)(object)__instance == (Object)null) && __instance.UIData != null && !(__instance.UIData.itemName != DroppablePassportFeature.TargetItem)) { __instance.UIData.canDrop = true; __instance.UIData.canThrow = true; } } } public sealed class GhostPingFeature : ModFeature { public override string Name => "GhostPing"; public override string Description => "Lets dead players ping locations, so they can still help the group navigate."; public override string Replaces => "boxofbiscuits97-GhostPing"; public override string Section => "Communication"; protected override void OnEnable(PatchHost patches, ModLog log) { if (!patches.PatchType(typeof(GhostPingPatch))) { ((ModFeature)this).Fail("could not apply GhostPingPatch"); } } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] internal static class GhostPingPatch { [HarmonyPostfix] private static void Postfix(PointPinger __instance, ref bool __result) { if (!__result) { Character value = Traverse.Create((object)__instance).Field("character").GetValue(); if (!((Object)(object)value == (Object)null) && !((Object)(object)value.data == (Object)null) && (value.IsGhost || value.data.dead)) { bool value2 = Traverse.Create((object)__instance).Property("inCooldown", (object[])null).GetValue(); __result = !value2; } } } } public sealed class ItemInfoFeature : ModFeature { internal static ConfigEntry ShowUses; internal static ConfigEntry FontSize; private TextMeshProUGUI _label; private ModLog _log; private string _lastText = string.Empty; private Item _lastItem; private int _lastUses = int.MinValue; public override string Name => "ItemInfo"; public override string Description => "Shows remaining uses and details for the item you are holding."; public override string Replaces => "Yueby-ItemInfo"; public override string Section => "Display"; internal void Configure(ConfigFile config) { ShowUses = config.Bind("Display", "ItemInfo_ShowUses", true, "Shows remaining uses for consumable items."); FontSize = config.Bind("Display", "ItemInfo_FontSize", 18f, "Font size for the held-item readout."); } protected override void OnEnable(PatchHost patches, ModLog log) { _log = log; } internal void Tick() { Character localCharacter = Character.localCharacter; Item val = (((Object)(object)localCharacter != (Object)null && (Object)(object)localCharacter.data != (Object)null) ? localCharacter.data.currentItem : null); if ((Object)(object)val == (Object)null) { _lastItem = null; _lastUses = int.MinValue; SetText(string.Empty); return; } int remainingUses = GetRemainingUses(val); if (val != _lastItem || remainingUses != _lastUses) { _lastItem = val; _lastUses = remainingUses; SetText(BuildReadout(val, remainingUses)); } } private static string BuildReadout(Item item, int remaining) { string text = ((item.UIData != null) ? item.UIData.itemName : null); if (string.IsNullOrEmpty(text)) { return string.Empty; } if (ShowUses == null || !ShowUses.Value) { return text; } if (item.totalUses <= 0) { return text; } if (remaining < 0) { return text; } return $"{text} {remaining}/{item.totalUses}"; } private static int GetRemainingUses(Item item) { try { if (!item.HasData((DataEntryKey)2)) { return -1; } OptionableIntItemData data = item.GetData((DataEntryKey)2); if (data == null || !data.HasData) { return -1; } return data.Value; } catch { return -1; } } private void SetText(string text) { if (text == _lastText) { return; } if (string.IsNullOrEmpty(text)) { if ((Object)(object)_label != (Object)null) { ((TMP_Text)_label).text = string.Empty; } _lastText = text; return; } if ((Object)(object)_label == (Object)null) { _label = HudRoot.CreateLabel("SpongeQOL_ItemInfo", (UiAnchor)3, FontSize?.Value ?? 18f); if ((Object)(object)_label == (Object)null) { return; } ModLog log = _log; if (log != null) { log.Debug((object)"ItemInfo label created."); } } ((TMP_Text)_label).text = text; _lastText = text; } internal void Cleanup() { if ((Object)(object)_label != (Object)null) { Object.Destroy((Object)(object)((Component)_label).gameObject); } _label = null; _lastText = string.Empty; } } public sealed class LastChanceFeature : ModFeature { private const float PassOutThreshold = 0.999f; internal static ConfigEntry Enabled; public override string Name => "LastChanceCast"; public override string Description => "Delays passing out while you are mid-cast, so a last-second item use is not wasted."; public override string Replaces => "IntRabbit-Last_Chance"; public override string Section => "Survival"; internal static float Threshold => 0.999f; internal void Configure(ConfigFile config) { Enabled = config.Bind("Survival", "LastChance_Enabled", true, "Holds off passing out while an item cast is in progress. The reprieve ends as soon as the cast finishes or is cancelled."); } protected override void OnEnable(PatchHost patches, ModLog log) { if (!patches.PatchType(typeof(LastChancePatch))) { ((ModFeature)this).Fail("could not apply LastChancePatch"); } } internal static bool IsMidCast(Character character) { if ((Object)(object)character == (Object)null || (Object)(object)character.data == (Object)null) { return false; } Item currentItem = character.data.currentItem; if ((Object)(object)currentItem == (Object)null) { return false; } if (currentItem.castProgress > 0f) { return !currentItem.finishedCast; } return false; } } [HarmonyPatch(typeof(Character), "HandleLife")] internal static class LastChancePatch { [HarmonyPrefix] private static bool Prefix(Character __instance) { if (LastChanceFeature.Enabled == null || !LastChanceFeature.Enabled.Value) { return true; } if ((Object)(object)__instance == (Object)null || (Object)(object)__instance.data == (Object)null || __instance.refs == null) { return true; } CharacterAfflictions afflictions = __instance.refs.afflictions; if ((Object)(object)afflictions == (Object)null) { return true; } if (afflictions.statusSum < 1f) { return true; } if (__instance.data.passOutValue <= LastChanceFeature.Threshold) { return true; } return !LastChanceFeature.IsMidCast(__instance); } } public sealed class MushroomInfoFeature : ModFeature { private const int EffectCount = 10; internal static ConfigEntry ToggleKey; internal static ConfigEntry StartVisible; internal static ConfigEntry FontSize; private TextMeshProUGUI _label; private ModLog _log; private bool _visible; private MushroomManager _lastManager; private string _cached; public override string Name => "MushroomInfo"; public override string Description => "Shows what each mushroom does in this run, instead of learning by trial and error."; public override string Replaces => "Kirshoo-MushroomsNOW"; public override string Section => "Display"; internal void Configure(ConfigFile config) { ToggleKey = config.Bind("Display", "MushroomInfo_ToggleKey", (KeyCode)288, "Key that shows or hides the mushroom effect list."); StartVisible = config.Bind("Display", "MushroomInfo_StartVisible", false, "Whether the list is shown without pressing the toggle key."); FontSize = config.Bind("Display", "MushroomInfo_FontSize", 15f, "Font size for the mushroom effect list."); } protected override void OnEnable(PatchHost patches, ModLog log) { _log = log; _visible = StartVisible?.Value ?? false; } internal void Tick() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) if (!GameState.InRun) { SetText(string.Empty); return; } if (ToggleKey != null && Input.GetKeyDown(ToggleKey.Value)) { _visible = !_visible; } MushroomManager instance = MushroomManager.instance; if (!_visible || (Object)(object)instance == (Object)null) { SetText(string.Empty); return; } if ((Object)(object)instance != (Object)(object)_lastManager || _cached == null) { _lastManager = instance; _cached = BuildReadout(instance); } SetText(_cached); } private static string BuildReadout(MushroomManager manager) { int[] mushroomEffects = manager.mushroomEffects; if (mushroomEffects == null || mushroomEffects.Length == 0) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("Mushrooms\n"); int num = Mathf.Min(mushroomEffects.Length, 10); for (int i = 0; i < num; i++) { int effect = mushroomEffects[i]; string arg = (IsGood(effect) ? "#7FD97F" : "#E08A8A"); stringBuilder.Append($"#{i + 1} {DescribeEffect(effect)}\n"); } return stringBuilder.ToString(); } private static bool IsGood(int effect) { int[] goodEffects = Action_RandomMushroomEffect.GoodEffects; if (goodEffects == null) { return effect <= 4; } for (int i = 0; i < goodEffects.Length; i++) { if (goodEffects[i] == effect) { return true; } } return false; } private static string DescribeEffect(int effect) { return effect switch { 0 => "Infinite stamina (4s)", 1 => "Faster climbing (5s)", 2 => "Low gravity (15s)", 3 => "Invincibility (10s)", 4 => "Cures hunger, injury and poison", 5 => "Spore explosion, launches you", 6 => "Blindness (60s)", 7 => "Fall damage", 8 => "Spores +25%", 9 => "Numbness (60s)", _ => $"Unknown effect {effect}", }; } private void SetText(string text) { if (string.IsNullOrEmpty(text)) { if ((Object)(object)_label != (Object)null && ((TMP_Text)_label).text.Length > 0) { ((TMP_Text)_label).text = string.Empty; } return; } if ((Object)(object)_label == (Object)null) { _label = HudRoot.CreateLabel("SpongeQOL_MushroomInfo", (UiAnchor)1, FontSize?.Value ?? 15f); if ((Object)(object)_label == (Object)null) { return; } ModLog log = _log; if (log != null) { log.Debug((object)"Mushroom info label created."); } } if (((TMP_Text)_label).text != text) { ((TMP_Text)_label).text = text; } } internal void Cleanup() { if ((Object)(object)_label != (Object)null) { Object.Destroy((Object)(object)((Component)_label).gameObject); } _label = null; _cached = null; _lastManager = null; } } public sealed class PushFeature : ModFeature { public const string RuleId = "AllowPushing"; public const bool RuleDefault = true; private ConfigEntry _key; private ConfigEntry _force; private ConfigEntry _range; private ConfigEntry _cooldown; private ModLog _log; private float _nextPush; public override string Name => "Push"; public override string Description => "Lets players shove each other. On by default; the host owns it for the whole lobby, because being pushed is not something the person falling gets to opt out of."; public override string Replaces => "boxofbiscuits97-PushMod"; public override string Section => "Movement"; internal static HostRuleSet Rules { get; set; } public void Configure(ConfigFile config) { _key = config.Bind("Movement", "PushKey", (KeyCode)102, "Key that shoves the player you are looking at. Only works when the host allows pushing."); _force = config.Bind("Movement", "PushForce", 18f, "Impulse strength. Around 18 is a firm shove; much higher launches people, which is funny once and then ends someone's run."); _range = config.Bind("Movement", "PushRange", 3.5f, "How close you must be, in metres."); _cooldown = config.Bind("Movement", "PushCooldown", 1f, "Seconds between pushes. Prevents holding the key from stunlocking someone and flooding the room with RPCs."); } protected override void OnEnable(PatchHost patches, ModLog log) { _log = log; log.Info((object)"Push ready; host rule 'AllowPushing' decides whether it is allowed."); } public void Tick() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) if (((ModFeature)this).IsActive && Rules != null && Rules.IsOn("AllowPushing")) { ConfigEntry key = _key; if (Input.GetKeyDown((KeyCode)((key == null) ? 102 : ((int)key.Value))) && !(Time.time < _nextPush)) { TryPush(); } } } private void TryPush() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_014c: 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_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) try { Character localCharacter = Character.localCharacter; if ((Object)(object)localCharacter == (Object)null || (Object)(object)localCharacter.data == (Object)null || localCharacter.data.dead) { return; } Camera main = Camera.main; if ((Object)(object)main == (Object)null) { return; } Vector3 center = localCharacter.Center; Vector3 forward = ((Component)main).transform.forward; float num = Mathf.Clamp(_range?.Value ?? 3.5f, 1f, 10f); Character val = null; float num2 = 0.5f; foreach (Character allCharacter in Character.AllCharacters) { if ((Object)(object)allCharacter == (Object)null || (Object)(object)allCharacter == (Object)(object)localCharacter || allCharacter.isBot || (Object)(object)allCharacter.data == (Object)null || allCharacter.data.dead) { continue; } Vector3 val2 = allCharacter.Center - center; float magnitude = ((Vector3)(ref val2)).magnitude; if (!(magnitude > num) && !(magnitude < 0.01f)) { float num3 = Vector3.Dot(forward, val2 / magnitude); if (num3 > num2) { num2 = num3; val = allCharacter; } } } if (!((Object)(object)val == (Object)null)) { Push(val, forward); } } catch (Exception ex) { ModLog log = _log; if (log != null) { log.Exception("Push failed", ex); } } } private void Push(Character target, Vector3 direction) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: 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) //IL_0080: Unknown result type (might be due to invalid IL or missing references) PhotonView val = target.refs?.view; if (!((Object)(object)val == (Object)null)) { float num = Mathf.Clamp(_force?.Value ?? 18f, 1f, 60f); Vector3 val2 = direction + Vector3.up * 0.25f; Vector3 val3 = ((Vector3)(ref val2)).normalized * num; val.RPC("RPCA_AddForceAtPosition", (RpcTarget)0, new object[3] { val3, target.Center, 1.5f }); _nextPush = Time.time + Mathf.Max(0.1f, _cooldown?.Value ?? 1f); ModLog log = _log; if (log != null) { log.Debug((object)("Pushed " + target.characterName + ".")); } } } } public sealed class RopeCutFeature : ModFeature { public const string RuleId = "AllowRopeCutting"; public const bool RuleDefault = true; private ConfigEntry _key; private ConfigEntry _range; private ConfigEntry _requireEmpty; private ModLog _log; private Rope _pending; private float _pendingUntil; public override string Name => "RopeCutting"; public override string Description => "Lets players cut a rope down so a badly placed one is not permanent. Refuses a rope with someone on it unless that guard is disabled. The host can switch it off."; public override string Replaces => "xammen-RopeCut"; public override string Section => "Movement"; internal static HostRuleSet Rules { get; set; } public void Configure(ConfigFile config) { _key = config.Bind("Movement", "RopeCutKey", (KeyCode)103, "Key that cuts the nearest rope. Only works when the host allows rope cutting."); _range = config.Bind("Movement", "RopeCutRange", 3f, "How close to the rope you must be, in metres."); _requireEmpty = config.Bind("Movement", "RopeCutRequireEmpty", true, "Refuse to cut a rope somebody is currently climbing. Turning this off allows dropping other players, which is why the host rule exists at all."); } protected override void OnEnable(PatchHost patches, ModLog log) { _log = log; log.Info((object)"Rope cutting ready; host rule 'AllowRopeCutting' decides whether it is allowed."); } public void Tick() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) if (!((ModFeature)this).IsActive) { return; } CompletePendingCut(); if (Rules != null && Rules.IsOn("AllowRopeCutting")) { ConfigEntry key = _key; if (Input.GetKeyDown((KeyCode)((key == null) ? 103 : ((int)key.Value)))) { TryCut(); } } } private void CompletePendingCut() { if ((Object)(object)_pending == (Object)null) { return; } if (Time.time > _pendingUntil) { ModLog log = _log; if (log != null) { log.Debug((object)"Ownership transfer timed out; rope not cut."); } _pending = null; return; } PhotonView photonView = ((MonoBehaviourPun)_pending).photonView; if ((Object)(object)photonView == (Object)null) { _pending = null; } else if (photonView.IsMine) { Destroy(_pending); _pending = null; } } private void TryCut() { //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) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) try { Character localCharacter = Character.localCharacter; if ((Object)(object)localCharacter == (Object)null || (Object)(object)localCharacter.data == (Object)null || localCharacter.data.dead) { return; } float num = Mathf.Clamp(_range?.Value ?? 3f, 1f, 10f); Vector3 center = localCharacter.Center; Rope val = null; float num2 = num; Rope[] array = Object.FindObjectsByType((FindObjectsInactive)0, (FindObjectsSortMode)0); foreach (Rope val2 in array) { if ((Object)(object)val2 == (Object)null || !val2.IsActive()) { continue; } ConfigEntry requireEmpty = _requireEmpty; if ((requireEmpty != null && !requireEmpty.Value) || val2.charactersClimbing == null || val2.charactersClimbing.Count <= 0) { float num3 = NearestSegmentDistance(val2, center); if (num3 < num2) { num2 = num3; val = val2; } } } if (!((Object)(object)val == (Object)null)) { Cut(val); } } catch (Exception ex) { ModLog log = _log; if (log != null) { log.Exception("Rope cut failed", ex); } } } private static float NearestSegmentDistance(Rope rope, Vector3 point) { //IL_0018: 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_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) List ropeSegments = rope.GetRopeSegments(); if (ropeSegments == null || ropeSegments.Count == 0) { return Vector3.Distance(((Component)rope).transform.position, point); } float num = float.MaxValue; foreach (Transform item in ropeSegments) { if (!((Object)(object)item == (Object)null)) { float num2 = Vector3.Distance(item.position, point); if (num2 < num) { num = num2; } } } return num; } private void Cut(Rope rope) { PhotonView photonView = ((MonoBehaviourPun)rope).photonView; if ((Object)(object)photonView == (Object)null) { return; } if (photonView.IsMine) { Destroy(rope); return; } photonView.RequestOwnership(); _pending = rope; _pendingUntil = Time.time + 2f; ModLog log = _log; if (log != null) { log.Debug((object)"Requested rope ownership before cutting."); } } private void Destroy(Rope rope) { try { PhotonNetwork.Destroy(((Component)rope).gameObject); ModLog log = _log; if (log != null) { log.Info((object)"Cut a rope."); } } catch (Exception ex) { ModLog log2 = _log; if (log2 != null) { log2.Exception("Could not destroy the rope", ex); } } } } public sealed class RopeSwingFeature : ModFeature { public const string RuleId = "AllowRopeSwinging"; public const bool RuleDefault = true; private ConfigEntry _maxAngle; internal const float VanillaAngle = 90f; public override string Name => "RopeSwinging"; public override string Description => "Lets players swing on ropes rather than being dropped when the rope passes vertical. The host decides whether it is allowed for the lobby."; public override string Replaces => "lamiiasuu-RopeSwingingMod"; public override string Section => "Movement"; internal static HostRuleSet Rules { get; set; } internal static float SwingAngle { get; private set; } = 150f; public void Configure(ConfigFile config) { _maxAngle = config.Bind("Movement", "RopeSwingMaxAngle", 150f, "How far past vertical a rope may swing before it drops you, in degrees. The game uses 90. Higher allows a fuller swing; beyond about 170 the rope can invert and look wrong."); } protected override void OnEnable(PatchHost patches, ModLog log) { SwingAngle = Mathf.Clamp(_maxAngle?.Value ?? 150f, 90f, 170f); if (!patches.PatchType(typeof(RopeSwingPatch))) { ((ModFeature)this).Fail("could not patch CharacterRopeHandling.Update"); } else { log.Info((object)"Rope swinging ready; host rule 'AllowRopeSwinging' decides whether it applies."); } } } [HarmonyPatch(typeof(CharacterRopeHandling), "Update")] internal static class RopeSwingPatch { [HarmonyPrefix] private static void Prefix(CharacterRopeHandling __instance) { if (!((Object)(object)__instance == (Object)null)) { bool flag = RopeSwingFeature.Rules != null && RopeSwingFeature.Rules.IsOn("AllowRopeSwinging"); __instance.maxRopeAngle = (flag ? RopeSwingFeature.SwingAngle : 90f); } } } public sealed class TeammateStatsFeature : ModFeature { private sealed class Readout { public HudBar Bar; public TextMeshProUGUI Label; } internal static ConfigEntry ShowNames; internal static ConfigEntry MaxDistance; private readonly Dictionary _readouts = new Dictionary(); private readonly List _stale = new List(); private ModLog _log; public override string Name => "TeammateStats"; public override string Description => "Shows nearby teammates' stamina above their heads so you can tell who needs a rest."; public override string Replaces => "nickklmao-PeakStats"; public override string Section => "Display"; internal void Configure(ConfigFile config) { ShowNames = config.Bind("Display", "TeammateStats_ShowNames", true, "Shows each teammate's name above their stamina bar."); MaxDistance = config.Bind("Display", "TeammateStats_MaxDistance", 30f, "Metres within which a teammate's stamina is shown."); } protected override void OnEnable(PatchHost patches, ModLog log) { _log = log; } internal void Tick() { //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: 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_00c1: 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) Character localCharacter = Character.localCharacter; if ((Object)(object)localCharacter == (Object)null || !HudRoot.IsReady) { HideAll(); return; } Camera main = Camera.main; if ((Object)(object)main == (Object)null) { HideAll(); return; } float num = MaxDistance?.Value ?? 30f; float num2 = num * num; List allCharacters = Character.AllCharacters; if (allCharacters == null) { return; } for (int i = 0; i < allCharacters.Count; i++) { Character val = allCharacters[i]; if ((Object)(object)val == (Object)null || (Object)(object)val == (Object)(object)localCharacter || (Object)(object)val.data == (Object)null) { continue; } if (val.isBot || val.data.dead) { Hide(val); continue; } Vector3 head = val.Head; Vector3 val2 = head - localCharacter.Center; if (((Vector3)(ref val2)).sqrMagnitude > num2) { Hide(val); } else { Draw(val, head, main); } } PruneDestroyed(); } private void Draw(Character character, Vector3 head, Camera camera) { //IL_0029: 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_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0074: 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_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) if (!_readouts.TryGetValue(character, out var value)) { value = CreateReadout(character); if (value == null) { return; } _readouts[character] = value; } Vector3 val = head + Vector3.up * 0.45f; if (!HudRoot.PositionAtWorldPoint(value.Bar.Transform, val, camera)) { SetVisible(value, visible: false); return; } if ((Object)(object)value.Label != (Object)null) { HudRoot.PositionAtWorldPoint(((TMP_Text)value.Label).rectTransform, val + Vector3.up * 0.25f, camera); } float maxStamina = character.GetMaxStamina(); float currentStamina = character.data.currentStamina; float num = ((maxStamina > 0f) ? Mathf.Clamp01(currentStamina / maxStamina) : 0f); value.Bar.Value = num; value.Bar.Color = ColourFor(num); SetVisible(value, visible: true); } private static Color ColourFor(float fraction) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) if (fraction > 0.5f) { return new Color(0.5f, 0.85f, 0.5f); } if (fraction > 0.25f) { return new Color(0.9f, 0.8f, 0.4f); } return new Color(0.9f, 0.45f, 0.45f); } private Readout CreateReadout(Character character) { HudBar val = HudBar.Create("SpongeQOL_Stam_" + character.characterName, 90f, 7f); if (val == null) { return null; } TextMeshProUGUI val2 = null; if (ShowNames == null || ShowNames.Value) { val2 = HudRoot.CreateLabel("SpongeQOL_Name_" + character.characterName, (UiAnchor)5, 13f); if ((Object)(object)val2 != (Object)null) { ((TMP_Text)val2).text = character.characterName; } } ModLog log = _log; if (log != null) { log.Debug((object)("Created stamina readout for '" + character.characterName + "'.")); } return new Readout { Bar = val, Label = val2 }; } private void Hide(Character character) { if (_readouts.TryGetValue(character, out var value)) { SetVisible(value, visible: false); } } private void HideAll() { foreach (KeyValuePair readout in _readouts) { SetVisible(readout.Value, visible: false); } } private static void SetVisible(Readout readout, bool visible) { HudBar bar = readout.Bar; if (bar != null) { bar.SetVisible(visible); } if ((Object)(object)readout.Label != (Object)null && ((Component)readout.Label).gameObject.activeSelf != visible) { ((Component)readout.Label).gameObject.SetActive(visible); } } private void PruneDestroyed() { _stale.Clear(); foreach (KeyValuePair readout in _readouts) { if ((Object)(object)readout.Key == (Object)null) { _stale.Add(readout.Key); } } foreach (Character item in _stale) { if (_readouts.TryGetValue(item, out var value)) { DestroyReadout(value); } _readouts.Remove(item); } } private static void DestroyReadout(Readout readout) { HudBar bar = readout.Bar; if (bar != null) { bar.Destroy(); } if ((Object)(object)readout.Label != (Object)null) { Object.Destroy((Object)(object)((Component)readout.Label).gameObject); } } internal void Cleanup() { foreach (KeyValuePair readout in _readouts) { DestroyReadout(readout.Value); } _readouts.Clear(); } } }