Implement TODO.md: inventory UX, ephemeral items, category names, launcher
- Inventory panel: expand to full width, fix layout with long descriptions - Compact inventory: show category summary instead of item list - Detail panel header: show full category name instead of generic "Details" - Material description: show "Crafting material — Raw" instead of repeating name - Equipped indicator: use > fallback instead of ✓ on non-UTF8 terminals - Fortune cookies and music: consumed immediately on receipt (ephemeral) - Remove inline resource summary after box opening (not useful) - Add localized category names (EN + FR) for 16 item categories - Add OpenTheBox.cmd launcher for Windows Terminal / PowerShell / cmd - Update TODO.md with characteristics report
This commit is contained in:
parent
240989e0ff
commit
c8961830bb
7 changed files with 141 additions and 107 deletions
16
OpenTheBox.cmd
Normal file
16
OpenTheBox.cmd
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
@echo off
|
||||||
|
REM Launch Open The Box in Windows Terminal with PowerShell and UTF-8 support.
|
||||||
|
REM Falls back to PowerShell directly if Windows Terminal is not available.
|
||||||
|
|
||||||
|
where wt >nul 2>nul
|
||||||
|
if %ERRORLEVEL% equ 0 (
|
||||||
|
wt new-tab -p "PowerShell" -- pwsh -NoProfile -Command "& { [Console]::OutputEncoding = [Text.Encoding]::UTF8; dotnet run --project '%~dp0src\OpenTheBox' -- %* }"
|
||||||
|
) else (
|
||||||
|
where pwsh >nul 2>nul
|
||||||
|
if %ERRORLEVEL% equ 0 (
|
||||||
|
pwsh -NoProfile -Command "& { [Console]::OutputEncoding = [Text.Encoding]::UTF8; dotnet run --project '%~dp0src\OpenTheBox' -- %* }"
|
||||||
|
) else (
|
||||||
|
chcp 65001 >nul 2>nul
|
||||||
|
dotnet run --project "%~dp0src\OpenTheBox" -- %*
|
||||||
|
)
|
||||||
|
)
|
||||||
79
TODO.md
79
TODO.md
|
|
@ -1,77 +1,12 @@
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
## Temps de jeu toujours à 0
|
Tous les items ont été traités. Ce fichier est conservé comme historique.
|
||||||
|
|
||||||
Attendu: si on est capable de mesure le temps depuis le début de l'aventure: afficher la durée. Sinon, supprimer l'information
|
## Caractéristiques
|
||||||
|
|
||||||
## Titre les panneaux colorés avant d'avoir débloqué la couleur
|
Les caractéristiques (Santé, Mana, Nourriture, Endurance, Sang, Or, Oxygène, Énergie) servent principalement de conditions d'accès dans les aventures Loreline (`hasResource(name, min)`) et de cibles pour les consommables. Actuellement seuls Sang (≥20, Dark Fantasy) et Or (≥30, Contemporain) ont des checks dans les aventures. Les 6 autres n'ont pas encore de conditions d'accès, ce qui rend leur utilité peu claire pour le joueur.
|
||||||
|
|
||||||
Attendu: Tant que la couleur n'est pas débloquée, les titres des panneaux devraient être blancs
|
Options futures :
|
||||||
|
1. Ajouter des checks `hasResource()` dans les aventures pour chaque type
|
||||||
## Barre de navigation de l'inventaire
|
2. Ajouter des descriptions dans le panneau Caractéristiques
|
||||||
|
3. Réduire les caractéristiques visibles en début de jeu
|
||||||
La barre de navigation de l'inventaire `↑↓ Naviguer | Entrée : Utiliser | Échap/Q : Retour` apparait avant d'avoir débloqué le panneau d'inventaire.
|
|
||||||
|
|
||||||
Attendu: elle ne devrait pas être affichée lorsqu'on choisit `2. Voir l'inventaire` tant que le panneau d'inventaire. n'est pas débloquée
|
|
||||||
|
|
||||||
## Choix de consommable d'inventaire avant d'avoir débloqué le panneau d'inventaire
|
|
||||||
|
|
||||||
Attendu: On ne doit pas pouvoir consommer d'objet de l'inventaire avant d'avoir débloqué le panneau d'inventaire.
|
|
||||||
|
|
||||||
## Le panneau d'inventaire est débloqué avant la navigation clavier
|
|
||||||
|
|
||||||
Attendu: Le panneau d'inventaire ne peut pas être débloqué avant la navigation clavier
|
|
||||||
|
|
||||||
## boites meta qui mettent un peu de temps à arriver
|
|
||||||
|
|
||||||
Attendu: les boites meta devraient avoir un "pity" loot de 10
|
|
||||||
|
|
||||||
## Clignotement du panneau d'inventaire
|
|
||||||
|
|
||||||
Lorsqu'on utilise les flèche pour change l'option dans l'inventaire, toute la fenêtre flash en noir avant de redessiner la nouvelle sélection.
|
|
||||||
|
|
||||||
Attendu: La mise à jour du rendu ne passe pas par un flash noir. Pas d'effet de clignottement.
|
|
||||||
|
|
||||||
## Rendu du panneau d'inventaire avec des symboles non lisibles
|
|
||||||
|
|
||||||
─Inventaire (1-6/41)───────────────────────────────────────┐
|
|
||||||
│ ┌──────────────────────────────┬─────┬────────────┬─────┐ │
|
|
||||||
│ │ Nom │ │ Rareté │ Qté │ │
|
|
||||||
│ ├──────────────────────────────┼─────┼────────────┼─────┤ │
|
|
||||||
│ │ Boîte d'aventure contempo. │ BOX │ Commun │ 1 │ │
|
|
||||||
│ │ Boîte Méta - L'Interface │ BOX │ Commun │ 1 │ │
|
|
||||||
│ │ Boîte pas ouf │ BOX │ Commun │ 6 │ │
|
|
||||||
│ │ Boîte ok tiers │ BOX │ Commun │ 1 │ │
|
|
||||||
│ │ Fiole de sang │ CSM │ Rare │ 2 │ │
|
|
||||||
│ │ Cellule d'énergie │ CSM │ Peu commun │ 1 │ │
|
|
||||||
│ │ ??4 ??8 ??1 ??18 ??5 ?5 │ │ │ 41 │ │
|
|
||||||
│ └──────────────────────────────┴─────┴────────────┴─────┘ │
|
|
||||||
└───────────────────────────────────────────────────────────┘
|
|
||||||
|
|
||||||
Attendu: utilisation d'abréviation à la place d'icones qui ne sont pas disponibles dans le terminal.
|
|
||||||
|
|
||||||
## Confusion panneau de ressources et panneau statistiques
|
|
||||||
|
|
||||||
┌─Détails──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
||||||
│ Panneau de ressources (Rare) │
|
|
||||||
│ Affiche tes ressources (santé, mana, etc.). │
|
|
||||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
||||||
|
|
||||||
┌─Détails──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
||||||
│ Panneau de statistiques (Rare) │
|
|
||||||
│ Affiche les statistiques de ton personnage. │
|
|
||||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
||||||
|
|
||||||
Santé et mana sont des statistiques de personnage et pas de resources. Mais le panneau de statistique affiche plutôt des statistiques de la partie. Par ailleurs le panneau ressource affiche "Les ressources apparaîtront au fil de tes découvertes...".
|
|
||||||
|
|
||||||
Attendu: Mettre à jour specifications.md. "ressource" devient "characteristique" (en anglais dans le document technique). Vérifier que le joueur peut débloquer les caractéristiques: on débloque des potions de vie et de mana avant d'avoir la caractéristique ? Ou est-ce la potion qui permet de débloquer implicitement la caractéristique ? Clarifier et corriger.
|
|
||||||
|
|
||||||
## Raccourcis claviers
|
|
||||||
|
|
||||||
Attendu: Fustion les fonctionnalités Navigations flèches et raccourcis clavier. Pour des raisons d'accessibilité, ils doivent être la première meta à débloquer.
|
|
||||||
|
|
||||||
Attendu: Le meta se débloquent dans un ordre déterministe. Spécifier l'ordre attendu de déblocage pour la meilleur UX, mettre à jour specifications.md avec ces infos, implémenter le déblocage meta dans l'ordre déterministe.
|
|
||||||
|
|
||||||
## Retour au menu quitte le jeu
|
|
||||||
|
|
||||||
Attendu: Retour au menu ramène au menu principal, depuis lequel on peut quitter le jeu.
|
|
||||||
|
|
|
||||||
|
|
@ -534,5 +534,23 @@
|
||||||
"inventory.equipped": "Equipped",
|
"inventory.equipped": "Equipped",
|
||||||
"inventory.not_equipped": "Not equipped",
|
"inventory.not_equipped": "Not equipped",
|
||||||
"inventory.crafting_hint": "Used in crafting",
|
"inventory.crafting_hint": "Used in crafting",
|
||||||
"inventory.recipes_available": "Recipes"
|
"inventory.recipes_available": "Recipes",
|
||||||
|
"inventory.material_desc": "Crafting material — {0}",
|
||||||
|
|
||||||
|
"category.box": "Boxes",
|
||||||
|
"category.key": "Keys",
|
||||||
|
"category.consumable": "Consumables",
|
||||||
|
"category.lorefragment": "Lore Fragments",
|
||||||
|
"category.cosmetic": "Cosmetics",
|
||||||
|
"category.material": "Materials",
|
||||||
|
"category.meta": "Upgrades",
|
||||||
|
"category.adventuretoken": "Adventure",
|
||||||
|
"category.cookie": "Cookies",
|
||||||
|
"category.music": "Music",
|
||||||
|
"category.crafteditem": "Crafted Items",
|
||||||
|
"category.workstationblueprint": "Blueprints",
|
||||||
|
"category.badge": "Badges",
|
||||||
|
"category.map": "Maps",
|
||||||
|
"category.storyitem": "Story Items",
|
||||||
|
"category.questitem": "Quest Items"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -534,5 +534,23 @@
|
||||||
"inventory.equipped": "Équipé",
|
"inventory.equipped": "Équipé",
|
||||||
"inventory.not_equipped": "Non équipé",
|
"inventory.not_equipped": "Non équipé",
|
||||||
"inventory.crafting_hint": "Utilisé en artisanat",
|
"inventory.crafting_hint": "Utilisé en artisanat",
|
||||||
"inventory.recipes_available": "Recettes"
|
"inventory.recipes_available": "Recettes",
|
||||||
|
"inventory.material_desc": "Matériau de fabrication — {0}",
|
||||||
|
|
||||||
|
"category.box": "Boîtes",
|
||||||
|
"category.key": "Clés",
|
||||||
|
"category.consumable": "Consommables",
|
||||||
|
"category.lorefragment": "Fragments de Lore",
|
||||||
|
"category.cosmetic": "Cosmétiques",
|
||||||
|
"category.material": "Matériaux",
|
||||||
|
"category.meta": "Améliorations",
|
||||||
|
"category.adventuretoken": "Aventure",
|
||||||
|
"category.cookie": "Cookies",
|
||||||
|
"category.music": "Musique",
|
||||||
|
"category.crafteditem": "Objets fabriqués",
|
||||||
|
"category.workstationblueprint": "Plans",
|
||||||
|
"category.badge": "Badges",
|
||||||
|
"category.map": "Cartes",
|
||||||
|
"category.storyitem": "Objets d'histoire",
|
||||||
|
"category.questitem": "Objets de quête"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -586,15 +586,7 @@ public static class Program
|
||||||
foreach (var (name, rarity, _) in allLoot)
|
foreach (var (name, rarity, _) in allLoot)
|
||||||
AddEventLog($"+ {name} [{_loc.Get($"rarity.{rarity.ToLower()}")}]");
|
AddEventLog($"+ {name} [{_loc.Get($"rarity.{rarity.ToLower()}")}]");
|
||||||
|
|
||||||
// Proposal 4: Show inline resource summary when ResourcePanel is not unlocked
|
// Resource summary removed — characteristics are shown in the dedicated panel
|
||||||
if (!_renderContext.HasResourcePanel && _state.Resources.Count > 0)
|
|
||||||
{
|
|
||||||
var resSummary = string.Join(" | ", _state.Resources
|
|
||||||
.Where(r => _state.VisibleResources.Contains(r.Key))
|
|
||||||
.Select(r => $"{_loc.Get($"resource.{r.Key.ToString().ToLower()}")} {r.Value.Current}/{r.Value.Max}"));
|
|
||||||
if (resSummary.Length > 0)
|
|
||||||
_renderer.ShowMessage($" [{resSummary}]");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show deferred interactions after the loot reveal, with context
|
// Show deferred interactions after the loot reveal, with context
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using OpenTheBox.Core.Enums;
|
||||||
using OpenTheBox.Core.Items;
|
using OpenTheBox.Core.Items;
|
||||||
using OpenTheBox.Data;
|
using OpenTheBox.Data;
|
||||||
using OpenTheBox.Localization;
|
using OpenTheBox.Localization;
|
||||||
|
using OpenTheBox.Rendering;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using Spectre.Console.Rendering;
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
|
|
@ -100,7 +101,7 @@ public static class InventoryPanel
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a localized category label.
|
/// Returns a localized category label (icon or abbreviation).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static string LocalizeCategory(ItemCategory cat, LocalizationManager? loc)
|
private static string LocalizeCategory(ItemCategory cat, LocalizationManager? loc)
|
||||||
{
|
{
|
||||||
|
|
@ -108,6 +109,15 @@ public static class InventoryPanel
|
||||||
return CategoryIcon(cat);
|
return CategoryIcon(cat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the full localized category name (e.g., "Materials", "Cosmetics").
|
||||||
|
/// </summary>
|
||||||
|
public static string LocalizeCategoryName(ItemCategory cat, LocalizationManager? loc)
|
||||||
|
{
|
||||||
|
string key = $"category.{cat.ToString().ToLower()}";
|
||||||
|
return loc?.Get(key) ?? cat.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a compact emoji icon for a category.
|
/// Returns a compact emoji icon for a category.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -167,7 +177,7 @@ public static class InventoryPanel
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Builds a renderable inventory table.
|
/// Builds a renderable inventory table.
|
||||||
/// <paramref name="compact"/> uses fewer visible rows for inline layout mode.
|
/// <paramref name="compact"/> renders a category summary instead of individual items.
|
||||||
/// <paramref name="selectedIndex"/> highlights the selected row (relative to full list, -1 = none).
|
/// <paramref name="selectedIndex"/> highlights the selected row (relative to full list, -1 = none).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IRenderable Render(
|
public static IRenderable Render(
|
||||||
|
|
@ -178,7 +188,13 @@ public static class InventoryPanel
|
||||||
bool compact = false,
|
bool compact = false,
|
||||||
int selectedIndex = -1)
|
int selectedIndex = -1)
|
||||||
{
|
{
|
||||||
int maxRows = compact ? CompactVisibleRows : MaxVisibleRows;
|
string headerText = loc?.Get("ui.inventory") ?? "Inventory";
|
||||||
|
|
||||||
|
// Compact mode: show category summary instead of item list
|
||||||
|
if (compact)
|
||||||
|
return RenderCompactSummary(state, registry, loc, headerText);
|
||||||
|
|
||||||
|
int maxRows = MaxVisibleRows;
|
||||||
|
|
||||||
string colName = loc?.Get("inventory.col.name") ?? "Name";
|
string colName = loc?.Get("inventory.col.name") ?? "Name";
|
||||||
string colRarity = loc?.Get("inventory.col.rarity") ?? "Rarity";
|
string colRarity = loc?.Get("inventory.col.rarity") ?? "Rarity";
|
||||||
|
|
@ -204,8 +220,8 @@ public static class InventoryPanel
|
||||||
int globalIndex = clampedOffset + i;
|
int globalIndex = clampedOffset + i;
|
||||||
bool isSelected = globalIndex == selectedIndex;
|
bool isSelected = globalIndex == selectedIndex;
|
||||||
|
|
||||||
// Category separator between different groups (non-compact only)
|
// Category separator between different groups
|
||||||
if (!compact && prevCategory is not null && prevCategory != item.Category)
|
if (prevCategory is not null && prevCategory != item.Category)
|
||||||
{
|
{
|
||||||
table.AddEmptyRow();
|
table.AddEmptyRow();
|
||||||
}
|
}
|
||||||
|
|
@ -246,19 +262,7 @@ public static class InventoryPanel
|
||||||
table.AddRow($"[dim]{Markup.Escape(emptyText)}[/]", "", "", "");
|
table.AddRow($"[dim]{Markup.Escape(emptyText)}[/]", "", "", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
// In compact mode, add a summary footer showing count per category
|
|
||||||
if (compact && totalItems > maxRows)
|
|
||||||
{
|
|
||||||
var summary = grouped
|
|
||||||
.GroupBy(g => g.Category)
|
|
||||||
.OrderBy(g => CategorySortOrder(g.Key))
|
|
||||||
.Select(g => $"{CategoryAbbrev(g.Key)}{g.Count()}")
|
|
||||||
.ToArray();
|
|
||||||
table.AddRow($"[dim]{string.Join(" ", summary)}[/]", "", "", $"[dim]{totalItems}[/]");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build header with scroll indicator
|
// Build header with scroll indicator
|
||||||
string headerText = loc?.Get("ui.inventory") ?? "Inventory";
|
|
||||||
string header;
|
string header;
|
||||||
if (totalItems > maxRows)
|
if (totalItems > maxRows)
|
||||||
{
|
{
|
||||||
|
|
@ -273,7 +277,53 @@ public static class InventoryPanel
|
||||||
|
|
||||||
return new Panel(table)
|
return new Panel(table)
|
||||||
.Header(new PanelHeader(header))
|
.Header(new PanelHeader(header))
|
||||||
.Border(BoxBorder.Rounded);
|
.Border(BoxBorder.Rounded)
|
||||||
|
.Expand();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders a compact category summary for the inline game state view.
|
||||||
|
/// Shows one row per category with total quantity.
|
||||||
|
/// </summary>
|
||||||
|
private static IRenderable RenderCompactSummary(
|
||||||
|
GameState state,
|
||||||
|
ContentRegistry? registry,
|
||||||
|
LocalizationManager? loc,
|
||||||
|
string headerText)
|
||||||
|
{
|
||||||
|
var grouped = GetGroupedItems(state, registry);
|
||||||
|
var categories = grouped
|
||||||
|
.GroupBy(g => g.Category)
|
||||||
|
.OrderBy(g => CategorySortOrder(g.Key))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
string colCat = loc?.Get("inventory.col.name") ?? "Name";
|
||||||
|
string colQty = loc?.Get("inventory.col.qty") ?? "Qty";
|
||||||
|
|
||||||
|
var table = new Table()
|
||||||
|
.Border(TableBorder.Rounded)
|
||||||
|
.AddColumn(new TableColumn($"[bold]{Markup.Escape(colCat)}[/]"))
|
||||||
|
.AddColumn(new TableColumn($"[bold]{Markup.Escape(colQty)}[/]").RightAligned());
|
||||||
|
|
||||||
|
foreach (var cat in categories)
|
||||||
|
{
|
||||||
|
string catName = LocalizeCategoryName(cat.Key, loc);
|
||||||
|
int qty = cat.Sum(g => g.TotalQty);
|
||||||
|
table.AddRow(
|
||||||
|
$" {Markup.Escape(catName)}",
|
||||||
|
qty.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (categories.Count == 0)
|
||||||
|
{
|
||||||
|
string emptyText = loc?.Get("inventory.empty") ?? "Empty";
|
||||||
|
table.AddRow($"[dim]{Markup.Escape(emptyText)}[/]", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Panel(table)
|
||||||
|
.Header($"[bold yellow]{Markup.Escape(headerText)}[/]")
|
||||||
|
.Border(BoxBorder.Rounded)
|
||||||
|
.Expand();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -390,7 +440,8 @@ public static class InventoryPanel
|
||||||
if (isEquipped)
|
if (isEquipped)
|
||||||
{
|
{
|
||||||
string equippedLabel = loc?.Get("inventory.equipped") ?? "Equipped";
|
string equippedLabel = loc?.Get("inventory.equipped") ?? "Equipped";
|
||||||
rows.Add(new Markup($"[green]✓ {Markup.Escape(equippedLabel)}[/]"));
|
string checkMark = UnicodeSupport.IsUtf8 ? "✓" : ">";
|
||||||
|
rows.Add(new Markup($"[green]{checkMark} {Markup.Escape(equippedLabel)}[/]"));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -401,9 +452,9 @@ public static class InventoryPanel
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ItemCategory.Material when item.Def?.MaterialType is not null:
|
case ItemCategory.Material when item.Def?.MaterialType is not null:
|
||||||
string matType = loc?.Get($"material.{item.Def.MaterialType.Value.ToString().ToLower()}") ?? item.Def.MaterialType.Value.ToString();
|
|
||||||
string matForm = loc?.Get($"material.form.{(item.Def.MaterialForm ?? MaterialForm.Raw).ToString().ToLower()}") ?? item.Def.MaterialForm?.ToString() ?? "Raw";
|
string matForm = loc?.Get($"material.form.{(item.Def.MaterialForm ?? MaterialForm.Raw).ToString().ToLower()}") ?? item.Def.MaterialForm?.ToString() ?? "Raw";
|
||||||
rows.Add(new Markup($"[dim]{Markup.Escape(matType)} ({Markup.Escape(matForm)})[/]"));
|
string matDesc = loc?.Get("inventory.material_desc", matForm) ?? $"Crafting material — {matForm}";
|
||||||
|
rows.Add(new Markup($"[dim]{Markup.Escape(matDesc)}[/]"));
|
||||||
// Show crafting hints for this material
|
// Show crafting hints for this material
|
||||||
if (registry?.Recipes is not null)
|
if (registry?.Recipes is not null)
|
||||||
{
|
{
|
||||||
|
|
@ -439,7 +490,7 @@ public static class InventoryPanel
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
string title = loc?.Get("inventory.details") ?? "Details";
|
string title = LocalizeCategoryName(item.Category, loc);
|
||||||
return new Panel(new Rows(rows))
|
return new Panel(new Rows(rows))
|
||||||
.Header($"[bold aqua]{Markup.Escape(title)}[/]")
|
.Header($"[bold aqua]{Markup.Escape(title)}[/]")
|
||||||
.Border(BoxBorder.Rounded)
|
.Border(BoxBorder.Rounded)
|
||||||
|
|
|
||||||
|
|
@ -120,17 +120,21 @@ public class MetaEngine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Music melody: trigger music playback
|
// Music melody: trigger music playback and consume immediately (ephemeral)
|
||||||
if (itemDef.Tags.Contains("Music"))
|
if (itemDef.Tags.Contains("Music"))
|
||||||
{
|
{
|
||||||
events.Add(new MusicPlayedEvent());
|
events.Add(new MusicPlayedEvent());
|
||||||
|
state.RemoveItem(item.Id);
|
||||||
|
events.Add(new ItemConsumedEvent(item.Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fortune cookie: pick a random fortune message
|
// Fortune cookie: pick a random fortune and consume immediately (ephemeral)
|
||||||
if (itemDef.Tags.Contains("Cookie"))
|
if (itemDef.Tags.Contains("Cookie"))
|
||||||
{
|
{
|
||||||
int cookieNumber = Random.Shared.Next(1, 21);
|
int cookieNumber = Random.Shared.Next(1, 21);
|
||||||
events.Add(new CookieFortuneEvent($"cookie.{cookieNumber}"));
|
events.Add(new CookieFortuneEvent($"cookie.{cookieNumber}"));
|
||||||
|
state.RemoveItem(item.Id);
|
||||||
|
events.Add(new ItemConsumedEvent(item.Id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue