diff options
| author | historia <[not public]> | 2026-06-19 18:20:26 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-06-19 18:20:26 -0400 |
| commit | 0ea4eec5dd2122c6704216971eb1249276297867 (patch) | |
| tree | 430fbbef04e837820f1d031c190f52ecd6112e25 /internal/game/cmd_shop.go | |
| parent | 3a58125d14bb307f861b38c6c5b0a63babde7e08 (diff) | |
| download | thehouseoficarus-0ea4eec5dd2122c6704216971eb1249276297867.tar.gz | |
shop fixes, 'toggle' completely removed, movement speed increased
Diffstat (limited to 'internal/game/cmd_shop.go')
| -rw-r--r-- | internal/game/cmd_shop.go | 276 |
1 files changed, 205 insertions, 71 deletions
diff --git a/internal/game/cmd_shop.go b/internal/game/cmd_shop.go index a3f7613..2742151 100644 --- a/internal/game/cmd_shop.go +++ b/internal/game/cmd_shop.go @@ -2,11 +2,12 @@ package game import ( "fmt" + "strconv" "strings" "thehouseoficarus/internal/action" "thehouseoficarus/internal/net" - "thehouseoficarus/internal/player" + "thehouseoficarus/internal/object" ) func (g *Game) handleShopInput(sess *net.Session, input string) { @@ -27,17 +28,9 @@ func (g *Game) handleShopInput(sess *net.Session, input string) { switch cmd { case "buy": - if len(parts) < 2 { - sess.WriteLine("Buy what? Use 'browse' to see available items.") - } else { - g.doShopBuy(sess, strings.Join(parts[1:], " ")) - } + g.cmdShopBuy(sess, parts[1:]) case "sell": - if len(parts) < 2 { - sess.WriteLine("Sell what?") - } else { - g.doShopSell(sess, strings.Join(parts[1:], " ")) - } + g.cmdShopSell(sess, parts[1:]) case "browse", "list": g.showShopBrowse(sess) case "leave", "bye", "exit", "quit": @@ -49,15 +42,65 @@ func (g *Game) handleShopInput(sess *net.Session, input string) { g.writeShopPrompt(sess) } +func parseShopQty(args []string) (int, string, bool) { + if len(args) == 0 { + return 1, "", false + } + if strings.EqualFold(args[0], "all") { + rest := strings.Join(args[1:], " ") + return 0, rest, true + } + if n, err := strconv.Atoi(args[0]); err == nil && n > 0 { + return n, strings.Join(args[1:], " "), false + } + return 1, strings.Join(args, " "), false +} + +func (g *Game) cmdShopBuy(sess *net.Session, args []string) { + cfg := g.shopConfig(sess) + if cfg == nil { + return + } + + qty, itemName, all := parseShopQty(args) + if itemName == "" { + sess.WriteLine("Buy what? Use 'browse' to see available items.") + return + } + + g.doShopBuy(sess, itemName, qty, all) +} + +func (g *Game) cmdShopSell(sess *net.Session, args []string) { + cfg := g.shopConfig(sess) + if cfg == nil { + return + } + + qty, itemName, all := parseShopQty(args) + if itemName == "" { + sess.WriteLine("Sell what?") + return + } + + g.doShopSell(sess, itemName, qty, all) +} + func (g *Game) writeShopPrompt(sess *net.Session) { sess.Write(fmt.Sprintf("\n%s", g.colorize(sess, "dialog", "> "))) } func (g *Game) shopConfig(sess *net.Session) *action.ShopConfig { - cfg, _ := sess.Shop.(*action.ShopConfig) + cfg := sess.Shop return cfg } +type shopMatch struct { + si *action.ShopItem + name string + def *object.ItemDef +} + func (g *Game) showShopBrowse(sess *net.Session) { cfg := g.shopConfig(sess) if cfg == nil { @@ -74,16 +117,16 @@ func (g *Game) showShopBrowse(sess *net.Session) { } unicode := true - if p, ok := sess.Player.(*player.Player); ok { + if p := sess.Player; p != nil { unicode = p.OptionBool("unicode") } t := Table{ Title: "Shop Inventory", - Columns: []string{"#", "Item", "Buy Price", "Sell Price"}, + Columns: []string{"Item", "Buy Price", "Sell Price"}, } - for i, si := range cfg.Items { + for _, si := range cfg.Items { def, _ := g.ItemStore.Load(si.ItemID) itemName := si.ItemID if def != nil { @@ -95,7 +138,6 @@ func (g *Game) showShopBrowse(sess *net.Session) { sellStr = fmt.Sprintf("%d cr", si.SellPrice) } t.Rows = append(t.Rows, []string{ - fmt.Sprintf("%d", i+1), g.colorize(sess, "item", itemName), g.colorize(sess, "credits_pickup", buyStr), g.colorize(sess, "credits_pickup", sellStr), @@ -107,55 +149,84 @@ func (g *Game) showShopBrowse(sess *net.Session) { } } -func (g *Game) doShopBuy(sess *net.Session, input string) { +func (g *Game) findShopMatches(input string, cfg *action.ShopConfig) []shopMatch { + var matches []shopMatch + for i := range cfg.Items { + si := &cfg.Items[i] + itemName := si.ItemID + def, _ := g.ItemStore.Load(si.ItemID) + if def != nil { + itemName = def.Name + } + if action.WordPrefixMatch(input, itemName) { + matches = append(matches, shopMatch{si: si, name: itemName, def: def}) + } + } + return matches +} + +func (g *Game) doShopBuy(sess *net.Session, input string, qty int, all bool) { cfg := g.shopConfig(sess) if cfg == nil { return } - idx, err := parseChoiceIndex(input) - if err == nil && idx > 0 && idx <= len(cfg.Items) { - si := cfg.Items[idx-1] - g.buyShopItem(sess, si) + p := sess.Player + + matches := g.findShopMatches(input, cfg) + if len(matches) == 0 { + sess.WriteLine("That item isn't for sale here. Use 'browse' to see shop inventory.") return } - var match *action.ShopItem - for i := range cfg.Items { - si := &cfg.Items[i] - def, _ := g.ItemStore.Load(si.ItemID) - itemName := si.ItemID - if def != nil { - itemName = def.Name + if len(matches) > 1 { + sess.WriteLine("That's ambiguous, which one?") + for _, m := range matches { + sess.WriteLine(fmt.Sprintf(" - %s", g.colorize(sess, "item", m.name))) } + return + } - invMatches := g.collectMatches(input, func(yield func(string, int) bool) { - yield(itemName, -1) - }) + si := matches[0].si + def := matches[0].def - if len(invMatches) > 0 { - if match != nil { - sess.WriteLine(fmt.Sprintf("That's ambiguous, which one? (use #, e.g. buy %s 1)", si.ItemID)) - return + actualQty := qty + if all || qty == 0 { + affordable := p.Credits / si.BuyPrice + if def != nil && def.Stackable { + actualQty = affordable + } else { + freeSlots := p.FreeSlots() + actualQty = affordable + if actualQty > freeSlots { + actualQty = freeSlots } - match = si } } - if match != nil { - g.buyShopItem(sess, *match) + if actualQty <= 0 { + if p.Credits < si.BuyPrice { + sess.WriteLine(fmt.Sprintf("You need %d credits to buy that (you have %d).", si.BuyPrice, p.Credits)) + } else { + sess.WriteLine("Your inventory is full.") + } return } - sess.WriteLine("That item isn't for sale here. Use 'browse' to see shop inventory.") + g.buyShopItem(sess, *si, actualQty) } -func (g *Game) buyShopItem(sess *net.Session, si action.ShopItem) { - p, _ := sess.Player.(*player.Player) +func (g *Game) buyShopItem(sess *net.Session, si action.ShopItem, qty int) { + p := sess.Player - if p.Credits < si.BuyPrice { - sess.WriteLine(fmt.Sprintf("You need %d credits to buy that (you have %d).", si.BuyPrice, p.Credits)) - return + totalCost := si.BuyPrice * qty + if p.Credits < totalCost { + qty = p.Credits / si.BuyPrice + if qty <= 0 { + sess.WriteLine(fmt.Sprintf("You need %d credits to buy that (you have %d).", si.BuyPrice, p.Credits)) + return + } + totalCost = si.BuyPrice * qty } def, _ := g.ItemStore.Load(si.ItemID) @@ -168,37 +239,83 @@ func (g *Game) buyShopItem(sess *net.Session, si action.ShopItem) { for i := 0; i < 28; i++ { slot := p.InvSlot(i) if slot != nil && slot.ItemID == si.ItemID { - p.Credits -= si.BuyPrice - slot.Quantity++ + p.Credits -= totalCost + slot.Quantity += qty g.AccountStore.SaveCharacter(p) itemColor := g.itemColorize(sess, def, displayName) - sess.WriteLine(fmt.Sprintf("You buy a %s for %s credits.", itemColor, g.colorize(sess, "credits_pickup", fmt.Sprintf("%d", si.BuyPrice)))) + if qty == 1 { + sess.WriteLine(fmt.Sprintf("You buy a %s for %s credits.", itemColor, g.colorize(sess, "credits_pickup", fmt.Sprintf("%d", si.BuyPrice)))) + } else { + sess.WriteLine(fmt.Sprintf("You buy %d %s for %s credits.", qty, itemColor, g.colorize(sess, "credits_pickup", fmt.Sprintf("%d", totalCost)))) + } return } } + + freeSlot := p.FirstFreeSlot() + if freeSlot == -1 { + sess.WriteLine("Your inventory is full.") + return + } + + p.Credits -= totalCost + p.SetInvSlot(freeSlot, g.newInventorySlot(si.ItemID, qty)) + g.AccountStore.SaveCharacter(p) + + itemColor := g.itemColorize(sess, def, displayName) + if qty == 1 { + sess.WriteLine(fmt.Sprintf("You buy a %s for %s credits.", itemColor, g.colorize(sess, "credits_pickup", fmt.Sprintf("%d", si.BuyPrice)))) + } else { + sess.WriteLine(fmt.Sprintf("You buy %d %s for %s credits.", qty, itemColor, g.colorize(sess, "credits_pickup", fmt.Sprintf("%d", totalCost)))) + } + return + } + + freeSlots := p.FreeSlots() + if qty > freeSlots { + if freeSlots == 0 { + sess.WriteLine("Your inventory is full.") + return + } + qty = freeSlots + totalCost = si.BuyPrice * qty + if p.Credits < totalCost { + qty = p.Credits / si.BuyPrice + totalCost = si.BuyPrice * qty + } } - freeSlot := p.FirstFreeSlot() - if freeSlot == -1 { - sess.WriteLine("Your inventory is full.") + if qty <= 0 { + if p.Credits < si.BuyPrice { + sess.WriteLine(fmt.Sprintf("You need %d credits to buy that (you have %d).", si.BuyPrice, p.Credits)) + } else { + sess.WriteLine("Your inventory is full.") + } return } - p.Credits -= si.BuyPrice - p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: si.ItemID, Quantity: 1}) + p.Credits -= totalCost + for j := 0; j < qty; j++ { + freeSlot := p.FirstFreeSlot() + p.SetInvSlot(freeSlot, g.newInventorySlot(si.ItemID, 1)) + } g.AccountStore.SaveCharacter(p) itemColor := g.itemColorize(sess, def, displayName) - sess.WriteLine(fmt.Sprintf("You buy a %s for %s credits.", itemColor, g.colorize(sess, "credits_pickup", fmt.Sprintf("%d", si.BuyPrice)))) + if qty == 1 { + sess.WriteLine(fmt.Sprintf("You buy a %s for %s credits.", itemColor, g.colorize(sess, "credits_pickup", fmt.Sprintf("%d", si.BuyPrice)))) + } else { + sess.WriteLine(fmt.Sprintf("You buy %d %s for %s credits.", qty, itemColor, g.colorize(sess, "credits_pickup", fmt.Sprintf("%d", totalCost)))) + } } -func (g *Game) doShopSell(sess *net.Session, input string) { +func (g *Game) doShopSell(sess *net.Session, input string, qty int, all bool) { cfg := g.shopConfig(sess) if cfg == nil { return } - p, _ := sess.Player.(*player.Player) + p := sess.Player matches := g.findInventoryMatches(input, p) if len(matches) == 0 { @@ -208,6 +325,9 @@ func (g *Game) doShopSell(sess *net.Session, input string) { if len(matches) > 1 { sess.WriteLine("That's ambiguous, which one?") + for _, m := range uniqueItemNames(matches) { + sess.WriteLine(fmt.Sprintf(" - %s", g.colorize(sess, "item", m))) + } return } @@ -232,38 +352,52 @@ func (g *Game) doShopSell(sess *net.Session, input string) { return } - p.RemoveItem(match.ID, 1) - p.Credits += shopItem.SellPrice + totalInInv := p.CountItem(match.ID) + actualQty := qty + if all || qty == 0 { + actualQty = totalInInv + } else if actualQty > totalInInv { + actualQty = totalInInv + } + + if actualQty <= 0 { + sess.WriteLine("You don't have that item.") + return + } + + p.RemoveItem(match.ID, actualQty) + p.Credits += shopItem.SellPrice * actualQty g.AccountStore.SaveCharacter(p) def, _ := g.ItemStore.Load(match.ID) - displayName := match.ID - if def != nil { - displayName = def.Name - } + displayName := match.Name itemColor := g.itemColorize(sess, def, displayName) - sess.WriteLine(fmt.Sprintf("You sell a %s for %s credits.", itemColor, g.colorize(sess, "credits_pickup", fmt.Sprintf("%d", shopItem.SellPrice)))) + if actualQty == 1 { + sess.WriteLine(fmt.Sprintf("You sell a %s for %s credits.", itemColor, g.colorize(sess, "credits_pickup", fmt.Sprintf("%d", shopItem.SellPrice)))) + } else { + totalCredits := shopItem.SellPrice * actualQty + sess.WriteLine(fmt.Sprintf("You sell %d %s for %s credits.", actualQty, itemColor, g.colorize(sess, "credits_pickup", fmt.Sprintf("%d", totalCredits)))) + } } func (g *Game) leaveShop(sess *net.Session) { sess.Shop = nil - p, ok := sess.Player.(*player.Player) - if !ok || p == nil || p.Action == nil || p.Action.Type != "talk" { + p := sess.Player + if p == nil || p.Action == nil || p.Action.Type != "talk" { sess.State = net.StateGame if p != nil { - g.CancelAction(p) + g.cancelAction(p) } g.writePrompt(sess) return } - behaviorID, _ := p.Action.Data["behavior_id"].(string) nodeKey, _ := p.Action.Data["node"].(string) - cfg, err := g.BehaviorStore.LoadTalk(behaviorID) - if err != nil { + cfg, ok := p.Action.Data["talk_cfg"].(*action.TalkConfig) + if !ok || cfg == nil { sess.State = net.StateGame - g.CancelAction(p) + g.cancelAction(p) g.writePrompt(sess) return } @@ -271,7 +405,7 @@ func (g *Game) leaveShop(sess *net.Session) { node, ok := cfg.Nodes[nodeKey] if !ok { sess.State = net.StateGame - g.CancelAction(p) + g.cancelAction(p) g.writePrompt(sess) return } @@ -287,7 +421,7 @@ func (g *Game) leaveShop(sess *net.Session) { } if visible == 0 { sess.State = net.StateGame - g.CancelAction(p) + g.cancelAction(p) g.writePrompt(sess) return } |
