203 lines
9.0 KiB
JavaScript
203 lines
9.0 KiB
JavaScript
// 临时冒烟:验证 camp.js / wideworld.js 运行时行为(不加入正式测试)
|
||
import camp from './src/games/starve/mods/camp.js'
|
||
import wide from './src/games/starve/mods/wideworld.js'
|
||
|
||
let fails = 0
|
||
const ok = (c, m) => { if (c) console.log(' PASS', m); else { fails++; console.error(' FAIL', m) } }
|
||
|
||
const makeCtx = () => new Proxy({}, { get: (t, k) => (k in t ? t[k] : () => {}), set: (t, k, v) => { t[k] = v; return true } })
|
||
|
||
const items = {
|
||
log: { name: '木料', stack: 20 },
|
||
rock: { name: '石头', stack: 20 },
|
||
spear: { name: '长矛', stack: 1, equip: 'hand' },
|
||
}
|
||
function makeG(opts = {}) {
|
||
const world = { entities: opts.entities || [], monsters: opts.monsters || [], fires: [] }
|
||
const p = opts.player || { x: 1200, y: 1000, slots: [], pack: null, hp: 100, san: 100, dir: 1, dead: false }
|
||
const count = (code) => (p.slots || []).reduce((n, s) => n + (s && s.code === code ? s.n : 0), 0)
|
||
+ (p.pack || []).reduce((n, s) => n + (s && s.code === code ? s.n : 0), 0)
|
||
const g = {
|
||
get players() { return [p] },
|
||
get monsters() { return world.monsters },
|
||
get entities() { return world.entities },
|
||
get fires() { return world.fires },
|
||
mon: {
|
||
guard: { hp: 9999, speed: 150, dmg: 60, score: 0, friendly: true, name: '守卫' },
|
||
hound: { hp: 150, speed: 195, dmg: 20, score: 20, name: '猎犬', hostile: true },
|
||
},
|
||
items, tune: { stackMax: 20 },
|
||
worldW: 2400, worldH: 2000, day: 1, season: 'autumn', phase: 'day', time: 5,
|
||
isLand: () => true,
|
||
litAt: () => true,
|
||
findLand: (x, y) => ({ x, y }),
|
||
countItem: (pp, c) => count(c),
|
||
hasCost: (pp, cost) => Object.entries(cost).every(([c, n]) => count(c) >= n),
|
||
payCost: (pp, cost) => {
|
||
if (!Object.entries(cost).every(([c, n]) => count(c) >= n)) return false
|
||
Object.entries(cost).forEach(([c, need]) => {
|
||
let n = need
|
||
;[p.slots, p.pack].filter(Boolean).forEach((area) => {
|
||
for (let i = 0; i < area.length && n > 0; i++) {
|
||
const s = area[i]
|
||
if (s && s.code === c) { const t = Math.min(n, s.n); s.n -= t; n -= t; if (s.n <= 0) area[i] = null }
|
||
}
|
||
})
|
||
})
|
||
return true
|
||
},
|
||
addItem: (pp, c, n) => {
|
||
for (let i = 0; i < p.slots.length; i++) if (!p.slots[i]) { p.slots[i] = { code: c, n }; return }
|
||
p.slots.push({ code: c, n })
|
||
},
|
||
consumeAt: () => {},
|
||
me: () => p,
|
||
isHost: () => true,
|
||
rand: (a, b) => a,
|
||
addEntity: (type, x, y, extra) => { const e = { id: world.entities.length + 1, type, x, y, ...(extra || {}) }; world.entities.push(e); return e },
|
||
removeEntity: () => {},
|
||
markDirty: () => {},
|
||
spawnMob: (kind, x, y) => { const m = { id: world.monsters.length + 1, kind, x, y, hp: 100, cd: 0 }; world.monsters.push(m); return m },
|
||
removeMob: () => {},
|
||
killMob: () => {},
|
||
damageMob: (m, d) => { m.hp -= d },
|
||
moveToward: (m, x, y, sp, dt) => { const dd = Math.hypot(x - m.x, y - m.y) || 1; m.x += (x - m.x) / dd * sp * dt; m.y += (y - m.y) / dd * sp * dt },
|
||
smashStructure: () => {},
|
||
addFire: (x, y, o) => { const f = { fid: world.fires.length + 1, x, y, ...(o || {}) }; world.fires.push(f); return f },
|
||
addScore: () => {}, ptip: () => {}, swing: () => {}, tip: () => {}, shake: () => {}, invalidate: () => {},
|
||
...(opts.extra || {}),
|
||
}
|
||
g._player = p
|
||
g._world = world
|
||
return g
|
||
}
|
||
|
||
console.log('== wideworld ==')
|
||
ok(wide.id === 'wideworld' && wide.flags.worldScale === 1.5, `worldScale=${wide.flags && wide.flags.worldScale}`)
|
||
ok(wide.factor === 0.9 && wide.tag === 'rule', 'factor/tag 正确')
|
||
|
||
console.log('== camp 结构 ==')
|
||
ok(camp.recipes.every((r) => camp.icons[r.code]), '每个配方都有图标')
|
||
ok(camp.recipes.filter((r) => r.build).every((r) => camp.ents[r.code]), '每个 build 配方都有同名实体')
|
||
ok(camp.tabs[0].code === 'camp' && !!camp.icons[camp.tabs[0].icon], '页签图标存在')
|
||
const uniq = (arr) => new Set(arr).size === arr.length
|
||
ok(
|
||
uniq(camp.recipes.map((r) => r.code)) && uniq(Object.keys(camp.ents)) && uniq(Object.keys(camp.mobs)) && uniq(Object.keys(camp.icons)),
|
||
'各自命名空间内 code 唯一'
|
||
)
|
||
|
||
console.log('== onWorldGen ==')
|
||
{
|
||
const g = makeG()
|
||
camp.hooks.onWorldGen(g)
|
||
const house = g._world.entities.find((e) => e.type === 'camphouse')
|
||
const guard = g._world.monsters.find((m) => m.kind === 'guard')
|
||
ok(!!house && !!guard && g._world.fires.some((f) => f.eternal), '房屋/守卫/长明篝火就位')
|
||
const pd = Math.hypot(house.x - g._player.x, house.y - g._player.y)
|
||
ok(pd > 30, `房屋与出生点错开 (d=${pd.toFixed(0)})`)
|
||
}
|
||
|
||
console.log('== 房屋休息 ==')
|
||
{
|
||
const g = makeG()
|
||
const house = { id: 1, type: 'camphouse', mx: { restDay: 0 } }
|
||
camp.ents.camphouse.init(house)
|
||
const p = { san: 100, hp: 100 }
|
||
camp.ents.camphouse.interact(p, house, g)
|
||
ok(p.san === 130 && p.hp === 120 && house.mx.restDay === g.day, `休息 san+30 hp+20 (${p.san}/${p.hp})`)
|
||
camp.ents.camphouse.interact(p, house, g)
|
||
ok(p.san === 130 && p.hp === 120, '同日重复休息不再回复')
|
||
}
|
||
|
||
console.log('== 营地仓储 ==')
|
||
{
|
||
const p = { x: 1300, y: 1000, slots: [{ code: 'log', n: 5 }, { code: 'spear', n: 1 }, null], pack: null }
|
||
const g = makeG({ player: p })
|
||
const store = g.addEntity('campstore', 1300, 1000)
|
||
camp.ents.campstore.init(store)
|
||
camp.ents.campstore.interact(p, store, g)
|
||
const ctx = makeCtx()
|
||
const env = { camX: 0, camY: 0, W: 900, H: 620, time: 1 }
|
||
camp.hooks.onDrawUI(g, ctx, env)
|
||
// 点击某行“存入”按钮(复刻面板坐标:dep.x=572, sy≈198.5+30i)
|
||
let deposited = false
|
||
for (let i = 0; i < 8 && !deposited; i++) {
|
||
// 行内按钮 x 位置:右缘附近的“存入”钮
|
||
const sx = 900 / 2 + 470 / 2 - 10 - 4 - 60 - 6 - 33 // 近似“存入”按钮中心
|
||
const sy = Math.round((620 - (40 + 8 * 30 + 42 + 10)) / 2) + 40 + i * 30 + 14
|
||
const handled = camp.hooks.onClick(g, sx, sy)
|
||
if (handled === true) {
|
||
const box0 = store.mx.boxes[i]
|
||
if (Object.keys(box0.items).length) deposited = true
|
||
}
|
||
}
|
||
const anyBox = store.mx.boxes.find((b) => Object.keys(b.items).length)
|
||
ok(!!anyBox && anyBox.items.log === 5, `存入 log×5 (${JSON.stringify(store.mx.boxes.map((b) => b.items))})`)
|
||
ok(p.slots.find((s) => s && s.code === 'spear'), '工具(长矛)不被存入')
|
||
// 取出全部
|
||
const p2 = { x: 1300, y: 1000, slots: [], pack: null }
|
||
const g2 = makeG({ player: p2 })
|
||
g2._world.entities.push(store)
|
||
camp.hooks.onDrawUI(g2, ctx, env)
|
||
// 找出有内容的箱子行按钮
|
||
const idx = store.mx.boxes.findIndex((b) => Object.keys(b.items).length)
|
||
const sx = 900 / 2 + 470 / 2 - 10 - 4 - 30 // “取出”按钮中心
|
||
const sy = Math.round((620 - (40 + 8 * 30 + 42 + 10)) / 2) + 40 + idx * 30 + 14
|
||
const h = camp.hooks.onClick(g2, sx, sy)
|
||
ok(h === true && (p2.slots.some((s) => s && s.code === 'log')), '取出物资到背包')
|
||
// 面板外点击 → 关闭面板并吃掉
|
||
const outside = camp.hooks.onClick(g2, -9999, -9999)
|
||
ok(outside === true, '面板外点击返回 true(关闭)')
|
||
const noPanel = camp.hooks.onClick(g2, -9999, -9999)
|
||
ok(noPanel === undefined, '无面板时返回 undefined(不干扰)')
|
||
}
|
||
|
||
console.log('== 领地:敌对生物被推离 ==')
|
||
{
|
||
const g = makeG()
|
||
camp.hooks.onWorldGen(g)
|
||
const house = g._world.entities.find((e) => e.type === 'camphouse')
|
||
const hound = g.spawnMob('hound', house.x + 40, house.y + 20) // 营地内
|
||
camp.hooks.onTick(g)
|
||
const d = Math.hypot(hound.x - house.x, hound.y - house.y)
|
||
ok(d >= 420, `营地内猎犬被推到边界外 (d=${d.toFixed(0)})`)
|
||
}
|
||
|
||
console.log('== 守卫拦截 ==')
|
||
{
|
||
const g = makeG()
|
||
camp.hooks.onWorldGen(g)
|
||
const house = g._world.entities.find((e) => e.type === 'camphouse')
|
||
const guard = g._world.monsters.find((m) => m.kind === 'guard')
|
||
const hound = g.spawnMob('hound', house.x + 200, house.y + 60)
|
||
const before = Math.hypot(hound.x - guard.x, hound.y - guard.y)
|
||
for (let i = 0; i < 40; i++) { hound.x = house.x + 200; camp.mobs.guard.update(guard, 0.05, g) }
|
||
const after = Math.hypot(hound.x - guard.x, hound.y - guard.y)
|
||
ok(guard.hp === 1e9, '守卫每帧回满血(无限血)')
|
||
ok(after < before, `守卫向敌人靠拢 (${before.toFixed(0)}→${after.toFixed(0)})`)
|
||
}
|
||
|
||
console.log('== 绘制不抛异常 ==')
|
||
{
|
||
const g = makeG()
|
||
camp.hooks.onWorldGen(g)
|
||
const house = g._world.entities.find((e) => e.type === 'camphouse')
|
||
const store = g.addEntity('campstore', house.x + 40, house.y)
|
||
const ctx = makeCtx()
|
||
let err = null
|
||
try {
|
||
camp.ents.camphouse.draw(ctx, house, { time: 3 })
|
||
camp.ents.campstore.draw(ctx, store, { time: 3 })
|
||
camp.mobs.guard.draw(ctx, { dir: 1, moving: true, bob: 0 }, 3)
|
||
camp.hooks.onDraw(g, ctx, { camX: 100, camY: 60, W: 800, H: 600, time: 3 })
|
||
camp.ents.campstore.interact(g._player, store, g)
|
||
camp.hooks.onDrawUI(g, ctx, { camX: 100, camY: 60, W: 800, H: 600, time: 3 })
|
||
camp.icons.camphouse(ctx)
|
||
camp.icons.campstore(ctx)
|
||
} catch (e) { err = e.message }
|
||
ok(err === null, `绘制/UI 无异常 (${err || 'ok'})`)
|
||
}
|
||
|
||
console.log(`\n${fails} 项失败`)
|
||
process.exit(fails ? 1 : 0)
|