diff --git a/ChestsAnywhere/i18n/zh.json b/ChestsAnywhere/i18n/zh.json
index 76065fe40..3bb3c5f69 100644
--- a/ChestsAnywhere/i18n/zh.json
+++ b/ChestsAnywhere/i18n/zh.json
@@ -68,7 +68,7 @@
/****
** remote access
****/
- "config.title.remote-access": "Remote access", // TODO
+ "config.title.remote-access": "远程访问",
"config.range.name": "箱子显示范围",
"config.range.desc": "箱子可通过菜单访问的范围。",
@@ -86,16 +86,16 @@
/****
** open menu hotkey
****/
- "config.title.open-menu-hotkey": "Hotkey to open chest UI", // TODO
+ "config.title.open-menu-hotkey": "箱子菜单快捷键",
"config.toggle-ui-key.name": "切换UI",
"config.toggle-ui-key.desc": "显示或隐藏箱子UI的按键",
- "config.reopen-last-chest.name": "Reopen last chest", // TODO
- "config.reopen-last-chest.desc": "When you press the 'toggle UI' button, whether to reopen the last chest you selected yourself during the current game session.", // TODO
+ "config.reopen-last-chest.name": "默认打开上个箱子",
+ "config.reopen-last-chest.desc": "按‘切换UI’快捷键时,打开上个查看的箱子",
- "config.default-category.name": "Default category", // TODO
- "config.default-category.desc": "When you press the 'toggle UI' button, show the chests in this category by default (if set). This applies after 'reopen last chest'.", // TODO
+ "config.default-category.name": "默认分类",
+ "config.default-category.desc": "W按‘切换UI’快捷键时,优先显示此分类的箱子。此设定在‘默认打开上个箱子’之后起效",
/****
** controls when chest UI is open
diff --git a/CropsAnytimeAnywhere/i18n/zh.json b/CropsAnytimeAnywhere/i18n/zh.json
index 6af6b8fbc..abbd199a4 100644
--- a/CropsAnytimeAnywhere/i18n/zh.json
+++ b/CropsAnytimeAnywhere/i18n/zh.json
@@ -12,9 +12,8 @@
"config.grow-crops-out-of-season.name": "随时种植作物",
"config.grow-crops-out-of-season.desc": "作物是否可以在任何季节种植和生产。只有当“随处种植作物”启用时,该选项才会生效。",
- // TODO
- "config.use-fruit-trees-seasonal-sprites.name": "Seasonal fruit trees sprites",
- "config.use-fruit-trees-seasonal-sprites.desc": "Whether fruit trees should use their normal seasonal sprite (true) or their summer sprites (false) when 'grow crops in any season' is enabled.",
+ "config.use-fruit-trees-seasonal-sprites.name": "季节性果树外观",
+ "config.use-fruit-trees-seasonal-sprites.desc": "‘随时种植作物’开启时,控制果树是否使用季节外观。不启用时默认显示夏季外观。",
"config.force-till-dirt.name": "耕种所有土地瓦片",
"config.force-till-dirt.desc": "是否所有的土地瓦片都可以用锄头耕种,即使它们正常不能耕种。",
diff --git a/FastAnimations/i18n/zh.json b/FastAnimations/i18n/zh.json
index 4607e2d15..7bbb7aec5 100644
--- a/FastAnimations/i18n/zh.json
+++ b/FastAnimations/i18n/zh.json
@@ -115,8 +115,8 @@
/****
** 'Experimental' section
****/
- "config.experimental": "Experimental options", // TODO
- "config.experimental-warning": "These options aren't fully stable yet, and may cause issues like freezes and crashes. Enabling any of these isn't recommended.", // TODO
+ "config.experimental": "实验性功能",
+ "config.experimental-warning": "这些功能暂且不稳定,有可能导致游戏冻结或崩溃,不推荐使用。",
"config.event.name": "事件",
"config.event.tooltip": "事件的速度。默认{{defaultValue}}x,建议{{suggestedValue}}x."
diff --git a/LookupAnything/Components/LookupMenu.cs b/LookupAnything/Components/LookupMenu.cs
index a8ef2473c..e2b77518b 100644
--- a/LookupAnything/Components/LookupMenu.cs
+++ b/LookupAnything/Components/LookupMenu.cs
@@ -9,6 +9,7 @@
using Pathoschild.Stardew.LookupAnything.Framework.Constants;
using Pathoschild.Stardew.LookupAnything.Framework.Fields;
using Pathoschild.Stardew.LookupAnything.Framework.Lookups;
+using Pathoschild.Stardew.LookupAnything.Framework.Models;
using StardewModdingAPI;
using StardewValley;
using StardewValley.Menus;
@@ -78,12 +79,18 @@ internal class LookupMenu : BaseMenu, IScrollableMenu, IDisposable
/// Click areas for link fields that open a new subject.
private readonly IDictionary LinkFieldAreas = new Dictionary();
+ /// Click areas for link text within a field that open a new subject.
+ private readonly IList FieldsWithLinkTextArea = [];
+
/// Whether the game HUD was enabled when the menu was opened.
private readonly bool WasHudEnabled;
/// Whether to exit the menu on the next update tick.
private bool ExitOnNextTick;
+ /// Flag for , exit without restoring a previous if set.
+ internal bool ExitWithoutRestore = false;
+
/*********
** Public methods
@@ -236,7 +243,10 @@ public void HandleLeftClick(int x, int y)
{
// close menu when clicked outside
if (!this.isWithinBounds(x, y))
+ {
+ this.ExitWithoutRestore = true;
this.exitThisMenu();
+ }
// scroll up or down
else if (this.ScrollUpButton.containsPoint(x, y))
@@ -254,7 +264,19 @@ public void HandleLeftClick(int x, int y)
ISubject? subject = link.GetLinkSubject();
if (subject != null)
this.ShowNewPage(subject);
- break;
+ return;
+ }
+ }
+
+ foreach (ICustomField field in this.FieldsWithLinkTextArea)
+ {
+ foreach (LinkTextArea linkTextArea in field.LinkTextAreas!)
+ {
+ if (linkTextArea.Rect.Contains(x, y))
+ {
+ this.ShowNewPage(linkTextArea.Subject);
+ return;
+ }
}
}
}
@@ -398,6 +420,11 @@ public override void draw(SpriteBatch b)
// track link area
if (field is ILinkField linkField)
this.LinkFieldAreas[linkField] = new Rectangle((int)valuePosition.X, (int)valuePosition.Y, (int)valueSize.X, (int)valueSize.Y);
+ // track link text areas
+ if (field.LinkTextAreas != null)
+ {
+ this.FieldsWithLinkTextArea.Add(field);
+ }
// update offset
topOffset += Math.Max(labelSize.Y, valueSize.Y);
diff --git a/LookupAnything/Framework/Fields/FishPondDropsField.cs b/LookupAnything/Framework/Fields/FishPondDropsField.cs
index 83444139e..292622544 100644
--- a/LookupAnything/Framework/Fields/FishPondDropsField.cs
+++ b/LookupAnything/Framework/Fields/FishPondDropsField.cs
@@ -7,6 +7,8 @@
using Pathoschild.Stardew.Common.UI;
using Pathoschild.Stardew.LookupAnything.Framework.Data;
using Pathoschild.Stardew.LookupAnything.Framework.Fields.Models;
+using Pathoschild.Stardew.LookupAnything.Framework.Lookups;
+using Pathoschild.Stardew.LookupAnything.Framework.Models;
using StardewValley;
using StardewValley.Buildings;
using StardewValley.GameData.FishPonds;
@@ -38,13 +40,15 @@ internal class FishPondDropsField : GenericField
/// The current population for showing unlocked drops.
/// The fish pond data.
/// >The text to display before the list, if any.
- public FishPondDropsField(GameHelper gameHelper, string label, int currentPopulation, FishPondData data, string preface)
+ public FishPondDropsField(GameHelper gameHelper, string label, int currentPopulation, FishPondData data, string preface, Func? getSubjectByEntity = null)
: base(label)
{
this.GameHelper = gameHelper;
this.Drops = this.GetEntries(currentPopulation, data, gameHelper).ToArray();
this.HasValue = this.Drops.Any();
this.Preface = preface;
+ this.LinkTextAreas = [];
+ this.GetSubjectByEntity = getSubjectByEntity;
}
///
@@ -70,6 +74,7 @@ public FishPondDropsField(GameHelper gameHelper, string label, int currentPopula
Vector2 iconSize = new Vector2(font.MeasureString("ABC").Y);
int lastGroup = -1;
bool isPrevDropGuaranteed = false;
+ int idx = 0;
foreach (FishPondDrop drop in this.Drops)
{
bool disabled = !drop.IsUnlocked || isPrevDropGuaranteed;
@@ -108,6 +113,9 @@ public FishPondDropsField(GameHelper gameHelper, string label, int currentPopula
// draw drop
bool isGuaranteed = drop.Probability > .99f;
{
+ bool shouldLink = this.TryGetOrAddLinkTextArea(drop.SampleItem, ref idx, out LinkTextArea? linkTextArea);
+ Color textColor = (shouldLink ? Color.Blue : Color.Black) * (disabled ? 0.75f : 1f);
+
// draw icon
spriteBatch.DrawSpriteWithin(drop.Sprite, position.X + innerIndent, position.Y + height, iconSize, Color.White * (disabled ? 0.5f : 1f));
@@ -117,7 +125,10 @@ public FishPondDropsField(GameHelper gameHelper, string label, int currentPopula
text += $" ({I18n.Generic_Range(min: drop.MinDrop, max: drop.MaxDrop)})";
else if (drop.MinDrop > 1)
text += $" ({drop.MinDrop})";
- Vector2 textSize = spriteBatch.DrawTextBlock(font, text, position + new Vector2(innerIndent + iconSize.X + 5, height + 5), wrapWidth, disabled ? Color.Gray : Color.Black);
+ Vector2 textSize = spriteBatch.DrawTextBlock(font, text, position + new Vector2(innerIndent + iconSize.X + 5, height + 5), wrapWidth, textColor);
+
+ if (shouldLink)
+ linkTextArea!.Rect = new Rectangle((int)(position.X + innerIndent + iconSize.X + 5), (int)(position.Y + height + iconSize.Y / 2), (int)textSize.X, (int)textSize.Y);
// cross out if it's guaranteed not to drop
if (isPrevDropGuaranteed)
diff --git a/LookupAnything/Framework/Fields/GenericField.cs b/LookupAnything/Framework/Fields/GenericField.cs
index b192ec045..e0e052994 100644
--- a/LookupAnything/Framework/Fields/GenericField.cs
+++ b/LookupAnything/Framework/Fields/GenericField.cs
@@ -1,8 +1,13 @@
+using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Pathoschild.Stardew.LookupAnything.Framework.Constants;
+using Pathoschild.Stardew.LookupAnything.Framework.Lookups;
+using Pathoschild.Stardew.LookupAnything.Framework.Models;
+using StardewValley;
namespace Pathoschild.Stardew.LookupAnything.Framework.Fields;
@@ -24,6 +29,12 @@ internal class GenericField : ICustomField
///
public bool HasValue { get; protected set; }
+ ///
+ public IList? LinkTextAreas { get; protected set; }
+
+ /// The method, for use in populating .
+ protected Func? GetSubjectByEntity { get; set; }
+
/*********
** Public methods
@@ -55,6 +66,7 @@ public GenericField(string label, IEnumerable value, bool? hasVa
this.Label = label;
this.Value = value.ToArray();
this.HasValue = hasValue ?? this.Value?.Any() == true;
+ this.LinkTextAreas = null;
}
/// Draw the value (or return null to render the using the default format).
@@ -152,4 +164,28 @@ protected void CollapseByDefault(string linkText)
}
return I18n.List(priceStrings);
}
+
+ ///
+ /// Check if item should be added to link text areas, if added/updated, increment the index.
+ /// Make assumption that the linkable items in the field will not change over lifetime of menu, and that each item
+ /// will be processed by in the same order on every draw cycle.
+ ///
+ /// Entity to try to get subject and link to
+ /// Index of the link in
+ ///
+ protected virtual bool TryGetOrAddLinkTextArea(object? entity, ref int idx, [NotNullWhen(true)] out LinkTextArea? linkTextArea)
+ {
+ linkTextArea = null;
+ if (this.GetSubjectByEntity == null || this.LinkTextAreas == null || entity == null)
+ return false;
+ if (this.GetSubjectByEntity(entity, null) is not ISubject subject)
+ return false;
+ if (this.LinkTextAreas.Count == idx)
+ this.LinkTextAreas.Add(new(subject));
+ else if (this.LinkTextAreas.Count < idx) // misalignment in index and LinkTextAreas, abort
+ return false;
+ linkTextArea = this.LinkTextAreas[idx];
+ idx++;
+ return true;
+ }
}
diff --git a/LookupAnything/Framework/Fields/ICustomField.cs b/LookupAnything/Framework/Fields/ICustomField.cs
index 0859c750c..0e83a6b07 100644
--- a/LookupAnything/Framework/Fields/ICustomField.cs
+++ b/LookupAnything/Framework/Fields/ICustomField.cs
@@ -1,5 +1,7 @@
+using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
+using Pathoschild.Stardew.LookupAnything.Framework.Models;
namespace Pathoschild.Stardew.LookupAnything.Framework.Fields;
@@ -21,6 +23,8 @@ internal interface ICustomField
/// If the field is currently collapsed, the link to click to expand it.
LinkField? ExpandLink { get; }
+ /// List of clickable areas that should open a new page when clicked.
+ IList? LinkTextAreas { get; }
/*********
** Public methods
diff --git a/LookupAnything/Framework/Fields/ItemDropListField.cs b/LookupAnything/Framework/Fields/ItemDropListField.cs
index 7add3bf3f..cd4af3a7e 100644
--- a/LookupAnything/Framework/Fields/ItemDropListField.cs
+++ b/LookupAnything/Framework/Fields/ItemDropListField.cs
@@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Pathoschild.Stardew.Common;
using Pathoschild.Stardew.LookupAnything.Framework.Data;
+using Pathoschild.Stardew.LookupAnything.Framework.Lookups;
+using Pathoschild.Stardew.LookupAnything.Framework.Models;
using StardewValley;
namespace Pathoschild.Stardew.LookupAnything.Framework.Fields;
@@ -46,7 +49,7 @@ internal class ItemDropListField : GenericField
/// Whether to cross out non-guaranteed drops.
/// The text to display if there are no items (or null to hide the field).
/// The text to display before the list, if any.
- public ItemDropListField(GameHelper gameHelper, string label, IEnumerable drops, bool sort = true, bool fadeNonGuaranteed = false, bool crossOutNonGuaranteed = false, string? defaultText = null, string? preface = null)
+ public ItemDropListField(GameHelper gameHelper, string label, IEnumerable drops, bool sort = true, bool fadeNonGuaranteed = false, bool crossOutNonGuaranteed = false, string? defaultText = null, string? preface = null, Func? getSubjectByEntity = null)
: base(label)
{
this.GameHelper = gameHelper;
@@ -59,6 +62,8 @@ public ItemDropListField(GameHelper gameHelper, string label, IEnumerable
@@ -77,6 +82,7 @@ public ItemDropListField(GameHelper gameHelper, string label, IEnumerable .99f;
bool shouldFade = this.FadeNonGuaranteed && !isGuaranteed;
bool shouldCrossOut = this.CrossOutNonGuaranteed && !isGuaranteed;
+ bool shouldLink = this.TryGetOrAddLinkTextArea(item, ref idx, out LinkTextArea? linkTextArea);
+ Color textColor = (shouldLink ? Color.Blue : Color.Black) * (shouldFade ? 0.75f : 1f);
// draw icon
spriteBatch.DrawSpriteWithin(sprite, position.X, position.Y + height, iconSize, shouldFade ? Color.White * 0.5f : Color.White);
@@ -94,7 +102,10 @@ public ItemDropListField(GameHelper gameHelper, string label, IEnumerable 1)
text += $" ({drop.MinDrop})";
- Vector2 textSize = spriteBatch.DrawTextBlock(font, text, position + new Vector2(iconSize.X + 5, height + 5), wrapWidth, shouldFade ? Color.Gray : Color.Black);
+ Vector2 textSize = spriteBatch.DrawTextBlock(font, text, position + new Vector2(iconSize.X + 5, height + 5), wrapWidth, textColor);
+
+ if (shouldLink)
+ linkTextArea!.Rect = new Rectangle((int)(position.X + iconSize.X + 5), (int)((int)position.Y + height), (int)textSize.X, (int)textSize.Y);
// cross out item if it definitely won't drop
if (shouldCrossOut)
diff --git a/LookupAnything/Framework/Fields/ItemRecipesField.cs b/LookupAnything/Framework/Fields/ItemRecipesField.cs
index c931fbd7e..e68a9fb05 100644
--- a/LookupAnything/Framework/Fields/ItemRecipesField.cs
+++ b/LookupAnything/Framework/Fields/ItemRecipesField.cs
@@ -1,12 +1,15 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Pathoschild.Stardew.Common;
using Pathoschild.Stardew.LookupAnything.Framework.Fields.Models;
+using Pathoschild.Stardew.LookupAnything.Framework.Lookups;
using Pathoschild.Stardew.LookupAnything.Framework.Models;
using StardewValley;
+using StardewValley.Buildings;
using StardewValley.ItemTypeDefinitions;
using SObject = StardewValley.Object;
@@ -45,6 +48,9 @@ internal class ItemRecipesField : GenericField
/// The width and height of an item icon.
private float IconSize => this.LineHeight;
+ /// The that owns this field, for preventing circular links.
+ private readonly Item? Target = null;
+
/*********
** Public methods
@@ -58,7 +64,9 @@ internal class ItemRecipesField : GenericField
/// Whether to show recipes involving error items.
/// Whether to show the recipe group labels even if there's only one group.
/// Whether to show the output item for recipes.
- public ItemRecipesField(GameHelper gameHelper, string label, Item? ingredient, RecipeModel[] recipes, bool showUnknownRecipes, bool showInvalidRecipes, bool showLabelForSingleGroup = true, bool showOutputLabels = true)
+ /// The item that owns this field, for preventing circular links.
+ /// Callback to obtain an for link text.
+ public ItemRecipesField(GameHelper gameHelper, string label, Item? ingredient, RecipeModel[] recipes, bool showUnknownRecipes, bool showInvalidRecipes, bool showLabelForSingleGroup = true, bool showOutputLabels = true, Func? getSubjectByEntity = null)
: base(label, true)
{
this.GameHelper = gameHelper;
@@ -67,6 +75,27 @@ public ItemRecipesField(GameHelper gameHelper, string label, Item? ingredient, R
this.ShowInvalidRecipes = showInvalidRecipes;
this.ShowLabelForSingleGroup = showLabelForSingleGroup;
this.ShowOutputLabels = showOutputLabels;
+ this.Target = ingredient;
+ this.GetSubjectByEntity = getSubjectByEntity;
+ this.LinkTextAreas = [];
+ }
+
+ ///
+ /// Check if item should be added to link text areas, if added/updated, increment the index.
+ /// Make assumption that the linkable items in the field will not change over lifetime of menu, and that each item
+ /// will be processed by in the same order on every draw cycle.
+ ///
+ /// Entity to try to get subject and link to
+ /// Index of the link in
+ ///
+ protected override bool TryGetOrAddLinkTextArea(object? entity, ref int idx, [NotNullWhen(true)] out LinkTextArea? linkTextArea)
+ {
+ linkTextArea = null;
+ if (entity is Item item && (
+ item.ItemId == DataParser.ComplexRecipeId ||
+ item.QualifiedItemId == this.Target?.QualifiedItemId))
+ return false;
+ return base.TryGetOrAddLinkTextArea(entity, ref idx, out linkTextArea);
}
/// Get the number of displayed recipes.
@@ -109,6 +138,9 @@ public int GetShownRecipesCount()
// draw recipes
curPos.Y += groupVerticalMargin;
+ int idx = 0;
+ LinkTextArea? linkTextArea;
+ bool shouldLink;
foreach (RecipeByTypeGroup group in this.RecipesByType)
{
// check if we can align columns
@@ -149,11 +181,16 @@ public int GetShownRecipesCount()
float inputLeft = 0;
if (this.ShowOutputLabels)
{
- Vector2 outputSize = this.DrawIconText(spriteBatch, font, curPos, absoluteWrapWidth, entry.Output.DisplayText, textColor, entry.Output.Sprite, iconSize, iconColor, qualityIcon: entry.Output.Quality);
+ shouldLink = this.TryGetOrAddLinkTextArea(entry.Output.Entity, ref idx, out linkTextArea);
+
+ Vector2 outputSize = this.DrawIconText(spriteBatch, font, curPos, absoluteWrapWidth, entry.Output.DisplayText, shouldLink ? Color.Blue : textColor, entry.Output.Sprite, iconSize, iconColor, qualityIcon: entry.Output.Quality);
float outputWidth = alignColumns
? group.ColumnWidths[0]
: outputSize.X;
+ if (shouldLink)
+ linkTextArea!.Rect = new Rectangle((int)curPos.X, (int)curPos.Y, (int)outputWidth, (int)lineHeight);
+
inputLeft = curPos.X + outputWidth + itemSpacer;
curPos.X = inputLeft;
}
@@ -162,6 +199,7 @@ public int GetShownRecipesCount()
for (int i = 0, last = entry.Inputs.Length - 1; i <= last; i++)
{
RecipeItemEntry input = entry.Inputs[i];
+ shouldLink = this.TryGetOrAddLinkTextArea(input.Entity, ref idx, out linkTextArea);
// get icon size
Vector2 curIconSize = iconSize;
@@ -170,6 +208,7 @@ public int GetShownRecipesCount()
// move the draw position down to a new line if the next item would be drawn off the right edge
Vector2 inputSize = this.DrawIconText(spriteBatch, font, curPos, absoluteWrapWidth, input.DisplayText, textColor, input.Sprite, curIconSize, iconColor, input.Quality, probe: true);
+
if (alignColumns)
inputSize.X = group.ColumnWidths[i + 1];
@@ -180,9 +219,15 @@ public int GetShownRecipesCount()
y: curPos.Y + lineHeight + otherRecipeTopMargin
);
}
+
+ Color actualTextColor = shouldLink ? Color.Blue : textColor;
// draw input item (icon + name + count)
- this.DrawIconText(spriteBatch, font, curPos, absoluteWrapWidth, input.DisplayText, textColor, input.Sprite, curIconSize, iconColor, input.Quality);
+ this.DrawIconText(spriteBatch, font, curPos, absoluteWrapWidth, input.DisplayText, actualTextColor, input.Sprite, curIconSize, iconColor, input.Quality);
+
+ if (shouldLink)
+ linkTextArea!.Rect = new Rectangle((int)curPos.X, (int)curPos.Y, (int)inputSize.X, (int)lineHeight);
+
curPos = new Vector2(
x: curPos.X + inputSize.X,
y: curPos.Y
@@ -324,7 +369,8 @@ private IEnumerable BuildRecipeGroups(Item? ingredient, Recip
chance: recipe.OutputChance,
quality: recipe.Quality,
hasInputAndOutput: true,
- isValid: recipe.SpecialOutput?.IsValid
+ isValid: recipe.SpecialOutput?.IsValid,
+ specialEntity: recipe.SpecialOutput?.Entity
);
}
@@ -587,7 +633,7 @@ private RecipeItemEntry TryCreateItemEntry(RecipeIngredientModel ingredient)
/// The item quality that will be produced, if applicable.
/// Whether the item has both input and output ingredients.
/// Whether this recipe is valid, or null to determine it based on whether the output item exists.
- private RecipeItemEntry CreateItemEntry(string name, Item? item = null, SpriteInfo? sprite = null, int minCount = 1, int maxCount = 1, decimal chance = 100, int? quality = null, bool hasInputAndOutput = false, bool? isValid = null)
+ private RecipeItemEntry CreateItemEntry(string name, Item? item = null, SpriteInfo? sprite = null, int minCount = 1, int maxCount = 1, decimal chance = 100, int? quality = null, bool hasInputAndOutput = false, bool? isValid = null, object? specialEntity = null)
{
// get display text
string text;
@@ -614,7 +660,8 @@ private RecipeItemEntry CreateItemEntry(string name, Item? item = null, SpriteIn
DisplayText: text,
Quality: quality,
IsGoldPrice: false,
- IsValid: isValid ?? (item != null && ItemRegistry.Exists(item.QualifiedItemId))
+ IsValid: isValid ?? (item != null && ItemRegistry.Exists(item.QualifiedItemId)),
+ Entity: specialEntity ?? item
);
}
}
diff --git a/LookupAnything/Framework/Fields/Models/RecipeItemEntry.cs b/LookupAnything/Framework/Fields/Models/RecipeItemEntry.cs
index f786dc250..2609bf79c 100644
--- a/LookupAnything/Framework/Fields/Models/RecipeItemEntry.cs
+++ b/LookupAnything/Framework/Fields/Models/RecipeItemEntry.cs
@@ -1,4 +1,5 @@
using Pathoschild.Stardew.Common;
+using StardewValley;
namespace Pathoschild.Stardew.LookupAnything.Framework.Fields.Models;
@@ -8,4 +9,4 @@ namespace Pathoschild.Stardew.LookupAnything.Framework.Fields.Models;
/// The item quality that will be produced, if applicable.
/// Whether this is a gold price, rather than an ingredient.
/// Whether this recipe input or output is valid.
-internal record RecipeItemEntry(SpriteInfo? Sprite, string DisplayText, int? Quality, bool IsGoldPrice, bool IsValid = true);
+internal record RecipeItemEntry(SpriteInfo? Sprite, string DisplayText, int? Quality, bool IsGoldPrice, bool IsValid = true, object? Entity = null);
diff --git a/LookupAnything/Framework/Lookups/Buildings/BuildingLookupProvider.cs b/LookupAnything/Framework/Lookups/Buildings/BuildingLookupProvider.cs
index ac95f46e4..8076acd23 100644
--- a/LookupAnything/Framework/Lookups/Buildings/BuildingLookupProvider.cs
+++ b/LookupAnything/Framework/Lookups/Buildings/BuildingLookupProvider.cs
@@ -67,6 +67,14 @@ public override IEnumerable GetSearchSubjects()
}
}
+ ///
+ public override ISubject? GetSubjectFor(object entity, GameLocation? location)
+ {
+ return entity is Building building
+ ? this.BuildSubject(building)
+ : null;
+ }
+
/*********
** Private methods
diff --git a/LookupAnything/Framework/Lookups/Buildings/BuildingSubject.cs b/LookupAnything/Framework/Lookups/Buildings/BuildingSubject.cs
index cf0c8367c..70d1c41f9 100644
--- a/LookupAnything/Framework/Lookups/Buildings/BuildingSubject.cs
+++ b/LookupAnything/Framework/Lookups/Buildings/BuildingSubject.cs
@@ -173,7 +173,7 @@ public override IEnumerable GetData()
// drops
int chanceOfAnyDrop = (int)Math.Round(Utility.Lerp(0.15f, 0.95f, pond.currentOccupants.Value / 10f) * 100);
- yield return new FishPondDropsField(this.GameHelper, I18n.Building_FishPond_Drops(), pond.currentOccupants.Value, pondData, preface: I18n.Building_FishPond_Drops_Preface(chance: chanceOfAnyDrop.ToString()));
+ yield return new FishPondDropsField(this.GameHelper, I18n.Building_FishPond_Drops(), pond.currentOccupants.Value, pondData, preface: I18n.Building_FishPond_Drops_Preface(chance: chanceOfAnyDrop.ToString()), getSubjectByEntity: this.Codex.GetByEntity);
// quests
if (pondData.PopulationGates?.Any(gate => gate.Key > pond.lastUnlockedPopulationGate.Value) == true)
@@ -236,7 +236,7 @@ public override IEnumerable GetData()
if (recipes.Length > 0)
{
- var field = new ItemRecipesField(this.GameHelper, I18n.Building_ConstructionCosts(), null, recipes, showUnknownRecipes: true, showLabelForSingleGroup: false, showInvalidRecipes: this.ShowInvalidRecipes, showOutputLabels: false);
+ var field = new ItemRecipesField(this.GameHelper, I18n.Building_ConstructionCosts(), null, recipes, showUnknownRecipes: true, showLabelForSingleGroup: false, showInvalidRecipes: this.ShowInvalidRecipes, showOutputLabels: false, getSubjectByEntity: this.Codex.GetByEntity);
if (this.CollapseFieldsConfig.Enabled)
field.CollapseIfLengthExceeds(this.CollapseFieldsConfig.BuildingRecipes, recipes.Length);
yield return field;
diff --git a/LookupAnything/Framework/Lookups/Characters/CharacterSubject.cs b/LookupAnything/Framework/Lookups/Characters/CharacterSubject.cs
index d89a155bf..a854c4a69 100644
--- a/LookupAnything/Framework/Lookups/Characters/CharacterSubject.cs
+++ b/LookupAnything/Framework/Lookups/Characters/CharacterSubject.cs
@@ -271,7 +271,7 @@ private IEnumerable GetDataForMonster(Monster monster)
yield return new GenericField(I18n.Monster_Invincible(), I18n.Generic_Seconds(count: monster.invincibleCountdown), hasValue: monster.isInvincible());
yield return new PercentageBarField(I18n.Monster_Health(), monster.Health, monster.MaxHealth, Color.Green, Color.Gray, I18n.Generic_PercentRatio(percent: (int)Math.Round((monster.Health / (monster.MaxHealth * 1f) * 100)), value: monster.Health, max: monster.MaxHealth));
- yield return new ItemDropListField(this.GameHelper, I18n.Monster_Drops(), this.GetMonsterDrops(monster), fadeNonGuaranteed: true, crossOutNonGuaranteed: !canRerollDrops, defaultText: I18n.Monster_Drops_Nothing());
+ yield return new ItemDropListField(this.GameHelper, I18n.Monster_Drops(), this.GetMonsterDrops(monster), fadeNonGuaranteed: true, crossOutNonGuaranteed: !canRerollDrops, defaultText: I18n.Monster_Drops_Nothing(), getSubjectByEntity: this.Codex.GetByEntity);
yield return new GenericField(I18n.Monster_Experience(), this.Stringify(monster.ExperienceGained));
yield return new GenericField(I18n.Monster_Defense(), this.Stringify(monster.resilience.Value));
yield return new GenericField(I18n.Monster_Attack(), this.Stringify(monster.DamageToFarmer));
diff --git a/LookupAnything/Framework/Lookups/Items/ItemSubject.cs b/LookupAnything/Framework/Lookups/Items/ItemSubject.cs
index f7639c473..28747e125 100644
--- a/LookupAnything/Framework/Lookups/Items/ItemSubject.cs
+++ b/LookupAnything/Framework/Lookups/Items/ItemSubject.cs
@@ -305,7 +305,7 @@ select name
if (recipes.Length > 0)
{
- var field = new ItemRecipesField(this.GameHelper, I18n.Item_Recipes(), item, recipes, this.ShowUnknownRecipes, this.ShowInvalidRecipes);
+ var field = new ItemRecipesField(this.GameHelper, I18n.Item_Recipes(), item, recipes, this.ShowUnknownRecipes, this.ShowInvalidRecipes, getSubjectByEntity: this.Codex.GetByEntity);
if (this.CollapseFieldsConfig.Enabled)
field.CollapseIfLengthExceeds(this.CollapseFieldsConfig.ItemRecipes, recipes.Length);
yield return field;
@@ -325,7 +325,7 @@ select name
int minChanceOfAnyDrop = (int)Math.Round(Utility.Lerp(0.15f, 0.95f, 1 / 10f) * 100);
int maxChanceOfAnyDrop = (int)Math.Round(Utility.Lerp(0.15f, 0.95f, FishPond.MAXIMUM_OCCUPANCY / 10f) * 100);
string preface = I18n.Building_FishPond_Drops_Preface(chance: I18n.Generic_Range(min: minChanceOfAnyDrop, max: maxChanceOfAnyDrop));
- yield return new FishPondDropsField(this.GameHelper, I18n.Item_FishPondDrops(), -1, fishPondData, preface);
+ yield return new FishPondDropsField(this.GameHelper, I18n.Item_FishPondDrops(), -1, fishPondData, preface, getSubjectByEntity: this.Codex.GetByEntity);
}
}
diff --git a/LookupAnything/Framework/Lookups/TerrainFeatures/BushSubject.cs b/LookupAnything/Framework/Lookups/TerrainFeatures/BushSubject.cs
index 6e6322fd8..3227a326a 100644
--- a/LookupAnything/Framework/Lookups/TerrainFeatures/BushSubject.cs
+++ b/LookupAnything/Framework/Lookups/TerrainFeatures/BushSubject.cs
@@ -25,6 +25,9 @@ internal class BushSubject : BaseSubject
/// The underlying target.
private readonly Bush Target;
+ /// Provides subject entries.
+ private readonly ISubjectRegistry Codex;
+
/*********
** Public methods
@@ -32,10 +35,11 @@ internal class BushSubject : BaseSubject
/// Construct an instance.
/// Provides utility methods for interacting with the game code.
/// The lookup target.
- public BushSubject(GameHelper gameHelper, Bush bush)
+ public BushSubject(ISubjectRegistry codex, GameHelper gameHelper, Bush bush)
: base(gameHelper)
{
this.Target = bush;
+ this.Codex = codex;
if (this.TryGetCustomBush(bush, out ICustomBush? customBush))
this.Initialize(TokenParser.ParseText(customBush.DisplayName), TokenParser.ParseText(customBush.Description), I18n.Type_Bush());
@@ -90,7 +94,7 @@ public override IEnumerable GetData()
? I18n.Generic_Now()
: $"{this.Stringify(nextHarvest)} ({this.GetRelativeDateStr(nextHarvest)})";
if (this.TryGetCustomBushDrops(bush, out IList? drops))
- yield return new ItemDropListField(this.GameHelper, I18n.Bush_NextHarvest(), drops, preface: nextHarvestStr);
+ yield return new ItemDropListField(this.GameHelper, I18n.Bush_NextHarvest(), drops, preface: nextHarvestStr, getSubjectByEntity: this.Codex.GetByEntity);
else
{
string harvestSchedule = isTeaBush ? I18n.Bush_Schedule_Tea() : I18n.Bush_Schedule_Berry();
diff --git a/LookupAnything/Framework/Lookups/TerrainFeatures/TerrainFeatureLookupProvider.cs b/LookupAnything/Framework/Lookups/TerrainFeatures/TerrainFeatureLookupProvider.cs
index e4b7d52b9..7a4131cb3 100644
--- a/LookupAnything/Framework/Lookups/TerrainFeatures/TerrainFeatureLookupProvider.cs
+++ b/LookupAnything/Framework/Lookups/TerrainFeatures/TerrainFeatureLookupProvider.cs
@@ -93,7 +93,7 @@ public override IEnumerable GetTargets(GameLocation location, Vector2 l
/// The entity to look up.
private ISubject BuildSubject(Bush bush)
{
- return new BushSubject(this.GameHelper, bush);
+ return new BushSubject(this.Codex, this.GameHelper, bush);
}
/// Build a subject.
diff --git a/LookupAnything/Framework/Models/LinkTextArea.cs b/LookupAnything/Framework/Models/LinkTextArea.cs
new file mode 100644
index 000000000..4a9852378
--- /dev/null
+++ b/LookupAnything/Framework/Models/LinkTextArea.cs
@@ -0,0 +1,11 @@
+using Microsoft.Xna.Framework;
+using Pathoschild.Stardew.LookupAnything.Framework.Lookups;
+
+namespace Pathoschild.Stardew.LookupAnything.Framework.Models;
+
+/// Record for info about a linked text area within a field.
+/// Subject to open
+internal record LinkTextArea(ISubject Subject)
+{
+ internal Rectangle Rect { get; set; } = Rectangle.Empty;
+};
\ No newline at end of file
diff --git a/LookupAnything/Framework/Models/RecipeModel.cs b/LookupAnything/Framework/Models/RecipeModel.cs
index b7a885f3c..29523ab3f 100644
--- a/LookupAnything/Framework/Models/RecipeModel.cs
+++ b/LookupAnything/Framework/Models/RecipeModel.cs
@@ -159,7 +159,8 @@ public RecipeModel(Building building, RecipeIngredientModel[] ingredients, int g
DisplayText: TokenParser.ParseText(building.GetData()?.Name) ?? building.buildingType.Value,
Quality: null,
IsGoldPrice: false,
- IsValid: true
+ IsValid: true,
+ Entity: building
);
}
diff --git a/LookupAnything/ModEntry.cs b/LookupAnything/ModEntry.cs
index 7f9e14132..ef2242106 100644
--- a/LookupAnything/ModEntry.cs
+++ b/LookupAnything/ModEntry.cs
@@ -174,8 +174,22 @@ private void OnMenuChanged(object? sender, MenuChangedEventArgs e)
// restore the previous menu if it was hidden to show the lookup UI
this.Monitor.InterceptErrors("restoring the previous menu", () =>
{
- if (e.NewMenu == null && (e.OldMenu is LookupMenu or SearchMenu) && this.PreviousMenus.Value.Any())
- Game1.activeClickableMenu = this.PreviousMenus.Value.Pop();
+ if (e.NewMenu == null && (e.OldMenu is LookupMenu or SearchMenu) && this.PreviousMenus.Value.Any()){
+ // Special case for stack of lookup menus from showNewPage calls
+ // Rather than restore previous LookupMenus, exit directly to the first non LookupMenu
+ if (e.OldMenu is LookupMenu lookupMenu && lookupMenu.ExitWithoutRestore)
+ {
+ while (this.PreviousMenus.Value.Any() && this.PreviousMenus.Value.First() is LookupMenu)
+ {
+ // this triggers menu dispose properly, might be better way
+ Game1.activeClickableMenu = this.PreviousMenus.Value.Pop();
+ }
+ if (this.PreviousMenus.Value.Any())
+ Game1.activeClickableMenu = this.PreviousMenus.Value.Pop();
+ }
+ else
+ Game1.activeClickableMenu = this.PreviousMenus.Value.Pop();
+ }
});
}
diff --git a/LookupAnything/i18n/zh.json b/LookupAnything/i18n/zh.json
index 65636c8e5..511f74503 100644
--- a/LookupAnything/i18n/zh.json
+++ b/LookupAnything/i18n/zh.json
@@ -399,7 +399,7 @@
"item.fish-spawn-rules.min-fishing-level": "最低钓鱼等级:{{level}}",
"item.fish-spawn-rules.not-caught-yet": "尚未被抓住(只能被抓住一次)",
- "item.fish-spawn-rules.extended-family-quest-active": "\"Extended Family\" quest active", // TODO
+ "item.fish-spawn-rules.extended-family-quest-active": "\"大家族\"特别任务进行中",
"item.fish-spawn-rules.locations": "地点:{{locations}}",
"item.fish-spawn-rules.locations-by-season.label": "地点:",
"item.fish-spawn-rules.locations-by-season.season-locations": "{{season}}: {{locations}}",