aboutsummaryrefslogtreecommitdiff
path: root/internal/game/cmd_shop.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/cmd_shop.go')
-rw-r--r--internal/game/cmd_shop.go295
1 files changed, 295 insertions, 0 deletions
diff --git a/internal/game/cmd_shop.go b/internal/game/cmd_shop.go
new file mode 100644
index 0000000..a3f7613
--- /dev/null
+++ b/internal/game/cmd_shop.go
@@ -0,0 +1,295 @@
+package game
+
+import (
+ "fmt"
+ "strings"
+
+ "thehouseoficarus/internal/action"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+)
+
+func (g *Game) handleShopInput(sess *net.Session, input string) {
+ if sess.Player == nil {
+ sess.State = net.StateGame
+ g.writePrompt(sess)
+ return
+ }
+
+ input = strings.TrimSpace(input)
+ parts := strings.Fields(strings.ToLower(input))
+ if len(parts) == 0 {
+ g.showShopBrowse(sess)
+ return
+ }
+
+ cmd := parts[0]
+
+ 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:], " "))
+ }
+ case "sell":
+ if len(parts) < 2 {
+ sess.WriteLine("Sell what?")
+ } else {
+ g.doShopSell(sess, strings.Join(parts[1:], " "))
+ }
+ case "browse", "list":
+ g.showShopBrowse(sess)
+ case "leave", "bye", "exit", "quit":
+ g.leaveShop(sess)
+ return
+ default:
+ sess.WriteLine("Commands: buy <item>, sell <item>, browse, leave")
+ }
+ g.writeShopPrompt(sess)
+}
+
+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)
+ return cfg
+}
+
+func (g *Game) showShopBrowse(sess *net.Session) {
+ cfg := g.shopConfig(sess)
+ if cfg == nil {
+ return
+ }
+
+ if cfg.Message != "" {
+ sess.WriteLine(fmt.Sprintf("\n%s", g.colorize(sess, "dialog", cfg.Message)))
+ }
+
+ if len(cfg.Items) == 0 {
+ sess.WriteLine("This shop has nothing for sale.")
+ return
+ }
+
+ unicode := true
+ if p, ok := sess.Player.(*player.Player); ok {
+ unicode = p.OptionBool("unicode")
+ }
+
+ t := Table{
+ Title: "Shop Inventory",
+ Columns: []string{"#", "Item", "Buy Price", "Sell Price"},
+ }
+
+ for i, si := range cfg.Items {
+ def, _ := g.ItemStore.Load(si.ItemID)
+ itemName := si.ItemID
+ if def != nil {
+ itemName = def.Name
+ }
+ buyStr := fmt.Sprintf("%d cr", si.BuyPrice)
+ sellStr := "\u2014"
+ if si.SellPrice > 0 {
+ 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),
+ })
+ }
+
+ for _, line := range t.Render(unicode) {
+ sess.WriteLine(line)
+ }
+}
+
+func (g *Game) doShopBuy(sess *net.Session, input string) {
+ 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)
+ 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
+ }
+
+ invMatches := g.collectMatches(input, func(yield func(string, int) bool) {
+ yield(itemName, -1)
+ })
+
+ 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
+ }
+ match = si
+ }
+ }
+
+ if match != nil {
+ g.buyShopItem(sess, *match)
+ return
+ }
+
+ sess.WriteLine("That item isn't for sale here. Use 'browse' to see shop inventory.")
+}
+
+func (g *Game) buyShopItem(sess *net.Session, si action.ShopItem) {
+ p, _ := sess.Player.(*player.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
+ }
+
+ def, _ := g.ItemStore.Load(si.ItemID)
+ displayName := si.ItemID
+ if def != nil {
+ displayName = def.Name
+ }
+
+ if def != nil && def.Stackable {
+ for i := 0; i < 28; i++ {
+ slot := p.InvSlot(i)
+ if slot != nil && slot.ItemID == si.ItemID {
+ p.Credits -= si.BuyPrice
+ slot.Quantity++
+ 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))))
+ return
+ }
+ }
+ }
+
+ freeSlot := p.FirstFreeSlot()
+ if freeSlot == -1 {
+ sess.WriteLine("Your inventory is full.")
+ return
+ }
+
+ p.Credits -= si.BuyPrice
+ p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: si.ItemID, Quantity: 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))))
+}
+
+func (g *Game) doShopSell(sess *net.Session, input string) {
+ cfg := g.shopConfig(sess)
+ if cfg == nil {
+ return
+ }
+
+ p, _ := sess.Player.(*player.Player)
+
+ matches := g.findInventoryMatches(input, p)
+ if len(matches) == 0 {
+ sess.WriteLine("You don't have that item.")
+ return
+ }
+
+ if len(matches) > 1 {
+ sess.WriteLine("That's ambiguous, which one?")
+ return
+ }
+
+ match := matches[0]
+
+ var shopItem *action.ShopItem
+ for i := range cfg.Items {
+ si := &cfg.Items[i]
+ if si.ItemID == match.ID && si.SellPrice > 0 {
+ shopItem = si
+ break
+ }
+ }
+
+ if shopItem == nil {
+ def, _ := g.ItemStore.Load(match.ID)
+ displayName := match.ID
+ if def != nil {
+ displayName = def.Name
+ }
+ sess.WriteLine(fmt.Sprintf("The shop doesn't want to buy %s.", g.colorize(sess, "item", displayName)))
+ return
+ }
+
+ p.RemoveItem(match.ID, 1)
+ p.Credits += shopItem.SellPrice
+ g.AccountStore.SaveCharacter(p)
+
+ def, _ := g.ItemStore.Load(match.ID)
+ displayName := match.ID
+ if def != nil {
+ displayName = def.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))))
+}
+
+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" {
+ sess.State = net.StateGame
+ if p != nil {
+ 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 {
+ sess.State = net.StateGame
+ g.CancelAction(p)
+ g.writePrompt(sess)
+ return
+ }
+
+ node, ok := cfg.Nodes[nodeKey]
+ if !ok {
+ sess.State = net.StateGame
+ g.CancelAction(p)
+ g.writePrompt(sess)
+ return
+ }
+
+ sess.State = net.StateTalk
+ visible := 0
+ for _, opt := range node.Options {
+ if opt.Condition != nil && !g.checkCondition(sess, opt.Condition) {
+ continue
+ }
+ visible++
+ sess.WriteLine(fmt.Sprintf(" %d. %s", visible, opt.Text))
+ }
+ if visible == 0 {
+ sess.State = net.StateGame
+ g.CancelAction(p)
+ g.writePrompt(sess)
+ return
+ }
+ sess.Write("\nChoice: ")
+}