195 lines
7.1 KiB
TypeScript
195 lines
7.1 KiB
TypeScript
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)
|
||
})
|
||
})
|