From 308ddd4aa139c123558a6c260daac7e68c40494f Mon Sep 17 00:00:00 2001 From: mushymato Date: Mon, 4 Nov 2024 18:49:02 -0500 Subject: [PATCH 1/2] zh translations --- ChestsAnywhere/i18n/zh.json | 12 ++++++------ CropsAnytimeAnywhere/i18n/zh.json | 5 ++--- FastAnimations/i18n/zh.json | 4 ++-- LookupAnything/i18n/zh.json | 2 +- 4 files changed, 11 insertions(+), 12 deletions(-) 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/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}}", From b621503d030ce7ba3771d62f5fcd11a7dccbd52a Mon Sep 17 00:00:00 2001 From: mushymato Date: Mon, 4 Nov 2024 22:35:41 -0500 Subject: [PATCH 2/2] Redo the item recipe links PR --- LookupAnything/Components/LookupMenu.cs | 29 ++++++++- .../Framework/Fields/FishPondDropsField.cs | 15 ++++- .../Framework/Fields/GenericField.cs | 36 +++++++++++ .../Framework/Fields/ICustomField.cs | 4 ++ .../Framework/Fields/ItemDropListField.cs | 15 ++++- .../Framework/Fields/ItemRecipesField.cs | 59 +++++++++++++++++-- .../Fields/Models/RecipeItemEntry.cs | 3 +- .../Buildings/BuildingLookupProvider.cs | 8 +++ .../Lookups/Buildings/BuildingSubject.cs | 4 +- .../Lookups/Characters/CharacterSubject.cs | 2 +- .../Framework/Lookups/Items/ItemSubject.cs | 4 +- .../Lookups/TerrainFeatures/BushSubject.cs | 8 ++- .../TerrainFeatureLookupProvider.cs | 2 +- .../Framework/Models/LinkTextArea.cs | 11 ++++ .../Framework/Models/RecipeModel.cs | 3 +- LookupAnything/ModEntry.cs | 18 +++++- 16 files changed, 198 insertions(+), 23 deletions(-) create mode 100644 LookupAnything/Framework/Models/LinkTextArea.cs 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(); + } }); }