aboutsummaryrefslogtreecommitdiff
path: root/internal/game/production_menu.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/production_menu.go')
-rw-r--r--internal/game/production_menu.go416
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)
+}