From d079faa2e6965db8fc603594af14c3716000a812 Mon Sep 17 00:00:00 2001 From: Samuel Bouchet Date: Sun, 15 Mar 2026 20:00:47 +0100 Subject: [PATCH] Stop meta box drops when all unlocks obtained, add next-step hints - BoxEngine: treat meta box chain items as "already obtained" when the entire meta unlock sequence is complete, preventing useless box_meta_mastery drops that only contain more boxes. - Add progression hints under completion tracker showing lore fragment count, adventure completion count, and a teaser for the final adventure. --- content/strings/en.json | 3 +++ content/strings/fr.json | 3 +++ src/OpenTheBox/Program.cs | 21 +++++++++++++++++++++ src/OpenTheBox/Rendering/RenderContext.cs | 3 +++ src/OpenTheBox/Rendering/SpectreRenderer.cs | 8 ++++++++ src/OpenTheBox/Simulation/BoxEngine.cs | 4 ++++ 6 files changed, 42 insertions(+) diff --git a/content/strings/en.json b/content/strings/en.json index 2d9f796..57dc3b7 100644 --- a/content/strings/en.json +++ b/content/strings/en.json @@ -43,6 +43,9 @@ "loot.category": "Category", "ui.feature_unlocked": "NEW FEATURE UNLOCKED: {0}", "ui.completion": "Completion: {0}%", + "hint.lore": "Lore Fragments: {0}/{1}", + "hint.adventures": "Adventures completed: {0}/{1}", + "hint.destiny": "Complete all adventures to unlock the final adventure…", "prompt.what_do": "What do you do?", "prompt.invalid_choice": "Please enter a number between 1 and {0}.", diff --git a/content/strings/fr.json b/content/strings/fr.json index 45dd915..9f202dc 100644 --- a/content/strings/fr.json +++ b/content/strings/fr.json @@ -43,6 +43,9 @@ "loot.category": "Catégorie", "ui.feature_unlocked": "NOUVELLE FONCTIONNALITÉ : {0}", "ui.completion": "Complétion : {0}%", + "hint.lore": "Fragments de Lore : {0}/{1}", + "hint.adventures": "Aventures terminées : {0}/{1}", + "hint.destiny": "Termine toutes les aventures pour débloquer l'aventure finale…", "prompt.what_do": "Que fais-tu ?", "prompt.invalid_choice": "Entre un nombre entre 1 et {0}.", diff --git a/src/OpenTheBox/Program.cs b/src/OpenTheBox/Program.cs index 9b48577..e8d4772 100644 --- a/src/OpenTheBox/Program.cs +++ b/src/OpenTheBox/Program.cs @@ -1013,6 +1013,27 @@ public static class Program + _state.VisibleStats.Count; _renderContext.CompletionPercent = total > 0 ? (int)(unlocked * 100.0 / total) : 0; + + // Build next-step hints + var hints = new List(); + + // Lore fragments + var totalLore = _registry.Items.Values.Count(i => i.Category == ItemCategory.LoreFragment); + var ownedLore = _state.Inventory.Count(i => + _registry.GetItem(i.DefinitionId)?.Category == ItemCategory.LoreFragment); + if (ownedLore < totalLore) + hints.Add(_loc.Get("hint.lore", ownedLore, totalLore)); + + // Adventures completed + var completedAdv = _state.CompletedAdventures.Count; + if (completedAdv < totalAdventures) + hints.Add(_loc.Get("hint.adventures", completedAdv, totalAdventures)); + + // Destiny adventure not yet unlocked — show condition + if (!_state.UnlockedAdventures.Contains(AdventureTheme.Destiny)) + hints.Add(_loc.Get("hint.destiny")); + + _renderContext.NextHints = hints; } private static string GetUIFeatureLocKey(UIFeature feature) => feature switch diff --git a/src/OpenTheBox/Rendering/RenderContext.cs b/src/OpenTheBox/Rendering/RenderContext.cs index 6fb3946..9bb8969 100644 --- a/src/OpenTheBox/Rendering/RenderContext.cs +++ b/src/OpenTheBox/Rendering/RenderContext.cs @@ -40,6 +40,9 @@ public sealed class RenderContext /// Completion percentage (0-100), updated before each render call. public int CompletionPercent { get; set; } + /// Short hints about what to do next, displayed under completion tracker. + public List NextHints { get; set; } = []; + /// /// Builds a that mirrors the features already unlocked in a /// . diff --git a/src/OpenTheBox/Rendering/SpectreRenderer.cs b/src/OpenTheBox/Rendering/SpectreRenderer.cs index 335bffa..ad61970 100644 --- a/src/OpenTheBox/Rendering/SpectreRenderer.cs +++ b/src/OpenTheBox/Rendering/SpectreRenderer.cs @@ -497,7 +497,11 @@ public sealed class SpectreRenderer : IRenderer rightItems.Add(CraftingPanel.Render(state, _registry, _loc)); if (context.HasCompletionTracker) + { rightItems.Add(new Markup($"[bold cyan]{Markup.Escape(_loc.Get("ui.completion", context.CompletionPercent))}[/]")); + foreach (var hint in context.NextHints) + rightItems.Add(new Markup($" [dim]{Markup.Escape(hint)}[/]")); + } IRenderable rightPanel = rightItems.Count > 0 ? new Rows(rightItems) @@ -545,6 +549,10 @@ public sealed class SpectreRenderer : IRenderer AnsiConsole.Write(CraftingPanel.Render(state, _registry, _loc)); if (context.HasCompletionTracker) + { AnsiConsole.Write(new Rule($"[bold cyan]{Markup.Escape(_loc.Get("ui.completion", context.CompletionPercent))}[/]").RuleStyle("cyan")); + foreach (var hint in context.NextHints) + AnsiConsole.MarkupLine($" [dim]{Markup.Escape(hint)}[/]"); + } } } diff --git a/src/OpenTheBox/Simulation/BoxEngine.cs b/src/OpenTheBox/Simulation/BoxEngine.cs index 0420583..8390879 100644 --- a/src/OpenTheBox/Simulation/BoxEngine.cs +++ b/src/OpenTheBox/Simulation/BoxEngine.cs @@ -221,6 +221,10 @@ public class BoxEngine(ContentRegistry registry) if (itemDef.MetaUnlock.HasValue && state.UnlockedUIFeatures.Contains(itemDef.MetaUnlock.Value)) return true; + // Meta box chain: treat as obtained when the entire chain is exhausted + if (Array.IndexOf(MetaBoxChain, itemDefId) >= 0 && MetaEngine.GetNextMetaItemId(state, registry) is null) + return true; + // Resource visibility already unlocked (only Meta items, not consumables) if (itemDef.ResourceType.HasValue && itemDef.Category == ItemCategory.Meta && state.VisibleResources.Contains(itemDef.ResourceType.Value))