Compare commits
4 Commits
5f63d0bb46
...
842a6e0d76
Author | SHA1 | Date |
---|---|---|
Jill | 842a6e0d76 | |
Jill | 97229ab6b7 | |
Jill | 50d7e8eece | |
Jill | 3a8694ab6f |
|
@ -5,7 +5,8 @@
|
|||
"features": {
|
||||
"color": "f9b08f",
|
||||
|
||||
"weight": 0.86,
|
||||
"weight": 0.82,
|
||||
"lung_capacity": 1.2,
|
||||
"loudness": 1.4,
|
||||
|
||||
"alignments": {
|
||||
|
@ -22,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" }
|
||||
],
|
||||
|
||||
|
@ -36,24 +37,37 @@
|
|||
|
||||
"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": [
|
||||
"!wait 10",
|
||||
"A little creature, on the floor of my chamber.",
|
||||
"!wait 8",
|
||||
"You have an... interesting appearance. A translucent purposed organism is not something I recognize; and with the mark no less.",
|
||||
"You don't appear to be a messenger, and I can't give you anything you don't already have. What is it that you want? What is your purpose?",
|
||||
"The little beasts that tend to visit my chambers seem to seek a way out of the cycle, but I'm hesitant to give it to you.<LINE>I don't want to meddle with other iterators' endeavours more than I already have.",
|
||||
"...",
|
||||
"!wait 8",
|
||||
"I shouldn't be spending my time thinking about this. Go. I have more important work to attend to. Maybe you'll be more of use to another iterator,<LINE>but you have no purpose here.",
|
||||
"!wait 10",
|
||||
"!wait 12",
|
||||
"I shouldn't be spending my time thinking about this. Go. I have more important work to attend to. Maybe you'll be more of use to another iterator,<LINE>but you have no purpose here."
|
||||
],
|
||||
"jillo/pebbles_lines_2": [
|
||||
"..although perhaps it was foolish of me to mention escaping the cycle. If you don't wish to serve your purpose,<LINE>there is another way for you.",
|
||||
"The old path. Go to the west past the Farm Arrays, and then down into the earth where the land fissures, as deep as you can reach,<LINE>where the ancients built their temples and danced their silly rituals.",
|
||||
"Now leave, creature. You have no reason to be here anymore, and I'm not willing to<LINE>entertain your existance anymore."
|
||||
],
|
||||
|
||||
"jillo/moon_lines": [
|
||||
"Hello, <PlayerName>. Just what are you exactly? I don't recognize your blueprints, yet you've been given the mark by an iterator..<LINE>What brings you here? You clearly have a purpose, I'm just not sure what it is...",
|
||||
"How come you're so jelly? Is this an adaptation for your goal, or a side effect of your creation's circumstances?",
|
||||
"!wait 10",
|
||||
"...",
|
||||
"I'll stop asking so many questions you won't be able to answer, <PlayerName>. You probably don't know your purpose either. Poor thing...",
|
||||
"!wait 20",
|
||||
"You're welcome to stay for as long as you need, but I'm not sure what I can offer you besides company."
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Mono.Cecil.Cil;
|
||||
using MonoMod.Cil;
|
||||
using MoreSlugcats;
|
||||
using RWCustom;
|
||||
using UnityEngine;
|
||||
using static AbstractPhysicalObject;
|
||||
|
||||
namespace JilloSlug;
|
||||
|
||||
// fisobs? never heard of them
|
||||
|
||||
public static class JilloSlimeMold {
|
||||
private static ConditionalWeakTable<AbstractPhysicalObject, StrongBox<bool>> JilloMolds = new ConditionalWeakTable<AbstractPhysicalObject, StrongBox<bool>>();
|
||||
|
||||
public static bool IsJilloSlimeMold(AbstractPhysicalObject mold) {
|
||||
return JilloMolds.TryGetValue(mold, out var isJillo) && isJillo.Value;
|
||||
}
|
||||
public static bool IsJilloSlimeMold(PhysicalObject mold) {
|
||||
return IsJilloSlimeMold(mold.abstractPhysicalObject);
|
||||
}
|
||||
|
||||
public static void SetJilloSlimeMold(AbstractPhysicalObject mold, bool value = true) {
|
||||
JilloMolds.Add(mold, new StrongBox<bool>(value));
|
||||
}
|
||||
public static void SetJilloSlimeMold(PhysicalObject mold, bool value = true) {
|
||||
SetJilloSlimeMold(mold.abstractPhysicalObject, value);
|
||||
}
|
||||
|
||||
public static void AddHooks() {
|
||||
On.SlimeMold.ApplyPalette += SlimeMold_ApplyPalette;
|
||||
On.Player.TossObject += Player_TossObject;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private static void Player_TossObject(On.Player.orig_TossObject orig, Player self, int grasp, bool eu) {
|
||||
if (self.grasps[grasp] != null && IsJilloSlimeMold(self.grasps[grasp].grabbed)) {
|
||||
PhysicalObject grabbed = self.grasps[grasp].grabbed;
|
||||
|
||||
IntVector2 throwDir = new IntVector2(self.ThrowDirection, 0);
|
||||
bool throwVertical = self.input[0].y < 0;
|
||||
if (ModManager.MMF && MMF.cfgUpwardsSpearThrow.Value) {
|
||||
throwVertical = self.input[0].y != 0;
|
||||
}
|
||||
if (self.animation == Player.AnimationIndex.Flip && throwVertical && self.input[0].x == 0) {
|
||||
throwDir = new IntVector2(0, (ModManager.MMF && MMF.cfgUpwardsSpearThrow.Value) ? self.input[0].y : (-1));
|
||||
}
|
||||
if (ModManager.MMF && self.bodyMode == Player.BodyModeIndex.ZeroG && MMF.cfgUpwardsSpearThrow.Value) {
|
||||
int y = self.input[0].y;
|
||||
throwDir = ((y == 0) ? new IntVector2(self.ThrowDirection, 0) : new IntVector2(0, y));
|
||||
}
|
||||
Vector2 thrownPos = self.firstChunk.pos + throwDir.ToVector2() * 10f + new Vector2(0f, 4f);
|
||||
if (self.room.GetTile(thrownPos).Solid) {
|
||||
thrownPos = self.mainBodyChunk.pos;
|
||||
}
|
||||
|
||||
self.room.PlaySound(SoundID.Slugcat_Throw_Misc_Inanimate, self.grasps[grasp].grabbedChunk, loop: false, 1f, 1f);
|
||||
//(grabbed as Weapon).Thrown(self, thrownPos, self.mainBodyChunk.pos - throwDir.ToVector2() * 10f, throwDir, Mathf.Lerp(1f, 1.5f, self.Adrenaline), eu);
|
||||
|
||||
for (int l = 0; l < grabbed.bodyChunks.Length; l++) {
|
||||
grabbed.bodyChunks[l].vel = Vector2.Lerp(grabbed.bodyChunks[l].vel * 0.35f, self.mainBodyChunk.vel, Custom.LerpMap(grabbed.TotalMass, 0.2f, 0.5f, 0.6f, 0.3f));
|
||||
grabbed.bodyChunks[l].vel += throwDir.ToVector2() * 40f * Mathf.Lerp(1f, 1.5f, self.Adrenaline);
|
||||
if (throwDir.x != 0) {
|
||||
self.firstChunk.vel.y += 1.5f;
|
||||
}
|
||||
}
|
||||
|
||||
if (self.animation == Player.AnimationIndex.ClimbOnBeam && ModManager.MMF && MMF.cfgClimbingGrip.Value) {
|
||||
self.bodyChunks[0].vel += throwDir.ToVector2() * 2f;
|
||||
self.bodyChunks[1].vel -= throwDir.ToVector2() * 8f;
|
||||
} else {
|
||||
self.bodyChunks[0].vel += throwDir.ToVector2() * 8f;
|
||||
self.bodyChunks[1].vel -= throwDir.ToVector2() * 4f;
|
||||
}
|
||||
|
||||
if (self.graphicsModule != null) (self.graphicsModule as PlayerGraphics).ThrowObject(grasp, grabbed);
|
||||
|
||||
self.Blink(15);
|
||||
|
||||
self.dontGrabStuff = (self.isNPC ? 45 : 15);
|
||||
|
||||
if (self.graphicsModule != null) (self.graphicsModule as PlayerGraphics).LookAtObject(grabbed);
|
||||
|
||||
if (grabbed is PlayerCarryableItem) {
|
||||
(grabbed as PlayerCarryableItem).Forbid();
|
||||
}
|
||||
|
||||
self.ReleaseGrasp(grasp);
|
||||
} else {
|
||||
orig(self, grasp, eu);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,7 +11,6 @@ class Plugin : BaseUnityPlugin {
|
|||
private const string MOD_ID = "zone.oat.jilloslug";
|
||||
internal static ManualLogSource Log;
|
||||
|
||||
// Add hooks
|
||||
public void OnEnable() {
|
||||
Plugin.Log = base.Logger;
|
||||
|
||||
|
@ -21,8 +20,12 @@ class Plugin : BaseUnityPlugin {
|
|||
BounceFeature.AddHooks();
|
||||
ImmuneToDartMaggotsFeature.AddHooks();
|
||||
MarkFeature.AddHooks();
|
||||
CreateSlimeMoldFeature.AddHooks();
|
||||
TweakDeathChanceFeature.AddHooks();
|
||||
|
||||
Pebbles.AddHooks();
|
||||
Iterators.AddHooks();
|
||||
|
||||
JilloSlimeMold.AddHooks();
|
||||
} catch (Exception err) {
|
||||
Logger.LogError($"error initializing: {err}");
|
||||
}
|
||||
|
|
|
@ -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<bool> 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<Player>("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<Func<Player, bool>>(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<Player>("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<Func<Player, bool>>(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<PlayerGraphics>("player"),
|
||||
i => i.MatchLdfld<Player>("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<PlayerGraphics>("player"),
|
||||
i => i.MatchLdfld<Player>("SlugCatClass"),
|
||||
i => i.MatchLdsfld<MoreSlugcats.MoreSlugcatsEnums.SlugcatStatsName>("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<Func<PlayerGraphics, bool>>(playerGraphics => {
|
||||
return CreateSlimeMold.TryGet(playerGraphics.player, out var shouldCreateSlimeMold) && shouldCreateSlimeMold;
|
||||
});
|
||||
// if it's true, proceed as usual
|
||||
c.Emit(OpCodes.Brtrue_S, proceedCond);
|
||||
}
|
||||
}
|
|
@ -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<float> 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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,327 @@
|
|||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using Mono.Cecil.Cil;
|
||||
using MonoMod.Cil;
|
||||
using MoreSlugcats;
|
||||
using SlugBase;
|
||||
using SlugBase.Features;
|
||||
using static SlugBase.Features.FeatureTypes;
|
||||
using static SSOracleBehavior;
|
||||
using SSAction = SSOracleBehavior.Action;
|
||||
using SubBehavID = SSOracleBehavior.SubBehavior.SubBehavID;
|
||||
|
||||
namespace JilloSlug.Story;
|
||||
|
||||
internal static class Iterators {
|
||||
public static readonly PlayerFeature<string[]> PebblesLines = PlayerStrings("jillo/pebbles_lines");
|
||||
public static readonly PlayerFeature<string[]> PebblesLines2 = PlayerStrings("jillo/pebbles_lines_2");
|
||||
public static readonly PlayerFeature<string[]> MoonLines = PlayerStrings("jillo/moon_lines");
|
||||
|
||||
public static readonly SSAction MeetJilloSS_Init = new SSAction("MeetJilloSS_Init", register: true);
|
||||
public static readonly SSAction MeetJilloSS_Fakeout = new SSAction("MeetJilloSS_Fakeout", register: true);
|
||||
public static readonly SSAction MeetJilloSS_End = new SSAction("MeetJilloSS_End", register: true);
|
||||
public static readonly SSAction MeetJilloDM_Init = new SSAction("MeetJilloDM_Init", register: true);
|
||||
public static readonly SSAction MeetJilloDM_Done = new SSAction("MeetJilloDM_Done", register: true);
|
||||
|
||||
public static readonly SubBehavID MeetJilloSS = new SubBehavID("MeetJilloSS", register: true);
|
||||
public static readonly SubBehavID MeetJilloDM = new SubBehavID("MeetJilloDM", register: true);
|
||||
|
||||
public static readonly Conversation.ID Pebbles_Jillo = new Conversation.ID("Pebbles_Jillo", register: true);
|
||||
public static readonly Conversation.ID Pebbles_Jillo_End = new Conversation.ID("Pebbles_Jillo_End", register: true);
|
||||
public static readonly Conversation.ID Moon_Jillo = new Conversation.ID("Moon_Jillo", register: true);
|
||||
|
||||
public class SSOracleMeetJillo : ConversationBehavior {
|
||||
public override bool Gravity {
|
||||
get {
|
||||
return base.action != MeetJilloSS_Fakeout;
|
||||
}
|
||||
}
|
||||
|
||||
public SSOracleMeetJillo(SSOracleBehavior owner) : base(owner, MeetJilloSS, Pebbles_Jillo) {
|
||||
owner.getToWorking = 0f;
|
||||
if (ModManager.MMF && owner.oracle.room.game.IsStorySession && owner.oracle.room.game.GetStorySession.saveState.miscWorldSaveData.memoryArraysFrolicked && base.oracle.room.world.rainCycle.timer > base.oracle.room.world.rainCycle.cycleLength / 4) {
|
||||
base.oracle.room.world.rainCycle.timer = base.oracle.room.world.rainCycle.cycleLength / 4;
|
||||
base.oracle.room.world.rainCycle.dayNightCounter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update() {
|
||||
base.Update();
|
||||
owner.LockShortcuts();
|
||||
if (base.action == MeetJilloSS_Init) {
|
||||
base.movementBehavior = MovementBehavior.KeepDistance;
|
||||
if (base.inActionCounter > 180) {
|
||||
owner.NewAction(SSAction.General_MarkTalk);
|
||||
}
|
||||
} else if (base.action == SSAction.General_MarkTalk) {
|
||||
base.movementBehavior = MovementBehavior.Talk;
|
||||
if (base.inActionCounter == 15 && (owner.conversation == null || owner.conversation.id != convoID)) {
|
||||
owner.InitateConversation(convoID, this);
|
||||
}
|
||||
if (owner.conversation != null && owner.conversation.id == convoID && owner.conversation.slatedForDeletion) {
|
||||
owner.conversation = null;
|
||||
owner.NewAction(MeetJilloSS_Fakeout);
|
||||
}
|
||||
} else if (base.action == MeetJilloSS_Fakeout) {
|
||||
base.movementBehavior = MovementBehavior.KeepDistance;
|
||||
if (base.inActionCounter > 240) {
|
||||
owner.NewAction(MeetJilloSS_End);
|
||||
}
|
||||
} else if (base.action == MeetJilloSS_End) {
|
||||
base.movementBehavior = MovementBehavior.Talk;
|
||||
if (base.inActionCounter == 80 && (owner.conversation == null || owner.conversation.id != Pebbles_Jillo_End)) {
|
||||
owner.InitateConversation(Pebbles_Jillo_End, this);
|
||||
}
|
||||
if (owner.conversation != null && owner.conversation.id == Pebbles_Jillo_End && owner.conversation.slatedForDeletion) {
|
||||
owner.conversation = null;
|
||||
owner.NewAction(SSAction.ThrowOut_ThrowOut);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DMOracleMeetJillo : ConversationBehavior {
|
||||
public DMOracleMeetJillo(SSOracleBehavior owner) : base(owner, MeetJilloDM, Moon_Jillo) {
|
||||
base.owner.TurnOffSSMusic(abruptEnd: true);
|
||||
owner.getToWorking = 0f;
|
||||
if (base.owner.conversation != null) {
|
||||
base.owner.conversation.Destroy();
|
||||
base.owner.conversation = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update() {
|
||||
base.Update();
|
||||
if (base.action == MeetJilloDM_Init) {
|
||||
owner.LockShortcuts();
|
||||
base.movementBehavior = MovementBehavior.Investigate;
|
||||
if (base.inActionCounter > 200) {
|
||||
owner.NewAction(SSAction.General_MarkTalk);
|
||||
}
|
||||
} else if (base.action == SSAction.General_MarkTalk) {
|
||||
base.movementBehavior = MovementBehavior.Talk;
|
||||
if (base.inActionCounter == 15 && (owner.conversation == null || owner.conversation.id != convoID)) {
|
||||
owner.InitateConversation(convoID, this);
|
||||
}
|
||||
if (owner.conversation != null && owner.conversation.id == convoID && owner.conversation.slatedForDeletion) {
|
||||
owner.conversation = null;
|
||||
owner.NewAction(MeetJilloDM_Done);
|
||||
}
|
||||
} else if (base.action == MeetJilloDM_Done) {
|
||||
owner.getToWorking = 1f;
|
||||
base.movementBehavior = MovementBehavior.Meditate;
|
||||
owner.UnlockShortcuts();
|
||||
|
||||
// TODO: why will moon not read pearls?
|
||||
|
||||
// we need Some savedata to use to indicate this, and this works Well Enough
|
||||
(owner.oracle.room.world.game.session as StoryGameSession).saveState.miscWorldSaveData.smPearlTagged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddHooks() {
|
||||
// patch to load the correct dialog w/ the Conversation.IDs
|
||||
On.SSOracleBehavior.PebblesConversation.AddEvents += PebblesConversation_AddEvents;
|
||||
// patch to enter the Action as appropriate
|
||||
IL.SSOracleBehavior.SeePlayer += SSOracleBehavior_SeePlayer;
|
||||
// patch the action -> subbehavior conversion
|
||||
IL.SSOracleBehavior.NewAction += SSOracleBehavior_NewAction;
|
||||
}
|
||||
|
||||
// highly references SLOracleBehaviorHasMark.NameForPlayer
|
||||
private static string NameForPlayer(SSOracleBehavior self, bool capitalized = false) {
|
||||
string titleRaw = "creature";
|
||||
if (UnityEngine.Random.value < 0.4f) {
|
||||
titleRaw = "friend";
|
||||
}
|
||||
if (self.rainWorld.inGameTranslator.currentLanguage == InGameTranslator.LanguageID.Portuguese) {
|
||||
string title = self.Translate(titleRaw);
|
||||
if (capitalized && InGameTranslator.LanguageID.UsesCapitals(self.rainWorld.inGameTranslator.currentLanguage)) {
|
||||
title = char.ToUpper(title[0]) + title.Substring(1);
|
||||
}
|
||||
return title;
|
||||
} else {
|
||||
string title = self.Translate(titleRaw);
|
||||
string prefix = self.Translate("little");
|
||||
if (UnityEngine.Random.value < 0.3f) {
|
||||
prefix = self.Translate("gelatinous");
|
||||
}
|
||||
if (capitalized && InGameTranslator.LanguageID.UsesCapitals(self.rainWorld.inGameTranslator.currentLanguage))
|
||||
{
|
||||
prefix = char.ToUpper(prefix[0]) + prefix.Substring(1);
|
||||
}
|
||||
return prefix + " " + title;
|
||||
}
|
||||
}
|
||||
|
||||
// = SLOracleBehaviorHasMark.ReplaceParts
|
||||
public static string ReplaceParts(SSOracleBehavior self, string s) {
|
||||
s = Regex.Replace(s, "<PLAYERNAME>", NameForPlayer(self, capitalized: false));
|
||||
s = Regex.Replace(s, "<CAPPLAYERNAME>", NameForPlayer(self, capitalized: true));
|
||||
s = Regex.Replace(s, "<PlayerName>", NameForPlayer(self, capitalized: false));
|
||||
s = Regex.Replace(s, "<CapPlayerName>", NameForPlayer(self, capitalized: true));
|
||||
return s;
|
||||
}
|
||||
|
||||
private static void AddLines(SSOracleBehavior.PebblesConversation self, string[] lines) {
|
||||
foreach (string line in lines) {
|
||||
if (line.StartsWith("!wait ")) {
|
||||
int wait = int.Parse(line.Split(' ')[1]);
|
||||
self.events.Add(new SSOracleBehavior.PebblesConversation.PauseAndWaitForStillEvent(self, self.convBehav, wait));
|
||||
} else {
|
||||
self.events.Add(new Conversation.TextEvent(self, 0, ReplaceParts(self.owner, self.Translate(line)), 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void PebblesConversation_AddEvents(On.SSOracleBehavior.PebblesConversation.orig_AddEvents orig, SSOracleBehavior.PebblesConversation self) {
|
||||
if (self.id == Pebbles_Jillo && PebblesLines.TryGet(self.convBehav.player, out var pebsLines)) {
|
||||
AddLines(self, pebsLines);
|
||||
} else if (self.id == Pebbles_Jillo_End && PebblesLines2.TryGet(self.convBehav.player, out var pebsLines2)) {
|
||||
AddLines(self, pebsLines2);
|
||||
} else if (self.id == Moon_Jillo && MoonLines.TryGet(self.convBehav.player, out var moonLines)) {
|
||||
AddLines(self, moonLines);
|
||||
} else {
|
||||
orig(self);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SSOracleBehavior_SeePlayer(ILContext il) {
|
||||
ILCursor c = new ILCursor(il);
|
||||
|
||||
// matching for `oracle.ID == MoreSlugcatsEnums.OracleID.DM`
|
||||
c.GotoNext(MoveType.After,
|
||||
i => i.MatchLdarg(0),
|
||||
i => i.MatchLdfld<OracleBehavior>("oracle"),
|
||||
i => i.MatchLdfld<Oracle>("ID"),
|
||||
i => i.MatchLdsfld<MoreSlugcatsEnums.OracleID>("DM"),
|
||||
i => i.Match(OpCodes.Call), // this is a mess of generics; not matching this, but it's the equation call
|
||||
i => i.Match(OpCodes.Brfalse),
|
||||
|
||||
i => i.MatchLdarg(0) // match the next call's ldarg to not remove the label by accident - this is messy!
|
||||
);
|
||||
|
||||
// stuff inside this if will be for checking for different characters; let's override this
|
||||
// first let's grab the label to skip to once we're done
|
||||
|
||||
ILCursor skipC = c.Clone();
|
||||
|
||||
// matching for `NewAction(Action.MeetRed_Init);`
|
||||
skipC.GotoNext(MoveType.After,
|
||||
i => i.MatchLdarg(0),
|
||||
i => i.MatchLdsfld<SSAction>("MeetRed_Init"),
|
||||
i => i.MatchCall<SSOracleBehavior>("NewAction")
|
||||
);
|
||||
// the if is exited right after this statement
|
||||
skipC.GotoNext(MoveType.Before, i => i.Match(OpCodes.Br));
|
||||
// capture the label it goes to
|
||||
ILLabel skipLabel = skipC.Next.Operand as ILLabel;
|
||||
|
||||
// now that we're done, we can do the usual delegate injection
|
||||
// note the ldarg captured during our first match - we don't need to put one down now
|
||||
|
||||
//c.Emit(OpCodes.Ldarg_0); // implicit
|
||||
c.EmitDelegate<Func<SSOracleBehavior, bool>>(self => {
|
||||
// we get thrown in here if:
|
||||
// pebbles: first time meeting
|
||||
// past the first meeting, you'll get the usual throw out behavior. this is Fine
|
||||
// moon: all times
|
||||
// this is fine too; we want to tweak all idle lines of dialog
|
||||
|
||||
if (self.oracle.ID != MoreSlugcatsEnums.OracleID.DM) {
|
||||
// pebbles
|
||||
if (PebblesLines.TryGet(self.player, out var lines)) {
|
||||
self.NewAction(MeetJilloSS_Init);
|
||||
return false; // skip
|
||||
}
|
||||
} else {
|
||||
// moon
|
||||
if (MoonLines.TryGet(self.player, out var lines)) {
|
||||
if ((self.oracle.room.world.game.session as StoryGameSession).saveState.miscWorldSaveData.smPearlTagged) {
|
||||
self.NewAction(MeetJilloDM_Done);
|
||||
} else {
|
||||
self.NewAction(MeetJilloDM_Init);
|
||||
}
|
||||
return false; // skip
|
||||
}
|
||||
}
|
||||
|
||||
return true; // proceed as usual
|
||||
});
|
||||
c.Emit(OpCodes.Brfalse, skipLabel);
|
||||
|
||||
// clean up after ourselves to account for the ldarg we captured
|
||||
c.Emit(OpCodes.Ldarg_0);
|
||||
}
|
||||
|
||||
private static void SSOracleBehavior_NewAction(ILContext il) {
|
||||
ILCursor c = new ILCursor(il);
|
||||
|
||||
// match `currSubBehavior.NewAction(action, nextAction);`; this is the first line after the subbehavior decision
|
||||
c.GotoNext(MoveType.Before,
|
||||
i => i.MatchLdarg(0),
|
||||
i => i.MatchLdfld<SSOracleBehavior>("currSubBehavior"),
|
||||
i => i.MatchLdarg(0),
|
||||
i => i.MatchLdfld<SSOracleBehavior>("action"),
|
||||
i => i.MatchLdarg(1),
|
||||
i => i.MatchCallOrCallvirt<SSOracleBehavior.SubBehavior>("NewAction")
|
||||
);
|
||||
|
||||
// we want to now set the subbehavior variable to whatever corresponds with the action
|
||||
|
||||
c.Emit(OpCodes.Ldarg_1); // `nextAction`
|
||||
c.Emit(OpCodes.Ldloc_0); // the subbehavior variable
|
||||
c.EmitDelegate<Func<SSAction, SubBehavID, SubBehavID>>((action, id) => {
|
||||
if (action == MeetJilloSS_Init || action == MeetJilloSS_Fakeout || action == MeetJilloSS_End) {
|
||||
return MeetJilloSS;
|
||||
} else if (action == MeetJilloDM_Init || action == MeetJilloDM_Done) {
|
||||
return MeetJilloDM;
|
||||
} else {
|
||||
return id;
|
||||
}
|
||||
});
|
||||
// the returned value is the new id; let's set the variable to it
|
||||
c.Emit(OpCodes.Stloc_0);
|
||||
|
||||
// unrelatedly, we also need to patch setting the subbehavior field based on the id!
|
||||
// match `subBehavior = new SSOracleMeetWhite(this);`
|
||||
c.GotoNext(MoveType.After,
|
||||
i => i.MatchLdarg(0),
|
||||
i => i.MatchNewobj<SSOracleBehavior.SSOracleMeetWhite>(),
|
||||
i => i.MatchStloc(1),
|
||||
i => i.Match(OpCodes.Br)
|
||||
);
|
||||
|
||||
// capture the label it goes to once it's done
|
||||
ILLabel skipLabel = c.Prev.Operand as ILLabel;
|
||||
// capture an ldloc.0 to preserve labels
|
||||
c.GotoNext(MoveType.After, i => i.MatchLdloc(0));
|
||||
|
||||
//c.Emit(OpCodes.Ldloc_0); // implicit; the subbehavior variable
|
||||
c.Emit(OpCodes.Ldarg_0); // this
|
||||
c.EmitDelegate<Func<SubBehavID, SSOracleBehavior, SSOracleBehavior.SubBehavior>>((id, self) => {
|
||||
if (id == MeetJilloSS) {
|
||||
return new SSOracleMeetJillo(self);
|
||||
} else if (id == MeetJilloDM) {
|
||||
return new DMOracleMeetJillo(self);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
// we either have null or a subbehavior!
|
||||
// let's set the subbehavior to whatever we got - setting it to null won't do anything, since it's already null by default
|
||||
c.Emit(OpCodes.Stloc_1);
|
||||
// now we skip the rest if it's null
|
||||
// stloc pops the value so we must load it back up (https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes.stloc_0?view=net-7.0)
|
||||
c.Emit(OpCodes.Ldloc_1);
|
||||
// skip to our skiplabel if it's no longer null
|
||||
// the code actually does this the same way above at `if (subBehavior == null)`
|
||||
c.Emit(OpCodes.Brtrue, skipLabel);
|
||||
// past this point we know it's null; rebuild the bytecode state back to how the rest of it expects it to be
|
||||
// we put down our previously captured ldloc.0
|
||||
c.Emit(OpCodes.Ldloc_0);
|
||||
// and now we've cleaned everything up!
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
using System;
|
||||
using Mono.Cecil.Cil;
|
||||
using MonoMod.Cil;
|
||||
using SlugBase;
|
||||
using SlugBase.Features;
|
||||
using static SlugBase.Features.FeatureTypes;
|
||||
|
||||
namespace JilloSlug.Story;
|
||||
|
||||
internal static class Pebbles {
|
||||
public static readonly PlayerFeature<string[]> PebblesLines = PlayerStrings("jillo/pebbles_lines");
|
||||
|
||||
public static void AddHooks() {
|
||||
On.SSOracleBehavior.PebblesConversation.AddEvents += PebblesConversation_AddEvents;
|
||||
}
|
||||
|
||||
private static void PebblesConversation_AddEvents(On.SSOracleBehavior.PebblesConversation.orig_AddEvents orig, SSOracleBehavior.PebblesConversation self) {
|
||||
if (PebblesLines.TryGet(self.convBehav.player, out var lines)) {
|
||||
foreach (string line in lines) {
|
||||
if (line.StartsWith("!wait ")) {
|
||||
int wait = int.Parse(line.Split(' ')[1]);
|
||||
self.events.Add(new SSOracleBehavior.PebblesConversation.PauseAndWaitForStillEvent(self, self.convBehav, wait));
|
||||
} else {
|
||||
self.events.Add(new Conversation.TextEvent(self, 0, self.Translate(line), 0));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
orig(self);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
- graphics adjustements
|
||||
- give jillo a chip
|
||||
- remove ears
|
||||
- potentially more
|
||||
- gameplay
|
||||
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)
|
||||
- taking damage has a chance to drop slime mold & a pip
|
||||
|
||||
x increased lung capacity slightly
|
||||
- centi changes
|
||||
- electrocution should only ever stun
|
Loading…
Reference in New Issue