aboutsummaryrefslogtreecommitdiff
path: root/internal/game/bank_transport_regression_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/bank_transport_regression_test.go')
-rw-r--r--internal/game/bank_transport_regression_test.go114
1 files changed, 114 insertions, 0 deletions
diff --git a/internal/game/bank_transport_regression_test.go b/internal/game/bank_transport_regression_test.go
new file mode 100644
index 0000000..529df84
--- /dev/null
+++ b/internal/game/bank_transport_regression_test.go
@@ -0,0 +1,114 @@
+package game
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "thehouseoficarus/internal/behavior"
+ "thehouseoficarus/internal/casino"
+ "thehouseoficarus/internal/combat"
+ "thehouseoficarus/internal/item"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+ "thehouseoficarus/internal/world"
+)
+
+// regressionGame builds a minimal Game with real stores over a temp data dir.
+func regressionGame(t *testing.T) (*Game, *net.Session) {
+ t.Helper()
+ dir := t.TempDir()
+ for _, sub := range []string{"items", "rooms"} {
+ if err := os.MkdirAll(filepath.Join(dir, sub), 0o755); err != nil {
+ t.Fatal(err)
+ }
+ }
+ if err := os.WriteFile(filepath.Join(dir, "items", "chips.yaml"), []byte("name: chips\nstackable: true\n"), 0o644); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(filepath.Join(dir, "rooms", "1.yaml"), []byte("name: test\n"), 0o644); err != nil {
+ t.Fatal(err)
+ }
+ g := &Game{
+ Deps: Deps{
+ World: world.New(dir),
+ ItemStore: item.NewItemStore(dir),
+ AccountStore: player.NewAccountStore(dir),
+ },
+ Casino: casino.NewManager(1),
+ Combat: combat.NewTracker(),
+ }
+ sess := &net.Session{Player: player.New("alice"), Conn: &casinoTestConn{}}
+ sess.Player.RoomID = 1
+ return g, sess
+}
+
+// Regression: a stackable withdrawal must succeed via stack merging even
+// when the inventory has zero free slots.
+func TestBankWithdrawMergesStackIntoFullInventory(t *testing.T) {
+ g, sess := regressionGame(t)
+ for i := 0; i < 28; i++ {
+ sess.Player.SetInvSlot(i, &player.InventorySlot{ItemID: "filler", Quantity: 1})
+ }
+ sess.Player.SetInvSlot(0, &player.InventorySlot{ItemID: "chips", Quantity: 10})
+ sess.Player.SetBankSlot(0, &player.InventorySlot{ItemID: "chips", Quantity: 5})
+
+ g.doBankWithdraw(sess, "1")
+
+ inv := sess.Player.InvSlot(0)
+ if inv == nil || inv.Quantity != 15 {
+ t.Fatalf("stack withdrawal blocked by a full inventory: inventory=%+v", inv)
+ }
+ if sess.Player.BankSlot(0) != nil {
+ t.Fatal("bank slot was not cleared after the withdrawal")
+ }
+}
+
+// Regression: stacks of the same item but different quality must not merge,
+// which would silently destroy the withdrawn stack's properties.
+func TestBankWithdrawKeepsIncompatibleQualitySeparate(t *testing.T) {
+ g, sess := regressionGame(t)
+ sess.Player.SetInvSlot(0, &player.InventorySlot{ItemID: "chips", Quantity: 10, Quality: 3, MaxQuality: 10})
+ sess.Player.SetBankSlot(0, &player.InventorySlot{ItemID: "chips", Quantity: 5, Quality: 8, MaxQuality: 10})
+
+ g.doBankWithdraw(sess, "1")
+
+ first, second := sess.Player.InvSlot(0), sess.Player.InvSlot(1)
+ if first == nil || first.Quantity != 10 || first.Quality != 3 {
+ t.Fatalf("existing stack was disturbed: %+v", first)
+ }
+ if second == nil || second.Quantity != 5 || second.Quality != 8 || second.MaxQuality != 10 {
+ t.Fatalf("incompatible stack was merged or metadata lost: first=%+v second=%+v", first, second)
+ }
+}
+
+// Regression: sequenced transport modules (like transport_home) bypass
+// triggerTransport entirely; the casino gate must also apply when the module
+// is started.
+func TestSequencedTransportBlockedWhilePlaying(t *testing.T) {
+ g, sess := regressionGame(t)
+
+ oldMods, oldIndex := AllMods, modByID
+ t.Cleanup(func() { AllMods, modByID = oldMods, oldIndex })
+ mod := &ModDef{
+ ID: "transport_home", Name: "transport home", Category: ModTransport,
+ Level: 1, Destination: 2,
+ Sequence: []behavior.ModTriggerStep{{Message: "wait", Delay: 1}},
+ }
+ AllMods = []*ModDef{mod}
+ modByID = map[string]*ModDef{mod.ID: mod}
+
+ g.Casino.JoinMachine(1, casino.MachineConfig{ID: "slots", Game: casino.GameSlots}, sess.Player.Name)
+ conn := sess.Conn.(*casinoTestConn)
+ conn.data = nil
+
+ g.doTrigger(sess, "transport_home")
+
+ if sess.Player.Action != nil {
+ t.Fatalf("transport sequence scheduled despite casino participation: %+v", sess.Player.Action)
+ }
+ if !strings.Contains(string(conn.data), "playing a game") {
+ t.Fatalf("missing casino-gate message: %q", string(conn.data))
+ }
+}