using System; using Mono.Cecil.Cil; using MonoMod.Cil; using SlugBase.Features; using static SlugBase.Features.FeatureTypes; namespace JilloSlug.Features; internal static class CreateSlimeMoldFeature { public static readonly PlayerFeature CreateSlimeMold = PlayerBool("jillo/create_slime_mold"); public static void AddHooks() { On.Player.Regurgitate += Player_Regurgitate; IL.Player.GrabUpdate += Player_GrabUpdate; IL.PlayerGraphics.Update += PlayerGraphics_Update; } private static void Player_Regurgitate(On.Player.orig_Regurgitate orig, Player self) { if (self.objectInStomach == null && CreateSlimeMold.TryGet(self, out var createSlimeMold) && createSlimeMold) { self.objectInStomach = new AbstractConsumable(self.room.world, AbstractPhysicalObject.AbstractObjectType.SlimeMold, null, self.room.GetWorldCoordinate(self.firstChunk.pos), self.room.game.GetNewID(), -1, -1, null); JilloSlimeMold.SetJilloSlimeMold(self.objectInStomach); } orig(self); } private static void Player_GrabUpdate(ILContext il) { ILCursor c = new ILCursor(il); // replace all mentions of `isGourmand` with the equivalent of `(isGourmand || isJillo)` // match `isGourmand`, brfalse edition while ( c.TryGotoNext(MoveType.After, i => i.MatchLdarg(0), i => i.MatchCallOrCallvirt("get_isGourmand"), i => i.Match(OpCodes.Brfalse_S) || i.Match(OpCodes.Brfalse) ) ) { // this is the condition we should skip to if our check succeeds, replicating the behavior if the vanilla check was to succeed ILLabel skipGourmandCond = c.MarkLabel(); c.GotoPrev(MoveType.Before, i => i.MatchLdarg(0), i => i.Match(OpCodes.Call) || i.Match(OpCodes.Callvirt)); // insert the condition c.Emit(OpCodes.Ldarg_0); c.EmitDelegate>(player => { return CreateSlimeMold.TryGet(player, out var shouldCreateSlimeMold) && shouldCreateSlimeMold; }); // if it's true, skip ahead c.Emit(OpCodes.Brtrue_S, skipGourmandCond); // move forwards to avoid an infloop c.GotoNext(MoveType.After, i => i.Match(OpCodes.Brfalse_S) || i.Match(OpCodes.Brfalse)); } c.Index = 0; // match `isGourmand`, brtrue edition while ( c.TryGotoNext(MoveType.After, i => i.MatchLdarg(0), i => i.MatchCallOrCallvirt("get_isGourmand"), i => i.Match(OpCodes.Brtrue_S) || i.Match(OpCodes.Brtrue) ) ) { // a lot easier here, since you can just insert another cond ILLabel proceedCond = c.Prev.Operand as ILLabel; // insert the condition c.Emit(OpCodes.Ldarg_0); c.EmitDelegate>(player => { return CreateSlimeMold.TryGet(player, out var shouldCreateSlimeMold) && shouldCreateSlimeMold; }); // if it's true, proceed as usual c.Emit(OpCodes.Brtrue_S, proceedCond); } } private static void PlayerGraphics_Update(ILContext il) { ILCursor c = new ILCursor(il); // match for `player.objectInStomach != null` c.GotoNext(MoveType.After, i => i.MatchLdarg(0), i => i.MatchLdfld("player"), i => i.MatchLdfld("objectInStomach"), i => i.Match(OpCodes.Brtrue_S) ); // match for `player.SlugCatClass == MoreSlugcatsEnums.SlugcatStatsName.Gourmand ...` c.GotoNext(MoveType.After, i => i.MatchLdarg(0), i => i.MatchLdfld("player"), i => i.MatchLdfld("SlugCatClass"), i => i.MatchLdsfld("Gourmand"), i => i.Match(OpCodes.Call), // this is a mess of generics; not matching this, but it's the equation call i => i.Match(OpCodes.Brtrue_S) ); ILLabel proceedCond = c.Prev.Operand as ILLabel; // insert our condition c.Emit(OpCodes.Ldarg_0); c.EmitDelegate>(playerGraphics => { return CreateSlimeMold.TryGet(playerGraphics.player, out var shouldCreateSlimeMold) && shouldCreateSlimeMold; }); // if it's true, proceed as usual c.Emit(OpCodes.Brtrue_S, proceedCond); } }