JilloSlug/src/features/BounceFeature.cs

134 lines
4.4 KiB
C#

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<float> 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<Func<Player, IntVector2, float, bool>>((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<IntVector2>("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<Creature>("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<IntVector2>("y"),
i => i.Match(OpCodes.Ldc_I4_0),
i => i.Match(OpCodes.Bge_S),
i => i.MatchLdarg(0),
i => i.MatchCallOrCallvirt<Creature>("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<UpdatableAndDeletable>("room"),
i => i.MatchLdsfld<SoundID>("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<UpdatableAndDeletable>("room"),
i => i.MatchLdsfld<SoundID>("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<UpdatableAndDeletable>("room"),
i => i.MatchLdsfld<SoundID>("Slugcat_Terrain_Impact_Hard")
);
ILInsertHandleBounce(c, skipLabel);
}
}