diff options
| author | historia <[not public]> | 2026-06-25 15:40:48 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-06-25 15:40:48 -0400 |
| commit | abd612c15799f604e671e83dc7c410ed2b44185f (patch) | |
| tree | 0597ca92350de73aac2a7cf26b2f2d6595dac0cc /internal/game/production_menu.go | |
| parent | 2725e2927a1595c7b100d942d1f14146252adeb7 (diff) | |
| download | thehouseoficarus-abd612c15799f604e671e83dc7c410ed2b44185f.tar.gz | |
slop refactor
Diffstat (limited to 'internal/game/production_menu.go')
| -rw-r--r-- | internal/game/production_menu.go | 416 |
1 files changed, 416 insertions, 0 deletions
diff --git a/internal/game/production_menu.go b/internal/game/production_menu.go new file mode 100644 index 0000000..7181a0e --- /dev/null +++ b/internal/game/production_menu.go @@ -0,0 +1,416 @@ +package game + +import ( + "fmt" + "sort" + "strconv" + "strings" + + "thehouseoficarus/internal/behavior" + "thehouseoficarus/internal/color" + "thehouseoficarus/internal/item" + "thehouseoficarus/internal/net" + "thehouseoficarus/internal/player" + "thehouseoficarus/internal/ui" +) + +func (g *Game) handleRecipeChoice(sess *net.Session, input string) { + input = strings.TrimSpace(input) + + menuData := sess.PendingMenu + if len(menuData) == 0 { + sess.State = net.StateGame + g.reprompt(sess) + return + } + + if input == "" { + sess.PendingMenu = nil + sess.State = net.StateGame + sess.WriteLine("Never mind.") + g.reprompt(sess) + return + } + + if choice, err := strconv.Atoi(input); err == nil { + sess.PendingMenu = nil + sess.State = net.StateGame + if choice <= 0 || choice > len(menuData) { + sess.WriteLine("Never mind.") + g.reprompt(sess) + return + } + g.dispatchMenuEntry(sess, menuData[choice-1]) + return + } + + lower := strings.ToLower(input) + matchIdx := -1 + for i, entry := range menuData { + name := g.menuEntryName(entry) + if name == "" { + continue + } + if strings.ToLower(name) == lower || behavior.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 + } + + sess.PendingMenu = nil + sess.State = net.StateGame + sess.WriteLine("Never mind.") + g.reprompt(sess) +} + +func (g *Game) menuEntryName(entry map[string]string) string { + if iid, ok := entry["item_id"]; ok { + if def, _ := g.ItemStore.Load(iid); def != nil { + return def.Name + } + return iid + } + if barID, ok := entry["bar_id"]; ok { + if def, _ := g.ItemStore.Load(barID); def != nil { + return def.Name + } + return barID + } + return "" +} + +func (g *Game) dispatchMenuEntry(sess *net.Session, entry map[string]string) { + if iid, ok := entry["item_id"]; ok { + g.promptHowMany(sess, iid) + return + } + if barID, ok := entry["bar_id"]; ok { + g.showSmithTable(sess, sess.Player, barID) + return + } + g.reprompt(sess) +} + +func (g *Game) formatMaterials(sess *net.Session, item *item.ItemDef) string { + craft := item.FirstCraft() + if craft == nil { + return "" + } + var parts []string + for _, e := range craft.Consume { + itemName := e.Items[0] + if def, err := g.ItemStore.Load(e.Items[0]); err == nil { + itemName = g.itemColorize(sess, def, def.Name) + } + if e.Quantity > 1 { + parts = append(parts, fmt.Sprintf("%s x%d", itemName, e.Quantity)) + } else { + parts = append(parts, itemName) + } + } + return strings.Join(parts, ", ") +} + +func (g *Game) showProductionTable(sess *net.Session, p *player.Player, items []*item.ItemDef, title, skill, lastFlagKey, promptVerb string, background bool) { + sort.Slice(items, func(i, j int) bool { + return items[i].FirstCraft().Level < items[j].FirstCraft().Level + }) + + mode := g.colorMode(sess) + skillLevel := p.Level(player.SkillName(skill)) + dimSpec := color.Parse("240") + + tbl := &ui.Table{ + Title: title, + Columns: []string{"#", "Product", "Materials", "Level"}, + } + + for i, item := range items { + craft := item.FirstCraft() + outDef, _ := g.ItemStore.Load(item.ID) + productName := item.ID + if outDef != nil { + productName = outDef.Name + } + + outputQty := craft.OutputQty + if outputQty <= 0 { + outputQty = 1 + } + if outDef != nil && outDef.Stackable && outputQty > 1 { + productName = fmt.Sprintf("%s x%d", productName, outputQty) + } + + matStr := g.formatMaterials(sess, item) + levelStr := fmt.Sprint(craft.Level) + numStr := fmt.Sprintf("%d", i+1) + + canMake := skillLevel >= craft.Level && craftHasAllItemsQty(craft, p.CountItem) + if canMake { + productName = g.itemColorize(sess, outDef, productName) + } else { + productName = color.Render(mode, dimSpec, productName) + matStr = color.Render(mode, dimSpec, matStr) + levelStr = color.Render(mode, dimSpec, levelStr) + numStr = color.Render(mode, dimSpec, numStr) + } + + tbl.Rows = append(tbl.Rows, []string{numStr, productName, matStr, levelStr}) + } + + unicode := p.OptionBool("unicode") + sess.WriteLine("") + for _, line := range tbl.Render(unicode) { + sess.WriteLine(line) + } + + lastItemID, _ := p.Flags[lastFlagKey].(string) + hint := "" + if lastItemID != "" { + for _, item := range items { + if item.ID == lastItemID { + if outDef, err := g.ItemStore.Load(item.ID); err == nil { + hint = outDef.Name + } + break + } + } + } + + if hint != "" { + sess.Write(fmt.Sprintf("%s what (enter for all %s): ", promptVerb, hint)) + } else { + sess.Write(fmt.Sprintf("%s what: ", promptVerb)) + } + + sess.PendingMenu = itemMenuData(itemIDs(items)) + sess.PendingSkill = skill + sess.PendingLastFlag = lastFlagKey + sess.PendingBackground = background + sess.State = net.StateProductChoice +} + +func (g *Game) handleProductChoice(sess *net.Session, input string) { + p := sess.Player + menuData := sess.PendingMenu + skill := sess.PendingSkill + lastFlagKey := sess.PendingLastFlag + background := sess.PendingBackground + sess.PendingMenu = nil + sess.PendingSkill = "" + sess.PendingLastFlag = "" + sess.PendingBackground = false + sess.State = net.StateGame + + input = strings.TrimSpace(input) + + var items []*item.ItemDef + for _, entry := range menuData { + iid := entry["item_id"] + if item := g.loadCraftItem(iid); item != nil { + items = append(items, item) + } + } + + if len(items) == 0 { + g.reprompt(sess) + return + } + + sort.Slice(items, func(i, j int) bool { + return items[i].FirstCraft().Level < items[j].FirstCraft().Level + }) + + lastItemID, _ := p.Flags[lastFlagKey].(string) + + sel, errMsg := g.resolveCraftByInput(input, items, lastItemID) + switch errMsg { + case "ambiguous": + sess.WriteLine("That's ambiguous.") + g.reprompt(sess) + return + case "never_mind": + sess.WriteLine("Never mind.") + g.reprompt(sess) + return + } + + craft := sel.Item.FirstCraft() + skillLevel := p.Level(player.SkillName(skill)) + if skillLevel < craft.Level { + sess.WriteLine(fmt.Sprintf("You need level %d %s to make that.", craft.Level, skill)) + g.reprompt(sess) + return + } + if !craftHasAllItemsQty(craft, p.CountItem) { + sess.WriteLine("You don't have the materials for that.") + g.reprompt(sess) + return + } + + p.EnsureFlags() + p.Flags[lastFlagKey] = sel.Item.ID + g.AccountStore.SaveCharacter(p) + + if background { + g.startFletchAction(sess, p, sel.Item, sel.Count) + } else { + g.startProductionFromItem(sess, p, sel.Item, sel.Count) + } +} + +func itemMenuData(itemIDs []string) []map[string]string { + out := make([]map[string]string, len(itemIDs)) + for i, id := range itemIDs { + out[i] = map[string]string{"item_id": id} + } + return out +} + +func itemIDs(defs []*item.ItemDef) []string { + ids := make([]string, len(defs)) + for i, d := range defs { + ids[i] = d.ID + } + return ids +} + +func (g *Game) promptHowMany(sess *net.Session, itemID string) { + sess.PendingItemID = itemID + sess.State = net.StateHowMany + sess.Write("How many (return for all)?: ") +} + +type craftSelection struct { + Item *item.ItemDef + Count int +} + +func (g *Game) resolveCraftByInput(input string, items []*item.ItemDef, lastItemID string) (sel craftSelection, errMsg string) { + if input == "" { + if lastItemID == "" { + return sel, "never_mind" + } + for i := range items { + if items[i].ID == lastItemID { + sel.Item = items[i] + return sel, "" + } + } + return sel, "never_mind" + } + + qty, productName := parseQty(input) + productName = strings.TrimSpace(productName) + + if idx, err := strconv.Atoi(productName); err == nil && idx > 0 && idx <= len(items) { + sel.Item = items[idx-1] + sel.Count = qty + return sel, "" + } + + matchCount := 0 + for i := range items { + outDef, _ := g.ItemStore.Load(items[i].ID) + name := items[i].ID + if outDef != nil { + name = outDef.Name + } + if behavior.WordPrefixMatch(productName, name) { + if matchCount == 0 { + sel.Item = items[i] + sel.Count = qty + } + matchCount++ + } + } + if matchCount > 1 { + return sel, "ambiguous" + } + if sel.Item == nil { + return sel, "never_mind" + } + return sel, "" +} + +func (g *Game) showMenuTable(sess *net.Session, title string, names []string) { + p := sess.Player + tbl := &ui.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) itemName(sess *net.Session, item *item.ItemDef) string { + if item != nil { + if def, err := g.ItemStore.Load(item.ID); err == nil { + return g.itemColorize(sess, def, def.Name) + } + return item.ID + } + return "" +} + +func (g *Game) showRecipeChoice(sess *net.Session, p *player.Player, items []*item.ItemDef, emptyMsg, title string, autoOption string) { + if len(items) == 0 { + sess.WriteLine(emptyMsg) + return + } + if len(items) == 1 { + if autoOption != "" && p.OptionBool(autoOption) { + g.startProductionFromItem(sess, p, items[0], 0) + return + } + g.promptHowMany(sess, items[0].ID) + return + } + sess.State = net.StateRecipeChoice + sess.PendingMenu = itemMenuData(itemIDs(items)) + var names []string + for _, item := range items { + names = append(names, g.itemName(sess, item)) + } + g.showMenuTable(sess, title, names) +} + +func (g *Game) handleHowMany(sess *net.Session, input string) { + p := sess.Player + itemID := sess.PendingItemID + sess.PendingItemID = "" + sess.State = net.StateGame + + input = strings.TrimSpace(input) + + var count int + if input == "" { + count = 0 + } else if n, err := strconv.Atoi(input); err == nil && n > 0 { + count = n + } else { + sess.WriteLine("Never mind.") + g.reprompt(sess) + return + } + + item := g.loadCraftItem(itemID) + if item == nil { + g.reprompt(sess) + return + } + + g.startProductionFromItem(sess, p, item, count) +} |
