Skip to content

Commit 2623d38

Browse files
committed
test: cover anti-stack use-to-item rejection
1 parent 7cdbd98 commit 2623d38

3 files changed

Lines changed: 54 additions & 2 deletions

File tree

docs/qa/manual-client-checklist.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ Expected result:
248248
- the consumed source cell disappears only on a full merge
249249
- if the target has only partial room, both source and target counts refresh, and item/non-item quickslots bound to either still-occupied cell remain unchanged
250250
- all item quickslots for a removed source cell are cleared in deterministic quickslot-position order on full merge, target item quickslots remain stable on full merge, and unrelated skill/command quickslots remain
251-
- restricted or invalid states (`anti_stack`, transfer anti-flags, missing/non-stackable/malformed/mismatched templates, source/target `vnum` mismatches, locked source/target stacks, selected-character job/sex/min-level restrictions, duplicate source/target item instance IDs, duplicate live occupancy of the source or target carried cell, already-full targets, source/target counts already above template `max_count`, or selected characters at the bootstrap zero-HP floor) fail closed with no visible mutation
251+
- restricted or invalid states (`anti_stack`, transfer anti-flags, missing/non-stackable/malformed/mismatched templates, source/target `vnum` mismatches, locked source/target stacks, selected-character job/sex/min-level restrictions, duplicate source/target item instance IDs, duplicate live occupancy of the source or target carried cell, already-full targets, source/target counts already above template `max_count`, or selected characters at the bootstrap zero-HP floor) fail closed with no visible mutation; for `anti_stack`, both carried stacks and item quickslots should remain unchanged
252252
- a `min_level` restriction above the selected character's level or a selected character at the bootstrap zero-HP floor leaves both carried stacks and any source-cell item quickslot unchanged even when the source and target are otherwise compatible
253253

254254
### 4.5.3 Retarget an item quickslot (`QUICKSLOT_ADD`)

internal/minimal/item_use_runtime_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,6 +1206,58 @@ func TestGameSessionFlowItemUseToItemRejectsMissingSourceTemplateWithoutMutation
12061206
}
12071207
}
12081208

