1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
package game
// Regression: failed-craft delivery used FirstFreeSlot only, while the
// pre-roll fit gate (craftFailFits -> craftOutputFits) counts a matching
// stack. A stackable fail replacement could therefore be destroyed on a full
// inventory even though the gate promised it would fit. Delivery must merge
// like craft/harvest/pickup does.
import (
"path/filepath"
"testing"
"thehouseoficarus/internal/behavior"
"thehouseoficarus/internal/item"
"thehouseoficarus/internal/net"
"thehouseoficarus/internal/player"
)
const polishFailRecipeYAML = `id: polish_out
name: polish out
stackable: true
craft:
- type: smithing
subtype: smelting
fail: polish_ash
ingredients:
- items:
- polish_ore
quantity: 1
success:
base: 0
cap: 0
per_level: 0
output_qty: 1
ticks_per_cycle: 1
`
func seedPolishFailItems(t *testing.T, dir string) {
t.Helper()
writeSeedFile(t, filepath.Join(dir, "items", "polish_out.yaml"), []byte(polishFailRecipeYAML))
writeSeedFile(t, filepath.Join(dir, "items", "polish_ash.yaml"),
[]byte("id: polish_ash\nname: polish ash\nstackable: true\nvalue: 1\n"))
writeSeedFile(t, filepath.Join(dir, "items", "polish_ore.yaml"),
[]byte("id: polish_ore\nname: polish ore\nstackable: true\nvalue: 1\n"))
}
func TestPlaceCraftFailMergesIntoMatchingStack(t *testing.T) {
h := newHarness(t)
seedPolishFailItems(t, h.dir)
p := player.New("alice")
h.g.RunOnOwner(func() {
h.g.ItemStore.Reload(h.dir)
p.SetInvSlot(0, &player.InventorySlot{ItemID: "polish_ash", Quantity: 3})
fillInventoryExcept(p)
h.g.placeCraftFail(p, &item.CraftDef{Fail: "polish_ash"})
if slot := p.InvSlot(0); slot == nil || slot.Quantity != 4 {
t.Fatalf("fail replacement was not merged into the matching stack: %+v", p.InvSlot(0))
}
})
}
func TestCraftFailureMergesFailStackWhenFull(t *testing.T) {
h := newHarness(t)
h.seedAccount("alice")
p := h.seedCharacter("alice", "alice")
seedPolishFailItems(t, h.dir)
sess := &net.Session{Conn: newChanConn(), Player: p}
sess.SetState(net.StateGame)
h.g.RunOnOwner(func() {
h.g.ItemStore.Reload(h.dir)
// Full inventory: a matching output stack, a matching fail stack and
// the ingredient. Consuming the ingredient frees its slot, so the old
// FirstFreeSlot delivery landed the fail item there as a second stack.
p.SetInvSlot(0, &player.InventorySlot{ItemID: "polish_ash", Quantity: 1})
p.SetInvSlot(1, &player.InventorySlot{ItemID: "polish_ore", Quantity: 1})
p.SetInvSlot(2, &player.InventorySlot{ItemID: "polish_out", Quantity: 1})
for i := 3; i < 28; i++ {
p.SetInvSlot(i, &player.InventorySlot{ItemID: "filler", Quantity: 1})
}
p.Action = &behavior.Action{
Type: behavior.TypeSmelt,
TargetID: "polish_out",
Data: &behavior.ProductionData{ItemID: "polish_out", Phase: 1, Remaining: 1},
}
h.g.advanceProduction(sess, p)
if got := p.CountItem("polish_ore"); got != 0 {
t.Errorf("ingredient = %d, want 0 (consumed)", got)
}
if got := p.CountItem("polish_ash"); got != 2 {
t.Errorf("fail product = %d, want 2", got)
}
stacks := 0
for i := 0; i < 28; i++ {
if s := p.InvSlot(i); s != nil && s.ItemID == "polish_ash" {
stacks++
}
}
if stacks != 1 {
t.Errorf("fail product was delivered as %d stacks, want 1 merged stack", stacks)
}
if slot := p.InvSlot(0); slot == nil || slot.Quantity != 2 {
t.Errorf("matching fail stack = %+v, want quantity 2", p.InvSlot(0))
}
})
}
|