From 538933f530d0db2c8241194e40d5ab3333182719 Mon Sep 17 00:00:00 2001 From: Simon Kelly Date: Thu, 21 Dec 2023 11:15:57 +0000 Subject: [PATCH] Add a bunch of stuff --- Jungle/Buttons/CooldownButton.cs | 12 +- Jungle/Buttons/EffectButton.cs | 62 +++ Jungle/Buttons/TargetButton.cs | 56 +++ Jungle/Buttons/TargetEffectButton.cs | 58 +++ Jungle/Config/MapConfig.cs | 35 +- Jungle/Debugging/DebugKeybinds.cs | 103 +++++ Jungle/Effects/Patches/HudManager_Patches.cs | 15 - Jungle/Effects/RpcAddEffect.cs | 5 +- Jungle/Enums/CustomRpcs.cs | 13 + Jungle/HUDMap/CustomMapBehaviour.cs | 1 + Jungle/Map/LavaColorChanger.cs | 92 ++++ Jungle/Map/SpriteSwapper.cs | 98 +++++ Jungle/Systems/CustomSystemType.cs | 38 ++ .../Systems/DefaultSystems/CounterSystem.cs | 95 +++++ Jungle/Systems/DefaultSystems/HealthSystem.cs | 171 ++++++++ .../Systems/DefaultSystems/ProgressSystem.cs | 119 ++++++ Jungle/Systems/ICustomSystemType.cs | 16 + Jungle/Systems/Patches/ShipStatus_Patches.cs | 33 ++ Jungle/Utils/PlayerFinder.cs | 42 ++ Jungle/Utils/Reimpl/MEffects.cs | 402 ++++++++++++++++++ 20 files changed, 1442 insertions(+), 24 deletions(-) create mode 100644 Jungle/Buttons/EffectButton.cs create mode 100644 Jungle/Buttons/TargetButton.cs create mode 100644 Jungle/Buttons/TargetEffectButton.cs create mode 100644 Jungle/Debugging/DebugKeybinds.cs delete mode 100644 Jungle/Effects/Patches/HudManager_Patches.cs create mode 100644 Jungle/Enums/CustomRpcs.cs create mode 100644 Jungle/Map/LavaColorChanger.cs create mode 100644 Jungle/Map/SpriteSwapper.cs create mode 100644 Jungle/Systems/CustomSystemType.cs create mode 100644 Jungle/Systems/DefaultSystems/CounterSystem.cs create mode 100644 Jungle/Systems/DefaultSystems/HealthSystem.cs create mode 100644 Jungle/Systems/DefaultSystems/ProgressSystem.cs create mode 100644 Jungle/Systems/ICustomSystemType.cs create mode 100644 Jungle/Systems/Patches/ShipStatus_Patches.cs create mode 100644 Jungle/Utils/PlayerFinder.cs create mode 100644 Jungle/Utils/Reimpl/MEffects.cs diff --git a/Jungle/Buttons/CooldownButton.cs b/Jungle/Buttons/CooldownButton.cs index df4ba08..e914e11 100644 --- a/Jungle/Buttons/CooldownButton.cs +++ b/Jungle/Buttons/CooldownButton.cs @@ -22,7 +22,7 @@ public class CooldownButton : MonoBehaviour public static T Create() where T : CooldownButton { - var buttonObj = new GameObject(typeof(T).Name) {layer = 5}; + GameObject buttonObj = new GameObject(typeof(T).Name) {layer = 5}; buttonObj.transform.parent = HudManager.Instance.transform; return buttonObj.AddComponent(); } @@ -44,7 +44,7 @@ public void Awake() { Buttons.Add(this); - var buttonObj = gameObject; + GameObject buttonObj = gameObject; // SpriteRenderer Renderer = buttonObj.AddComponent(); @@ -60,7 +60,7 @@ public void Awake() Aspect.parentCam = HudManager.Instance.UICamera; // PassiveButton - var buttonColl = buttonObj.AddComponent(); + BoxCollider2D buttonColl = buttonObj.AddComponent(); buttonColl.size = new Vector2(1.15f, 1.15f); Button = buttonObj.AddComponent(); @@ -104,7 +104,7 @@ public virtual void Update() public void OnDestroy() { - var myHashCode = GetHashCode(); + int myHashCode = GetHashCode(); Buttons.RemoveAll(b => b.GetHashCode() == myHashCode); } @@ -114,7 +114,7 @@ public void OnDestroy() public void SetPosition(AspectPosition.EdgeAlignments alignment, int buttonIndex = Int32.MaxValue) { - var myHashCode = GetHashCode(); + int myHashCode = GetHashCode(); if (buttonIndex == Int32.MaxValue) buttonIndex = Buttons.Count(c => c.GetHashCode() != myHashCode && c.Aspect!.Alignment == alignment); Vector3 distanceFromEdge = new(0.7f, 0.7f, -5f); @@ -158,7 +158,7 @@ public virtual bool ShouldBeVisible() { if (!ShipStatus.Instance) return false; if (!HudManager.Instance.UseButton.gameObject.active) return false; - var localPlayer = PlayerControl.LocalPlayer; + PlayerControl localPlayer = PlayerControl.LocalPlayer; if (!localPlayer || localPlayer.Data == null) return false; if (localPlayer.Data.IsDead) return false; return true; diff --git a/Jungle/Buttons/EffectButton.cs b/Jungle/Buttons/EffectButton.cs new file mode 100644 index 0000000..ba6f299 --- /dev/null +++ b/Jungle/Buttons/EffectButton.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections; +using BepInEx.Unity.IL2CPP.Utils.Collections; +using Il2CppInterop.Runtime.Attributes; +using Jungle.Effects; +using Reactor.Utilities.Attributes; +using UnityEngine; + +namespace Jungle.Buttons; + +[RegisterInIl2Cpp] +public class EffectButton : CooldownButton +{ + public EffectButton(IntPtr ptr) : base(ptr) { } + + public IEffect Effect; + public Coroutine EffectRoutine; + + public override void PerformClick() + { + if (!ShouldCooldown() || !ShouldBeVisible() || !CanUse()) return; + + CurrentTime = int.MaxValue; + StopAllCoroutines(); + Effect = null; + EffectRoutine = StartCoroutine(ShowEffectDuration().WrapToIl2Cpp()); + OnClickAction?.Invoke(); + } + + public override bool CanUse() + { + return EffectRoutine == null && base.CanUse(); + } + + [HideFromIl2Cpp] + public IEnumerator ShowEffectDuration() + { + while (Effect == null) + { + CurrentTime = int.MaxValue; + yield return null; + } + + float duration = Effect.Timer; + + while (Effect is { Timer: > 0 }) + { + CurrentTime = int.MaxValue; + TimerText!.text = Mathf.CeilToInt(Effect.Timer).ToString(); + TimerText.color = Effect.Timer / duration < 0.5 + ? Color.Lerp(new Color32(255, 0, 0, 255), new Color32(255, 242, 0, 255), (Effect.Timer / duration) * 2) + : Color.Lerp(new Color32(255, 242, 0, 255), new Color32(30, 150, 0, 255), ((Effect.Timer / duration) - 0.5f) * 2); + + yield return null; + } + + CurrentTime = Cooldown; + TimerText!.color = Color.white; + EffectRoutine = null; + Effect = null; + } +} \ No newline at end of file diff --git a/Jungle/Buttons/TargetButton.cs b/Jungle/Buttons/TargetButton.cs new file mode 100644 index 0000000..65a07f4 --- /dev/null +++ b/Jungle/Buttons/TargetButton.cs @@ -0,0 +1,56 @@ +using System; +using AmongUs.GameOptions; +using Reactor.Utilities.Attributes; +using UnityEngine; + +namespace Jungle.Buttons; + +[RegisterInIl2Cpp] +// ReSharper disable once ClassWithVirtualMembersNeverInherited.Global +public class TargetButton : CooldownButton +{ + public TargetButton(IntPtr ptr) : base(ptr) { } + + public PlayerControl Target { get; set; } + + public virtual float TargetRange() => GameOptionsData.KillDistances[Mathf.Clamp(GameOptionsManager.Instance.currentNormalGameOptions.KillDistance, 0, 2)]; + + public virtual PlayerControl GetClosest() + { + PlayerControl current = null; + float maxDistance = TargetRange(); + Vector2 myPosition = PlayerControl.LocalPlayer.GetTruePosition(); + foreach (PlayerControl player in PlayerControl.AllPlayerControls) + { + if (player.AmOwner || player.Data.IsDead) continue; + float distance = Vector2.Distance(myPosition, player.GetTruePosition()); + if (distance <= maxDistance) + { + current = player; + maxDistance = distance; + } + } + + return current; + } + + public override void PerformClick() + { + if (!Target) return; + base.PerformClick(); + } + + public override bool CanUse() + { + if (!Target) return false; + return base.CanUse(); + } + + public override void Update() + { + base.Update(); + + Target = GetClosest(); + SetButtonSaturation(Target); + } +} \ No newline at end of file diff --git a/Jungle/Buttons/TargetEffectButton.cs b/Jungle/Buttons/TargetEffectButton.cs new file mode 100644 index 0000000..01d22c2 --- /dev/null +++ b/Jungle/Buttons/TargetEffectButton.cs @@ -0,0 +1,58 @@ +using System; +using AmongUs.GameOptions; +using Reactor.Utilities.Attributes; +using UnityEngine; + +namespace Jungle.Buttons; + +[RegisterInIl2Cpp] +// ReSharper disable once ClassWithVirtualMembersNeverInherited.Global +public class TargetEffectButton : EffectButton +{ + public TargetEffectButton(IntPtr ptr) : base(ptr) { } + + public PlayerControl Target { get; set; } + public virtual float TargetRange() => GameOptionsData.KillDistances[Mathf.Clamp(GameOptionsManager.Instance.currentNormalGameOptions.KillDistance, 0, 2)]; + + + public virtual PlayerControl GetClosest() + { + PlayerControl current = null; + float maxDistance = TargetRange(); + Vector2 myPosition = PlayerControl.LocalPlayer.GetTruePosition(); + foreach (PlayerControl player in PlayerControl.AllPlayerControls) + { + if (player.AmOwner || player.Data.IsDead) continue; + float distance = Vector2.Distance(myPosition, player.GetTruePosition()); + if (distance <= maxDistance) + { + current = player; + maxDistance = distance; + } + } + + return current; + } + + public override void PerformClick() + { + if (!Target) return; + base.PerformClick(); + } + + public override bool CanUse() + { + if (!Target) return false; + return base.CanUse(); + } + + public override void Update() + { + base.Update(); + if (CurrentTime != int.MaxValue) + { + Target = GetClosest(); + SetButtonSaturation(Target); + } + } +} \ No newline at end of file diff --git a/Jungle/Config/MapConfig.cs b/Jungle/Config/MapConfig.cs index 380e36a..c8ef329 100644 --- a/Jungle/Config/MapConfig.cs +++ b/Jungle/Config/MapConfig.cs @@ -1,5 +1,8 @@ using System.Collections.Generic; +using System.Linq; using HarmonyLib; +using Jungle.Extensions; +using Jungle.Map; using UnityEngine; namespace Jungle.Config; @@ -19,6 +22,17 @@ public static class MapConfig /// public static bool DisableRoomTracker { get; set; } + /// + /// Removes the snow particles on polus + /// + public static bool DisableSnow { get; set; } + + /// + /// Sets the new Colors for the lava + /// Leaving as default will keep original + /// + public static Color32[] LavaColors { get; set; } = LavaColorChanger.Default; + /// /// AssetBundles containing the Sprites used to swap textures on the map /// @@ -37,7 +51,7 @@ public static class MapConfigPatches { [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Awake))] [HarmonyPostfix] - public static void SetupConfig() + public static void SetupConfig(ShipStatus __instance) { if (MapConfig.DisableShadows) { @@ -51,6 +65,25 @@ public static void SetupConfig() if (MapConfig.DisableRoomTracker) HudManager.Instance.roomTracker.text.renderer.enabled = false; + foreach (AssetBundle textureSwapBundle in MapConfig.TextureSwapBundles) + { + if (!MapConfig.SwapRawTextures) + { + List sprites = textureSwapBundle.LoadAllAssets().OfIl2CppType().ToList(); + SpriteSwapper.Swap(__instance.gameObject, sprites); + } + else + { + SpriteSwapper.SwapMapSpritesRaw(textureSwapBundle); + } + } + + // Map specific + if (__instance.Type == ShipStatus.MapType.Pb) + { + if (MapConfig.DisableSnow) Object.FindObjectOfType()?.gameObject.SetActive(false); + } + // TODO: Texture swapping } } diff --git a/Jungle/Debugging/DebugKeybinds.cs b/Jungle/Debugging/DebugKeybinds.cs new file mode 100644 index 0000000..0fcc2e5 --- /dev/null +++ b/Jungle/Debugging/DebugKeybinds.cs @@ -0,0 +1,103 @@ +using HarmonyLib; +using Jungle.Effects.Managers; +using Jungle.Enums; +using Jungle.HUDMap; +using Jungle.Player; +using Jungle.Player.Extensions; +using Jungle.Utils; +using Reactor.Networking.Attributes; +using Reactor.Utilities.Extensions; +using UnityEngine; + +namespace Jungle.Debugging; + +[HarmonyPatch] +public static class DebugKeybinds +{ + [MethodRpc((uint)CustomRPCs.HardReset)] + private static void HardReset(PlayerControl player) + { + if (player.AmOwner) + { + HudManager.Instance.FullScreen.enabled = false; + CameraZoomController.Instance!.OrthographicSize = 3; + HudManager.Instance.PlayerCam.Target = player; + HudManager.Instance.PlayerCam.Locked = false; + Transform camTransform = Camera.main!.transform; + for (int i = 5; i < camTransform.childCount; i++) camTransform.GetChild(i).gameObject.Destroy(); + } + + PlayerEffectManager playerEffectManager = player.GetEffectManager(); + playerEffectManager.ClearEffects(); + Moveable.Clear(player); + Visible.Clear(player); + SpeedModifier.Clear(player.MyPhysics); + SizeModifer.Clear(player.MyPhysics); + player.moveable = true; + player.Visible = true; + player.Collider.enabled = true; + player.transform.localEulerAngles = Vector3.zero; + HudManager.Instance.SetHudActive(true); + } + + [MethodRpc((uint)CustomRPCs.ResetMovement)] + private static void ResetMovement(PlayerControl player) + { + player.Collider.enabled = true; + Moveable.Clear(player); + SpeedModifier.Clear(player.MyPhysics); + player.moveable = true; + player.Visible = true; + player.NetTransform.enabled = true; + } + + [MethodRpc((uint)CustomRPCs.EndGame)] + private static void RpcEndGame(PlayerControl _) + { + if (AmongUsClient.Instance.AmHost) + { + GameManager.Instance.RpcEndGame(GameOverReason.HumansByTask, false); + } + } + + [HarmonyPatch(typeof(KeyboardJoystick), nameof(KeyboardJoystick.Update))] + public static void WatchForKeyboardInputPatch() + { + if (Input.GetKey(KeyCode.F11) && Input.GetKeyDown(KeyCode.F10) || + Input.GetKey(KeyCode.F10) && Input.GetKeyDown(KeyCode.F11)) + { + RpcEndGame(PlayerControl.LocalPlayer); + } + + if (Input.GetKey(KeyCode.F1) && Input.GetKeyDown(KeyCode.F2) || + Input.GetKey(KeyCode.F2) && Input.GetKeyDown(KeyCode.F1)) + { + HardReset(PlayerControl.LocalPlayer); + } + + if (Input.GetKeyDown(KeyCode.T)) + { + PlayerControl.LocalPlayer.Collider.enabled = !PlayerControl.LocalPlayer.Collider.enabled; + } + + if (Input.GetKey(KeyCode.F3) && Input.GetKeyDown(KeyCode.F4) || + Input.GetKey(KeyCode.F4) && Input.GetKeyDown(KeyCode.F3)) + { + ResetMovement(PlayerControl.LocalPlayer); + } + + // Teleport + if (Input.GetKey(KeyCode.F5) && Input.GetKeyDown(KeyCode.F6) || + Input.GetKey(KeyCode.F6) && Input.GetKeyDown(KeyCode.F5)) + { + void MouseUpEvent(CustomMapBehaviour instance, int mouseButton, Vector2 worldPos) + { + if (mouseButton != 1) return; + instance.Parent!.Close(); + PlayerControl.LocalPlayer.moveable = true; + PlayerControl.LocalPlayer.NetTransform.RpcSnapTo(worldPos); + } + CustomMapBehaviour.ShowWithAllPlayers(new Color32(158, 240, 103, 255), MouseUpEvent); + } + } +} \ No newline at end of file diff --git a/Jungle/Effects/Patches/HudManager_Patches.cs b/Jungle/Effects/Patches/HudManager_Patches.cs deleted file mode 100644 index 8549e65..0000000 --- a/Jungle/Effects/Patches/HudManager_Patches.cs +++ /dev/null @@ -1,15 +0,0 @@ -using HarmonyLib; -using Jungle.Effects.Managers; -using UnityEngine; - -namespace Jungle.Effects.Patches; - -[HarmonyPatch(typeof(HudManager), nameof(HudManager.Start))] -public static class HudManager_Start_Patch -{ - public static void Postfix() - { - Camera.main!.gameObject.AddComponent(); - if (!GlobalEffectManager.Instance) GlobalEffectManager.Instance = new GameObject("GlobalEffects").AddComponent(); - } -} \ No newline at end of file diff --git a/Jungle/Effects/RpcAddEffect.cs b/Jungle/Effects/RpcAddEffect.cs index 48ab029..ca4ed96 100644 --- a/Jungle/Effects/RpcAddEffect.cs +++ b/Jungle/Effects/RpcAddEffect.cs @@ -2,6 +2,7 @@ using Hazel; using InnerNet; using Jungle.Effects.Managers; +using Jungle.Enums; using Jungle.Player.Extensions; using Reactor.Networking.Attributes; using Reactor.Networking.Rpc; @@ -9,9 +10,9 @@ namespace Jungle.Effects; [RegisterCustomRpc((uint)CustomRPCs.AddEffect)] -public class RpcAddEffect : PlayerCustomRpc +public class RpcAddEffect : PlayerCustomRpc { - public RpcAddEffect(LaboratoryPlugin plugin, uint id) : base(plugin, id) + public RpcAddEffect(JunglePlugin plugin, uint id) : base(plugin, id) { } diff --git a/Jungle/Enums/CustomRpcs.cs b/Jungle/Enums/CustomRpcs.cs new file mode 100644 index 0000000..8c46648 --- /dev/null +++ b/Jungle/Enums/CustomRpcs.cs @@ -0,0 +1,13 @@ +namespace Jungle.Enums; + +public enum CustomRPCs : uint +{ + ChangeHealth, + CustomMurder, + AddEffect, + HardReset, + ResetMovement, + EndGame, + SetCount, + ChangeCount, +} diff --git a/Jungle/HUDMap/CustomMapBehaviour.cs b/Jungle/HUDMap/CustomMapBehaviour.cs index 0c669b8..16257c4 100644 --- a/Jungle/HUDMap/CustomMapBehaviour.cs +++ b/Jungle/HUDMap/CustomMapBehaviour.cs @@ -178,6 +178,7 @@ private void Update() if (MouseUpEvent != null) InvokeEvent(MouseUpEvent); return; + [HideFromIl2Cpp] void InvokeEvent(MouseClickEvent mouseEvent) { if (Input.GetMouseButtonDown(0)) mouseEvent(this, 0, GetMapPosition(ref mapPositionSet, ref mapPosition)); diff --git a/Jungle/Map/LavaColorChanger.cs b/Jungle/Map/LavaColorChanger.cs new file mode 100644 index 0000000..fa9ddac --- /dev/null +++ b/Jungle/Map/LavaColorChanger.cs @@ -0,0 +1,92 @@ +using System; +using Il2CppInterop.Runtime.InteropTypes.Arrays; +using Il2CppSystem.Collections.Generic; +using UnityEngine; + +namespace Jungle.Map; + +public static class LavaColorChanger +{ + /// + /// Default color scheme for lava colors + /// + public static Color32[] Default { get; } = + { + new Color32(255, 98, 0, 255), + new Color32(255, 103, 1, 255), + new Color32(255, 42, 0, 255), + new Color32(255, 225, 21, 255), + new Color32(255, 98, 0, 255), + }; + + /// + /// Blue / Water color scheme for lava colors + /// + public static Color32[] Blue { get; } = + { + new Color32(17, 111, 137, 255), + new Color32(33, 143, 173, 255), + new Color32(24, 79, 94, 255), + new Color32(105, 198, 227, 255), + new Color32(17, 111, 137, 255), + }; + + /// + /// Reference color scheme for lava colors + /// + public static Color32[] Reference { get; } = + { + new Color32(255, 0, 0, 255), + new Color32(255, 170, 170, 255), + new Color32(0, 255, 0, 255), + new Color32(0, 0, 255, 255), + new Color32(0, 0, 0, 255), + }; + + /// + /// This allows you to replace the colors of the lava in polus + /// + /// The colors that should be provided to replace the lava colors with. + /// https://media.discordapp.net/attachments/885556508866265098/890628223862448149/Lava_Recolor_Guide.png + public static void ChangeColors(Color32[] colors) + { + if (colors.Length != 5) throw new ArgumentException("You need exactly 5 replacement colors", nameof(colors)); + + if (colors != Default) + { + GameObject.Find("BubbleParent")?.SetActive(false); + + MeshAnimator meshAnimator = GameObject.Find("LavaOrange").GetComponent(); + + foreach (Mesh mesh in meshAnimator.Frames) + { + List currentColors = new(); + mesh.GetColors(currentColors); + + Il2CppStructArray replacedColors = new Il2CppStructArray(currentColors.Count); + for (int i = 0; i < currentColors.Count; i++) + { + for (int j = 0; j < 4; j++) + { + // DNF is hard clearing this sus line // But it's not even throwing a warning anymore... + // ReSharper disable once SuspiciousTypeConversion.Global + if (currentColors[(Index) i].Equals(Default[j])) + { + replacedColors[i] = colors[j]; + } + } + } + + mesh.SetColors(replacedColors); + } + } + + // Makes a sprite to go under the clear part of the lava + SpriteRenderer blankSprite = new GameObject { layer = 9 }.AddComponent(); + blankSprite.sprite = Sprite.Create(Texture2D.whiteTexture, new Rect(0, 0, 4, 4), new Vector2(0.5f, 0.5f), 4); + blankSprite.drawMode = SpriteDrawMode.Sliced; + blankSprite.size = new Vector2(8.63f, 1.28f); + blankSprite.transform.position = new Vector3(40.038f, -15.413f, 15); + blankSprite.color = colors[4]; + } +} diff --git a/Jungle/Map/SpriteSwapper.cs b/Jungle/Map/SpriteSwapper.cs new file mode 100644 index 0000000..f3b73d6 --- /dev/null +++ b/Jungle/Map/SpriteSwapper.cs @@ -0,0 +1,98 @@ +using System.Collections.Generic; +using System.Linq; +using Il2CppInterop.Runtime.InteropTypes.Arrays; +using Jungle.Extensions; +using Reactor.Utilities.Extensions; +using UnityEngine; + +namespace Jungle.Map; + +public static class SpriteSwapper +{ + public static void Swap(GameObject gameObject, List replacementSprites) + { + foreach (SpriteRenderer renderer in gameObject.GetComponentsInChildren()) + { + Sprite sprite = renderer.sprite; + if (!sprite) continue; + + Sprite replacement = replacementSprites.FirstOrDefault(s => s.name == sprite!.name && s.texture.name == sprite.texture.name); + if (replacement) + { + renderer.sprite = replacement; + } + } + + CreateGround(replacementSprites); + } + + public static void SwapMapSpritesRaw(AssetBundle bundle) + { + IEnumerable newTex = bundle.LoadAllAssets().OfIl2CppType(); + newTex = newTex.OrderBy(t => t.name.Length); + + Il2CppArrayBase mapRends = ShipStatus.Instance.GetComponentsInChildren(); + + foreach (SpriteRenderer spriteRenderer in mapRends) + { + if (spriteRenderer.sprite && spriteRenderer.sprite.texture) + { + // ReSharper disable once PossibleMultipleEnumeration + Texture2D match = newTex.FirstOrDefault(t => t.name.Replace('_', '-').Contains(spriteRenderer.sprite.texture.name.Replace('_', '-') + '-')); + if (match) + { + MaterialPropertyBlock block = new(); + block.AddTexture("_MainTex", match); + spriteRenderer.SetPropertyBlock(block); + } + } + } + + CreateGround(bundle.LoadAllAssets().OfIl2CppType().ToList()); + } + + public static void CreateGround(List sprites) + { + if (ShipStatus.Instance.Type == ShipStatus.MapType.Pb) + { + float z = -1f; + + foreach (Sprite groundSprite in sprites.Where(s => s.name.Contains("Ground"))) + { + GameObject.Find("NewMapGround")?.Destroy(); + + Transform background = ShipStatus.Instance.transform.Find("Background"); + Transform ground = new GameObject() { layer = 9, name = "NewMapGround" }.transform; + ground.parent = background; + ground.localPosition = new Vector3(19.97f, -13.5f, z); + ground.localScale = new Vector3(0.3915f, 0.3915f, 1f); + SpriteRenderer groundRend = ground.gameObject.AddComponent(); + groundRend.sprite = groundSprite; + + z -= 0.001f; + } + + return; + } + + if (ShipStatus.Instance.Type == ShipStatus.MapType.Ship) + { + Sprite groundSprite = sprites.FirstOrDefault(s => s.name.Contains("SkeldFloor")); + if (groundSprite) + { + GameObject.Find("NewMapGround")?.Destroy(); + + Transform background = ShipStatus.Instance.transform.Find("Hull2"); + background.GetComponent().enabled = false; + Transform ground = new GameObject() { layer = 9, name = "NewMapGround" }.transform; + ground.parent = background; + ground.localPosition = new Vector3(10.64f, -8.46f, -0.001f); + ground.localScale = new Vector3(0.1644f, 0.1644f, 1f); + SpriteRenderer groundRend = ground.gameObject.AddComponent(); + groundRend.sprite = groundSprite; + } + + return; + } + } +} diff --git a/Jungle/Systems/CustomSystemType.cs b/Jungle/Systems/CustomSystemType.cs new file mode 100644 index 0000000..9a97185 --- /dev/null +++ b/Jungle/Systems/CustomSystemType.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Jungle.Systems; + +public class CustomSystemType +{ +#pragma warning disable CS0414 + private static int _lastId = -1; +#pragma warning restore CS0414 + + private static readonly List _list = new(); + + public static IReadOnlyList List => _list.AsReadOnly(); + + public static CustomSystemType Register() where T : ICustomSystemType + { + CustomSystemType customStringName = new CustomSystemType(_lastId--, typeof(T)); + _list.Add(customStringName); + SystemTypeHelpers.AllTypes = SystemTypeHelpers.AllTypes.Append(customStringName).ToArray(); + + return customStringName; + } + + public int Id { get; } + + public Type Value { get; } + + private CustomSystemType(int id, Type value) + { + Id = id; + Value = value; + } + + public static implicit operator SystemTypes(CustomSystemType name) => (SystemTypes)name.Id; + public static explicit operator CustomSystemType(SystemTypes name) => List.SingleOrDefault(x => x.Id == (int)name); +} diff --git a/Jungle/Systems/DefaultSystems/CounterSystem.cs b/Jungle/Systems/DefaultSystems/CounterSystem.cs new file mode 100644 index 0000000..ddad4d4 --- /dev/null +++ b/Jungle/Systems/DefaultSystems/CounterSystem.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using Hazel; +using Il2CppInterop.Runtime.Attributes; +using Il2CppInterop.Runtime.Injection; +using Jungle.Enums; +using Reactor.Networking.Attributes; +using Reactor.Utilities.Attributes; +using IntPtr = System.IntPtr; +using Object = Il2CppSystem.Object; + +namespace Jungle.Systems.DefaultSystems; + +[RegisterInIl2Cpp(typeof(ISystemType))] +public class CounterSystem : Object, ICustomSystemType +{ + public static int GetCount(uint key) + { + Instance!.Counters.TryGetValue(key, out int res); + return res; + } + + [MethodRpc((uint)CustomRPCs.SetCount)] + public static void SetCount(PlayerControl player, uint key, int value) + { + if (AmongUsClient.Instance.AmHost) + { + Instance!.Counters[key] = value; + Instance.IsDirty = true; + } + } + + [MethodRpc((uint)CustomRPCs.ChangeCount)] + public static void ChangeCount(PlayerControl player, uint key, int change) + { + if (AmongUsClient.Instance.AmHost) + { + SetCount(player, key, GetCount(key) + change); + } + } + + private static CounterSystem _instance; + public static CounterSystem Instance => ShipStatus.Instance ? _instance : null; + + public static CustomSystemType SystemType { get; } = CustomSystemType.Register(); + + public CounterSystem(IntPtr ptr) : base(ptr) + { + } + + public CounterSystem() : base(ClassInjector.DerivedConstructorPointer()) + { + ClassInjector.DerivedConstructorBody(this); + _instance = this; + } + + public bool IsDirty { get; set; } + + [HideFromIl2Cpp] + public Dictionary Counters { get; } = new(); + + public void Serialize(MessageWriter writer, bool initialState) + { + writer.Write(Counters.Count); + foreach ((uint key, int value) in Counters) + { + writer.Write(key); + writer.Write(value); + } + + IsDirty = false; + } + + public void Deserialize(MessageReader reader, bool initialState) + { + Counters.Clear(); + int count = reader.ReadInt32(); + for (int i = 0; i < count; i++) + { + Counters[reader.ReadUInt32()] = reader.ReadInt32(); + } + } + + public void Detoriorate(float deltaTime) { } + + public void RepairDamage(PlayerControl player, byte amount) + { + throw new InvalidOperationException(); + } + + public void UpdateSystem(PlayerControl player, MessageReader msgReader) + { + throw new InvalidOperationException(); + } +} \ No newline at end of file diff --git a/Jungle/Systems/DefaultSystems/HealthSystem.cs b/Jungle/Systems/DefaultSystems/HealthSystem.cs new file mode 100644 index 0000000..fe5e42b --- /dev/null +++ b/Jungle/Systems/DefaultSystems/HealthSystem.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using Hazel; +using Il2CppInterop.Runtime.Attributes; +using Il2CppInterop.Runtime.Injection; +using Jungle.Enums; +using Reactor.Networking.Attributes; +using Reactor.Utilities.Attributes; +using Reactor.Utilities.Extensions; +using IntPtr = System.IntPtr; +using Math = System.Math; +using Object = Il2CppSystem.Object; + +namespace Jungle.Systems.DefaultSystems; + +/// +/// Generic system to manage player healths +/// +[RegisterInIl2Cpp(typeof(ISystemType))] +public class HealthSystem : Object, ICustomSystemType +{ + public static CustomSystemType SystemType { get; } = CustomSystemType.Register(); + + /// + /// The starting and maximum health of each player + /// + public static int MaxHealth { get; set; } = 100; + + /// + /// Set if the players name should be updated when their health changes + /// + public static bool UpdateNameText { get; set; } = true; + + /// + /// Should a player be killed when their health hits zero + /// + public static bool KillWhenNoHealth { get; set; } = true; + + /// + /// Action invoked when a player's health is changed with their old and new health values + /// + public static Action OnHealthChange { get; set; } + + /// + /// The current instance of the health system + /// + public static HealthSystem Instance => ShipStatus.Instance ? _instance : null; + + private static HealthSystem _instance; + + /// + /// Changes the damage of a specified player + /// + /// The player to change the health of + /// The amount of health to be added to a player (can be negative) + [MethodRpc((uint)CustomRPCs.ChangeHealth)] + public static void CmdChangeHealth(PlayerControl player, int change) + { + if (Instance is not null) + { + byte pid = player.PlayerId; + Instance.SetHealth(pid, Instance.GetHealth(pid) + change); + } + } + + public HealthSystem(IntPtr ptr) : base(ptr) + { + } + + public HealthSystem() : base(ClassInjector.DerivedConstructorPointer()) + { + ClassInjector.DerivedConstructorBody(this); + _instance = this; + + foreach (PlayerControl playerInfo in PlayerControl.AllPlayerControls) + { + SetHealth(playerInfo.PlayerId, GetMaxHealth(playerInfo.PlayerId)); + } + } + + public int GetMaxHealth(byte playerId) => MaxHealth; + + [HideFromIl2Cpp] + internal Dictionary PlayerHealths { get; } = new(); + + /// + /// Sets the health of a player and updated their name + /// + /// The player id of the player being changed + /// The new amount of heath to set the player with + public void SetHealth(byte playerId, int newHealth) + { + GameData.PlayerInfo data = GameData.Instance.GetPlayerById(playerId); + int oldHealth = GetHealth(playerId); + int playerHealth = PlayerHealths[playerId] = Math.Clamp(newHealth, 0, GetMaxHealth(playerId)); + IsDirty = true; + + if (data != null && data.Object) + { + PlayerControl player = data.Object; + OnHealthChange?.Invoke(player, (oldHealth, newHealth)); + if (UpdateNameText) UpdateHealthText(player, data, playerHealth); + + if (!data.Role.IsImpostor && AmongUsClient.Instance.AmHost && playerHealth <= 0) + { + if (KillWhenNoHealth) throw new NotImplementedException(); // player.RpcCustomMurder(player, true); + } + } + } + + public void UpdateHealthText(PlayerControl player, GameData.PlayerInfo data, int playerHealth) + { + player.cosmetics.nameText.text = data.Role.IsImpostor + ? data.PlayerName + : $"{playerHealth}\n{data.PlayerName}"; + } + + /// + /// Get the current health of a player + /// + /// The player id of the player being checked + /// + public int GetHealth(byte playerId) + { + if (!PlayerHealths.ContainsKey(playerId)) return PlayerHealths[playerId] = GetMaxHealth(playerId); + return PlayerHealths[playerId]; + } + + #region ISystemType Implementation + + public bool IsDirty { get; set; } = true; + + public void Deserialize(MessageReader reader, bool initialState) + { + byte length = reader.ReadByte(); + for (int i = 0; i < length; i++) + { + bool dirty = IsDirty; + SetHealth(reader.ReadByte(), reader.ReadInt32()); + IsDirty = dirty; + } + } + + public void Serialize(MessageWriter writer, bool initialState) + { + writer.Write((byte)PlayerHealths.Count); + foreach ((byte playerId, int health) in PlayerHealths) + { + writer.Write(playerId); + writer.Write(health); + } + + IsDirty = false; + } + + public void Detoriorate(float deltaTime) + { + } + + public void UpdateSystem(PlayerControl player, MessageReader msgReader) + { + throw new NotSupportedException(); + } + + public void RepairDamage(PlayerControl player, byte amount) + { + throw new NotSupportedException(); + } + + #endregion +} diff --git a/Jungle/Systems/DefaultSystems/ProgressSystem.cs b/Jungle/Systems/DefaultSystems/ProgressSystem.cs new file mode 100644 index 0000000..a787025 --- /dev/null +++ b/Jungle/Systems/DefaultSystems/ProgressSystem.cs @@ -0,0 +1,119 @@ +using System; +using System.Linq; +using Hazel; +using Il2CppInterop.Runtime.Injection; +using Reactor.Utilities.Attributes; +using UnityEngine; +using IntPtr = System.IntPtr; +using Object = Il2CppSystem.Object; + +namespace Jungle.Systems.DefaultSystems; + +[RegisterInIl2Cpp(typeof(ISystemType))] +public class ProgressSystem : Object, ICustomSystemType +{ + private static ProgressSystem _instance; + public static ProgressSystem Instance => ShipStatus.Instance ? _instance : null; + public static CustomSystemType SystemType { get; } = CustomSystemType.Register(); + public static float[] Stages { get; set; } = { + 60f, + 60f, + 60f, + 60f, + 60f, + 60f, + 60f, + 60f, + 60f, + 60f, + }; + + public ProgressSystem(IntPtr ptr) : base(ptr) + { + } + + public ProgressSystem() : base(ClassInjector.DerivedConstructorPointer()) + { + ClassInjector.DerivedConstructorBody(this); + _instance = this; + } + + public float Timer { get; set; } + public bool IsDirty { get; set; } + + private bool _shouldRun = true; + public bool ShouldRun + { + get => _shouldRun; + set + { + _shouldRun = value; + IsDirty = true; + } + } + + + private float _totalTime = -1; + public float TotalTime => _totalTime < 0 ? _totalTime = Stages.Sum() : _totalTime; + + private int _stage; + public int Stage + { + get => _stage; + set + { + _stage = value; + IsDirty = true; + } + } + + public float Progress + { + get + { + int stageLength = Stages.Length; + return Stage == stageLength ? 1 : Mathf.Clamp01((Timer - Stages.Take(Stage).Sum()) / (Stages[Stage] * stageLength) + (float) Stage / stageLength); + } + } + + public void Detoriorate(float deltaTime) + { + if (ShouldRun) Timer += deltaTime; + Timer = Mathf.Clamp(Timer, 0, TotalTime); + if (!AmongUsClient.Instance.AmHost) return; + float sum = 0; + int stage = 0; + foreach (float t in Stages) + { + sum += t; + if (Timer >= sum) stage++; + } + + if (Stage != stage) Stage = stage; + } + + public void Serialize(MessageWriter writer, bool initialState) + { + writer.Write((byte)Stage); + writer.Write(Timer); + writer.Write(ShouldRun); + IsDirty = false; + } + + public void Deserialize(MessageReader reader, bool initialState) + { + Stage = reader.ReadByte(); + Timer = reader.ReadSingle(); + ShouldRun = reader.ReadBoolean(); + } + + public void RepairDamage(PlayerControl player, byte amount) + { + throw new InvalidOperationException(); + } + + public void UpdateSystem(PlayerControl player, MessageReader msgReader) + { + throw new InvalidOperationException(); + } +} \ No newline at end of file diff --git a/Jungle/Systems/ICustomSystemType.cs b/Jungle/Systems/ICustomSystemType.cs new file mode 100644 index 0000000..5f21215 --- /dev/null +++ b/Jungle/Systems/ICustomSystemType.cs @@ -0,0 +1,16 @@ +using Hazel; + +namespace Jungle.Systems; + +/// +/// Clone of ISystemType interface to ensure all members are implemented +/// +public interface ICustomSystemType +{ + bool IsDirty { get; } + void Detoriorate(float deltaTime); + void RepairDamage(PlayerControl player, byte amount); + void UpdateSystem(PlayerControl player, MessageReader msgReader); + void Serialize(MessageWriter writer, bool initialState); + void Deserialize(MessageReader reader, bool initialState); +} \ No newline at end of file diff --git a/Jungle/Systems/Patches/ShipStatus_Patches.cs b/Jungle/Systems/Patches/ShipStatus_Patches.cs new file mode 100644 index 0000000..208ef21 --- /dev/null +++ b/Jungle/Systems/Patches/ShipStatus_Patches.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using HarmonyLib; +using Il2CppInterop.Runtime.InteropTypes; + +namespace Jungle.Systems.Patches; + +[HarmonyPatch] +public static class ShipStatus_OnEnable_Patch +{ + public static IEnumerable TargetMethods() + { + yield return AccessTools.Method(typeof(ShipStatus), nameof(ShipStatus.OnEnable)); + yield return AccessTools.Method(typeof(PolusShipStatus), nameof(PolusShipStatus.OnEnable)); + yield return AccessTools.Method(typeof(AirshipStatus), nameof(AirshipStatus.OnEnable)); + } + + public static void Prefix(ShipStatus __instance, out bool __state) + { + __state = __instance.Systems == null; + } + + public static void Postfix(ShipStatus __instance, bool __state) + { + if (!__state) return; + + foreach (CustomSystemType customSystemType in CustomSystemType.List) + { + __instance.Systems[customSystemType] = ((Il2CppObjectBase)Activator.CreateInstance(customSystemType.Value)!).TryCast(); + } + } +} diff --git a/Jungle/Utils/PlayerFinder.cs b/Jungle/Utils/PlayerFinder.cs new file mode 100644 index 0000000..73a1a2e --- /dev/null +++ b/Jungle/Utils/PlayerFinder.cs @@ -0,0 +1,42 @@ +using System; +using System.Linq; +using UnityEngine; + +namespace Jungle.Utils; + +public static class PlayerFinder +{ + public static Predicate Alive { get; } = player => !player.Data.IsDead; + public static Predicate Visible { get; } = player => player.Visible; + public static Predicate Crewmate { get; } = player => !player.Data.Role.IsImpostor; + public static Predicate Impostor { get; } = player => player.Data.Role.IsImpostor; + + public static PlayerControl FindPlayer(Vector3 position, float maxDistance, params Predicate[] filters) + { + PlayerControl current = null; + + foreach (PlayerControl player in PlayerControl.AllPlayerControls) + { + if (filters.Any(filter => !filter(player))) continue; + + float delta = Vector2.Distance(position, player.transform.position); + if (delta < maxDistance) + { + maxDistance = delta; + current = player; + } + } + + return current; + } + + public static PlayerControl FindCrewmate(Vector3 position, float maxDistance, params Predicate[] filters) + { + return FindPlayer(position, maxDistance, Alive, Visible, Crewmate, player => filters.All(filter => filter(player))); + } + + public static PlayerControl FindImpostor(Vector3 position, float maxDistance, params Predicate[] filters) + { + return FindPlayer(position, maxDistance, Alive, Visible, Impostor, player => filters.All(filter => filter(player))); + } +} diff --git a/Jungle/Utils/Reimpl/MEffects.cs b/Jungle/Utils/Reimpl/MEffects.cs new file mode 100644 index 0000000..fecc27a --- /dev/null +++ b/Jungle/Utils/Reimpl/MEffects.cs @@ -0,0 +1,402 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using TMPro; +using UnityEngine; + +namespace Jungle.Utils.Reimpl; + +/// +/// Clone of so you can use +/// +public static class MEffects +{ + private static readonly HashSet _activeShakes = new(); + + public static IEnumerator Action(Action todo) + { + todo(); + yield break; + } + + public static IEnumerator Wait(float duration) + { + for (float t = 0f; t < duration; t += Time.deltaTime) + { + yield return null; + } + } + + public static IEnumerator Sequence(params IEnumerator[] items) + { + int i = 0; + while (i < items.Length) + { + yield return items[i]; + int num = i + 1; + i = num; + } + } + + public static IEnumerator All(params IEnumerator[] items) + { + Stack[] enums = new Stack[items.Length]; + for (int i = 0; i < items.Length; i++) + { + enums[i] = new Stack(); + enums[i].Push(items[i]); + } + + int cap = 0; + while (cap < 100000) + { + bool flag = false; + foreach (Stack t in enums) + { + if (t.Count <= 0) + { + continue; + } + + flag = true; + IEnumerator enumerator = t.Peek(); + if (enumerator.MoveNext()) + { + if (enumerator.Current is IEnumerator current) + { + t.Push(current); + } + } + else + { + t.Pop(); + } + } + + if (flag) + { + yield return null; + int num = cap + 1; + cap = num; + continue; + } + + break; + } + } + + public static IEnumerator Lerp(float duration, Action action) + { + for (float t = 0f; t < duration; t += Time.deltaTime) + { + action(t / duration); + yield return null; + } + + action(1f); + } + + public static IEnumerator Overlerp(float duration, Action action, float overextend = 0.05f) + { + float d1 = duration * 0.95f; + for (float t2 = 0f; t2 < d1; t2 += Time.deltaTime) + { + action(Mathf.Lerp(0f, 1f + overextend, t2 / d1)); + yield return null; + } + + float d2 = duration * 0.050000012f; + for (float t2 = 0f; t2 < d2; t2 += Time.deltaTime) + { + action(Mathf.Lerp(1f + overextend, 1f, t2 / d2)); + yield return null; + } + + action(1f); + } + + public static IEnumerator ScaleIn(Transform self, float source, float target, float duration) + { + if ((bool)self) + { + Vector3 localScale = default(Vector3); + for (float t = 0f; t < duration; t += Time.deltaTime) + { + localScale.x = (localScale.y = (localScale.z = Mathf.SmoothStep(source, target, t / duration))); + self.localScale = localScale; + yield return null; + } + + localScale.x = (localScale.y = (localScale.z = target)); + self.localScale = localScale; + } + } + + public static IEnumerator CycleColors(SpriteRenderer self, Color source, Color target, float rate, float duration) + { + if ((bool)self) + { + self.enabled = true; + for (float t = 0f; t < duration; t += Time.deltaTime) + { + float t2 = Mathf.Sin(t * (float)Math.PI / rate) / 2f + 0.5f; + self.color = Color.Lerp(source, target, t2); + yield return null; + } + + self.color = source; + } + } + + public static IEnumerator PulseColor(SpriteRenderer self, Color source, Color target, float duration = 0.5f) + { + if ((bool)self) + { + self.enabled = true; + for (float t = 0f; t < duration; t += Time.deltaTime) + { + self.color = Color.Lerp(target, source, t / duration); + yield return null; + } + + self.color = source; + } + } + + public static IEnumerator PulseColor(TextMeshPro self, Color source, Color target, float duration = 0.5f) + { + if ((bool)self) + { + for (float t = 0f; t < duration; t += Time.deltaTime) + { + self.color = Color.Lerp(target, source, t / duration); + yield return null; + } + + self.color = source; + } + } + + public static IEnumerator ColorFade(TextMeshPro self, Color source, Color target, float duration) + { + if ((bool)self) + { + self.enabled = true; + for (float t = 0f; t < duration; t += Time.deltaTime) + { + self.color = Color.Lerp(source, target, t / duration); + yield return null; + } + + self.color = target; + } + } + + public static IEnumerator ColorFade(SpriteRenderer self, Color source, Color target, float duration) + { + if ((bool)self) + { + self.enabled = true; + for (float t = 0f; t < duration; t += Time.deltaTime) + { + self.color = Color.Lerp(source, target, t / duration); + yield return null; + } + + self.color = target; + } + } + + public static IEnumerator Rotate2D(Transform target, float source, float dest, float duration = 0.75f) + { + Vector3 temp = target.localEulerAngles; + for (float time = 0f; time < duration; time += Time.deltaTime) + { + float t = time / duration; + temp.z = Mathf.SmoothStep(source, dest, t); + target.localEulerAngles = temp; + yield return null; + } + + temp.z = dest; + target.localEulerAngles = temp; + } + + public static IEnumerator Slide3D(Transform target, Vector3 source, Vector3 dest, float duration = 0.75f) + { + Vector3 localPosition = default(Vector3); + for (float time = 0f; time < duration; time += Time.deltaTime) + { + float t = time / duration; + localPosition.x = Mathf.SmoothStep(source.x, dest.x, t); + localPosition.y = Mathf.SmoothStep(source.y, dest.y, t); + localPosition.z = Mathf.Lerp(source.z, dest.z, t); + target.localPosition = localPosition; + yield return null; + } + + target.localPosition = dest; + } + + public static IEnumerator Slide2D(Transform target, Vector2 source, Vector2 dest, float duration = 0.75f) + { + Vector3 temp = default(Vector3); + temp.z = target.localPosition.z; + for (float time = 0f; time < duration; time += Time.deltaTime) + { + float t = time / duration; + temp.x = Mathf.SmoothStep(source.x, dest.x, t); + temp.y = Mathf.SmoothStep(source.y, dest.y, t); + target.localPosition = temp; + yield return null; + } + + temp.x = dest.x; + temp.y = dest.y; + target.localPosition = temp; + } + + public static IEnumerator Slide2DWorld(Transform target, Vector2 source, Vector2 dest, float duration = 0.75f) + { + Vector3 temp = default(Vector3); + temp.z = target.position.z; + for (float time = 0f; time < duration; time += Time.deltaTime) + { + float t = time / duration; + temp.x = Mathf.SmoothStep(source.x, dest.x, t); + temp.y = Mathf.SmoothStep(source.y, dest.y, t); + target.position = temp; + yield return null; + } + + temp.x = dest.x; + temp.y = dest.y; + target.position = temp; + } + + public static IEnumerator Bounce(Transform target, float duration = 0.3f, float height = 0.15f) + { + if (!target) + { + yield break; + } + + Vector3 origin = target.localPosition; + Vector3 temp = origin; + for (float timer = 0f; timer < duration; timer += Time.deltaTime) + { + float num = timer / duration; + float num2 = 1f - num; + temp.y = origin.y + height * Mathf.Abs(Mathf.Sin(num * (float)Math.PI * 3f)) * num2; + if (!target) + { + yield break; + } + + target.localPosition = temp; + yield return null; + } + + if ((bool)target) + { + target.transform.localPosition = origin; + } + } + + public static IEnumerator Shake(Transform target, float duration, float halfWidth, bool taper) + { + _ = target.localPosition; + for (float timer = 0f; timer < duration; timer += Time.deltaTime) + { + float num = timer / duration; + Vector3 vector = (Vector3)UnityEngine.Random.insideUnitCircle * halfWidth; + if (taper) + { + vector *= 1f - num; + } + + target.localPosition += vector; + yield return null; + } + } + + public static IEnumerator SwayX(Transform target, float duration = 0.75f, float halfWidth = 0.25f) + { + if (_activeShakes.Add(target)) + { + Vector3 origin = target.localPosition; + for (float timer = 0f; timer < duration; timer += Time.deltaTime) + { + float num = timer / duration; + target.localPosition = origin + Vector3.right * (halfWidth * Mathf.Sin(num * 30f) * (1f - num)); + yield return null; + } + + target.transform.localPosition = origin; + _activeShakes.Remove(target); + } + } + + public static IEnumerator Bloop(float delay, Transform target, float finalSize = 1f, float duration = 0.5f) + { + for (float t = 0f; t < delay; t += Time.deltaTime) + { + yield return null; + } + + Vector3 localScale = default(Vector3); + for (float t = 0f; t < duration; t += Time.deltaTime) + { + float z = ElasticOut(t, duration) * finalSize; + localScale.x = (localScale.y = (localScale.z = z)); + target.localScale = localScale; + yield return null; + } + + localScale.x = (localScale.y = (localScale.z = finalSize)); + target.localScale = localScale; + } + + public static IEnumerator ArcSlide(float duration, Transform target, Vector2 sourcePos, Vector2 targetPos, float anchorDistance) + { + Vector2 vector = (targetPos - sourcePos) / 2f; + Vector2 anchor = sourcePos + vector + vector.Rotate(90f).normalized * anchorDistance; + float z = target.localPosition.z; + for (float timer = 0f; timer < duration; timer += Time.deltaTime) + { + Vector3 localPosition = Bezier(timer / duration, sourcePos, targetPos, anchor); + localPosition.z = z; + target.localPosition = localPosition; + yield return null; + } + + target.transform.localPosition = targetPos; + } + + public static Vector3 Bezier(float t, Vector3 src, Vector3 dest, Vector3 anchor) + { + t = Mathf.Clamp(t, 0f, 1f); + float num = 1f - t; + return num * num * src + 2f * num * t * anchor + t * t * dest; + } + + public static Vector2 Bezier(float t, Vector2 src, Vector2 dest, Vector2 anchor) + { + t = Mathf.Clamp(t, 0f, 1f); + float num = 1f - t; + return num * num * src + 2f * num * t * anchor + t * t * dest; + } + + private static float ElasticOut(float time, float duration) + { + time /= duration; + float num = time * time; + float num2 = num * time; + return 33f * num2 * num + -106f * num * num + 126f * num2 + -67f * num + 15f * time; + } + + public static float ExpOut(float t) + { + return Mathf.Clamp(1f - Mathf.Pow(2f, -10f * t), 0f, 1f); + } +}