1209+
func TestGameSessionFlowItemUseToItemRejectsAuthoredAntiStackTemplateWithoutMutation(t *testing.T) {
1210+
ticketStore := loginticket.NewFileStore(t.TempDir())
1211+
accounts := accountstore.NewFileStore(t.TempDir())
1212+
owner := peerVisibilityCharacter("UseToItemAntiStack", 0x010305bd, 0x020405bd, 1100, 2100, 0, 101, 201)
1213+
owner.Inventory = []inventory.ItemInstance{
1214+
{ID: 201, Vnum: 27001, Count: 7, Slot: 5},
1215+
{ID: 202, Vnum: 27001, Count: 8, Slot: 6},
1216+
}
1217+
owner.Quickslots = []loginticket.Quickslot{
1218+
{Position: 2, Type: quickslotproto.TypeItem, Slot: 5},
1219+
{Position: 4, Type: quickslotproto.TypeItem, Slot: 6},
1220+
}
1221+
issuePeerTicket(t, ticketStore, "uit-anti-stack", 0x505050bd, owner)
1222+
if err := accounts.Save(accountstore.Account{Login: "uit-anti-stack", Empire: owner.Empire, Characters: cloneCharacters([]loginticket.Character{owner})}); err != nil {
1223+
t.Fatalf("seed anti-stack item-use-to-item account: %v", err)
1224+
}
1225+
itemStore := newItemTemplateStore(t, []itemcatalog.Template{{
1226+
Vnum: 27001,
1227+
Name: "Anti Stack Potion",
1228+
Stackable: true,
1229+
MaxCount: 20,
1230+
AntiStack: true,
1231+
}})
1232+
runtime, err := newGameRuntimeWithStoresAndTransferTriggersAndItemStore(config.Service{LegacyAddr: ":13000", PublicAddr: "127.0.0.1"}, ticketStore, accounts, nil, nil, itemStore, nil)
1233+
if err != nil {
1234+
t.Fatalf("unexpected anti-stack item-use-to-item runtime error: %v", err)
1235+
}
1236+
flow, _ := enterGameWithLoginTicket(t, runtime.SessionFactory(), "uit-anti-stack", 0x505050bd)
1237+
defer closeSessionFlow(t, flow)
1238+
1239+
out, err := flow.HandleClientFrame(decodeSingleFrame(t, itemproto.EncodeClientUseToItem(itemproto.ClientUseToItemPacket{Source: itemproto.InventoryPosition(5), Target: itemproto.InventoryPosition(6)})))
1240+
if err != nil {
1241+
t.Fatalf("unexpected anti-stack item-use-to-item packet error: %v", err)
1242+
}
1243+
if len(out) != 0 {
1244+
t.Fatalf("expected anti-stack ITEM_USE_TO_ITEM to emit no frames, got %d", len(out))
1245+
}
1246+
if queued := flushServerFrames(t, flow); len(queued) != 0 {
1247+
t.Fatalf("expected no queued frames after anti-stack ITEM_USE_TO_ITEM rejection, got %d", len(queued))
1248+
}
1249+
persisted, err := accounts.Load("uit-anti-stack")
1250+
if err != nil {
1251+
t.Fatalf("load persisted anti-stack item-use-to-item account: %v", err)
1252+
}
1253+
if !reflect.DeepEqual(persisted.Characters[0].Inventory, owner.Inventory) {
1254+
t.Fatalf("anti-stack ITEM_USE_TO_ITEM mutated inventory: got %+v want %+v", persisted.Characters[0].Inventory, owner.Inventory)
1255+
}
1256+
if !reflect.DeepEqual(persisted.Characters[0].Quickslots, owner.Quickslots) {
1257+
t.Fatalf("anti-stack ITEM_USE_TO_ITEM mutated quickslots: got %+v want %+v", persisted.Characters[0].Quickslots, owner.Quickslots)
1258+
}
1259+
}
1260+
12091261
func TestGameSessionFlowItemUseToItemPartialMergePreservesAllTargetItemQuickslots(t *testing.T) {
12101262
ticketStore := loginticket.NewFileStore(t.TempDir())
12111263
accounts := accountstore.NewFileStore(t.TempDir())

spec/protocol/item-use-bootstrap.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ The first owned live `ITEM_USE_TO_ITEM` use case is intentionally only stack-on-
4747
- the minimal session/runtime packet path now freezes equippable templates with an authored `equip_slot` as no-frame/no-mutation rejections for drag-to-item stack consolidation, matching the existing player mutation boundary guard
4848
- the resolved source template `vnum` must match the live source stack `vnum`; a mismatched template is treated like unresolved/malformed metadata and fails closed
4949
- the template-authored `max_count` must fit the currently owned one-byte item refresh count range (`<= 255`) because `ITEM_SET` / `ITEM_UPDATE` expose count as `uint8` in this bootstrap packet family; the minimal session/runtime packet path now freezes an over-`uint8` runtime template max as a no-frame/no-mutation rejection even if such a malformed template is injected after store validation
50-
- templates with authored `anti_stack = true`, `anti_drop = true`, `anti_give = true`, or `anti_sell = true` are rejected for drag-to-item stack consolidation even when the live stacks otherwise match
50+
- templates with authored `anti_stack = true`, `anti_drop = true`, `anti_give = true`, or `anti_sell = true` are rejected for drag-to-item stack consolidation even when the live stacks otherwise match; the minimal session/runtime packet path now freezes authored `anti_stack` as a no-frame/no-mutation rejection with carried stacks and item quickslots unchanged
5151
- templates with authored job/sex restrictions for the selected character (`anti_warrior`, `anti_assassin`, `anti_sura`, `anti_shaman`, `anti_male`, or `anti_female`) are rejected for drag-to-item stack consolidation even when the live stacks otherwise match
5252
- templates with authored `min_level` above the selected character's current persisted `level` are rejected for drag-to-item stack consolidation even when the live stacks otherwise match
5353
- the live source stack must have non-zero count, must not already exceed the template-authored `max_count`, and must validate as a well-formed carried inventory item even when the merge will remove it entirely

0 commit comments

Comments
 (0)