Improve inventory UX: localized names, smart sorting, icons, box details, lore progress
- Resolve box names via BoxDefinition.NameKey instead of showing raw IDs - Reorder inventory categories: Box → Consumable → Lore → Cosmetic → Material → Meta - Replace English category/rarity text with emoji icons and localized rarity labels - Show box description in detail panel when selecting a box item - Add lore collection progress counter (N/10) in lore fragment detail panel - Add FR rarity translations (Commun, Peu commun, Rare, Épique, Légendaire, Mythique)
This commit is contained in:
parent
930128a766
commit
71a35cd19d
6 changed files with 225 additions and 26 deletions
|
|
@ -478,5 +478,13 @@
|
|||
"inventory.effect": "Effect",
|
||||
"inventory.press_enter_use": "Press Enter to use",
|
||||
"inventory.item_used": "{0} used!",
|
||||
"inventory.cosmetic_slot": "Slot"
|
||||
"inventory.cosmetic_slot": "Slot",
|
||||
"inventory.lore_progress": "Collected",
|
||||
|
||||
"rarity.common": "Common",
|
||||
"rarity.uncommon": "Uncommon",
|
||||
"rarity.rare": "Rare",
|
||||
"rarity.epic": "Epic",
|
||||
"rarity.legendary": "Legendary",
|
||||
"rarity.mythic": "Mythic"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -478,5 +478,13 @@
|
|||
"inventory.effect": "Effet",
|
||||
"inventory.press_enter_use": "Appuyer sur Entrée pour utiliser",
|
||||
"inventory.item_used": "{0} utilisé !",
|
||||
"inventory.cosmetic_slot": "Emplacement"
|
||||
"inventory.cosmetic_slot": "Emplacement",
|
||||
"inventory.lore_progress": "Collectés",
|
||||
|
||||
"rarity.common": "Commun",
|
||||
"rarity.uncommon": "Peu commun",
|
||||
"rarity.rare": "Rare",
|
||||
"rarity.epic": "Épique",
|
||||
"rarity.legendary": "Légendaire",
|
||||
"rarity.mythic": "Mythique"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -637,7 +637,7 @@ public static class Program
|
|||
|
||||
// Detail panel for selected item
|
||||
var selectedGroup = grouped[selectedIndex];
|
||||
var detailPanel = InventoryPanel.RenderDetailPanel(selectedGroup, _registry, _loc);
|
||||
var detailPanel = InventoryPanel.RenderDetailPanel(selectedGroup, _registry, _loc, _state);
|
||||
if (detailPanel is not null)
|
||||
AnsiConsole.Write(detailPanel);
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,25 @@ public static class InventoryPanel
|
|||
/// <summary>Compact row count for inline layout mode (fits in ~24-line terminals).</summary>
|
||||
public const int CompactVisibleRows = 6;
|
||||
|
||||
/// <summary>
|
||||
/// Custom sort order for categories — interactive items first.
|
||||
/// </summary>
|
||||
private static int CategorySortOrder(ItemCategory cat) => cat switch
|
||||
{
|
||||
ItemCategory.Box => 0,
|
||||
ItemCategory.Consumable => 1,
|
||||
ItemCategory.LoreFragment => 2,
|
||||
ItemCategory.AdventureToken => 3,
|
||||
ItemCategory.Key => 4,
|
||||
ItemCategory.Cosmetic => 5,
|
||||
ItemCategory.Material => 6,
|
||||
ItemCategory.CraftedItem => 7,
|
||||
ItemCategory.Meta => 8,
|
||||
ItemCategory.Cookie => 9,
|
||||
ItemCategory.Music => 10,
|
||||
_ => 11
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Returns the total number of distinct item groups in the inventory.
|
||||
/// </summary>
|
||||
|
|
@ -38,7 +57,7 @@ public static class InventoryPanel
|
|||
state.Inventory.GroupBy(i => i.DefinitionId).Count();
|
||||
|
||||
/// <summary>
|
||||
/// Returns grouped inventory items sorted by category then definition id.
|
||||
/// Returns grouped inventory items sorted by category (interactive first) then definition id.
|
||||
/// </summary>
|
||||
public static List<InventoryGroup> GetGroupedItems(GameState state, ContentRegistry? registry = null)
|
||||
{
|
||||
|
|
@ -55,11 +74,73 @@ public static class InventoryPanel
|
|||
def?.Rarity ?? ItemRarity.Common,
|
||||
g.First());
|
||||
})
|
||||
.OrderBy(x => x.Category)
|
||||
.OrderBy(x => CategorySortOrder(x.Category))
|
||||
.ThenBy(x => x.DefId)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a localized name for an inventory item, falling back to box name or definition id.
|
||||
/// </summary>
|
||||
private static string ResolveName(InventoryGroup item, ContentRegistry? registry, LocalizationManager? loc)
|
||||
{
|
||||
// Item definition has a name key
|
||||
if (item.Def is not null && loc is not null)
|
||||
return loc.Get(item.Def.NameKey);
|
||||
|
||||
// No item def — might be a box
|
||||
if (registry is not null && loc is not null)
|
||||
{
|
||||
var boxDef = registry.GetBox(item.DefId);
|
||||
if (boxDef is not null)
|
||||
return loc.Get(boxDef.NameKey);
|
||||
}
|
||||
|
||||
return item.DefId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a localized category label.
|
||||
/// </summary>
|
||||
private static string LocalizeCategory(ItemCategory cat, LocalizationManager? loc)
|
||||
{
|
||||
if (loc is null) return CategoryIcon(cat);
|
||||
return CategoryIcon(cat);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a compact emoji icon for a category.
|
||||
/// </summary>
|
||||
private static string CategoryIcon(ItemCategory cat) => cat switch
|
||||
{
|
||||
ItemCategory.Box => "📦",
|
||||
ItemCategory.Key => "🔑",
|
||||
ItemCategory.Consumable => "🧪",
|
||||
ItemCategory.LoreFragment => "📜",
|
||||
ItemCategory.Cosmetic => "👗",
|
||||
ItemCategory.Material => "🔩",
|
||||
ItemCategory.Meta => "⚙",
|
||||
ItemCategory.AdventureToken => "🗺",
|
||||
ItemCategory.Cookie => "🍪",
|
||||
ItemCategory.Music => "🎵",
|
||||
ItemCategory.CraftedItem => "🔨",
|
||||
ItemCategory.WorkstationBlueprint => "🏗",
|
||||
ItemCategory.Badge => "🏅",
|
||||
ItemCategory.Map => "🗺",
|
||||
ItemCategory.StoryItem => "📖",
|
||||
ItemCategory.QuestItem => "❓",
|
||||
_ => "•"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Returns a localized rarity label.
|
||||
/// </summary>
|
||||
private static string LocalizeRarity(ItemRarity rarity, LocalizationManager? loc)
|
||||
{
|
||||
if (loc is null) return rarity.ToString();
|
||||
return loc.Get($"rarity.{rarity.ToString().ToLower()}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a renderable inventory table.
|
||||
/// <paramref name="compact"/> uses fewer visible rows for inline layout mode.
|
||||
|
|
@ -78,7 +159,7 @@ public static class InventoryPanel
|
|||
var table = new Table()
|
||||
.Border(TableBorder.Rounded)
|
||||
.AddColumn(new TableColumn("[bold]Name[/]").Width(MaxNameWidth))
|
||||
.AddColumn(new TableColumn("[bold]Cat.[/]").Centered())
|
||||
.AddColumn(new TableColumn("").Centered().Width(2))
|
||||
.AddColumn(new TableColumn("[bold]Rarity[/]").Centered())
|
||||
.AddColumn(new TableColumn("[bold]Qty[/]").RightAligned());
|
||||
|
||||
|
|
@ -95,14 +176,12 @@ public static class InventoryPanel
|
|||
bool isSelected = globalIndex == selectedIndex;
|
||||
|
||||
// Resolve localized name, truncate if needed
|
||||
string name = item.Def is not null && loc is not null
|
||||
? loc.Get(item.Def.NameKey)
|
||||
: item.DefId;
|
||||
string name = ResolveName(item, registry, loc);
|
||||
if (name.Length > MaxNameWidth)
|
||||
name = name[..(MaxNameWidth - 1)] + "…";
|
||||
|
||||
string category = item.Category.ToString();
|
||||
string rarity = item.Rarity.ToString();
|
||||
string catIcon = LocalizeCategory(item.Category, loc);
|
||||
string rarity = LocalizeRarity(item.Rarity, loc);
|
||||
string color = RarityColor(item.Rarity);
|
||||
|
||||
if (isSelected)
|
||||
|
|
@ -110,7 +189,7 @@ public static class InventoryPanel
|
|||
// Highlighted row: reverse video style with arrow indicator
|
||||
table.AddRow(
|
||||
$"[bold {color} on grey23]► {Markup.Escape(name)}[/]",
|
||||
$"[bold on grey23]{Markup.Escape(category)}[/]",
|
||||
$"[bold on grey23]{catIcon}[/]",
|
||||
$"[bold {color} on grey23]{Markup.Escape(rarity)}[/]",
|
||||
$"[bold on grey23]{item.TotalQty}[/]");
|
||||
}
|
||||
|
|
@ -118,7 +197,7 @@ public static class InventoryPanel
|
|||
{
|
||||
table.AddRow(
|
||||
$"[{color}] {Markup.Escape(name)}[/]",
|
||||
$"[dim]{Markup.Escape(category)}[/]",
|
||||
$"[dim]{catIcon}[/]",
|
||||
$"[{color}]{Markup.Escape(rarity)}[/]",
|
||||
item.TotalQty.ToString());
|
||||
}
|
||||
|
|
@ -156,20 +235,22 @@ public static class InventoryPanel
|
|||
public static IRenderable? RenderDetailPanel(
|
||||
InventoryGroup? item,
|
||||
ContentRegistry? registry = null,
|
||||
LocalizationManager? loc = null)
|
||||
LocalizationManager? loc = null,
|
||||
GameState? state = null)
|
||||
{
|
||||
if (item?.Def is null)
|
||||
if (item is null)
|
||||
return null;
|
||||
|
||||
var rows = new List<IRenderable>();
|
||||
string color = RarityColor(item.Rarity);
|
||||
|
||||
// Item name
|
||||
string name = loc is not null ? loc.Get(item.Def.NameKey) : item.DefId;
|
||||
rows.Add(new Markup($"[bold {color}]{Markup.Escape(name)}[/] [dim]({Markup.Escape(item.Rarity.ToString())})[/]"));
|
||||
// Item name — resolve via item def or box def
|
||||
string name = ResolveName(item, registry, loc);
|
||||
string rarityLabel = LocalizeRarity(item.Rarity, loc);
|
||||
rows.Add(new Markup($"[bold {color}]{Markup.Escape(name)}[/] [dim]({Markup.Escape(rarityLabel)})[/]"));
|
||||
|
||||
// Description if available
|
||||
if (item.Def.DescriptionKey is not null && loc is not null)
|
||||
// Description if available (item def)
|
||||
if (item.Def?.DescriptionKey is not null && loc is not null)
|
||||
{
|
||||
string desc = loc.Get(item.Def.DescriptionKey);
|
||||
rows.Add(new Markup($"[italic]{Markup.Escape(desc)}[/]"));
|
||||
|
|
@ -178,7 +259,20 @@ public static class InventoryPanel
|
|||
// Category-specific details
|
||||
switch (item.Category)
|
||||
{
|
||||
case ItemCategory.Consumable when item.Def.ResourceType.HasValue && item.Def.ResourceAmount.HasValue:
|
||||
case ItemCategory.Box:
|
||||
// Show box description if available
|
||||
if (registry is not null)
|
||||
{
|
||||
var boxDef = registry.GetBox(item.DefId);
|
||||
if (boxDef is not null && loc is not null)
|
||||
{
|
||||
string boxDesc = loc.Get(boxDef.DescriptionKey);
|
||||
rows.Add(new Markup($"[italic dim]{Markup.Escape(boxDesc)}[/]"));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ItemCategory.Consumable when item.Def?.ResourceType is not null && item.Def.ResourceAmount is not null:
|
||||
string resName = loc?.Get($"resource.{item.Def.ResourceType.Value.ToString().ToLower()}") ?? item.Def.ResourceType.Value.ToString();
|
||||
string sign = item.Def.ResourceAmount.Value >= 0 ? "+" : "";
|
||||
rows.Add(new Markup($"[green]{Markup.Escape(loc?.Get("inventory.effect") ?? "Effect")}:[/] {sign}{item.Def.ResourceAmount.Value} {Markup.Escape(resName)}"));
|
||||
|
|
@ -194,14 +288,25 @@ public static class InventoryPanel
|
|||
rows.Add(new Markup(""));
|
||||
rows.Add(new Markup($"[italic dim]{Markup.Escape(loreText)}[/]"));
|
||||
}
|
||||
// Show collection progress
|
||||
if (state is not null)
|
||||
{
|
||||
int collected = state.Inventory
|
||||
.Select(i => i.DefinitionId)
|
||||
.Where(id => id.StartsWith("lore_"))
|
||||
.Distinct()
|
||||
.Count();
|
||||
string progressLabel = loc?.Get("inventory.lore_progress") ?? "Collected";
|
||||
rows.Add(new Markup($"[dim]{Markup.Escape(progressLabel)}: {collected}/10[/]"));
|
||||
}
|
||||
break;
|
||||
|
||||
case ItemCategory.Cosmetic when item.Def.CosmeticSlot.HasValue:
|
||||
case ItemCategory.Cosmetic when item.Def?.CosmeticSlot is not null:
|
||||
string slotName = loc?.Get($"cosmetic.slot.{item.Def.CosmeticSlot.Value.ToString().ToLower()}") ?? item.Def.CosmeticSlot.Value.ToString();
|
||||
rows.Add(new Markup($"[dim]{Markup.Escape(loc?.Get("inventory.cosmetic_slot") ?? "Slot")}: {Markup.Escape(slotName)}[/]"));
|
||||
break;
|
||||
|
||||
case ItemCategory.Material when item.Def.MaterialType.HasValue:
|
||||
case ItemCategory.Material when item.Def?.MaterialType is not null:
|
||||
rows.Add(new Markup($"[dim]{Markup.Escape(item.Def.MaterialType.Value.ToString())} ({Markup.Escape(item.Def.MaterialForm?.ToString() ?? "Raw")})[/]"));
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
78
suggestions_2.md
Normal file
78
suggestions_2.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# Suggestions d'amélioration #2
|
||||
|
||||
Basées sur l'analyse des rendus InventoryRenderCapture et PlaythroughCapture.
|
||||
|
||||
| # | Suggestion | Priorité | Statut |
|
||||
|---|-----------|----------|--------|
|
||||
| 1 | Noms localisés pour les boîtes dans l'inventaire | ★★★★★ | ✅ DONE |
|
||||
| 2 | Tri de l'inventaire : consommables et lore avant matériaux | ★★★★★ | ✅ DONE |
|
||||
| 3 | Colonnes Category/Rarity localisées | ★★★★☆ | ✅ DONE |
|
||||
| 4 | Panneau de détail pour les boîtes (contenu possible) | ★★★★☆ | ✅ DONE |
|
||||
| 5 | Compteur de lore collectés (N/10) dans le panneau détails | ★★★★☆ | ✅ DONE |
|
||||
| 6 | Icônes/emojis par catégorie dans l'inventaire | ★★★☆☆ |
|
||||
| 7 | Résumé de l'inventaire dans le panneau compact (nombre par catégorie) | ★★★☆☆ |
|
||||
| 8 | Panneau de détail pour les items Meta (explication du feature) | ★★★☆☆ |
|
||||
| 9 | Message de bienvenue adaptatif au nombre de boîtes ouvertes | ★★☆☆☆ |
|
||||
| 10 | Indicateur visuel de scroll restant (↑↓ en haut/bas du tableau) | ★★☆☆☆ |
|
||||
|
||||
---
|
||||
|
||||
## 1. Noms localisés pour les boîtes dans l'inventaire — ★★★★★
|
||||
|
||||
**Constat** : Les boîtes apparaissent avec leur ID technique (`box_not_great`, `box_legendhair`, `box_meta_basics`) au lieu de leur nom localisé. C'est le défaut visuel le plus flagrant dans l'inventaire.
|
||||
|
||||
**Solution** : Dans `InventoryPanel.GetGroupedItems()` ou `Render()`, quand l'item n'a pas de `ItemDefinition` (c'est une boîte), résoudre le nom via le `BoxDefinition.NameKey` du registre.
|
||||
|
||||
## 2. Tri de l'inventaire : consommables et lore avant matériaux — ★★★★★
|
||||
|
||||
**Constat** : L'ordre actuel (Box, Cosmetic, Material, Consumable, Meta, LoreFragment…) met les items interactifs (consommables, lore) loin dans la liste, après des dizaines de cosmétiques et matériaux non-actionnables.
|
||||
|
||||
**Solution** : Modifier l'ordre de tri dans `GetGroupedItems()` pour prioriser : Box → Consumable → LoreFragment → Cosmetic → Material → Meta → reste.
|
||||
|
||||
## 3. Colonnes Category/Rarity localisées — ★★★★☆
|
||||
|
||||
**Constat** : Les colonnes "Cat." et "Rarity" affichent les valeurs enum en anglais (`Consumable`, `Uncommon`, `Material`) même en locale FR. Ça casse l'immersion.
|
||||
|
||||
**Solution** : Ajouter des clés de localisation `category.box`, `category.consumable`, `rarity.common`, `rarity.uncommon`, etc. et les utiliser dans `Render()`.
|
||||
|
||||
## 4. Panneau de détail pour les boîtes (contenu possible) — ★★★★☆
|
||||
|
||||
**Constat** : Sélectionner une boîte dans l'inventaire ne montre aucun détail utile. Le joueur ne sait pas ce qu'elle peut contenir.
|
||||
|
||||
**Solution** : Ajouter un cas `ItemCategory.Box` dans `RenderDetailPanel()` qui affiche le nom localisé de la boîte et un indice sur son contenu (rareté, thème).
|
||||
|
||||
## 5. Compteur de lore collectés (N/10) dans le panneau détails — ★★★★☆
|
||||
|
||||
**Constat** : Quand on sélectionne un fragment de lore, on ne sait pas combien on en a collecté sur le total (10). C'est une mécanique de collection qui bénéficierait d'un indicateur de progression.
|
||||
|
||||
**Solution** : Compter les `lore_*` distincts dans l'inventaire et afficher "Fragments : 3/10" dans le panneau de détail des lore.
|
||||
|
||||
## 6. Icônes/emojis par catégorie dans l'inventaire — ★★★☆☆
|
||||
|
||||
**Constat** : La colonne catégorie est textuelle et prend de la place. Des icônes compactes seraient plus lisibles.
|
||||
|
||||
**Solution** : Remplacer le texte de catégorie par des emojis (📦 Box, 🧪 Consumable, 📜 Lore, 👗 Cosmetic, 🔩 Material, ⚙ Meta).
|
||||
|
||||
## 7. Résumé de l'inventaire dans le panneau compact — ★★★☆☆
|
||||
|
||||
**Constat** : En mode compact (FullLayout), seuls 6 items sont visibles, sans indication du nombre total par catégorie.
|
||||
|
||||
**Solution** : Ajouter une ligne de pied de tableau montrant un résumé : "📦3 🧪5 📜2 👗12 🔩8".
|
||||
|
||||
## 8. Panneau de détail pour les items Meta — ★★★☆☆
|
||||
|
||||
**Constat** : Les items Meta (ex: "Couleurs de texte") n'ont aucune description dans le panneau de détails.
|
||||
|
||||
**Solution** : Ajouter des `descriptionKey` pour les items meta dans items.json et les afficher dans le panneau détails.
|
||||
|
||||
## 9. Message de bienvenue adaptatif — ★★☆☆☆
|
||||
|
||||
**Constat** : Le message de bienvenue est identique que le joueur ait 0 ou 500 boîtes ouvertes. C'est une occasion manquée de donner du feedback.
|
||||
|
||||
**Solution** : Adapter le message d'accueil selon la progression.
|
||||
|
||||
## 10. Indicateur visuel de scroll dans l'inventaire — ★★☆☆☆
|
||||
|
||||
**Constat** : Quand l'inventaire est scrollable, le joueur ne voit que "(1-15/55)" dans le titre. Il n'y a pas d'indication visuelle dans le tableau lui-même.
|
||||
|
||||
**Solution** : Ajouter des symboles ▲/▼ en première/dernière ligne quand il y a du contenu au-dessus/en-dessous.
|
||||
|
|
@ -1632,7 +1632,7 @@ public class ContentValidationTests
|
|||
if (grouped.Count > 0)
|
||||
{
|
||||
writer.GetStringBuilder().Clear();
|
||||
var detail = InventoryPanel.RenderDetailPanel(grouped[0], registry, loc);
|
||||
var detail = InventoryPanel.RenderDetailPanel(grouped[0], registry, loc, state);
|
||||
if (detail is not null)
|
||||
{
|
||||
console.Write(detail);
|
||||
|
|
@ -1642,7 +1642,7 @@ public class ContentValidationTests
|
|||
}
|
||||
|
||||
// Show detail panel for specific item types if present
|
||||
var interestingTypes = new[] { ItemCategory.Consumable, ItemCategory.LoreFragment, ItemCategory.Cosmetic, ItemCategory.Material };
|
||||
var interestingTypes = new[] { ItemCategory.Box, ItemCategory.Consumable, ItemCategory.LoreFragment, ItemCategory.Cosmetic, ItemCategory.Material };
|
||||
foreach (var cat in interestingTypes)
|
||||
{
|
||||
var sample = grouped.FirstOrDefault(g => g.Category == cat);
|
||||
|
|
@ -1651,7 +1651,7 @@ public class ContentValidationTests
|
|||
int idx = grouped.IndexOf(sample);
|
||||
report.AppendLine($"│ ── Detail for [{cat}]: {sample.DefId} ──");
|
||||
writer.GetStringBuilder().Clear();
|
||||
var detailAlt = InventoryPanel.RenderDetailPanel(sample, registry, loc);
|
||||
var detailAlt = InventoryPanel.RenderDetailPanel(sample, registry, loc, state);
|
||||
if (detailAlt is not null)
|
||||
{
|
||||
console.Write(detailAlt);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue