diff --git a/mod/slugbase/jillo.json b/mod/slugbase/jillo.json index 9ae1361..534f683 100644 --- a/mod/slugbase/jillo.json +++ b/mod/slugbase/jillo.json @@ -23,7 +23,7 @@ }, "custom_colors": [ - { "name": "Body", "story": { "r": 0.9764705882, "g": 0.6901960784, "b": 0.5607843137, "a": 0.5 } }, + { "name": "Body", "story": { "r": 0.9764705882, "g": 0.6901960784, "b": 0.5607843137, "a": 0.6 } }, { "name": "Eyes", "story": "ffffff" } ], @@ -37,9 +37,12 @@ "world_state": "Spear", - "jillo/bounce": 0.6, + "jillo/bounce": 0.72, "jillo/immune_to_dart_maggots": true, "jillo/has_mark": true, + "jillo/death_chance_mult": 0.2, + + "jillo/create_slime_mold": true, "jillo/pebbles_lines": [ "A little creature, on the floor of my chamber.", diff --git a/src/JilloSlimeMold.cs b/src/JilloSlimeMold.cs new file mode 100644 index 0000000..ff0bf1e --- /dev/null +++ b/src/JilloSlimeMold.cs @@ -0,0 +1,49 @@ +using System.Runtime.CompilerServices; +using RWCustom; +using UnityEngine; +using static AbstractPhysicalObject; + +namespace JilloSlug; + +// fisobs? never heard of them + +public static class JilloSlimeMold { + private static ConditionalWeakTable> JilloMolds = new ConditionalWeakTable>(); + + public static bool IsJilloSlimeMold(AbstractPhysicalObject mold) { + return JilloMolds.TryGetValue(mold, out var isJillo) && isJillo.Value; + } + public static bool IsJilloSlimeMold(SlimeMold mold) { + return IsJilloSlimeMold(mold.abstractPhysicalObject); + } + + public static void SetJilloSlimeMold(AbstractPhysicalObject mold, bool value = true) { + JilloMolds.Add(mold, new StrongBox(value)); + } + public static void SetJilloSlimeMold(SlimeMold mold, bool value = true) { + SetJilloSlimeMold(mold.abstractPhysicalObject, value); + } + + public static void AddHooks() { + On.SlimeMold.ApplyPalette += SlimeMold_ApplyPalette; + } + + private static void SlimeMold_ApplyPalette(On.SlimeMold.orig_ApplyPalette orig, SlimeMold self, RoomCamera.SpriteLeaser sLeaser, RoomCamera rCam, RoomPalette palette) { + if (!IsJilloSlimeMold(self)) { + orig(self, sLeaser, rCam, palette); + return; + } + self.darkMode = Mathf.InverseLerp(0.3f, 0.9f, palette.darkness); + self.color = SlimeMoldColorFromPalette(palette); + } + + private static Color SlimeMoldColorFromPalette(RoomPalette palette) { + Color col = Color.Lerp( + Custom.HSL2RGB(Mathf.Lerp(0.05f, 0.045f, palette.darkness), 0.59f, 0.63f), + palette.fogColor, + Mathf.Lerp(0.15f, 0.25f, palette.fogAmount) * Mathf.Lerp(0.1f, 0.5f, palette.darkness) + ); + col.a = 0.7f; + return col; + } +} diff --git a/src/Plugin.cs b/src/Plugin.cs index a3a1d6e..403fba1 100644 --- a/src/Plugin.cs +++ b/src/Plugin.cs @@ -20,8 +20,12 @@ class Plugin : BaseUnityPlugin { BounceFeature.AddHooks(); ImmuneToDartMaggotsFeature.AddHooks(); MarkFeature.AddHooks(); + CreateSlimeMoldFeature.AddHooks(); + TweakDeathChanceFeature.AddHooks(); Iterators.AddHooks(); + + JilloSlimeMold.AddHooks(); } catch (Exception err) { Logger.LogError($"error initializing: {err}"); } diff --git a/src/features/CreateSlimeMoldFeature.cs b/src/features/CreateSlimeMoldFeature.cs new file mode 100644 index 0000000..3d511cf --- /dev/null +++ b/src/features/CreateSlimeMoldFeature.cs @@ -0,0 +1,110 @@ +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); + } +} diff --git a/src/features/TweakDeathChanceFeature.cs b/src/features/TweakDeathChanceFeature.cs new file mode 100644 index 0000000..cf35ab3 --- /dev/null +++ b/src/features/TweakDeathChanceFeature.cs @@ -0,0 +1,20 @@ +using SlugBase.Features; +using static SlugBase.Features.FeatureTypes; +using SlugBase; + +namespace JilloSlug.Features; + +internal static class TweakDeathChanceFeature { + public static readonly PlayerFeature DeathChance = PlayerFloat("jillo/death_chance_mult"); + + public static void AddHooks() { + On.Player.DeathByBiteMultiplier += Player_DeathByBiteMultiplier; + } + + private static float Player_DeathByBiteMultiplier(On.Player.orig_DeathByBiteMultiplier orig, Player self) { + if (DeathChance.TryGet(self, out float mult)) { + return mult; + } + return orig(self); + } +} diff --git a/todo.txt b/todo.txt index 84197a4..0e0dabb 100644 --- a/todo.txt +++ b/todo.txt @@ -3,7 +3,8 @@ - remove ears - potentially more - gameplay - - hold eat to remove food pips for offensive slime mold + x hold eat to remove food pips for offensive slime mold + - custom type - could maybe slow down enemies similar to spit - still edible - sleeping transforms all food pips into slime mold (maybe)