using System; using UnityEngine; using SlugBase.Features; using static SlugBase.Features.FeatureTypes; using MonoMod.Cil; using Mono.Cecil.Cil; using RWCustom; namespace JilloSlug.Features; internal static class BounceFeature { // 0 = no bounce // 1 = preserve all velocity upon bouncing public static readonly PlayerFeature Bounce = PlayerFloat("jillo/bounce"); public static void AddHooks() { IL.Player.TerrainImpact += Player_TerrainImpact; } private static bool PlayerHandleBounce(Player player, IntVector2 direction, float speed) { float bounce; Bounce.TryGet(player, out bounce); bool shouldBounce = !player.dead && bounce >= 0; if (!shouldBounce) return true; if (speed > 0.5f) { player.room.PlaySound(SoundID.Slime_Mold_Terrain_Impact, player.mainBodyChunk, false, 0.7f + Mathf.Clamp(speed / 60f, 0f, 1f), 1f); } if (speed > 8f) { // handle bouncing foreach (var chunk in player.bodyChunks) { chunk.vel.x = Mathf.Lerp(chunk.vel.x, -chunk.vel.x * bounce, Mathf.Abs((float) direction.x)); chunk.vel.y = Mathf.Lerp(chunk.vel.y, -chunk.vel.y * bounce, Mathf.Abs((float) direction.y)); } } return false; // skip everything } private static void ILInsertHandleBounce(ILCursor c, ILLabel skipLabel) { c.Emit(OpCodes.Ldarg_0); c.Emit(OpCodes.Ldarg_2); // direction c.Emit(OpCodes.Ldarg_3); // speed c.EmitDelegate>((player, direction, speed) => { return PlayerHandleBounce(player, direction, speed); }); c.Emit(OpCodes.Brfalse, skipLabel); } private static void Player_TerrainImpact(ILContext il) { ILCursor c = new ILCursor(il); // patch high speed impact (death condition) // matching for `speed > num && direction.y < 0` c.GotoNext(MoveType.After, // speed > num i => i.MatchLdarg(3), i => i.MatchLdloc(0), i => i.Match(OpCodes.Ble_Un_S), // direction.y < 0 i => i.MatchLdarg(2), i => i.MatchLdfld("y"), i => i.Match(OpCodes.Ldc_I4_0), i => i.Match(OpCodes.Bge_S) ); // retrieve the label the end of the if would skip to // (illegal bytecode crimes) ILCursor endC = c.Clone(); endC.GotoNext(MoveType.After, i => i.MatchCallOrCallvirt("Die")); ILLabel skipLabel = endC.Next.Operand as ILLabel; ILInsertHandleBounce(c, skipLabel); // patch mid speed impact (stun condition) // matching for `speed > num2` c.GotoNext(MoveType.After, // speed > num2 i => i.MatchLdarg(3), i => i.MatchLdloc(1), i => i.Match(OpCodes.Ble_Un) ); ILInsertHandleBounce(c, skipLabel); // patch low speed impacts // matching for `direction.y < 0 && base.Consious` c.GotoNext(MoveType.After, i => i.MatchLdarg(2), i => i.MatchLdfld("y"), i => i.Match(OpCodes.Ldc_I4_0), i => i.Match(OpCodes.Bge_S), i => i.MatchLdarg(0), i => i.MatchCallOrCallvirt("get_Consious"), i => i.Match(OpCodes.Brfalse_S) ); ILInsertHandleBounce(c, skipLabel); // matching for `this.room.PlaySound(SoundID.Slugcat_Terrain_Impact_Light, ...);` c.GotoNext(MoveType.Before, i => i.MatchLdarg(0), i => i.MatchLdfld("room"), i => i.MatchLdsfld("Slugcat_Terrain_Impact_Light") ); ILInsertHandleBounce(c, skipLabel); // matching for `this.room.PlaySound(SoundID.Slugcat_Terrain_Impact_Medium, ...);` c.GotoNext(MoveType.Before, i => i.MatchLdarg(0), i => i.MatchLdfld("room"), i => i.MatchLdsfld("Slugcat_Terrain_Impact_Medium") ); ILInsertHandleBounce(c, skipLabel); // matching for `this.room.PlaySound(SoundID.Slugcat_Terrain_Impact_Hard, ...;` c.GotoNext(MoveType.Before, i => i.MatchLdarg(0), i => i.MatchLdfld("room"), i => i.MatchLdsfld("Slugcat_Terrain_Impact_Hard") ); ILInsertHandleBounce(c, skipLabel); } }