iterators done!
This commit is contained in:
parent
5f63d0bb46
commit
3a8694ab6f
|
@ -41,19 +41,29 @@
|
|||
"jillo/has_mark": 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."
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
@ -22,7 +21,7 @@ class Plugin : BaseUnityPlugin {
|
|||
ImmuneToDartMaggotsFeature.AddHooks();
|
||||
MarkFeature.AddHooks();
|
||||
|
||||
Pebbles.AddHooks();
|
||||
Iterators.AddHooks();
|
||||
} catch (Exception err) {
|
||||
Logger.LogError($"error initializing: {err}");
|
||||
}
|
||||
|
|
|
@ -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,14 @@
|
|||
- graphics adjustements
|
||||
- give jillo a chip
|
||||
- remove ears
|
||||
- potentially more
|
||||
- gameplay
|
||||
- hold eat to remove food pips for offensive slime mold
|
||||
- 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
|
||||
|
||||
- increased lung capacity slightly
|
||||
- centi changes
|
||||
- electrocution should only ever stun
|
Loading…
Reference in New Issue