From fae979b75e37b6ad57e57062a1b637a37ec87174 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Thu, 18 Jun 2026 02:34:56 -0400 Subject: cleaned up various crafting menus --- AGENTS.md | 6 +- data/help/cook.yaml | 8 ++- data/help/recipe.yaml | 4 ++ data/help/smelt.yaml | 4 +- data/help/smith.yaml | 17 +++-- internal/game/action_production.go | 139 ++++++++++++++++++++++++------------- internal/game/cmd_cook.go | 21 +++--- internal/game/cmd_drop.go | 10 +-- internal/game/cmd_eat.go | 5 +- internal/game/cmd_get.go | 10 +-- internal/game/cmd_search.go | 5 +- internal/game/cmd_smelt.go | 21 +++--- internal/game/cmd_smith.go | 66 ++++++++++++------ internal/game/cmd_use.go | 53 +++++++------- internal/game/utils.go | 14 ++++ 15 files changed, 231 insertions(+), 152 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3217614..49e0083 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -61,7 +61,7 @@ internal/ | `utils.go` | Helper functions (plural, parseQty, parseChoiceIndex, formatPickupList, etc.) | | `help.go` | Help topic YAML loading and display | | `color.go` | colorMode(), colorize(), colorTag() helpers | -| `table.go` | Unicode/ASCII table rendering for score/option/help | +| `table.go` | Unicode/ASCII table rendering for score/option/help/crafting menus | ## Key Conventions @@ -485,9 +485,9 @@ From `internal/net/server.go`: | `StateChangeDescription` | Setting player description | | `StateTalk` | NPC conversation input | | `StateDropAllConfirm` | Confirming `drop all` | -| `StateRecipeChoice` | Recipe/product selection menu | +| `StateRecipeChoice` | Recipe/product selection table menu (number or name input, enter cancels) | | `StateHowMany` | "How many?" production count prompt | -| `StateSmithProduct` | Smith product selection with table display | +| `StateSmithProduct` | Smith product selection with numbered table (#, name, or partial name input) | | `StateColorChoice` | New account color preference | ## Adding a New Skill diff --git a/data/help/cook.yaml b/data/help/cook.yaml index ec26e20..5f99b02 100644 --- a/data/help/cook.yaml +++ b/data/help/cook.yaml @@ -16,9 +16,11 @@ description: | A Cooking skill check determines whether you produce a cooked item or a burnt one. On failure, you get a burnt variant. - Typing "cook" with no arguments shows a menu of everything you - can cook with your current inventory and the available station. - If only one thing can be cooked, it skips the menu. + Typing "cook" with no arguments shows a table menu of everything + you can cook with your current inventory and the available + station. If only one thing can be cooked, it skips the menu. + Select by typing a number, an item name, or a partial name (if + unambiguous). Press enter to cancel the menu. You can also cook by using items on a cooking station: use trout on fire diff --git a/data/help/recipe.yaml b/data/help/recipe.yaml index bd8bde6..e0fe26b 100644 --- a/data/help/recipe.yaml +++ b/data/help/recipe.yaml @@ -16,6 +16,10 @@ description: | Item combinations — combine items directly from inventory: use with Combine two items (e.g., flour + water) + When multiple recipes match, a table menu appears. Select by + typing a number, an item name, or a partial name (if + unambiguous). Press enter to cancel the menu. + All production prompts "How many?" before starting. Press return for all, type a number, or anything else to cancel. diff --git a/data/help/smelt.yaml b/data/help/smelt.yaml index ac38546..e31d7be 100644 --- a/data/help/smelt.yaml +++ b/data/help/smelt.yaml @@ -11,7 +11,9 @@ description: | number, or type anything else to cancel. If an ore can produce multiple products (e.g. iron ore can make - iron bars or steel bars with coal), a menu appears. + iron bars or steel bars with coal), a table menu appears. Select + by typing a number, an item name, or a partial name (if + unambiguous). Press enter to cancel the menu. You can also smelt by using ore on the furnace: use copper ore on furnace diff --git a/data/help/smith.yaml b/data/help/smith.yaml index 9471d83..e8aa466 100644 --- a/data/help/smith.yaml +++ b/data/help/smith.yaml @@ -7,12 +7,19 @@ description: | smith Show products (auto-selects if one bar type) Smithing requires an anvil in the room and a hammer in your - inventory. A table shows all possible products for the bar type, - with unavailable items (insufficient level or bars) shown dimmed. + inventory. A numbered table shows all possible products for the + bar type, with unavailable items (insufficient level or bars) + shown dimmed. Stackable outputs show the quantity per bar (e.g., + "Bronze Nails x15"). - At the prompt, type a product name to start smithing. You can - prefix with a number to limit quantity (e.g., "3 dagger"). - Press enter to repeat the last product you made with that bar. + At the prompt, type a product number or name to start smithing. + Partial names work if unambiguous (e.g., "nai" for nails). You + can prefix with a count to limit quantity (e.g., "3 dagger" or + "3 1"). Press enter to repeat the last product you made with + that bar. + + When you have multiple bar types, a table menu lets you select + by number or name. Press enter to cancel. You can also smith by using bars on the anvil: use bronze bar on anvil diff --git a/internal/game/action_production.go b/internal/game/action_production.go index c0b76f3..0b8ca45 100644 --- a/internal/game/action_production.go +++ b/internal/game/action_production.go @@ -11,6 +11,7 @@ import ( "thehouseoficarus/internal/net" "thehouseoficarus/internal/object" "thehouseoficarus/internal/player" + "thehouseoficarus/internal/world" ) type recipeEntry struct { @@ -40,6 +41,19 @@ func (g *Game) promptHowMany(sess *net.Session, recipeID string) { sess.Write("How many (return for all)?: ") } +func (g *Game) showMenuTable(sess *net.Session, title string, names []string) { + p := sess.Player.(*player.Player) + tbl := &Table{Title: title} + for i, name := range names { + tbl.Rows = append(tbl.Rows, []string{fmt.Sprintf("%d", i+1), name}) + } + unicode := p.OptionBool("unicode") + sess.WriteLine("") + for _, line := range tbl.Render(unicode) { + sess.WriteLine(line) + } +} + func (g *Game) handleHowMany(sess *net.Session, input string) { p := sess.Player.(*player.Player) recipeID := sess.PendingRecipeID @@ -120,35 +134,18 @@ func (g *Game) startProduction(sess *net.Session, p *player.Player, recipe *acti skillLevel := p.Level(player.SkillName(recipe.Skill)) if skillLevel < recipe.Level { sess.WriteLine(fmt.Sprintf("You need level %d %s to do that.", recipe.Level, recipe.Skill)) + g.reprompt(sess) return } } if !recipe.HasAllItemsQty(p.CountItem) { sess.WriteLine("You don't have the required materials.") + g.reprompt(sess) return } - outputQty := recipe.OutputQty - if outputQty <= 0 { - outputQty = 1 - } - - canPlace := false outDef, _ := g.ItemStore.Load(recipe.Output) - if outDef != nil && outDef.Stackable { - for i := 0; i < 28; i++ { - slot := p.InvSlot(i) - if slot != nil && slot.ItemID == recipe.Output { - canPlace = true - break - } - } - } - if !canPlace && p.FirstFreeSlot() == -1 { - sess.WriteLine("Your inventory is too full!") - return - } wait := recipe.Wait if wait <= 0 { @@ -297,13 +294,13 @@ func (g *Game) advanceProduction(sess *net.Session, p *player.Player) bool { } } if !placed { + recipe.ConsumeAll(p.HasItem, p.RemoveItem) freeSlot := p.FirstFreeSlot() if freeSlot == -1 { sess.WriteLine("Your inventory is too full!") g.CancelAction(p) return false } - recipe.ConsumeAll(p.HasItem, p.RemoveItem) p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: recipe.Output, Quantity: outputQty}) } @@ -413,55 +410,103 @@ func (g *Game) canContinueProduction(p *player.Player, recipe *action.RecipeDef) func (g *Game) handleRecipeChoice(sess *net.Session, input string) { input = strings.TrimSpace(input) - if input == "" { + + menuData := sess.PendingMenu + if len(menuData) == 0 { + sess.State = net.StateGame + g.reprompt(sess) return } - choice, err := strconv.Atoi(input) - if err != nil { - sess.State = net.StateGame + if input == "" { sess.PendingMenu = nil + sess.State = net.StateGame sess.WriteLine("Never mind.") g.reprompt(sess) return } - if len(sess.PendingMenu) > 0 { - menuData := sess.PendingMenu + if choice, err := strconv.Atoi(input); err == nil { sess.PendingMenu = nil - if choice <= 0 || choice > len(menuData)+1 { - sess.WriteLine("Invalid choice.") - return - } sess.State = net.StateGame - if choice == len(menuData)+1 { + if choice <= 0 || choice > len(menuData) { sess.WriteLine("Never mind.") g.reprompt(sess) return } - entry := menuData[choice-1] + g.dispatchMenuEntry(sess, menuData[choice-1]) + return + } - if cid, ok := entry["combine_item"]; ok { - g.promptHowMany(sess, "combine_"+cid) - return + lower := strings.ToLower(input) + matchIdx := -1 + for i, entry := range menuData { + name := g.menuEntryName(entry) + if name == "" { + continue } - - if barID, ok := entry["bar_id"]; ok { - p := sess.Player.(*player.Player) - allRecipes, _ := g.RecipeStore.LoadAll() - g.showSmithTable(sess, p, barID, allRecipes) - return + if strings.ToLower(name) == lower || world.WordPrefixMatch(input, name) { + if matchIdx >= 0 { + sess.WriteLine("That's ambiguous.") + return + } + matchIdx = i } + } + if matchIdx >= 0 { + sess.PendingMenu = nil + sess.State = net.StateGame + g.dispatchMenuEntry(sess, menuData[matchIdx]) + return + } - if rid, ok := entry["recipe_id"]; ok { - g.promptHowMany(sess, rid) - return + sess.PendingMenu = nil + sess.State = net.StateGame + sess.WriteLine("Never mind.") + g.reprompt(sess) +} + +func (g *Game) menuEntryName(entry map[string]string) string { + if rid, ok := entry["recipe_id"]; ok { + all, _ := g.RecipeStore.LoadAll() + for _, r := range all { + if r.ID == rid { + if def, err := g.ItemStore.Load(r.Output); err == nil { + return def.Name + } + return r.Output + } + } + } + if barID, ok := entry["bar_id"]; ok { + if def, _ := g.ItemStore.Load(barID); def != nil { + return def.Name + } + return barID + } + if cid, ok := entry["combine_item"]; ok { + if def, _ := g.ItemStore.Load(cid); def != nil { + return def.Name } + return cid + } + return "" +} - g.reprompt(sess) +func (g *Game) dispatchMenuEntry(sess *net.Session, entry map[string]string) { + if cid, ok := entry["combine_item"]; ok { + g.promptHowMany(sess, "combine_"+cid) + return + } + if barID, ok := entry["bar_id"]; ok { + p := sess.Player.(*player.Player) + allRecipes, _ := g.RecipeStore.LoadAll() + g.showSmithTable(sess, p, barID, allRecipes) + return + } + if rid, ok := entry["recipe_id"]; ok { + g.promptHowMany(sess, rid) return } - - sess.State = net.StateGame g.reprompt(sess) } diff --git a/internal/game/cmd_cook.go b/internal/game/cmd_cook.go index ce33ccd..b27b293 100644 --- a/internal/game/cmd_cook.go +++ b/internal/game/cmd_cook.go @@ -40,10 +40,7 @@ func (g *Game) doCook(sess *net.Session, input string) { unique := uniqueItemNames(matches) if len(unique) > 1 { - sess.WriteLine("Which one?") - for _, name := range unique { - sess.WriteLine(fmt.Sprintf(" - %s", name)) - } + g.showWhichOne(sess, matches) return } @@ -71,11 +68,11 @@ func (g *Game) doCook(sess *net.Session, input string) { sess.PendingMenu = recipeMenuData(recipes) sess.State = net.StateRecipeChoice - sess.WriteLine(fmt.Sprintf("\nWhat would you like to cook on the %s?", stationName)) - for i, r := range recipes { - sess.WriteLine(fmt.Sprintf(" %d) %s", i+1, g.recipeName(sess, &r))) + var names []string + for _, r := range recipes { + names = append(names, g.recipeName(sess, &r)) } - sess.WriteLine(fmt.Sprintf(" %d) Nothing", len(recipes)+1)) + g.showMenuTable(sess, fmt.Sprintf("What would you like to cook on the %s?", stationName), names) } func (g *Game) showCookMenu(sess *net.Session, p *player.Player, allRecipes []action.RecipeDef, stationDefID, stationName string) { @@ -110,11 +107,11 @@ func (g *Game) showCookMenu(sess *net.Session, p *player.Player, allRecipes []ac } sess.State = net.StateRecipeChoice - sess.WriteLine(fmt.Sprintf("\nWhat would you like to cook on the %s?", stationName)) - for i, e := range entries { - sess.WriteLine(fmt.Sprintf(" %d) %s", i+1, e.ItemName)) + var names []string + for _, e := range entries { + names = append(names, e.ItemName) } - sess.WriteLine(fmt.Sprintf(" %d) Nothing", len(entries)+1)) + g.showMenuTable(sess, fmt.Sprintf("What would you like to cook on the %s?", stationName), names) sess.PendingMenu = entryMenuData(entries) } diff --git a/internal/game/cmd_drop.go b/internal/game/cmd_drop.go index 702d1db..1fd99b4 100644 --- a/internal/game/cmd_drop.go +++ b/internal/game/cmd_drop.go @@ -39,10 +39,7 @@ func (g *Game) doDropAllNamed(sess *net.Session, input string) { unique := uniqueItemNames(matches) if len(unique) > 1 { - sess.WriteLine("Which one?") - for _, name := range unique { - sess.WriteLine(fmt.Sprintf(" - %s", name)) - } + g.showWhichOne(sess, matches) return } @@ -102,10 +99,7 @@ func (g *Game) doDrop(sess *net.Session, input string) { unique := uniqueItemNames(matches) if len(unique) > 1 { - sess.WriteLine("Which one?") - for _, name := range unique { - sess.WriteLine(fmt.Sprintf(" - %s", name)) - } + g.showWhichOne(sess, matches) return } diff --git a/internal/game/cmd_eat.go b/internal/game/cmd_eat.go index 9d4bfd0..6a74979 100644 --- a/internal/game/cmd_eat.go +++ b/internal/game/cmd_eat.go @@ -28,10 +28,7 @@ func (g *Game) doEat(sess *net.Session, input string) { unique := uniqueItemNames(matches) if len(unique) > 1 { - sess.WriteLine("Which one?") - for _, name := range unique { - sess.WriteLine(fmt.Sprintf(" - %s", name)) - } + g.showWhichOne(sess, matches) return } diff --git a/internal/game/cmd_get.go b/internal/game/cmd_get.go index 8237ec9..6f08892 100644 --- a/internal/game/cmd_get.go +++ b/internal/game/cmd_get.go @@ -27,10 +27,7 @@ func (g *Game) doGet(sess *net.Session, input string) { if len(matches) > 1 { unique := uniqueItemNames(matches) if len(unique) > 1 { - sess.WriteLine("Which one?") - for _, name := range unique { - sess.WriteLine(fmt.Sprintf(" - %s", name)) - } + g.showWhichOne(sess, matches) return } } @@ -149,10 +146,7 @@ func (g *Game) doGetAllNamed(sess *net.Session, input string) { if len(matches) > 1 { unique := uniqueItemNames(matches) if len(unique) > 1 { - sess.WriteLine("Which one?") - for _, name := range unique { - sess.WriteLine(fmt.Sprintf(" - %s", name)) - } + g.showWhichOne(sess, matches) return } } diff --git a/internal/game/cmd_search.go b/internal/game/cmd_search.go index d4d2e72..cf37c90 100644 --- a/internal/game/cmd_search.go +++ b/internal/game/cmd_search.go @@ -26,10 +26,7 @@ func (g *Game) doSearch(sess *net.Session, input string) { unique := uniqueItemNames(matches) if len(unique) > 1 { - sess.WriteLine("Which one?") - for _, name := range unique { - sess.WriteLine(fmt.Sprintf(" - %s", name)) - } + g.showWhichOne(sess, matches) return } diff --git a/internal/game/cmd_smelt.go b/internal/game/cmd_smelt.go index e0889f6..1cd5436 100644 --- a/internal/game/cmd_smelt.go +++ b/internal/game/cmd_smelt.go @@ -37,10 +37,7 @@ func (g *Game) doSmelt(sess *net.Session, input string) { unique := uniqueItemNames(matches) if len(unique) > 1 { - sess.WriteLine("Which one?") - for _, name := range unique { - sess.WriteLine(fmt.Sprintf(" - %s", name)) - } + g.showWhichOne(sess, matches) return } @@ -69,11 +66,11 @@ func (g *Game) doSmelt(sess *net.Session, input string) { sess.PendingMenu = recipeMenuData(recipes) sess.State = net.StateRecipeChoice - sess.WriteLine("\nWhat would you like to smelt?") - for i, r := range recipes { - sess.WriteLine(fmt.Sprintf(" %d) %s", i+1, g.recipeName(sess, &r))) + var names []string + for _, r := range recipes { + names = append(names, g.recipeName(sess, &r)) } - sess.WriteLine(fmt.Sprintf(" %d) Nothing", len(recipes)+1)) + g.showMenuTable(sess, "What would you like to smelt?", names) } func (g *Game) showSmeltMenu(sess *net.Session, p *player.Player, allRecipes []action.RecipeDef, stationDefID, stationName string) { @@ -102,10 +99,10 @@ func (g *Game) showSmeltMenu(sess *net.Session, p *player.Player, allRecipes []a } sess.State = net.StateRecipeChoice - sess.WriteLine("\nWhat would you like to smelt?") - for i, e := range entries { - sess.WriteLine(fmt.Sprintf(" %d) %s", i+1, e.ItemName)) + var names []string + for _, e := range entries { + names = append(names, e.ItemName) } - sess.WriteLine(fmt.Sprintf(" %d) Nothing", len(entries)+1)) + g.showMenuTable(sess, "What would you like to smelt?", names) sess.PendingMenu = entryMenuData(entries) } diff --git a/internal/game/cmd_smith.go b/internal/game/cmd_smith.go index 30dcddc..7fc14d2 100644 --- a/internal/game/cmd_smith.go +++ b/internal/game/cmd_smith.go @@ -3,6 +3,7 @@ package game import ( "fmt" "sort" + "strconv" "strings" "thehouseoficarus/internal/action" @@ -10,6 +11,7 @@ import ( "thehouseoficarus/internal/net" "thehouseoficarus/internal/object" "thehouseoficarus/internal/player" + "thehouseoficarus/internal/world" ) func (g *Game) doSmith(sess *net.Session, input string) { @@ -41,10 +43,7 @@ func (g *Game) doSmith(sess *net.Session, input string) { } unique := uniqueItemNames(matches) if len(unique) > 1 { - sess.WriteLine("Which one?") - for _, name := range unique { - sess.WriteLine(fmt.Sprintf(" - %s", name)) - } + g.showWhichOne(sess, matches) return } barID := matches[0].ID @@ -69,16 +68,16 @@ func (g *Game) doSmith(sess *net.Session, input string) { sess.PendingMenu = barMenuData(barTypes) sess.State = net.StateRecipeChoice - sess.WriteLine("\nWhich bars would you like to smith?") - for i, barID := range barTypes { + var names []string + for _, barID := range barTypes { def, _ := g.ItemStore.Load(barID) name := barID if def != nil { name = g.itemColorize(sess, def, def.Name) } - sess.WriteLine(fmt.Sprintf(" %d) %s", i+1, name)) + names = append(names, name) } - sess.WriteLine(fmt.Sprintf(" %d) Nothing", len(barTypes)+1)) + g.showMenuTable(sess, "Which bars would you like to smith?", names) } func (g *Game) showSmithTable(sess *net.Session, p *player.Player, barID string, allRecipes []action.RecipeDef) { @@ -111,22 +110,31 @@ func (g *Game) showSmithTable(sess *net.Session, p *player.Player, barID string, tbl := &Table{ Title: titleCase(barName) + " Smithing", - Columns: []string{"Product", "Inputs", "Level Req."}, + Columns: []string{"#", "Product", "Inputs", "Level Req."}, } - for _, r := range recipes { + for i, r := range recipes { outDef, _ := g.ItemStore.Load(r.Output) productName := r.Output if outDef != nil { productName = outDef.Name } + outputQty := r.OutputQty + if outputQty <= 0 { + outputQty = 1 + } + if outDef != nil && outDef.Stackable && outputQty > 1 { + productName = fmt.Sprintf("%s x%d", productName, outputQty) + } + bars := barsRequired(r, barID) inputStr := "1 bar" if bars > 1 { inputStr = fmt.Sprintf("%d bars", bars) } levelStr := fmt.Sprint(r.Level) + numStr := fmt.Sprintf("%d", i+1) canMake := barCount >= bars && skillLevel >= r.Level if canMake { @@ -135,9 +143,10 @@ func (g *Game) showSmithTable(sess *net.Session, p *player.Player, barID string, productName = color.Render(mode, dimSpec, productName) inputStr = color.Render(mode, dimSpec, inputStr) levelStr = color.Render(mode, dimSpec, levelStr) + numStr = color.Render(mode, dimSpec, numStr) } - tbl.Rows = append(tbl.Rows, []string{productName, inputStr, levelStr}) + tbl.Rows = append(tbl.Rows, []string{numStr, productName, inputStr, levelStr}) } unicode := p.OptionBool("unicode") @@ -190,6 +199,10 @@ func (g *Game) handleSmithProduct(sess *net.Session, input string) { } } + sort.Slice(recipes, func(i, j int) bool { + return recipes[i].Level < recipes[j].Level + }) + var targetRecipe *action.RecipeDef var count int @@ -216,18 +229,29 @@ func (g *Game) handleSmithProduct(sess *net.Session, input string) { } else { qty, productName := parseQty(input) count = qty - productName = strings.ToLower(strings.TrimSpace(productName)) + productName = strings.TrimSpace(productName) - for i := range recipes { - outDef, _ := g.ItemStore.Load(recipes[i].Output) - name := recipes[i].Output - if outDef != nil { - name = outDef.Name + if idx, err := strconv.Atoi(productName); err == nil && idx > 0 && idx <= len(recipes) { + targetRecipe = &recipes[idx-1] + } else { + matchCount := 0 + for i := range recipes { + outDef, _ := g.ItemStore.Load(recipes[i].Output) + name := recipes[i].Output + if outDef != nil { + name = outDef.Name + } + if world.WordPrefixMatch(productName, name) { + if matchCount == 0 { + targetRecipe = &recipes[i] + } + matchCount++ + } } - lower := strings.ToLower(name) - if lower == productName || strings.HasPrefix(lower, productName) { - targetRecipe = &recipes[i] - break + if matchCount > 1 { + sess.WriteLine("That's ambiguous.") + g.reprompt(sess) + return } } diff --git a/internal/game/cmd_use.go b/internal/game/cmd_use.go index cb521f3..7c7bca0 100644 --- a/internal/game/cmd_use.go +++ b/internal/game/cmd_use.go @@ -56,10 +56,7 @@ func (g *Game) doUseItemOnTarget(sess *net.Session, p *player.Player, itemAName, uniqueA := uniqueItemNames(matchesA) if len(uniqueA) > 1 { - sess.WriteLine("Which one?") - for _, name := range uniqueA { - sess.WriteLine(fmt.Sprintf(" - %s", name)) - } + g.showWhichOne(sess, matchesA) return } @@ -102,14 +99,14 @@ func (g *Game) doUseItemOnTarget(sess *net.Session, p *player.Player, itemAName, g.promptHowMany(sess, filtered[0].ID) return } - sess.PendingMenu = recipeMenuData(filtered) - sess.State = net.StateRecipeChoice - sess.WriteLine("\nWhat would you like to smelt?") - for i, r := range filtered { - sess.WriteLine(fmt.Sprintf(" %d) %s", i+1, g.recipeName(sess, &r))) - } - sess.WriteLine(fmt.Sprintf(" %d) Nothing", len(filtered)+1)) - return + sess.PendingMenu = recipeMenuData(filtered) + sess.State = net.StateRecipeChoice + var names []string + for _, r := range filtered { + names = append(names, g.recipeName(sess, &r)) + } + g.showMenuTable(sess, "What would you like to smelt?", names) + return } if recipes[0].Type == "smithing" { if !g.hasToolType(p, "hammer") { @@ -124,13 +121,14 @@ func (g *Game) doUseItemOnTarget(sess *net.Session, p *player.Player, itemAName, g.promptHowMany(sess, recipes[0].ID) return } - sess.PendingMenu = recipeMenuData(recipes) - sess.State = net.StateRecipeChoice - sess.WriteLine(fmt.Sprintf("\nWhat would you like to make with %s on the %s?", itemAName, def.Name)) - for i, r := range recipes { - sess.WriteLine(fmt.Sprintf(" %d) %s", i+1, g.recipeName(sess, &r))) - } - return + sess.PendingMenu = recipeMenuData(recipes) + sess.State = net.StateRecipeChoice + var names []string + for _, r := range recipes { + names = append(names, g.recipeName(sess, &r)) + } + g.showMenuTable(sess, fmt.Sprintf("What would you like to make with %s on the %s?", itemAName, def.Name), names) + return } for _, ui := range def.UseInteractions { @@ -158,8 +156,14 @@ func (g *Game) doUseItemOnTarget(sess *net.Session, p *player.Player, itemAName, uniqueB := uniqueItemNames(matchesB) if len(uniqueB) > 1 { sess.WriteLine("Which '" + itemBName + "'?") - for _, name := range uniqueB { - sess.WriteLine(fmt.Sprintf(" - %s", name)) + seen := make(map[string]bool) + for _, m := range matchesB { + if seen[m.Name] { + continue + } + seen[m.Name] = true + def, _ := g.ItemStore.Load(m.ID) + sess.WriteLine(fmt.Sprintf(" - %s", g.itemColorize(sess, def, m.Name))) } return } @@ -174,10 +178,11 @@ func (g *Game) doUseItemOnTarget(sess *net.Session, p *player.Player, itemAName, if len(matched) > 1 { sess.PendingMenu = combineMenuData(matched) sess.State = net.StateRecipeChoice - sess.WriteLine("\nWhat would you like to make?") - for i, def := range matched { - sess.WriteLine(fmt.Sprintf(" %d) %s", i+1, g.itemColorize(sess, def, def.Name))) + var names []string + for _, def := range matched { + names = append(names, g.itemColorize(sess, def, def.Name)) } + g.showMenuTable(sess, "What would you like to make?", names) return } diff --git a/internal/game/utils.go b/internal/game/utils.go index 2379fc2..55e48c6 100644 --- a/internal/game/utils.go +++ b/internal/game/utils.go @@ -60,6 +60,20 @@ func uniqueItemNames(matches []itemMatch) []string { return out } +func (g *Game) showWhichOne(sess *net.Session, matches []itemMatch) { + sess.WriteLine("Which one?") + seen := make(map[string]bool) + for _, m := range matches { + if seen[m.Name] { + continue + } + seen[m.Name] = true + def, _ := g.ItemStore.Load(m.ID) + coloredName := g.itemColorize(sess, def, m.Name) + sess.WriteLine(fmt.Sprintf(" - %s", coloredName)) + } +} + func randInt(max int) int { if max <= 0 { return 0 -- cgit v1.2.3