diff --git a/SpecialOrdersExtended/DataModels/DialogueLog.cs b/SpecialOrdersExtended/DataModels/DialogueLog.cs
index 4387687..da2a49d 100644
--- a/SpecialOrdersExtended/DataModels/DialogueLog.cs
+++ b/SpecialOrdersExtended/DataModels/DialogueLog.cs
@@ -20,9 +20,10 @@ public DialogueLog(string savefile, long multiplayerID)
: base(savefile) => this.multiplayerID = multiplayerID;
///
- /// Gets or sets backing field that contains all the SeenDialogues.
+ /// Gets backing field that contains all the SeenDialogues.
///
- public Dictionary> SeenDialogues { get; set; } = new();
+ /// Avoid using this directly; use the TryAdd/TryRemove/Contains methods instead if possible.
+ public Dictionary> SeenDialogues { get; private set; } = new();
///
/// Loads a dialogueLog.
@@ -38,7 +39,7 @@ public static DialogueLog Load(long multiplayerID)
}
DialogueLog log = ModEntry.DataHelper.ReadGlobalData($"{Constants.SaveFolderName}{IDENTIFIER}{multiplayerID:X8}")
?? new DialogueLog(Constants.SaveFolderName, multiplayerID);
- log.multiplayerID = multiplayerID;
+ log.multiplayerID = multiplayerID; // fix the multiplayer ID since ReadGlobalData will use the default zero-parameter constructor.
return log;
}
@@ -87,7 +88,6 @@ public static DialogueLog LoadTempIfAvailable(long multiplayerID)
/// Exact dialogue key.
/// Which character to check.
/// True if found, false otheerwise.
- [Pure]
public bool Contains(string dialoguekey, string characterName)
{
if (this.SeenDialogues.TryGetValue(dialoguekey, out List? characterList))
@@ -139,7 +139,6 @@ public bool TryRemove(string dialoguekey, string characterName)
}
///
- [Pure]
public override string ToString()
{
StringBuilder stringBuilder = new();
diff --git a/SpecialOrdersExtended/DataModels/RecentCompletedSO.cs b/SpecialOrdersExtended/DataModels/RecentCompletedSO.cs
index 18fa2f6..aa93266 100644
--- a/SpecialOrdersExtended/DataModels/RecentCompletedSO.cs
+++ b/SpecialOrdersExtended/DataModels/RecentCompletedSO.cs
@@ -118,7 +118,6 @@ public List dayUpdate(uint daysPlayed)
/// Days to check.
/// True if order found and completed in the last X days, false otherwise.
/// Orders are removed from the list after seven days.
- [Pure]
public bool IsWithinXDays(string orderKey, uint days)
{
if (this.RecentOrdersCompleted.TryGetValue(orderKey, out uint dayCompleted))
@@ -133,7 +132,6 @@ public bool IsWithinXDays(string orderKey, uint days)
///
/// Number of days to look at.
/// IEnumerable of keys within the given timeframe.
- [Pure]
public IEnumerable GetKeys(uint days)
{
return this.RecentOrdersCompleted.Keys
@@ -141,7 +139,6 @@ public IEnumerable GetKeys(uint days)
}
///
- [Pure]
public override string ToString()
{
StringBuilder stringBuilder = new();
diff --git a/SpecialOrdersExtended/DialogueManager.cs b/SpecialOrdersExtended/DialogueManager.cs
index f77cad2..3aca291 100644
--- a/SpecialOrdersExtended/DialogueManager.cs
+++ b/SpecialOrdersExtended/DialogueManager.cs
@@ -193,7 +193,6 @@ public static void ConsoleSpecialOrderDialogue(string command, string[] args)
/// Name of character.
/// True if key has been said, false otherwise.
/// Save is not loaded.
- [Pure]
public static bool HasSeenDialogue(string key, string characterName)
{
if (!Context.IsWorldReady)
@@ -359,10 +358,12 @@ public static void PushPossibleDelayedDialogues()
{
if (result.PushIfPastTime(Game1.timeOfDay))
{
+ // Successfully pushed, remove from queue.
_ = DelayedDialogues.Value.Dequeue();
}
else
{
+ // Everyone else should be behind me in time, so skip to next timeslot.
return;
}
}
@@ -381,13 +382,17 @@ private static bool PushAndSaveDialogue(string dialogueKey, NPC npc)
{// I have already said this dialogue
return false;
}
+
+ // Empty NPC's current dialogue stack and keep it in a queue for now.
while (npc.CurrentDialogue.TryPop(out Dialogue? result))
{
DelayedDialogues.Value.Enqueue(new DelayedDialogue(
- time: Game1.timeOfDay + 100,
+ time: Game1.timeOfDay + 100, // delay by one hour.
npc: npc,
dialogue: result));
}
+
+ // Push my dialogue onto their stack.
npc.CurrentDialogue.Push(new Dialogue(npc.Dialogue[dialogueKey], npc) { removeOnNextMove = true });
if (ModEntry.Config.Verbose)
{
@@ -434,7 +439,10 @@ private static bool FindBestDialogue(string baseKey, NPC npc, int hearts)
}
}
- ModEntry.ModMonitor.DebugLog(I18n.Dialogue_NoKey(baseKey, npc.Name), LogLevel.Trace);
+ if (ModEntry.Config.Verbose)
+ {
+ ModEntry.ModMonitor.Log(I18n.Dialogue_NoKey(baseKey, npc.Name), LogLevel.Trace);
+ }
return false;
}
}
diff --git a/SpecialOrdersExtended/ModEntry.cs b/SpecialOrdersExtended/ModEntry.cs
index 65e5109..4843f5f 100644
--- a/SpecialOrdersExtended/ModEntry.cs
+++ b/SpecialOrdersExtended/ModEntry.cs
@@ -92,6 +92,7 @@ public override void Entry(IModHelper helper)
ModMonitor.Log($"Failed to patch NPC::checkForNewCurrentDialogue for Special Orders Dialogue. Dialogue will be disabled\n\n{ex}", LogLevel.Error);
}
+ // Register console commands.
helper.ConsoleCommands.Add(
name: "special_order_pool",
documentation: I18n.SpecialOrderPool_Description(),
@@ -108,6 +109,8 @@ public override void Entry(IModHelper helper)
name: "special_orders_dialogue",
documentation: $"{I18n.SpecialOrdersDialogue_Description()}\n\n{I18n.SpecialOrdersDialogue_Example()}\n {I18n.SpecialOrdersDialogue_Usage()}\n {I18n.SpecialOrdersDialogue_Save()}",
callback: DialogueManager.ConsoleSpecialOrderDialogue);
+
+ // Register event handlers.
helper.Events.GameLoop.GameLaunched += this.OnGameLaunched;
helper.Events.GameLoop.SaveLoaded += this.SaveLoaded;
helper.Events.GameLoop.Saving += this.Saving;
@@ -115,16 +118,34 @@ public override void Entry(IModHelper helper)
helper.Events.GameLoop.OneSecondUpdateTicking += this.OneSecondUpdateTicking;
}
+ ///
+ /// Raised every 10 in game minutes.
+ ///
+ /// Unknown, used by SMAPI.
+ /// TimeChanged params.
+ /// Currently handles: pushing delayed dialogue back onto the stack.
private void OnTimeChanged(object? sender, TimeChangedEventArgs e)
{
DialogueManager.PushPossibleDelayedDialogues();
}
+ ///
+ /// Raised every second.
+ ///
+ /// Unknown, used by SMAPI.
+ /// OneSecondUpdate params.
+ /// Currently handles: grabbing new recently completed special orders.
private void OneSecondUpdateTicking(object? sender, OneSecondUpdateTickingEventArgs e)
{
RecentSOManager.GrabNewRecentlyCompletedOrders();
}
+ ///
+ /// Raised on game launch.
+ ///
+ /// Unknown, used by SMAPI.
+ /// Game Launched arguments.
+ /// Used to bind APIs and register CP tokens.
private void OnGameLaunched(object? sender, GameLaunchedEventArgs e)
{
// Bind Spacecore API
@@ -148,6 +169,12 @@ private void OnGameLaunched(object? sender, GameLaunchedEventArgs e)
api.RegisterToken(this.ModManifest, "RecentCompleted", new Tokens.RecentCompletedSO());
}
+ ///
+ /// Raised right before the game is saved.
+ ///
+ /// Unknown, used by SMAPI.
+ /// Event arguments.
+ /// Used to handle day-end events.
private void Saving(object? sender, SavingEventArgs e)
{
this.Monitor.DebugLog("Event Saving raised");
@@ -167,6 +194,12 @@ private void Saving(object? sender, SavingEventArgs e)
RecentSOManager.Save();
}
+ ///
+ /// Raised when save is loaded.
+ ///
+ /// Unknown, used by SMAPI.
+ /// Parameters.
+ /// Used to load in this mod's data models.
private void SaveLoaded(object? sender, SaveLoadedEventArgs e)
{
this.Monitor.DebugLog("Event SaveLoaded raised");
@@ -174,6 +207,11 @@ private void SaveLoaded(object? sender, SaveLoadedEventArgs e)
RecentSOManager.Load();
}
+ ///
+ /// Console commands to check the value of a tag.
+ ///
+ /// Name of the command.
+ /// List of tags to check.
private void ConsoleCheckTag(string command, string[] args)
{
if (!Context.IsWorldReady)
@@ -199,6 +237,11 @@ private void ConsoleCheckTag(string command, string[] args)
}
}
+ ///
+ /// Console command to get all available orders.
+ ///
+ /// Name of command.
+ /// Arguments for command.
private void GetAvailableOrders(string command, string[] args)
{
if (!Context.IsWorldReady)
diff --git a/SpecialOrdersExtended/RecentSOManager.cs b/SpecialOrdersExtended/RecentSOManager.cs
index 3f7ef54..a2cbfa9 100644
--- a/SpecialOrdersExtended/RecentSOManager.cs
+++ b/SpecialOrdersExtended/RecentSOManager.cs
@@ -51,7 +51,6 @@ public static void SaveTemp()
///
/// current number of days played.
/// IEnumerable of keys within the given timeframe. May return null.
- [Pure]
public static IEnumerable? GetKeys(uint days) => recentCompletedSO?.GetKeys(days);
///
@@ -177,7 +176,6 @@ public static bool TryRemove(string questkey)
/// True if questkey found and was completed in X days, false otherwise.
/// Save not loaded.
/// The data model will delete any entries older than 7 days, so beyond that it just won't know.
- [Pure]
public static bool IsWithinXDays(string questkey, uint days)
{
if (!Context.IsWorldReady)
diff --git a/SpecialOrdersExtended/Tokens/AbstractToken.cs b/SpecialOrdersExtended/Tokens/AbstractToken.cs
index d68b22e..d316495 100644
--- a/SpecialOrdersExtended/Tokens/AbstractToken.cs
+++ b/SpecialOrdersExtended/Tokens/AbstractToken.cs
@@ -16,7 +16,6 @@ internal abstract class AbstractToken
/// Whether or not the token allows input. Default, true.
///
/// true - all derived tokens should allow input.
- [Pure]
public virtual bool AllowsInput() => false;
///
@@ -24,12 +23,10 @@ internal abstract class AbstractToken
///
/// Input to token.
/// False (no need for my own inputs).
- [Pure]
public virtual bool CanHaveMultipleValues(string? input = null) => true;
/// Get whether the token is available for use.
/// True if token ready, false otherwise.
- [Pure]
public virtual bool IsReady() => this.tokenCache is not null;
/// Validate that the provided input arguments are valid.
diff --git a/SpecialOrdersExtended/docs/CHANGELOG.MD b/SpecialOrdersExtended/docs/CHANGELOG.MD
index e355dde..1195163 100644
--- a/SpecialOrdersExtended/docs/CHANGELOG.MD
+++ b/SpecialOrdersExtended/docs/CHANGELOG.MD
@@ -3,6 +3,7 @@ Changelog
1. Add condition to just randomly reduce the chance of a quest appearing. (May need integrate with that one mod that changes the day special orders appear?)
2. Add condition for total number of quests completed, per board.
+
#### Stardew 1.6 todo
@@ -17,7 +18,8 @@ Changelog
#### Version 1.0.6
* Add verbosity config option. Intended mostly for mod authors.
-* `week_X` now handles extended months (in theory.)
+* Special Order Dialogue will now cause the NPC's original dialogue to be delayed by one in-game hour. (Previously, dialogue from this mod would just be added on top of the dialogue stack.)
+* `week_X` now handles extended months.
* Add `profession` tag. Makes `skilllevel` tag take into account Spacecore skills, if that's installed (in theory. Memo to self: test that).
* For the `_IsAvailable` dialogue series, remove from possible pool if quest is already accepted.
* Fixes splitscreen, but for real this time.
diff --git a/SpecialOrdersExtended/docs/DialogueKeys.MD b/SpecialOrdersExtended/docs/DialogueKeys.MD
index cb9c8cd..6317745 100644
--- a/SpecialOrdersExtended/docs/DialogueKeys.MD
+++ b/SpecialOrdersExtended/docs/DialogueKeys.MD
@@ -5,7 +5,7 @@ New in 1.0.2, there's dialogue keys. Special Order Dialogue Keys will only ever
Quest key is the key for the quest (as it is in the `SpecialOrders` file). Quest status is either `Completed` or `InProgress`. (The status `Failed` is technically also available, but I don't think you can actually talk to a NPC in that time.) `Completed` dialogue will persist to the end of the day after quest completion. Additionally, `IsAvailable` will reference quests currently available on the Quests board, and `RepeatOrder` can be used when the quest has been completed at least once before. `RepeatOrder` keys will also be cleared seven days after the successful completion of a quest.
-Preface/predicate limits when the key can be used, and follows a similar structure as to vanilla's location keys. can be any season, and season-specific keys will always take precedence. Predicate can be any of the following values, in priority order:
+Preface/predicate limits when the key can be used, and follows a similar structure as to vanilla's location keys. `` can be any season, and season-specific keys will always take precedence. Predicate can be any of the following values, in priority order:
1. `_`
2. `` (where valid heart levels are even numbers, checked counting down - ie 14,12,10,8,6,4,2 in vanilla)
@@ -15,6 +15,8 @@ For example: if I have a quest key that's `atravita.ValleyFlowers` and I'm writi
If I wanted a bit of dialogue to be said while the quest is in progress, I would use `atravita.ValleyFlowers_InProgress`, which will only ever be said once while the quest is in progress. On the other hand, `atravita.ValleyFlowers_RepeatOrder` can only be said while the quest is in progress AND has been completed at least once before.
+The NPC's original dialogue is held in a queue and re-added after an in-game hour.
+
### Storage
To prevent dialogue keys from this mod from becoming too spammy, they're only ever said once (except for `RepeatOrder` keys, but those have a cooldown period of seven days after the completion of the Special Order). As of 1.0.4+, previously seen keys are stored in the global mod data, locally (`.smapi/mod-data/atravita.specialordersextended`). The console command `special_orders_dialogue` is included to look at and and/remove from this data.
diff --git a/SpecialOrdersExtended/manifest.json b/SpecialOrdersExtended/manifest.json
index 833ad0f..baa3be9 100644
--- a/SpecialOrdersExtended/manifest.json
+++ b/SpecialOrdersExtended/manifest.json
@@ -2,7 +2,7 @@
"$schema": "https://smapi.io/schemas/manifest.json",
"Name": "Special Orders Tags Extended",
"Author": "atravita",
- "Version": "1.0.6-beta",
+ "Version": "1.0.6",
"Description": "Extends the number of tags available for special orders",
"UniqueID": "atravita.SpecialOrdersExtended",
"EntryDll": "SpecialOrdersExtended.dll",