diablo2-web/tests/physical-gold-piles.test.ts

195 lines
7.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, expect, it } from 'vitest'
import {
getInventoryGoldLimit,
getGoldFlippyRect,
resolveGroundItemSpriteRect,
} from '../src/ui/inventory.ts'
import { BAKED_UI_MANIFEST } from '../src/ui/baked-ui-meta.ts'
import { goldItem } from '../src/game/items.ts'
import { GameEngine } from '../src/game/engine.ts'
function createTestEngine(playerLevel = 1, initialGold = 0): GameEngine {
const engine = new GameEngine(
{ widthPx: 1000, heightPx: 1000, overlap: () => 0 },
{
spawn: { x: 100, y: 100 },
stats: [],
xpTable: [0, 500, 1500],
skills: [],
npcDefs: [],
questDefs: [],
combatOptions: {
playerSpeed: 4,
playerReach: 50,
playerCooldownTicks: 10,
playerDamage: 5,
playerManaPerAttack: 1,
respawnTicks: 100,
},
talkRadius: 50,
pickupRadius: 30,
inventoryCols: 10,
inventoryRows: 4,
},
)
engine.world.player.level = playerLevel
engine.bag.gold = initialGold
return engine
}
describe('Issue #419: Physical Gold Piles & Inventory Capacity (1.13c Parity)', () => {
it('T1: resolves gold piles into 3 authentic 1.13c graphic tiers', () => {
const flippyRects = BAKED_UI_MANIFEST.flippyRects
// Small tier: 1–49 gold -> flpgld_0
expect(getGoldFlippyRect(1)).toEqual(flippyRects['flpgld_0'])
expect(getGoldFlippyRect(25)).toEqual(flippyRects['flpgld_0'])
expect(getGoldFlippyRect(49)).toEqual(flippyRects['flpgld_0'])
expect(resolveGroundItemSpriteRect({ isGold: true, amount: 49, code: 'gld' })).toEqual(flippyRects['flpgld_0'])
// Medium tier: 50–499 gold -> flpgld_1
expect(getGoldFlippyRect(50)).toEqual(flippyRects['flpgld_1'])
expect(getGoldFlippyRect(250)).toEqual(flippyRects['flpgld_1'])
expect(getGoldFlippyRect(499)).toEqual(flippyRects['flpgld_1'])
expect(resolveGroundItemSpriteRect({ isGold: true, amount: 50, code: 'gld' })).toEqual(flippyRects['flpgld_1'])
// Large tier: 500+ gold -> flpgld_2
expect(getGoldFlippyRect(500)).toEqual(flippyRects['flpgld_2'])
expect(getGoldFlippyRect(5000)).toEqual(flippyRects['flpgld_2'])
expect(getGoldFlippyRect(50000)).toEqual(flippyRects['flpgld_2'])
expect(resolveGroundItemSpriteRect({ isGold: true, amount: 15000, code: 'gld' })).toEqual(flippyRects['flpgld_2'])
})
it('T2: calculates authentic character inventory gold capacity limit (level * 10,000)', () => {
// Level 1: 10,000
expect(getInventoryGoldLimit(1)).toBe(10_000)
// Level 10: 100,000
expect(getInventoryGoldLimit(10)).toBe(100_000)
// Level 20: 200,000
expect(getInventoryGoldLimit(20)).toBe(200_000)
// Level 85: 850,000
expect(getInventoryGoldLimit(85)).toBe(850_000)
// Level 99: 990,000
expect(getInventoryGoldLimit(99)).toBe(990_000)
// Edge case: Level <= 0 clamped to 1
expect(getInventoryGoldLimit(0)).toBe(10_000)
expect(getInventoryGoldLimit(-5)).toBe(10_000)
})
it('T3: full gold pickup when current + pile <= maxCapacity', () => {
const engine = createTestEngine(1, 0)
expect(engine.maxGoldCapacity).toBe(10_000)
const pile = engine.dropGold(2500, 100, 100)!
expect(pile).toBeDefined()
expect(engine.groundItems.count).toBe(1)
expect(engine.ground.length).toBe(1)
const res = engine.pickupGold(pile.id)
expect(res.success).toBe(true)
expect(res.amount).toBe(2500)
expect(res.remaining).toBe(0)
expect(engine.gold).toBe(2500)
expect(engine.groundItems.count).toBe(0)
expect(engine.ground.length).toBe(0)
})
it('T4: partial gold pickup when current + pile > maxCapacity', () => {
const engine = createTestEngine(1, 7000)
expect(engine.maxGoldCapacity).toBe(10_000)
const pile = engine.dropGold(8000, 100, 100)!
expect(pile.amount).toBe(8000)
const res = engine.pickupGold(pile.id)
expect(res.success).toBe(true)
expect(res.amount).toBe(3000) // 10000 - 7000
expect(res.remaining).toBe(5000) // 8000 - 3000
expect(engine.gold).toBe(10_000)
// Ground entity remains with updated amount and label
const remainingEntity = engine.groundItems.get(pile.id)!
expect(remainingEntity).toBeDefined()
expect(remainingEntity.amount).toBe(5000)
expect(remainingEntity.name).toBe('5000 Gold')
expect(remainingEntity.nameZh).toBe('5000 金币')
expect(engine.ground.length).toBe(1)
expect((engine.ground[0]!.item as any).stack).toBe(5000)
})
it('T5: graphic tier updates on partial pickup remainder', () => {
const engine = createTestEngine(1, 9970)
// Drop 80 gold pile (tier 1: medium, 50-499)
const pile = engine.dropGold(80, 100, 100)!
expect(resolveGroundItemSpriteRect(pile)).toEqual(BAKED_UI_MANIFEST.flippyRects['flpgld_1'])
// Pickup 30 gold -> remainder 50 gold (still medium tier: flpgld_1)
const res1 = engine.pickupGold(pile.id)
expect(res1.amount).toBe(30)
expect(res1.remaining).toBe(50)
expect(resolveGroundItemSpriteRect(pile)).toEqual(BAKED_UI_MANIFEST.flippyRects['flpgld_1'])
// Spend some gold so player has 9990
engine.bag.gold = 9990
// Pickup 10 gold from remainder 50 -> remainder becomes 40 gold (small tier: flpgld_0)
const res2 = engine.pickupGold(pile.id)
expect(res2.amount).toBe(10)
expect(res2.remaining).toBe(40)
expect(resolveGroundItemSpriteRect(pile)).toEqual(BAKED_UI_MANIFEST.flippyRects['flpgld_0'])
})
it('T6: rejects pickup when inventory gold is already at capacity', () => {
const engine = createTestEngine(1, 10_000)
const pile = engine.dropGold(5000, 100, 100)!
const initialPickups = engine.metrics.pickups
const res = engine.pickupGold(pile.id)
expect(res.success).toBe(false)
expect(res.amount).toBe(0)
expect(res.remaining).toBe(5000)
expect(engine.gold).toBe(10_000)
expect(engine.groundItems.count).toBe(1)
expect(engine.metrics.pickups).toBe(initialPickups)
// Also verify pickupItem delegation
const itemRes = engine.pickupItem(pile.id)
expect(itemRes.success).toBe(false)
expect(itemRes.reason).toBe('full')
expect(engine.metrics.inventoryRefusals).toBeGreaterThan(0)
})
it('T7: walkover auto-pickup when player steps within pickupRadius', () => {
const engine = createTestEngine(1, 0)
// Drop gold at (105, 105), near player spawn (100, 100)
engine.dropGold(1200, 105, 105)
expect(engine.groundItems.count).toBe(1)
expect(engine.gold).toBe(0)
// Tick engine with no inputs (player at 100, 100 is within 30px radius of 105, 105)
engine.tick({
movement: { x: 0, y: 0 },
attacking: false,
pickingUp: false,
talking: false,
digits: [],
saving: false,
loading: false,
})
expect(engine.gold).toBe(1200)
expect(engine.groundItems.count).toBe(0)
})
it('T8: goldItem does not clamp amounts > 5000', () => {
const item = goldItem(50_000)
expect(item.stack).toBe(50_000)
expect(item.value).toBe(50_000)
const engine = createTestEngine(10, 0) // Level 10 cap: 100,000
const pile = engine.dropGold(50_000, 100, 100)!
expect(pile.amount).toBe(50_000)
engine.pickupGold(pile.id)
expect(engine.gold).toBe(50_000)
})
})