fix(packs): restore full Acts 1..5 index and map/bake all 741 monster IDs across all acts (#33)
This commit is contained in:
parent
270c743aef
commit
2527678c10
|
|
@ -1078,7 +1078,37 @@ for (const entry of LEVELS) {
|
||||||
}
|
}
|
||||||
|
|
||||||
await mkdir(outDir, { recursive: true })
|
await mkdir(outDir, { recursive: true })
|
||||||
await writeFile(join(outDir, 'index.json'), JSON.stringify(index, null, 1))
|
const indexPath = join(outDir, 'index.json')
|
||||||
|
if (filterLevel !== undefined) {
|
||||||
|
try {
|
||||||
|
const { readFile } = await import('node:fs/promises')
|
||||||
|
const existingRaw = await readFile(indexPath, 'utf8')
|
||||||
|
const existing = JSON.parse(existingRaw) as { levels?: Array<{ label?: string }> }
|
||||||
|
if (Array.isArray(existing.levels)) {
|
||||||
|
const updatedByLabel = new Map<string, unknown>()
|
||||||
|
for (const item of index.levels as Array<{ label: string }>) {
|
||||||
|
updatedByLabel.set(item.label, item)
|
||||||
|
}
|
||||||
|
const merged: unknown[] = []
|
||||||
|
const seenLabels = new Set<string>()
|
||||||
|
for (const oldItem of existing.levels) {
|
||||||
|
if (oldItem.label && updatedByLabel.has(oldItem.label)) {
|
||||||
|
merged.push(updatedByLabel.get(oldItem.label)!)
|
||||||
|
seenLabels.add(oldItem.label)
|
||||||
|
} else {
|
||||||
|
merged.push(oldItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const [lbl, item] of updatedByLabel.entries()) {
|
||||||
|
if (!seenLabels.has(lbl)) merged.push(item)
|
||||||
|
}
|
||||||
|
index.levels = merged
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// No existing index.json or unreadable; write fresh index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await writeFile(indexPath, JSON.stringify(index, null, 1))
|
||||||
const { bakeEntities } = await import('./pack-entity-assets.ts')
|
const { bakeEntities } = await import('./pack-entity-assets.ts')
|
||||||
await bakeEntities(archiveDir, outDir)
|
await bakeEntities(archiveDir, outDir)
|
||||||
console.log(`\n打包完成:${String(totalLevels)} 张地图,PNG 合计 ${(totalPngBytes / 1048576).toFixed(1)} MB,输出 ${outDir}`)
|
console.log(`\n打包完成:${String(totalLevels)} 张地图,PNG 合计 ${(totalPngBytes / 1048576).toFixed(1)} MB,输出 ${outDir}`)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import { MountedArchives } from '../src/mpq/mount.ts'
|
||||||
import { decodePl2 } from '../src/formats/pl2.ts'
|
import { decodePl2 } from '../src/formats/pl2.ts'
|
||||||
import { loadCharacterSheet } from '../src/game/character.ts'
|
import { loadCharacterSheet } from '../src/game/character.ts'
|
||||||
import { compositeMonsterAnimation } from '../src/game/monster-art.ts'
|
import { compositeMonsterAnimation } from '../src/game/monster-art.ts'
|
||||||
import { MONSTER_ART_MAP } from '../src/game/monster-mapping.ts'
|
import { ACT_MONSTER_SPECS } from '../src/game/monster-mapping.ts'
|
||||||
import type { SpriteSheet } from '../src/formats/sprite.ts'
|
import type { SpriteSheet } from '../src/formats/sprite.ts'
|
||||||
import { encodeIndexedPng } from './png.ts'
|
import { encodeIndexedPng } from './png.ts'
|
||||||
|
|
||||||
|
|
@ -138,16 +138,19 @@ export async function bakeEntities(archiveDir = 'samples/d2', outDir = 'samples/
|
||||||
width: null,
|
width: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Collect unique monster (token, weapon) specs
|
// 2. Collect unique monster (token, weapon) specs across Acts 1..5
|
||||||
const uniqueSpecs = new Map<string, { token: string; weapon: string }>()
|
const uniqueSpecs = new Map<string, { token: string; weapon: string }>()
|
||||||
for (const spec of Object.values(MONSTER_ART_MAP)) {
|
for (let act = 1; act <= 5; act += 1) {
|
||||||
const key = `${spec.token.toLowerCase()}-${spec.weapon.toLowerCase()}`
|
const specs = ACT_MONSTER_SPECS[act] ?? []
|
||||||
if (!uniqueSpecs.has(key)) {
|
for (const spec of specs) {
|
||||||
uniqueSpecs.set(key, spec)
|
const key = `${spec.token.toLowerCase()}-${spec.weapon.toLowerCase()}`
|
||||||
|
if (!uniqueSpecs.has(key)) {
|
||||||
|
uniqueSpecs.set(key, spec)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Composite each monster animation once
|
// Composite each monster animation once (with concurrency limit 6)
|
||||||
const compositedMonsters = new Map<string, {
|
const compositedMonsters = new Map<string, {
|
||||||
key: string
|
key: string
|
||||||
token: string
|
token: string
|
||||||
|
|
@ -160,32 +163,41 @@ export async function bakeEntities(archiveDir = 'samples/d2', outDir = 'samples/
|
||||||
sheet: SpriteSheet
|
sheet: SpriteSheet
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
for (const [key, spec] of uniqueSpecs) {
|
const entries = [...uniqueSpecs.entries()]
|
||||||
try {
|
let cursor = 0
|
||||||
console.log(`Composing monster ${spec.token} (${spec.weapon})...`)
|
const workers = Array.from({ length: Math.min(6, entries.length) }, async () => {
|
||||||
const [walk, stand] = await Promise.all([
|
for (;;) {
|
||||||
compositeMonsterAnimation(archives, spec.token, 'wl', spec.weapon),
|
const idx = cursor
|
||||||
compositeMonsterAnimation(archives, spec.token, 'nu', spec.weapon),
|
cursor += 1
|
||||||
])
|
if (idx >= entries.length) return
|
||||||
const sheet: SpriteSheet = {
|
const [key, spec] = entries[idx]!
|
||||||
groups: [...walk.sheet.groups, ...stand.sheet.groups],
|
try {
|
||||||
width: null,
|
const [walk, stand] = await Promise.all([
|
||||||
|
compositeMonsterAnimation(archives, spec.token, 'wl', spec.weapon),
|
||||||
|
compositeMonsterAnimation(archives, spec.token, 'nu', spec.weapon),
|
||||||
|
])
|
||||||
|
const sheet: SpriteSheet = {
|
||||||
|
groups: [...walk.sheet.groups, ...stand.sheet.groups],
|
||||||
|
width: null,
|
||||||
|
}
|
||||||
|
compositedMonsters.set(key, {
|
||||||
|
key,
|
||||||
|
token: spec.token,
|
||||||
|
weapon: spec.weapon,
|
||||||
|
directions: walk.directions,
|
||||||
|
walkOffset: 0,
|
||||||
|
standOffset: walk.sheet.groups.length,
|
||||||
|
walkFrames: walk.framesPerDirection,
|
||||||
|
standFrames: stand.framesPerDirection,
|
||||||
|
sheet,
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`Failed to composite monster ${key}: ${(err as Error).message}`)
|
||||||
}
|
}
|
||||||
compositedMonsters.set(key, {
|
|
||||||
key,
|
|
||||||
token: spec.token,
|
|
||||||
weapon: spec.weapon,
|
|
||||||
directions: walk.directions,
|
|
||||||
walkOffset: 0,
|
|
||||||
standOffset: walk.sheet.groups.length,
|
|
||||||
walkFrames: walk.framesPerDirection,
|
|
||||||
standFrames: stand.framesPerDirection,
|
|
||||||
sheet,
|
|
||||||
})
|
|
||||||
} catch (err) {
|
|
||||||
console.warn(`Failed to composite monster ${key}: ${(err as Error).message}`)
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
await Promise.all(workers)
|
||||||
|
console.log(`Composited ${compositedMonsters.size} unique monster specs across Acts 1..5.`)
|
||||||
|
|
||||||
// 3. Encode indexed PNGs per Act palette
|
// 3. Encode indexed PNGs per Act palette
|
||||||
for (let act = 1; act <= 5; act += 1) {
|
for (let act = 1; act <= 5; act += 1) {
|
||||||
|
|
@ -213,8 +225,13 @@ export async function bakeEntities(archiveDir = 'samples/d2', outDir = 'samples/
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Pack monsters
|
// Pack monsters used in this act
|
||||||
for (const [key, m] of compositedMonsters) {
|
const actSpecs = ACT_MONSTER_SPECS[act] ?? []
|
||||||
|
let packedCount = 0
|
||||||
|
for (const spec of actSpecs) {
|
||||||
|
const key = `${spec.token.toLowerCase()}-${spec.weapon.toLowerCase()}`
|
||||||
|
const m = compositedMonsters.get(key)
|
||||||
|
if (!m) continue
|
||||||
const packedMon = packSheetToPng(m.sheet, palette)
|
const packedMon = packSheetToPng(m.sheet, palette)
|
||||||
const monPngName = `monster-${key}.png`
|
const monPngName = `monster-${key}.png`
|
||||||
await writeFile(join(actEntityDir, monPngName), packedMon.png)
|
await writeFile(join(actEntityDir, monPngName), packedMon.png)
|
||||||
|
|
@ -234,8 +251,9 @@ export async function bakeEntities(archiveDir = 'samples/d2', outDir = 'samples/
|
||||||
groups: packedMon.groups,
|
groups: packedMon.groups,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
packedCount += 1
|
||||||
}
|
}
|
||||||
console.log(`Act ${act}: packed char-so + ${compositedMonsters.size} monster atlases into ${actEntityDir}`)
|
console.log(`Act ${act}: packed char-so + ${packedCount} monster atlases into ${actEntityDir}`)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(`Act ${act} entity packing skipped: ${(err as Error).message}`)
|
console.warn(`Act ${act} entity packing skipped: ${(err as Error).message}`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,188 @@
|
||||||
|
import { readdir, readFile, writeFile } from 'node:fs/promises'
|
||||||
|
import { createHash } from 'node:crypto'
|
||||||
|
import { join } from 'node:path'
|
||||||
|
import { MpqArchive } from '../src/mpq/archive.ts'
|
||||||
|
import { fileSource } from '../src/mpq/file-source.ts'
|
||||||
|
import { MountedArchives } from '../src/mpq/mount.ts'
|
||||||
|
import { cell, loadActTables } from '../src/game/acts.ts'
|
||||||
|
import { decodePl2 } from '../src/formats/pl2.ts'
|
||||||
|
import { planLevelMonsters } from '../src/game/monsters.ts'
|
||||||
|
import { bakeEntities } from './pack-entity-assets.ts'
|
||||||
|
|
||||||
|
const MOUNTS = ['d2char.mpq', 'd2data.mpq', 'd2exp.mpq', 'Patch_D2.mpq'] as const
|
||||||
|
const PLAYER_WALK_SPEED_PX = 170
|
||||||
|
|
||||||
|
function levelSeed(ds1Name: string): number {
|
||||||
|
let hash = 0x811c9dc5
|
||||||
|
for (let i = 0; i < ds1Name.length; i += 1) {
|
||||||
|
hash ^= ds1Name.charCodeAt(i)
|
||||||
|
hash = Math.imul(hash, 0x01000193) >>> 0
|
||||||
|
}
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const archiveDir = 'samples/d2'
|
||||||
|
const outDir = 'samples/d2-packs'
|
||||||
|
const archives = new MountedArchives()
|
||||||
|
for (const name of MOUNTS) {
|
||||||
|
archives.add(name, await MpqArchive.open(await fileSource(join(archiveDir, name))))
|
||||||
|
}
|
||||||
|
const tables = await loadActTables(archives)
|
||||||
|
|
||||||
|
const palettes: Record<string, number[]> = {}
|
||||||
|
for (let act = 1; act <= 5; act += 1) {
|
||||||
|
const pl2 = decodePl2(await archives.read(`data\\global\\palette\\act${String(act)}\\pal.pl2`))
|
||||||
|
palettes[`act${String(act)}`] = [...pl2.rgb]
|
||||||
|
}
|
||||||
|
|
||||||
|
const levelsList: Array<{
|
||||||
|
act: number
|
||||||
|
levelId: number
|
||||||
|
kind: string
|
||||||
|
slug: string
|
||||||
|
label: string
|
||||||
|
levelName: string
|
||||||
|
ds1: string
|
||||||
|
path: string
|
||||||
|
cells: string
|
||||||
|
frames: number
|
||||||
|
objects: number
|
||||||
|
npcs: number
|
||||||
|
pages: number
|
||||||
|
objectPages: number
|
||||||
|
missingTiles: number
|
||||||
|
bytes: number
|
||||||
|
}> = []
|
||||||
|
|
||||||
|
let updatedMonstersCount = 0
|
||||||
|
|
||||||
|
for (let act = 1; act <= 5; act += 1) {
|
||||||
|
const actDir = join(outDir, `act${String(act)}`)
|
||||||
|
let entries: string[] = []
|
||||||
|
try {
|
||||||
|
entries = await readdir(actDir)
|
||||||
|
} catch {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const label of entries.sort()) {
|
||||||
|
if (label === 'entities') continue
|
||||||
|
const dirPath = join(actDir, label)
|
||||||
|
const scenePath = join(dirPath, 'scene.json')
|
||||||
|
let raw: string
|
||||||
|
try {
|
||||||
|
raw = await readFile(scenePath, 'utf8')
|
||||||
|
} catch {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const scene = JSON.parse(raw)
|
||||||
|
const levelId = Number(scene.levelId ?? 1)
|
||||||
|
const levelRow = tables.levels.rows.find(r => Number(cell(tables.levels, r, 'Id')) === levelId)
|
||||||
|
const drlg = levelRow ? Number(cell(tables.levels, levelRow, 'DrlgType')) : 2
|
||||||
|
const kind = drlg === 2 ? 'preset' : drlg === 1 ? 'maze' : 'wilderness'
|
||||||
|
|
||||||
|
// Determine seed matching pack-act-assets.ts
|
||||||
|
let seed = levelSeed(scene.ds1 ?? label)
|
||||||
|
const varMatch = label.match(/-var(\d+)$/)
|
||||||
|
if (varMatch) {
|
||||||
|
const v = Number(varMatch[1])
|
||||||
|
seed = (kind === 'maze' ? 0x5eed_0000 : 0x5eed_1000) + levelId * 10 + v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-plan monsters using latest planLevelMonsters
|
||||||
|
const population = planLevelMonsters(
|
||||||
|
tables,
|
||||||
|
levelId,
|
||||||
|
(scene.cellsX ?? 80) * (scene.cellsY ?? 80),
|
||||||
|
seed,
|
||||||
|
PLAYER_WALK_SPEED_PX,
|
||||||
|
)
|
||||||
|
|
||||||
|
scene.monsters = {
|
||||||
|
types: population.types,
|
||||||
|
budget: population.budget,
|
||||||
|
packs: population.packs,
|
||||||
|
}
|
||||||
|
if (scene.stats) {
|
||||||
|
scene.stats.monsters = population.packs.reduce((sum, pack) => sum + pack.members.length, 0)
|
||||||
|
scene.stats.monsterPacks = population.packs.length
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedMonstersCount += 1
|
||||||
|
const sceneBytes = new TextEncoder().encode(JSON.stringify(scene))
|
||||||
|
await writeFile(scenePath, sceneBytes)
|
||||||
|
|
||||||
|
// Update manifest.json if present
|
||||||
|
const manifestPath = join(dirPath, 'manifest.json')
|
||||||
|
try {
|
||||||
|
const mRaw = await readFile(manifestPath, 'utf8')
|
||||||
|
const manifest = JSON.parse(mRaw)
|
||||||
|
manifest.sceneBytes = sceneBytes.byteLength
|
||||||
|
manifest.sceneSha256 = createHash('sha256').update(sceneBytes).digest('hex')
|
||||||
|
await writeFile(manifestPath, JSON.stringify(manifest, null, 2))
|
||||||
|
} catch {
|
||||||
|
// ignore if no manifest
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine slug from levelId + levelName
|
||||||
|
const cleanName = String(scene.levelName ?? '')
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^a-z0-9]+/g, '-')
|
||||||
|
.replace(/^-|-$/g, '')
|
||||||
|
const slug = `${String(levelId)}-${cleanName}`
|
||||||
|
|
||||||
|
// Compute total bytes (scene.json + PNG pages)
|
||||||
|
let pngBytes = 0
|
||||||
|
const filesInDir = await readdir(dirPath)
|
||||||
|
for (const f of filesInDir) {
|
||||||
|
if (f.endsWith('.png')) {
|
||||||
|
const st = await readFile(join(dirPath, f))
|
||||||
|
pngBytes += st.byteLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
levelsList.push({
|
||||||
|
act: scene.act ?? act,
|
||||||
|
levelId,
|
||||||
|
kind,
|
||||||
|
slug,
|
||||||
|
label,
|
||||||
|
levelName: scene.levelName,
|
||||||
|
ds1: scene.ds1 ?? label,
|
||||||
|
path: `act${String(act)}/${label}`,
|
||||||
|
cells: `${String(scene.cellsX)}x${String(scene.cellsY)}`,
|
||||||
|
frames: Array.isArray(scene.frames) ? scene.frames.length : 0,
|
||||||
|
objects: Array.isArray(scene.objects) ? scene.objects.length : 0,
|
||||||
|
npcs: Array.isArray(scene.npcs) ? scene.npcs.length : 0,
|
||||||
|
pages: Array.isArray(scene.pages) ? scene.pages.length : 0,
|
||||||
|
objectPages: Array.isArray(scene.objectPages) ? scene.objectPages.length : 0,
|
||||||
|
missingTiles: scene.missingTiles ?? 0,
|
||||||
|
bytes: pngBytes + sceneBytes.byteLength,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
levelsList.sort((a, b) => a.act - b.act || a.levelId - b.levelId || a.label.localeCompare(b.label))
|
||||||
|
|
||||||
|
const index = {
|
||||||
|
version: 1,
|
||||||
|
generated: new Date().toISOString(),
|
||||||
|
archiveDir,
|
||||||
|
pageSize: 2048,
|
||||||
|
palettes,
|
||||||
|
levels: levelsList,
|
||||||
|
}
|
||||||
|
|
||||||
|
await writeFile(join(outDir, 'index.json'), JSON.stringify(index, null, 1))
|
||||||
|
console.log(`Rebuilt samples/d2-packs/index.json with ${levelsList.length} map variants across Acts 1..5, updated ${updatedMonstersCount} scene monster plans.`)
|
||||||
|
|
||||||
|
// Now bake all entity atlases for Acts 1..5
|
||||||
|
await bakeEntities(archiveDir, outDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(err => {
|
||||||
|
console.error(err)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
|
@ -38,15 +38,26 @@ export function findMonsterLayerSprite(
|
||||||
animation: string,
|
animation: string,
|
||||||
weapon: string,
|
weapon: string,
|
||||||
component: string,
|
component: string,
|
||||||
|
layerWeaponClass?: string,
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
const prefix = `${root}${component}\\${token}${component}`
|
const prefix = `${root}${component}\\${token}${component}`.toLowerCase()
|
||||||
const suffix = `${animation}${weapon}.dcc`
|
const weapons = Array.from(new Set([layerWeaponClass, weapon, 'hth'].filter((w): w is string => Boolean(w))))
|
||||||
const hits = names.filter(name =>
|
const anims = animation.toLowerCase() === 'wl' ? ['wl', 'nu'] : [animation.toLowerCase()]
|
||||||
name.toLowerCase().startsWith(prefix.toLowerCase()) &&
|
|
||||||
name.toLowerCase().endsWith(suffix.toLowerCase())
|
for (const anim of anims) {
|
||||||
)
|
for (const w of weapons) {
|
||||||
hits.sort()
|
const suffix = `${anim}${w.toLowerCase()}.dcc`
|
||||||
return hits[0]
|
const hits = names.filter(name => {
|
||||||
|
const lowerName = name.toLowerCase()
|
||||||
|
return lowerName.startsWith(prefix) && lowerName.endsWith(suffix)
|
||||||
|
})
|
||||||
|
if (hits.length > 0) {
|
||||||
|
hits.sort()
|
||||||
|
return hits[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -98,9 +109,31 @@ export async function compositeMonsterAnimation(
|
||||||
const cofMember = `${root}cof\\${lower}${animation}${weapon}.cof`
|
const cofMember = `${root}cof\\${lower}${animation}${weapon}.cof`
|
||||||
|
|
||||||
const names = await archives.listFiles()
|
const names = await archives.listFiles()
|
||||||
const matchedCof = names.find(n => n.toLowerCase() === cofMember.toLowerCase())
|
let usedAnim = animation.toLowerCase()
|
||||||
|
let usedWeapon = weapon.toLowerCase()
|
||||||
|
const candidates = [
|
||||||
|
`${root}cof\\${lower}${usedAnim}${usedWeapon}.cof`,
|
||||||
|
`${root}cof\\${lower}${usedAnim}hth.cof`,
|
||||||
|
...(usedAnim === 'wl'
|
||||||
|
? [
|
||||||
|
`${root}cof\\${lower}nu${usedWeapon}.cof`,
|
||||||
|
`${root}cof\\${lower}nuhth.cof`,
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
]
|
||||||
|
|
||||||
|
let matchedCof: string | undefined
|
||||||
|
for (const cand of candidates) {
|
||||||
|
matchedCof = names.find(n => n.toLowerCase() === cand.toLowerCase())
|
||||||
|
if (matchedCof) {
|
||||||
|
if (cand.includes('nu')) usedAnim = 'nu'
|
||||||
|
if (cand.endsWith('hth.cof')) usedWeapon = 'hth'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!matchedCof) {
|
if (!matchedCof) {
|
||||||
throw new Error(`Monster COF not found: ${cofMember}`)
|
throw new Error(`Monster COF not found: ${candidates[0]}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const cofBytes = await archives.read(matchedCof)
|
const cofBytes = await archives.read(matchedCof)
|
||||||
|
|
@ -109,7 +142,15 @@ export async function compositeMonsterAnimation(
|
||||||
const layerResults = await Promise.all(cof.layers.map(async (layer) => {
|
const layerResults = await Promise.all(cof.layers.map(async (layer) => {
|
||||||
const component = MONSTER_COMPONENTS[layer.type]
|
const component = MONSTER_COMPONENTS[layer.type]
|
||||||
if (component === undefined) return { sprite: null, member: undefined }
|
if (component === undefined) return { sprite: null, member: undefined }
|
||||||
const member = findMonsterLayerSprite(names, root, lower, animation, weapon, component)
|
const member = findMonsterLayerSprite(
|
||||||
|
names,
|
||||||
|
root,
|
||||||
|
lower,
|
||||||
|
usedAnim,
|
||||||
|
usedWeapon,
|
||||||
|
component,
|
||||||
|
layer.weaponClass,
|
||||||
|
)
|
||||||
if (member === undefined) return { sprite: null, member: undefined }
|
if (member === undefined) return { sprite: null, member: undefined }
|
||||||
try {
|
try {
|
||||||
const bytes = await archives.read(member)
|
const bytes = await archives.read(member)
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,785 @@
|
||||||
/**
|
/**
|
||||||
* Resolving monster ID to art token (Code) and weapon class (BaseW).
|
* Mapping between MonStats.txt monster IDs (and demo monster IDs)
|
||||||
|
* to their visual DCC 2-character token (`Code`) and weapon class (`BaseW`).
|
||||||
|
* Auto-derived from MonStats.txt + MonStats2.txt across Acts 1..5.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface MonsterArtSpec {
|
export interface MonsterArtSpec {
|
||||||
readonly token: string
|
readonly token: string
|
||||||
readonly weapon: string
|
readonly weapon: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Built-in art mapping for standard Diablo II monsters.
|
* Complete monster ID -> art spec table for Acts 1..5.
|
||||||
* Keyed by monsterId (e.g. 'fallen1', 'zombie1').
|
|
||||||
*/
|
*/
|
||||||
export const MONSTER_ART_MAP: Record<string, MonsterArtSpec> = {
|
export const MONSTER_ART_MAP: Record<string, MonsterArtSpec> = {
|
||||||
// Act 1 Wilderness / Caves
|
"act2child": { token: "2C", weapon: "hth" },
|
||||||
'fallen1': { token: 'FA', weapon: 'hth' },
|
"act2female": { token: "2F", weapon: "hth" },
|
||||||
'fallen2': { token: 'FA', weapon: 'hth' },
|
"act2guard1": { token: "GU", weapon: "hth" },
|
||||||
'fallenshaman1': { token: 'FS', weapon: 'hth' },
|
"act2guard2": { token: "GU", weapon: "hth" },
|
||||||
'zombie1': { token: 'ZM', weapon: 'hth' },
|
"act2guard3": { token: "SK", weapon: "hth" },
|
||||||
'zombie2': { token: 'ZM', weapon: 'hth' },
|
"act2guard4": { token: "GU", weapon: "hth" },
|
||||||
'quillrat1': { token: 'SI', weapon: 'hth' },
|
"act2guard5": { token: "GU", weapon: "hth" },
|
||||||
'quillrat2': { token: 'SI', weapon: 'hth' },
|
"act2hire": { token: "GU", weapon: "hth" },
|
||||||
'brute1': { token: 'YE', weapon: 'hth' },
|
"act2male": { token: "2M", weapon: "hth" },
|
||||||
'brute2': { token: 'YE', weapon: 'hth' },
|
"act2vendor1": { token: "M1", weapon: "hth" },
|
||||||
'skeleton1': { token: 'SK', weapon: '1hs' },
|
"act2vendor2": { token: "M2", weapon: "hth" },
|
||||||
'skeleton2': { token: 'SK', weapon: '1hs' },
|
"act3female": { token: "N3", weapon: "hth" },
|
||||||
'corruptrogue1': { token: 'CR', weapon: '1hs' },
|
"act3hire": { token: "IW", weapon: "1hs" },
|
||||||
'cr_archer1': { token: 'CR', weapon: 'bow' },
|
"act3male": { token: "N4", weapon: "hth" },
|
||||||
'cr_lancer1': { token: 'CR', weapon: '2ht' },
|
"act5barb1": { token: "0A", weapon: "1hs" },
|
||||||
'goatman1': { token: 'GM', weapon: '2hs' },
|
"act5barb2": { token: "0A", weapon: "2hs" },
|
||||||
'bloodraven': { token: 'CR', weapon: 'bow' },
|
"act5barb3": { token: "0A", weapon: "1hs" },
|
||||||
'hellbovine': { token: 'CW', weapon: 'hth' },
|
"act5barb4": { token: "0A", weapon: "2hs" },
|
||||||
'cow': { token: 'CW', weapon: 'hth' },
|
"act5hire1": { token: "0A", weapon: "1hs" },
|
||||||
'demo-fallen': { token: 'FA', weapon: 'hth' },
|
"act5hire2": { token: "0A", weapon: "2hs" },
|
||||||
'demo-zombie': { token: 'ZM', weapon: 'hth' },
|
"act5pow": { token: "0A", weapon: "hth" },
|
||||||
'demo-skeleton': { token: 'SK', weapon: '1hs' },
|
"akara": { token: "PS", weapon: "hth" },
|
||||||
|
"alkor": { token: "AL", weapon: "hth" },
|
||||||
|
"ancientbarb1": { token: "0D", weapon: "hth" },
|
||||||
|
"ancientbarb2": { token: "0F", weapon: "hth" },
|
||||||
|
"ancientbarb3": { token: "0E", weapon: "hth" },
|
||||||
|
"ancientstatue1": { token: "0G", weapon: "hth" },
|
||||||
|
"ancientstatue2": { token: "0H", weapon: "hth" },
|
||||||
|
"ancientstatue3": { token: "0I", weapon: "hth" },
|
||||||
|
"andariel": { token: "AN", weapon: "hth" },
|
||||||
|
"arach1": { token: "SP", weapon: "hth" },
|
||||||
|
"arach2": { token: "SP", weapon: "hth" },
|
||||||
|
"arach3": { token: "SP", weapon: "hth" },
|
||||||
|
"arach4": { token: "SP", weapon: "hth" },
|
||||||
|
"arach5": { token: "SP", weapon: "hth" },
|
||||||
|
"arach6": { token: "SP", weapon: "hth" },
|
||||||
|
"arach7": { token: "SP", weapon: "hth" },
|
||||||
|
"asheara": { token: "AH", weapon: "hth" },
|
||||||
|
"atma": { token: "AS", weapon: "hth" },
|
||||||
|
"baalclone": { token: "42", weapon: "hth" },
|
||||||
|
"baalcrab": { token: "42", weapon: "hth" },
|
||||||
|
"baalcrabstairs": { token: "42", weapon: "hth" },
|
||||||
|
"baalhighpriest": { token: "HP", weapon: "hth" },
|
||||||
|
"baalminion1": { token: "43", weapon: "hth" },
|
||||||
|
"baalminion2": { token: "43", weapon: "hth" },
|
||||||
|
"baalminion3": { token: "43", weapon: "hth" },
|
||||||
|
"baaltaunt": { token: "K9", weapon: "hth" },
|
||||||
|
"baaltentacle1": { token: "44", weapon: "hth" },
|
||||||
|
"baaltentacle2": { token: "44", weapon: "hth" },
|
||||||
|
"baaltentacle3": { token: "44", weapon: "hth" },
|
||||||
|
"baaltentacle4": { token: "44", weapon: "hth" },
|
||||||
|
"baaltentacle5": { token: "44", weapon: "hth" },
|
||||||
|
"baalthrone": { token: "41", weapon: "hth" },
|
||||||
|
"baboon1": { token: "BB", weapon: "hth" },
|
||||||
|
"baboon2": { token: "BB", weapon: "hth" },
|
||||||
|
"baboon3": { token: "BB", weapon: "hth" },
|
||||||
|
"baboon4": { token: "BB", weapon: "hth" },
|
||||||
|
"baboon5": { token: "BB", weapon: "hth" },
|
||||||
|
"baboon6": { token: "BB", weapon: "hth" },
|
||||||
|
"baboon7": { token: "BB", weapon: "hth" },
|
||||||
|
"baboon8": { token: "BB", weapon: "hth" },
|
||||||
|
"barricadedoor1": { token: "AJ", weapon: "hth" },
|
||||||
|
"barricadedoor2": { token: "AG", weapon: "hth" },
|
||||||
|
"barricadetower": { token: "AC", weapon: "hth" },
|
||||||
|
"barricadewall1": { token: "A6", weapon: "hth" },
|
||||||
|
"barricadewall2": { token: "AK", weapon: "hth" },
|
||||||
|
"bat": { token: "B9", weapon: "hth" },
|
||||||
|
"batdemon1": { token: "BT", weapon: "hth" },
|
||||||
|
"batdemon2": { token: "BT", weapon: "hth" },
|
||||||
|
"batdemon3": { token: "BT", weapon: "hth" },
|
||||||
|
"batdemon4": { token: "BT", weapon: "hth" },
|
||||||
|
"batdemon5": { token: "BT", weapon: "hth" },
|
||||||
|
"batdemon6": { token: "BT", weapon: "hth" },
|
||||||
|
"batdemon7": { token: "BT", weapon: "hth" },
|
||||||
|
"bear": { token: "TG", weapon: "hth" },
|
||||||
|
"bighead1": { token: "BH", weapon: "hth" },
|
||||||
|
"bighead10": { token: "BH", weapon: "hth" },
|
||||||
|
"bighead2": { token: "BH", weapon: "hth" },
|
||||||
|
"bighead3": { token: "BH", weapon: "hth" },
|
||||||
|
"bighead4": { token: "BH", weapon: "hth" },
|
||||||
|
"bighead5": { token: "BH", weapon: "hth" },
|
||||||
|
"bighead6": { token: "BH", weapon: "hth" },
|
||||||
|
"bighead7": { token: "BH", weapon: "hth" },
|
||||||
|
"bighead8": { token: "BH", weapon: "hth" },
|
||||||
|
"bighead9": { token: "BH", weapon: "hth" },
|
||||||
|
"bird1": { token: "BS", weapon: "hth" },
|
||||||
|
"bird2": { token: "BL", weapon: "hth" },
|
||||||
|
"bladecreeper": { token: "B8", weapon: "hth" },
|
||||||
|
"bloodgolem": { token: "G2", weapon: "hth" },
|
||||||
|
"bloodlord1": { token: "L3", weapon: "hth" },
|
||||||
|
"bloodlord2": { token: "L3", weapon: "hth" },
|
||||||
|
"bloodlord3": { token: "L3", weapon: "hth" },
|
||||||
|
"bloodlord4": { token: "L3", weapon: "hth" },
|
||||||
|
"bloodlord5": { token: "L3", weapon: "hth" },
|
||||||
|
"bloodlord6": { token: "L3", weapon: "hth" },
|
||||||
|
"bloodlord7": { token: "L3", weapon: "hth" },
|
||||||
|
"bloodmage1": { token: "XX", weapon: "hth" },
|
||||||
|
"bloodmage2": { token: "XX", weapon: "hth" },
|
||||||
|
"bloodmage3": { token: "XX", weapon: "hth" },
|
||||||
|
"bloodmage4": { token: "XX", weapon: "hth" },
|
||||||
|
"bloodmage5": { token: "XX", weapon: "hth" },
|
||||||
|
"bloodraven": { token: "CR", weapon: "bow" },
|
||||||
|
"blunderbore1": { token: "PN", weapon: "hth" },
|
||||||
|
"blunderbore2": { token: "PN", weapon: "hth" },
|
||||||
|
"blunderbore3": { token: "PN", weapon: "hth" },
|
||||||
|
"blunderbore4": { token: "PN", weapon: "hth" },
|
||||||
|
"blunderbore5": { token: "PN", weapon: "hth" },
|
||||||
|
"blunderbore6": { token: "PN", weapon: "hth" },
|
||||||
|
"bonefetish1": { token: "FK", weapon: "1hs" },
|
||||||
|
"bonefetish2": { token: "FK", weapon: "1hs" },
|
||||||
|
"bonefetish3": { token: "FK", weapon: "1hs" },
|
||||||
|
"bonefetish4": { token: "FK", weapon: "1hs" },
|
||||||
|
"bonefetish5": { token: "FK", weapon: "1hs" },
|
||||||
|
"bonefetish6": { token: "FK", weapon: "1hs" },
|
||||||
|
"bonefetish7": { token: "FK", weapon: "1hs" },
|
||||||
|
"boneprison1": { token: "67", weapon: "hth" },
|
||||||
|
"boneprison2": { token: "66", weapon: "hth" },
|
||||||
|
"boneprison3": { token: "69", weapon: "hth" },
|
||||||
|
"boneprison4": { token: "68", weapon: "hth" },
|
||||||
|
"bonewall": { token: "BW", weapon: "hth" },
|
||||||
|
"brute": { token: "YE", weapon: "hth" },
|
||||||
|
"brute1": { token: "YE", weapon: "hth" },
|
||||||
|
"brute2": { token: "YE", weapon: "hth" },
|
||||||
|
"brute3": { token: "YE", weapon: "hth" },
|
||||||
|
"brute4": { token: "YE", weapon: "hth" },
|
||||||
|
"brute5": { token: "YE", weapon: "hth" },
|
||||||
|
"bug": { token: "BG", weapon: "hth" },
|
||||||
|
"bunny": { token: "48", weapon: "hth" },
|
||||||
|
"cain1": { token: "DC", weapon: "hth" },
|
||||||
|
"cain2": { token: "DC", weapon: "hth" },
|
||||||
|
"cain3": { token: "DC", weapon: "hth" },
|
||||||
|
"cain4": { token: "DC", weapon: "hth" },
|
||||||
|
"cain5": { token: "1D", weapon: "hth" },
|
||||||
|
"cain6": { token: "DC", weapon: "hth" },
|
||||||
|
"camel": { token: "CM", weapon: "hth" },
|
||||||
|
"cantor1": { token: "ZP", weapon: "hth" },
|
||||||
|
"cantor2": { token: "ZP", weapon: "hth" },
|
||||||
|
"cantor3": { token: "ZP", weapon: "hth" },
|
||||||
|
"cantor4": { token: "ZP", weapon: "hth" },
|
||||||
|
"cantor5": { token: "ZP", weapon: "hth" },
|
||||||
|
"cantor6": { token: "ZP", weapon: "hth" },
|
||||||
|
"catapult1": { token: "65", weapon: "hth" },
|
||||||
|
"catapult2": { token: "64", weapon: "hth" },
|
||||||
|
"catapult3": { token: "64", weapon: "hth" },
|
||||||
|
"catapult4": { token: "UA", weapon: "hth" },
|
||||||
|
"catapultspotter1": { token: "K9", weapon: "hth" },
|
||||||
|
"catapultspotter2": { token: "K9", weapon: "hth" },
|
||||||
|
"catapultspotter3": { token: "K9", weapon: "hth" },
|
||||||
|
"catapultspotter4": { token: "K9", weapon: "hth" },
|
||||||
|
"chaoshorde1": { token: "CH", weapon: "hth" },
|
||||||
|
"chaoshorde2": { token: "CH", weapon: "hth" },
|
||||||
|
"chaoshorde3": { token: "CH", weapon: "hth" },
|
||||||
|
"chaoshorde4": { token: "CH", weapon: "hth" },
|
||||||
|
"chargeboltsentry": { token: "LG", weapon: "hth" },
|
||||||
|
"charsi": { token: "CI", weapon: "hth" },
|
||||||
|
"chicken": { token: "CK", weapon: "hth" },
|
||||||
|
"clawviper1": { token: "SD", weapon: "hth" },
|
||||||
|
"clawviper10": { token: "SD", weapon: "hth" },
|
||||||
|
"clawviper2": { token: "SD", weapon: "hth" },
|
||||||
|
"clawviper3": { token: "SD", weapon: "hth" },
|
||||||
|
"clawviper4": { token: "SD", weapon: "hth" },
|
||||||
|
"clawviper5": { token: "SD", weapon: "hth" },
|
||||||
|
"clawviper6": { token: "SD", weapon: "hth" },
|
||||||
|
"clawviper7": { token: "SD", weapon: "hth" },
|
||||||
|
"clawviper8": { token: "SD", weapon: "hth" },
|
||||||
|
"clawviper9": { token: "SD", weapon: "hth" },
|
||||||
|
"claygolem": { token: "G1", weapon: "hth" },
|
||||||
|
"compellingorb": { token: "9A", weapon: "hth" },
|
||||||
|
"corruptrogue1": { token: "CR", weapon: "1hs" },
|
||||||
|
"corruptrogue2": { token: "CR", weapon: "1hs" },
|
||||||
|
"corruptrogue3": { token: "CR", weapon: "1hs" },
|
||||||
|
"corruptrogue4": { token: "CR", weapon: "1hs" },
|
||||||
|
"corruptrogue5": { token: "CR", weapon: "1hs" },
|
||||||
|
"corruptrogue6": { token: "CR", weapon: "1hs" },
|
||||||
|
"councilmember1": { token: "HP", weapon: "hth" },
|
||||||
|
"councilmember2": { token: "HP", weapon: "hth" },
|
||||||
|
"councilmember3": { token: "HP", weapon: "hth" },
|
||||||
|
"cow": { token: "CW", weapon: "hth" },
|
||||||
|
"cr_archer1": { token: "CR", weapon: "bow" },
|
||||||
|
"cr_archer2": { token: "CR", weapon: "bow" },
|
||||||
|
"cr_archer3": { token: "CR", weapon: "bow" },
|
||||||
|
"cr_archer4": { token: "CR", weapon: "bow" },
|
||||||
|
"cr_archer5": { token: "CR", weapon: "bow" },
|
||||||
|
"cr_archer6": { token: "CR", weapon: "bow" },
|
||||||
|
"cr_archer7": { token: "CR", weapon: "bow" },
|
||||||
|
"cr_archer8": { token: "CR", weapon: "bow" },
|
||||||
|
"cr_lancer1": { token: "CR", weapon: "2ht" },
|
||||||
|
"cr_lancer2": { token: "CR", weapon: "2ht" },
|
||||||
|
"cr_lancer3": { token: "CR", weapon: "2ht" },
|
||||||
|
"cr_lancer4": { token: "CR", weapon: "2ht" },
|
||||||
|
"cr_lancer5": { token: "CR", weapon: "2ht" },
|
||||||
|
"cr_lancer6": { token: "CR", weapon: "2ht" },
|
||||||
|
"cr_lancer7": { token: "CR", weapon: "2ht" },
|
||||||
|
"cr_lancer8": { token: "CR", weapon: "2ht" },
|
||||||
|
"crownest1": { token: "BN", weapon: "hth" },
|
||||||
|
"crownest2": { token: "BN", weapon: "hth" },
|
||||||
|
"crownest3": { token: "BN", weapon: "hth" },
|
||||||
|
"crownest4": { token: "BN", weapon: "hth" },
|
||||||
|
"cycleoflife": { token: "K9", weapon: "hth" },
|
||||||
|
"darkguard1": { token: "XX", weapon: "hth" },
|
||||||
|
"darkguard2": { token: "XX", weapon: "hth" },
|
||||||
|
"darkguard3": { token: "XX", weapon: "hth" },
|
||||||
|
"darkguard4": { token: "XX", weapon: "hth" },
|
||||||
|
"darkguard5": { token: "XX", weapon: "hth" },
|
||||||
|
"darkwanderer": { token: "1Z", weapon: "hth" },
|
||||||
|
"deathmauler1": { token: "M5", weapon: "hth" },
|
||||||
|
"deathmauler2": { token: "M5", weapon: "hth" },
|
||||||
|
"deathmauler3": { token: "M5", weapon: "hth" },
|
||||||
|
"deathmauler4": { token: "M5", weapon: "hth" },
|
||||||
|
"deathmauler5": { token: "M5", weapon: "hth" },
|
||||||
|
"deathmauler6": { token: "M5", weapon: "hth" },
|
||||||
|
"deathsentry": { token: "LG", weapon: "hth" },
|
||||||
|
"demonhole": { token: "EH", weapon: "hth" },
|
||||||
|
"demonspawner": { token: "XA", weapon: "hth" },
|
||||||
|
"diablo": { token: "DI", weapon: "hth" },
|
||||||
|
"diabloclone": { token: "DI", weapon: "hth" },
|
||||||
|
"dkfig1": { token: "UM", weapon: "fgt" },
|
||||||
|
"dkfig2": { token: "UM", weapon: "fgt" },
|
||||||
|
"dkmag1": { token: "UM", weapon: "mgk" },
|
||||||
|
"dkmag2": { token: "UM", weapon: "mgk" },
|
||||||
|
"dkmag3": { token: "UM", weapon: "mgk" },
|
||||||
|
"doomknight1": { token: "UM", weapon: "fgt" },
|
||||||
|
"doomknight2": { token: "UM", weapon: "hth" },
|
||||||
|
"doomknight3": { token: "UM", weapon: "mgk" },
|
||||||
|
"dopplezon": { token: "VK", weapon: "hth" },
|
||||||
|
"drehya": { token: "XS", weapon: "hth" },
|
||||||
|
"drehyaiced": { token: "XS", weapon: "hth" },
|
||||||
|
"drognan": { token: "DR", weapon: "hth" },
|
||||||
|
"druidbear": { token: "B7", weapon: "hth" },
|
||||||
|
"druidhawk": { token: "HK", weapon: "hth" },
|
||||||
|
"duriel": { token: "DU", weapon: "hth" },
|
||||||
|
"eagle": { token: "EG", weapon: "hth" },
|
||||||
|
"elzix": { token: "EL", weapon: "hth" },
|
||||||
|
"evilhole1": { token: "EH", weapon: "hth" },
|
||||||
|
"evilhole2": { token: "EH", weapon: "hth" },
|
||||||
|
"evilhole3": { token: "EH", weapon: "hth" },
|
||||||
|
"evilhole4": { token: "EH", weapon: "hth" },
|
||||||
|
"evilhole5": { token: "EH", weapon: "hth" },
|
||||||
|
"evilhut": { token: "2T", weapon: "hth" },
|
||||||
|
"fallen": { token: "FA", weapon: "hth" },
|
||||||
|
"fallen1": { token: "FA", weapon: "hth" },
|
||||||
|
"fallen2": { token: "FA", weapon: "hth" },
|
||||||
|
"fallen3": { token: "FA", weapon: "hth" },
|
||||||
|
"fallen4": { token: "FA", weapon: "hth" },
|
||||||
|
"fallen5": { token: "FA", weapon: "hth" },
|
||||||
|
"fallen6": { token: "FA", weapon: "hth" },
|
||||||
|
"fallen7": { token: "FA", weapon: "hth" },
|
||||||
|
"fallen8": { token: "FA", weapon: "hth" },
|
||||||
|
"fallenshaman": { token: "FS", weapon: "hth" },
|
||||||
|
"fallenshaman1": { token: "FS", weapon: "hth" },
|
||||||
|
"fallenshaman2": { token: "FS", weapon: "hth" },
|
||||||
|
"fallenshaman3": { token: "FS", weapon: "hth" },
|
||||||
|
"fallenshaman4": { token: "FS", weapon: "hth" },
|
||||||
|
"fallenshaman5": { token: "FS", weapon: "hth" },
|
||||||
|
"fallenshaman6": { token: "FS", weapon: "hth" },
|
||||||
|
"fallenshaman7": { token: "FS", weapon: "hth" },
|
||||||
|
"fallenshaman8": { token: "FS", weapon: "hth" },
|
||||||
|
"familiar": { token: "FI", weapon: "hth" },
|
||||||
|
"fara": { token: "OF", weapon: "hth" },
|
||||||
|
"fenris": { token: "WF", weapon: "hth" },
|
||||||
|
"fetish1": { token: "FE", weapon: "1hs" },
|
||||||
|
"fetish11": { token: "FE", weapon: "1hs" },
|
||||||
|
"fetish2": { token: "FE", weapon: "1hs" },
|
||||||
|
"fetish3": { token: "FE", weapon: "1hs" },
|
||||||
|
"fetish4": { token: "FE", weapon: "1hs" },
|
||||||
|
"fetish5": { token: "FE", weapon: "1hs" },
|
||||||
|
"fetish6": { token: "FE", weapon: "1hs" },
|
||||||
|
"fetish7": { token: "FE", weapon: "1hs" },
|
||||||
|
"fetish8": { token: "FE", weapon: "1hs" },
|
||||||
|
"fetishblow1": { token: "FC", weapon: "hth" },
|
||||||
|
"fetishblow2": { token: "FC", weapon: "hth" },
|
||||||
|
"fetishblow3": { token: "FC", weapon: "hth" },
|
||||||
|
"fetishblow4": { token: "FC", weapon: "hth" },
|
||||||
|
"fetishblow5": { token: "FC", weapon: "hth" },
|
||||||
|
"fetishblow6": { token: "FC", weapon: "hth" },
|
||||||
|
"fetishblow7": { token: "FC", weapon: "hth" },
|
||||||
|
"fetishblow8": { token: "FC", weapon: "hth" },
|
||||||
|
"fetishshaman1": { token: "FW", weapon: "hth" },
|
||||||
|
"fetishshaman2": { token: "FW", weapon: "hth" },
|
||||||
|
"fetishshaman3": { token: "FW", weapon: "hth" },
|
||||||
|
"fetishshaman4": { token: "FW", weapon: "hth" },
|
||||||
|
"fetishshaman5": { token: "FW", weapon: "hth" },
|
||||||
|
"fetishshaman6": { token: "FW", weapon: "hth" },
|
||||||
|
"fetishshaman7": { token: "FW", weapon: "hth" },
|
||||||
|
"fetishshaman8": { token: "FW", weapon: "hth" },
|
||||||
|
"fingermage1": { token: "FR", weapon: "hth" },
|
||||||
|
"fingermage2": { token: "FR", weapon: "hth" },
|
||||||
|
"fingermage3": { token: "FR", weapon: "hth" },
|
||||||
|
"fingermage4": { token: "FR", weapon: "hth" },
|
||||||
|
"fingermage5": { token: "FR", weapon: "hth" },
|
||||||
|
"firebeast": { token: "FM", weapon: "hth" },
|
||||||
|
"firegolem": { token: "G3", weapon: "hth" },
|
||||||
|
"firetower": { token: "PB", weapon: "hth" },
|
||||||
|
"fish": { token: "FJ", weapon: "hth" },
|
||||||
|
"flyingscimitar": { token: "ST", weapon: "hth" },
|
||||||
|
"foulcrow1": { token: "BK", weapon: "hth" },
|
||||||
|
"foulcrow2": { token: "BK", weapon: "hth" },
|
||||||
|
"foulcrow3": { token: "BK", weapon: "hth" },
|
||||||
|
"foulcrow4": { token: "BK", weapon: "hth" },
|
||||||
|
"foulcrow5": { token: "BK", weapon: "hth" },
|
||||||
|
"foulcrow6": { token: "BK", weapon: "hth" },
|
||||||
|
"foulcrow7": { token: "BK", weapon: "hth" },
|
||||||
|
"foulcrow8": { token: "BK", weapon: "hth" },
|
||||||
|
"frogdemon1": { token: "FD", weapon: "hth" },
|
||||||
|
"frogdemon2": { token: "FD", weapon: "hth" },
|
||||||
|
"frogdemon3": { token: "FD", weapon: "hth" },
|
||||||
|
"frozenhorror1": { token: "F0", weapon: "hth" },
|
||||||
|
"frozenhorror2": { token: "F0", weapon: "hth" },
|
||||||
|
"frozenhorror3": { token: "F0", weapon: "hth" },
|
||||||
|
"frozenhorror4": { token: "F0", weapon: "hth" },
|
||||||
|
"frozenhorror5": { token: "F0", weapon: "hth" },
|
||||||
|
"gargoyletrap": { token: "GT", weapon: "hth" },
|
||||||
|
"geglash": { token: "GE", weapon: "hth" },
|
||||||
|
"gheed": { token: "GH", weapon: "hth" },
|
||||||
|
"goatman1": { token: "GM", weapon: "2hs" },
|
||||||
|
"goatman10": { token: "GM", weapon: "2hs" },
|
||||||
|
"goatman2": { token: "GM", weapon: "2hs" },
|
||||||
|
"goatman3": { token: "GM", weapon: "2hs" },
|
||||||
|
"goatman4": { token: "GM", weapon: "2hs" },
|
||||||
|
"goatman5": { token: "GM", weapon: "2hs" },
|
||||||
|
"goatman6": { token: "GM", weapon: "2hs" },
|
||||||
|
"goatman7": { token: "GM", weapon: "2hs" },
|
||||||
|
"goatman8": { token: "GM", weapon: "2hs" },
|
||||||
|
"goatman9": { token: "GM", weapon: "2hs" },
|
||||||
|
"gorgon1": { token: "GO", weapon: "hth" },
|
||||||
|
"gorgon2": { token: "GO", weapon: "hth" },
|
||||||
|
"gorgon3": { token: "GO", weapon: "hth" },
|
||||||
|
"gorgon4": { token: "GO", weapon: "hth" },
|
||||||
|
"greiz": { token: "GR", weapon: "hth" },
|
||||||
|
"griswold": { token: "GZ", weapon: "hth" },
|
||||||
|
"halbu": { token: "20", weapon: "hth" },
|
||||||
|
"heartofwolverine": { token: "X3", weapon: "hth" },
|
||||||
|
"hellbovine": { token: "CW", weapon: "hth" },
|
||||||
|
"hellmeteor": { token: "K9", weapon: "hth" },
|
||||||
|
"hephasto": { token: "5P", weapon: "hth" },
|
||||||
|
"hratli": { token: "HR", weapon: "hth" },
|
||||||
|
"hydra1": { token: "HX", weapon: "hth" },
|
||||||
|
"hydra2": { token: "21", weapon: "hth" },
|
||||||
|
"hydra3": { token: "HZ", weapon: "hth" },
|
||||||
|
"iceglobe": { token: "IM", weapon: "hth" },
|
||||||
|
"imp1": { token: "IP", weapon: "hth" },
|
||||||
|
"imp2": { token: "IP", weapon: "hth" },
|
||||||
|
"imp3": { token: "IP", weapon: "hth" },
|
||||||
|
"imp4": { token: "IP", weapon: "hth" },
|
||||||
|
"imp5": { token: "IP", weapon: "hth" },
|
||||||
|
"imp6": { token: "IP", weapon: "hth" },
|
||||||
|
"imp7": { token: "IP", weapon: "hth" },
|
||||||
|
"imp8": { token: "IP", weapon: "hth" },
|
||||||
|
"infernosentry": { token: "E9", weapon: "hth" },
|
||||||
|
"injuredbarb1": { token: "6Z", weapon: "hth" },
|
||||||
|
"injuredbarb2": { token: "7J", weapon: "hth" },
|
||||||
|
"injuredbarb3": { token: "7I", weapon: "hth" },
|
||||||
|
"invisopet": { token: "K9", weapon: "hth" },
|
||||||
|
"invisospawner": { token: "K9", weapon: "hth" },
|
||||||
|
"irongolem": { token: "G4", weapon: "hth" },
|
||||||
|
"izual": { token: "22", weapon: "hth" },
|
||||||
|
"izualghost": { token: "17", weapon: "hth" },
|
||||||
|
"jamella": { token: "JA", weapon: "hth" },
|
||||||
|
"jerhyn": { token: "JE", weapon: "hth" },
|
||||||
|
"kashya": { token: "RC", weapon: "hth" },
|
||||||
|
"larva": { token: "LV", weapon: "hth" },
|
||||||
|
"larzuk": { token: "XR", weapon: "hth" },
|
||||||
|
"lightningbeast": { token: "XX", weapon: "hth" },
|
||||||
|
"lightningsentry": { token: "LG", weapon: "hth" },
|
||||||
|
"lightningspire": { token: "AE", weapon: "hth" },
|
||||||
|
"lysander": { token: "LY", weapon: "hth" },
|
||||||
|
"maggot": { token: "MA", weapon: "hth" },
|
||||||
|
"maggotbaby1": { token: "SB", weapon: "hth" },
|
||||||
|
"maggotbaby2": { token: "SB", weapon: "hth" },
|
||||||
|
"maggotbaby3": { token: "SB", weapon: "hth" },
|
||||||
|
"maggotbaby4": { token: "SB", weapon: "hth" },
|
||||||
|
"maggotbaby5": { token: "SB", weapon: "hth" },
|
||||||
|
"maggotbaby6": { token: "SB", weapon: "hth" },
|
||||||
|
"maggotegg1": { token: "SE", weapon: "hth" },
|
||||||
|
"maggotegg2": { token: "SE", weapon: "hth" },
|
||||||
|
"maggotegg3": { token: "SE", weapon: "hth" },
|
||||||
|
"maggotegg4": { token: "SE", weapon: "hth" },
|
||||||
|
"maggotegg5": { token: "SE", weapon: "hth" },
|
||||||
|
"maggotegg6": { token: "SE", weapon: "hth" },
|
||||||
|
"maggotqueen1": { token: "MQ", weapon: "hth" },
|
||||||
|
"maggotqueen2": { token: "MQ", weapon: "hth" },
|
||||||
|
"maggotqueen3": { token: "MQ", weapon: "hth" },
|
||||||
|
"maggotqueen4": { token: "MQ", weapon: "hth" },
|
||||||
|
"maggotqueen5": { token: "MQ", weapon: "hth" },
|
||||||
|
"malachai": { token: "36", weapon: "hth" },
|
||||||
|
"malah": { token: "XT", weapon: "hth" },
|
||||||
|
"megademon1": { token: "DM", weapon: "hth" },
|
||||||
|
"megademon2": { token: "DM", weapon: "hth" },
|
||||||
|
"megademon3": { token: "DM", weapon: "hth" },
|
||||||
|
"megademon4": { token: "DM", weapon: "hth" },
|
||||||
|
"megademon5": { token: "DM", weapon: "hth" },
|
||||||
|
"megademon6": { token: "DM", weapon: "hth" },
|
||||||
|
"mephisto": { token: "MP", weapon: "hth" },
|
||||||
|
"mephistospirit": { token: "M6", weapon: "hth" },
|
||||||
|
"meshif1": { token: "MS", weapon: "hth" },
|
||||||
|
"meshif2": { token: "M3", weapon: "hth" },
|
||||||
|
"minion1": { token: "XX", weapon: "hth" },
|
||||||
|
"minion10": { token: "XX", weapon: "hth" },
|
||||||
|
"minion11": { token: "XX", weapon: "hth" },
|
||||||
|
"minion2": { token: "XX", weapon: "hth" },
|
||||||
|
"minion3": { token: "XX", weapon: "hth" },
|
||||||
|
"minion4": { token: "XX", weapon: "hth" },
|
||||||
|
"minion5": { token: "XX", weapon: "hth" },
|
||||||
|
"minion6": { token: "XX", weapon: "hth" },
|
||||||
|
"minion7": { token: "XX", weapon: "hth" },
|
||||||
|
"minion8": { token: "XX", weapon: "hth" },
|
||||||
|
"minion9": { token: "XX", weapon: "hth" },
|
||||||
|
"minionspawner1": { token: "XA", weapon: "hth" },
|
||||||
|
"minionspawner2": { token: "XA", weapon: "hth" },
|
||||||
|
"minionspawner3": { token: "XA", weapon: "hth" },
|
||||||
|
"minionspawner4": { token: "XA", weapon: "hth" },
|
||||||
|
"minionspawner5": { token: "XA", weapon: "hth" },
|
||||||
|
"minionspawner6": { token: "XA", weapon: "hth" },
|
||||||
|
"minionspawner7": { token: "XA", weapon: "hth" },
|
||||||
|
"minionspawner8": { token: "XA", weapon: "hth" },
|
||||||
|
"minispider": { token: "LS", weapon: "hth" },
|
||||||
|
"mosquito1": { token: "MO", weapon: "hth" },
|
||||||
|
"mosquito2": { token: "MO", weapon: "hth" },
|
||||||
|
"mosquito3": { token: "MO", weapon: "hth" },
|
||||||
|
"mosquito4": { token: "MO", weapon: "hth" },
|
||||||
|
"mummy1": { token: "MM", weapon: "hth" },
|
||||||
|
"mummy2": { token: "MM", weapon: "hth" },
|
||||||
|
"mummy3": { token: "MM", weapon: "hth" },
|
||||||
|
"mummy4": { token: "MM", weapon: "hth" },
|
||||||
|
"mummy5": { token: "MM", weapon: "hth" },
|
||||||
|
"mummy6": { token: "MM", weapon: "hth" },
|
||||||
|
"natalya": { token: "TZ", weapon: "hth" },
|
||||||
|
"navi": { token: "RG", weapon: "hth" },
|
||||||
|
"necromage": { token: "SK", weapon: "hth" },
|
||||||
|
"necroskeleton": { token: "SK", weapon: "1hs" },
|
||||||
|
"nihlathak": { token: "0J", weapon: "hth" },
|
||||||
|
"nihlathakboss": { token: "XU", weapon: "hth" },
|
||||||
|
"oaksage": { token: "XW", weapon: "hth" },
|
||||||
|
"ormus": { token: "OR", weapon: "hth" },
|
||||||
|
"overseer1": { token: "OS", weapon: "hth" },
|
||||||
|
"overseer2": { token: "OS", weapon: "hth" },
|
||||||
|
"overseer3": { token: "OS", weapon: "hth" },
|
||||||
|
"overseer4": { token: "OS", weapon: "hth" },
|
||||||
|
"overseer5": { token: "OS", weapon: "hth" },
|
||||||
|
"overseer6": { token: "OS", weapon: "hth" },
|
||||||
|
"painworm1": { token: "46", weapon: "hth" },
|
||||||
|
"painworm2": { token: "46", weapon: "hth" },
|
||||||
|
"painworm3": { token: "46", weapon: "hth" },
|
||||||
|
"painworm4": { token: "46", weapon: "hth" },
|
||||||
|
"painworm5": { token: "46", weapon: "hth" },
|
||||||
|
"pantherwoman1": { token: "PW", weapon: "hth" },
|
||||||
|
"pantherwoman2": { token: "PW", weapon: "hth" },
|
||||||
|
"pantherwoman3": { token: "PW", weapon: "hth" },
|
||||||
|
"pantherwoman4": { token: "PW", weapon: "hth" },
|
||||||
|
"pantherwoman5": { token: "PW", weapon: "hth" },
|
||||||
|
"pantherwoman6": { token: "PW", weapon: "hth" },
|
||||||
|
"parrot": { token: "PR", weapon: "hth" },
|
||||||
|
"plaguepoppy": { token: "K9", weapon: "hth" },
|
||||||
|
"poisonorb": { token: "PM", weapon: "hth" },
|
||||||
|
"prisondoor": { token: "2Q", weapon: "hth" },
|
||||||
|
"putriddefiler1": { token: "45", weapon: "hth" },
|
||||||
|
"putriddefiler2": { token: "45", weapon: "hth" },
|
||||||
|
"putriddefiler3": { token: "45", weapon: "hth" },
|
||||||
|
"putriddefiler4": { token: "45", weapon: "hth" },
|
||||||
|
"putriddefiler5": { token: "45", weapon: "hth" },
|
||||||
|
"qual-kehk": { token: "XV", weapon: "hth" },
|
||||||
|
"quill": { token: "SI", weapon: "hth" },
|
||||||
|
"quillbear1": { token: "S7", weapon: "hth" },
|
||||||
|
"quillbear2": { token: "S7", weapon: "hth" },
|
||||||
|
"quillbear3": { token: "S7", weapon: "hth" },
|
||||||
|
"quillbear4": { token: "S7", weapon: "hth" },
|
||||||
|
"quillbear5": { token: "S7", weapon: "hth" },
|
||||||
|
"quillrat": { token: "SI", weapon: "hth" },
|
||||||
|
"quillrat1": { token: "SI", weapon: "hth" },
|
||||||
|
"quillrat2": { token: "SI", weapon: "hth" },
|
||||||
|
"quillrat3": { token: "SI", weapon: "hth" },
|
||||||
|
"quillrat4": { token: "SI", weapon: "hth" },
|
||||||
|
"quillrat5": { token: "SI", weapon: "hth" },
|
||||||
|
"quillrat6": { token: "SI", weapon: "hth" },
|
||||||
|
"quillrat7": { token: "SI", weapon: "hth" },
|
||||||
|
"quillrat8": { token: "SI", weapon: "hth" },
|
||||||
|
"radament": { token: "RD", weapon: "hth" },
|
||||||
|
"rat": { token: "RT", weapon: "hth" },
|
||||||
|
"reanimatedhorde1": { token: "RE", weapon: "hth" },
|
||||||
|
"reanimatedhorde2": { token: "RE", weapon: "hth" },
|
||||||
|
"reanimatedhorde3": { token: "RE", weapon: "hth" },
|
||||||
|
"reanimatedhorde4": { token: "RE", weapon: "hth" },
|
||||||
|
"reanimatedhorde5": { token: "RE", weapon: "hth" },
|
||||||
|
"reanimatedhorde6": { token: "RE", weapon: "hth" },
|
||||||
|
"regurgitator1": { token: "CS", weapon: "hth" },
|
||||||
|
"regurgitator2": { token: "CS", weapon: "hth" },
|
||||||
|
"regurgitator3": { token: "CS", weapon: "hth" },
|
||||||
|
"regurgitator4": { token: "CS", weapon: "hth" },
|
||||||
|
"rogue1": { token: "RG", weapon: "hth" },
|
||||||
|
"rogue2": { token: "RG", weapon: "hth" },
|
||||||
|
"rogue3": { token: "RG", weapon: "hth" },
|
||||||
|
"roguehire": { token: "RG", weapon: "hth" },
|
||||||
|
"sandleaper1": { token: "SL", weapon: "hth" },
|
||||||
|
"sandleaper2": { token: "SL", weapon: "hth" },
|
||||||
|
"sandleaper3": { token: "SL", weapon: "hth" },
|
||||||
|
"sandleaper4": { token: "SL", weapon: "hth" },
|
||||||
|
"sandleaper5": { token: "SL", weapon: "hth" },
|
||||||
|
"sandleaper6": { token: "SL", weapon: "hth" },
|
||||||
|
"sandleaper7": { token: "SL", weapon: "hth" },
|
||||||
|
"sandmaggot1": { token: "SM", weapon: "hth" },
|
||||||
|
"sandmaggot2": { token: "SM", weapon: "hth" },
|
||||||
|
"sandmaggot3": { token: "SM", weapon: "hth" },
|
||||||
|
"sandmaggot4": { token: "SM", weapon: "hth" },
|
||||||
|
"sandmaggot5": { token: "SM", weapon: "hth" },
|
||||||
|
"sandmaggot6": { token: "SM", weapon: "hth" },
|
||||||
|
"sandmaggot7": { token: "SM", weapon: "hth" },
|
||||||
|
"sandraider1": { token: "SR", weapon: "hth" },
|
||||||
|
"sandraider10": { token: "SR", weapon: "hth" },
|
||||||
|
"sandraider2": { token: "SR", weapon: "hth" },
|
||||||
|
"sandraider3": { token: "SR", weapon: "hth" },
|
||||||
|
"sandraider4": { token: "SR", weapon: "hth" },
|
||||||
|
"sandraider5": { token: "SR", weapon: "hth" },
|
||||||
|
"sandraider6": { token: "SR", weapon: "hth" },
|
||||||
|
"sandraider7": { token: "SR", weapon: "hth" },
|
||||||
|
"sandraider8": { token: "SR", weapon: "hth" },
|
||||||
|
"sandraider9": { token: "SR", weapon: "hth" },
|
||||||
|
"sarcophagus": { token: "MG", weapon: "hth" },
|
||||||
|
"scarab1": { token: "SC", weapon: "hth" },
|
||||||
|
"scarab2": { token: "SC", weapon: "hth" },
|
||||||
|
"scarab3": { token: "SC", weapon: "hth" },
|
||||||
|
"scarab4": { token: "SC", weapon: "hth" },
|
||||||
|
"scarab5": { token: "SC", weapon: "hth" },
|
||||||
|
"scarab6": { token: "SC", weapon: "hth" },
|
||||||
|
"scarab7": { token: "SC", weapon: "hth" },
|
||||||
|
"scarab8": { token: "SC", weapon: "hth" },
|
||||||
|
"scorpion": { token: "DS", weapon: "hth" },
|
||||||
|
"seventombs": { token: "9A", weapon: "hth" },
|
||||||
|
"shadowmaster": { token: "K9", weapon: "hth" },
|
||||||
|
"shadowwarrior": { token: "K9", weapon: "hth" },
|
||||||
|
"siegebeast1": { token: "OX", weapon: "hth" },
|
||||||
|
"siegebeast2": { token: "OX", weapon: "hth" },
|
||||||
|
"siegebeast3": { token: "OX", weapon: "hth" },
|
||||||
|
"siegebeast4": { token: "OX", weapon: "hth" },
|
||||||
|
"siegebeast5": { token: "OX", weapon: "hth" },
|
||||||
|
"sk_archer1": { token: "SK", weapon: "bow" },
|
||||||
|
"sk_archer10": { token: "SK", weapon: "bow" },
|
||||||
|
"sk_archer11": { token: "SK", weapon: "bow" },
|
||||||
|
"sk_archer2": { token: "SK", weapon: "bow" },
|
||||||
|
"sk_archer3": { token: "SK", weapon: "bow" },
|
||||||
|
"sk_archer4": { token: "SK", weapon: "bow" },
|
||||||
|
"sk_archer5": { token: "SK", weapon: "bow" },
|
||||||
|
"sk_archer6": { token: "SK", weapon: "bow" },
|
||||||
|
"sk_archer7": { token: "SK", weapon: "bow" },
|
||||||
|
"sk_archer8": { token: "SK", weapon: "bow" },
|
||||||
|
"sk_archer9": { token: "SK", weapon: "bow" },
|
||||||
|
"skeleton": { token: "SK", weapon: "hth" },
|
||||||
|
"skeleton1": { token: "SK", weapon: "1hs" },
|
||||||
|
"skeleton2": { token: "SK", weapon: "1hs" },
|
||||||
|
"skeleton3": { token: "SK", weapon: "1hs" },
|
||||||
|
"skeleton4": { token: "SK", weapon: "1hs" },
|
||||||
|
"skeleton5": { token: "SK", weapon: "1hs" },
|
||||||
|
"skeleton6": { token: "SK", weapon: "1hs" },
|
||||||
|
"skeleton7": { token: "SK", weapon: "1hs" },
|
||||||
|
"skeleton8": { token: "SK", weapon: "1hs" },
|
||||||
|
"skmage_cold1": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_cold2": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_cold3": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_cold4": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_cold5": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_cold6": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_fire1": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_fire2": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_fire3": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_fire4": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_fire5": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_fire6": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_fire7": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_ltng1": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_ltng2": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_ltng3": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_ltng4": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_ltng5": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_ltng6": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_ltng7": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_pois1": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_pois2": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_pois3": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_pois4": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_pois5": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_pois6": { token: "SK", weapon: "hth" },
|
||||||
|
"skmage_pois7": { token: "SK", weapon: "hth" },
|
||||||
|
"slinger1": { token: "PW", weapon: "1ht" },
|
||||||
|
"slinger2": { token: "PW", weapon: "1ht" },
|
||||||
|
"slinger3": { token: "PW", weapon: "1ht" },
|
||||||
|
"slinger4": { token: "PW", weapon: "1ht" },
|
||||||
|
"slinger5": { token: "PW", weapon: "1ht" },
|
||||||
|
"slinger6": { token: "PW", weapon: "1ht" },
|
||||||
|
"slinger7": { token: "PW", weapon: "1ht" },
|
||||||
|
"slinger8": { token: "PW", weapon: "1ht" },
|
||||||
|
"slinger9": { token: "PW", weapon: "1ht" },
|
||||||
|
"smith": { token: "5P", weapon: "hth" },
|
||||||
|
"snake": { token: "CO", weapon: "hth" },
|
||||||
|
"snowyeti1": { token: "IO", weapon: "hth" },
|
||||||
|
"snowyeti2": { token: "IO", weapon: "hth" },
|
||||||
|
"snowyeti3": { token: "IO", weapon: "hth" },
|
||||||
|
"snowyeti4": { token: "IO", weapon: "hth" },
|
||||||
|
"spiritmummy": { token: "XX", weapon: "hth" },
|
||||||
|
"spiritofbarbs": { token: "X4", weapon: "hth" },
|
||||||
|
"spiritwolf": { token: "WF", weapon: "hth" },
|
||||||
|
"succubus1": { token: "0B", weapon: "hth" },
|
||||||
|
"succubus2": { token: "0B", weapon: "hth" },
|
||||||
|
"succubus3": { token: "0B", weapon: "hth" },
|
||||||
|
"succubus4": { token: "0B", weapon: "hth" },
|
||||||
|
"succubus5": { token: "0B", weapon: "hth" },
|
||||||
|
"succubus6": { token: "0B", weapon: "hth" },
|
||||||
|
"succubus7": { token: "0B", weapon: "hth" },
|
||||||
|
"succubus8": { token: "0B", weapon: "hth" },
|
||||||
|
"succubuswitch1": { token: "0C", weapon: "hth" },
|
||||||
|
"succubuswitch2": { token: "0C", weapon: "hth" },
|
||||||
|
"succubuswitch3": { token: "0C", weapon: "hth" },
|
||||||
|
"succubuswitch4": { token: "0C", weapon: "hth" },
|
||||||
|
"succubuswitch5": { token: "0C", weapon: "hth" },
|
||||||
|
"succubuswitch6": { token: "0C", weapon: "hth" },
|
||||||
|
"succubuswitch7": { token: "0C", weapon: "hth" },
|
||||||
|
"succubuswitch8": { token: "0C", weapon: "hth" },
|
||||||
|
"succubuswitch9": { token: "0C", weapon: "hth" },
|
||||||
|
"suckernest1": { token: "DH", weapon: "hth" },
|
||||||
|
"suckernest2": { token: "DH", weapon: "hth" },
|
||||||
|
"suckernest3": { token: "DH", weapon: "hth" },
|
||||||
|
"suckernest4": { token: "DH", weapon: "hth" },
|
||||||
|
"suicideminion1": { token: "XY", weapon: "hth" },
|
||||||
|
"suicideminion2": { token: "XY", weapon: "hth" },
|
||||||
|
"suicideminion3": { token: "XY", weapon: "hth" },
|
||||||
|
"suicideminion4": { token: "XY", weapon: "hth" },
|
||||||
|
"suicideminion5": { token: "XY", weapon: "hth" },
|
||||||
|
"suicideminion6": { token: "XY", weapon: "hth" },
|
||||||
|
"suicideminion7": { token: "XY", weapon: "hth" },
|
||||||
|
"suicideminion8": { token: "XY", weapon: "hth" },
|
||||||
|
"summoner": { token: "SU", weapon: "hth" },
|
||||||
|
"swarm1": { token: "SW", weapon: "hth" },
|
||||||
|
"swarm2": { token: "SW", weapon: "hth" },
|
||||||
|
"swarm3": { token: "SW", weapon: "hth" },
|
||||||
|
"swarm4": { token: "SW", weapon: "hth" },
|
||||||
|
"swarm5": { token: "SW", weapon: "hth" },
|
||||||
|
"tentacle1": { token: "TN", weapon: "hth" },
|
||||||
|
"tentacle2": { token: "TN", weapon: "hth" },
|
||||||
|
"tentacle3": { token: "TN", weapon: "hth" },
|
||||||
|
"tentaclehead1": { token: "TE", weapon: "hth" },
|
||||||
|
"tentaclehead2": { token: "TE", weapon: "hth" },
|
||||||
|
"tentaclehead3": { token: "TE", weapon: "hth" },
|
||||||
|
"thornhulk1": { token: "TH", weapon: "hth" },
|
||||||
|
"thornhulk2": { token: "TH", weapon: "hth" },
|
||||||
|
"thornhulk3": { token: "TH", weapon: "hth" },
|
||||||
|
"thornhulk4": { token: "TH", weapon: "hth" },
|
||||||
|
"thornhulk5": { token: "TH", weapon: "hth" },
|
||||||
|
"trap-firebolt": { token: "9A", weapon: "hth" },
|
||||||
|
"trap-horzmissile": { token: "9A", weapon: "hth" },
|
||||||
|
"trap-lightning": { token: "9A", weapon: "hth" },
|
||||||
|
"trap-melee": { token: "M4", weapon: "hth" },
|
||||||
|
"trap-nova": { token: "9A", weapon: "hth" },
|
||||||
|
"trap-poisoncloud": { token: "9A", weapon: "hth" },
|
||||||
|
"trap-vertmissile": { token: "9A", weapon: "hth" },
|
||||||
|
"trappedsoul1": { token: "10", weapon: "hth" },
|
||||||
|
"trappedsoul2": { token: "13", weapon: "hth" },
|
||||||
|
"turret1": { token: "PB", weapon: "hth" },
|
||||||
|
"turret2": { token: "PB", weapon: "hth" },
|
||||||
|
"turret3": { token: "PB", weapon: "hth" },
|
||||||
|
"tyrael1": { token: "TX", weapon: "hth" },
|
||||||
|
"tyrael2": { token: "TY", weapon: "hth" },
|
||||||
|
"tyrael3": { token: "TY", weapon: "hth" },
|
||||||
|
"uberandariel": { token: "AN", weapon: "hth" },
|
||||||
|
"uberbaal": { token: "42", weapon: "hth" },
|
||||||
|
"uberdiablo": { token: "DI", weapon: "hth" },
|
||||||
|
"uberduriel": { token: "DU", weapon: "hth" },
|
||||||
|
"uberizual": { token: "22", weapon: "hth" },
|
||||||
|
"ubermephisto": { token: "MP", weapon: "hth" },
|
||||||
|
"unraveler1": { token: "GY", weapon: "hth" },
|
||||||
|
"unraveler2": { token: "GY", weapon: "hth" },
|
||||||
|
"unraveler3": { token: "GY", weapon: "hth" },
|
||||||
|
"unraveler4": { token: "GY", weapon: "hth" },
|
||||||
|
"unraveler5": { token: "GY", weapon: "hth" },
|
||||||
|
"unraveler6": { token: "GY", weapon: "hth" },
|
||||||
|
"unraveler7": { token: "GY", weapon: "hth" },
|
||||||
|
"unraveler8": { token: "GY", weapon: "hth" },
|
||||||
|
"unraveler9": { token: "GY", weapon: "hth" },
|
||||||
|
"valkyrie": { token: "VK", weapon: "hth" },
|
||||||
|
"vampire1": { token: "VA", weapon: "hth" },
|
||||||
|
"vampire2": { token: "VA", weapon: "hth" },
|
||||||
|
"vampire3": { token: "VA", weapon: "hth" },
|
||||||
|
"vampire4": { token: "VA", weapon: "hth" },
|
||||||
|
"vampire5": { token: "VA", weapon: "hth" },
|
||||||
|
"vampire6": { token: "VA", weapon: "hth" },
|
||||||
|
"vampire7": { token: "VA", weapon: "hth" },
|
||||||
|
"vampire8": { token: "VA", weapon: "hth" },
|
||||||
|
"vampire9": { token: "VA", weapon: "hth" },
|
||||||
|
"venomlord": { token: "DM", weapon: "hth" },
|
||||||
|
"vilechild1": { token: "VC", weapon: "hth" },
|
||||||
|
"vilechild2": { token: "VC", weapon: "hth" },
|
||||||
|
"vilechild3": { token: "VC", weapon: "hth" },
|
||||||
|
"vilechild4": { token: "VC", weapon: "hth" },
|
||||||
|
"vilechild5": { token: "VC", weapon: "hth" },
|
||||||
|
"vilemother1": { token: "VM", weapon: "hth" },
|
||||||
|
"vilemother2": { token: "VM", weapon: "hth" },
|
||||||
|
"vilemother3": { token: "VM", weapon: "hth" },
|
||||||
|
"vilemother4": { token: "VM", weapon: "hth" },
|
||||||
|
"vilemother5": { token: "VM", weapon: "hth" },
|
||||||
|
"vinecreature": { token: "K9", weapon: "hth" },
|
||||||
|
"vulture1": { token: "VD", weapon: "hth" },
|
||||||
|
"vulture2": { token: "VD", weapon: "hth" },
|
||||||
|
"vulture3": { token: "VD", weapon: "hth" },
|
||||||
|
"vulture4": { token: "VD", weapon: "hth" },
|
||||||
|
"vulture5": { token: "VD", weapon: "hth" },
|
||||||
|
"wakeofdestruction": { token: "E9", weapon: "hth" },
|
||||||
|
"warriv1": { token: "WA", weapon: "hth" },
|
||||||
|
"warriv2": { token: "WX", weapon: "hth" },
|
||||||
|
"willowisp1": { token: "WW", weapon: "hth" },
|
||||||
|
"willowisp2": { token: "WW", weapon: "hth" },
|
||||||
|
"willowisp3": { token: "WW", weapon: "hth" },
|
||||||
|
"willowisp4": { token: "WW", weapon: "hth" },
|
||||||
|
"willowisp5": { token: "WW", weapon: "hth" },
|
||||||
|
"willowisp6": { token: "WW", weapon: "hth" },
|
||||||
|
"willowisp7": { token: "WW", weapon: "hth" },
|
||||||
|
"willowisp8": { token: "WW", weapon: "hth" },
|
||||||
|
"window1": { token: "VH", weapon: "hth" },
|
||||||
|
"window2": { token: "VJ", weapon: "hth" },
|
||||||
|
"wolf": { token: "40", weapon: "hth" },
|
||||||
|
"wolfrider1": { token: "WR", weapon: "hth" },
|
||||||
|
"wolfrider2": { token: "WR", weapon: "hth" },
|
||||||
|
"wolfrider3": { token: "WR", weapon: "hth" },
|
||||||
|
"worldstoneeffect": { token: "K9", weapon: "hth" },
|
||||||
|
"wraith1": { token: "WR", weapon: "hth" },
|
||||||
|
"wraith2": { token: "WR", weapon: "hth" },
|
||||||
|
"wraith3": { token: "WR", weapon: "hth" },
|
||||||
|
"wraith4": { token: "WR", weapon: "hth" },
|
||||||
|
"wraith5": { token: "WR", weapon: "hth" },
|
||||||
|
"wraith6": { token: "WR", weapon: "hth" },
|
||||||
|
"wraith7": { token: "WR", weapon: "hth" },
|
||||||
|
"wraith8": { token: "WR", weapon: "hth" },
|
||||||
|
"wraith9": { token: "WR", weapon: "hth" },
|
||||||
|
"yeti": { token: "YE", weapon: "hth" },
|
||||||
|
"zealot1": { token: "ZZ", weapon: "hth" },
|
||||||
|
"zealot2": { token: "ZZ", weapon: "hth" },
|
||||||
|
"zealot3": { token: "ZZ", weapon: "hth" },
|
||||||
|
"zealot4": { token: "ZZ", weapon: "hth" },
|
||||||
|
"zealot5": { token: "ZZ", weapon: "hth" },
|
||||||
|
"zombie": { token: "ZM", weapon: "hth" },
|
||||||
|
"zombie1": { token: "ZM", weapon: "hth" },
|
||||||
|
"zombie2": { token: "ZM", weapon: "hth" },
|
||||||
|
"zombie3": { token: "ZM", weapon: "hth" },
|
||||||
|
"zombie4": { token: "ZM", weapon: "hth" },
|
||||||
|
"zombie5": { token: "ZM", weapon: "hth" },
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve monster id to token and weapon.
|
* Unique monster (token, weapon) specs appearing in each Act (1..5),
|
||||||
|
* used by the offline entity baker to pre-pack all level monsters per act.
|
||||||
|
*/
|
||||||
|
export const ACT_MONSTER_SPECS: Record<number, readonly MonsterArtSpec[]> = {
|
||||||
|
1: [{ token: "FA", weapon: "hth" }, { token: "ZM", weapon: "hth" }, { token: "SI", weapon: "hth" }, { token: "YE", weapon: "hth" }, { token: "SK", weapon: "hth" }, { token: "CW", weapon: "hth" }, { token: "CR", weapon: "1hs" }, { token: "FS", weapon: "hth" }, { token: "CR", weapon: "2ht" }, { token: "SK", weapon: "1hs" }, { token: "BN", weapon: "hth" }, { token: "GM", weapon: "2hs" }, { token: "CR", weapon: "bow" }, { token: "SK", weapon: "bow" }, { token: "BH", weapon: "hth" }, { token: "WR", weapon: "hth" }, { token: "FE", weapon: "1hs" }, { token: "SP", weapon: "hth" }, { token: "VA", weapon: "hth" }],
|
||||||
|
2: [{ token: "FA", weapon: "hth" }, { token: "ZM", weapon: "hth" }, { token: "SI", weapon: "hth" }, { token: "YE", weapon: "hth" }, { token: "SK", weapon: "hth" }, { token: "CW", weapon: "hth" }, { token: "SL", weapon: "hth" }, { token: "PW", weapon: "hth" }, { token: "SC", weapon: "hth" }, { token: "VD", weapon: "hth" }, { token: "PW", weapon: "1ht" }, { token: "BN", weapon: "hth" }, { token: "SM", weapon: "hth" }, { token: "SW", weapon: "hth" }, { token: "SR", weapon: "hth" }, { token: "SD", weapon: "hth" }, { token: "MM", weapon: "hth" }, { token: "GY", weapon: "hth" }, { token: "SK", weapon: "1hs" }, { token: "SK", weapon: "bow" }, { token: "BB", weapon: "hth" }, { token: "PN", weapon: "hth" }, { token: "BT", weapon: "hth" }, { token: "WR", weapon: "hth" }, { token: "VA", weapon: "hth" }, { token: "GM", weapon: "2hs" }],
|
||||||
|
3: [{ token: "FA", weapon: "hth" }, { token: "ZM", weapon: "hth" }, { token: "SI", weapon: "hth" }, { token: "YE", weapon: "hth" }, { token: "SK", weapon: "hth" }, { token: "CW", weapon: "hth" }, { token: "BB", weapon: "hth" }, { token: "MO", weapon: "hth" }, { token: "TH", weapon: "hth" }, { token: "FE", weapon: "1hs" }, { token: "FC", weapon: "hth" }, { token: "BK", weapon: "hth" }, { token: "FD", weapon: "hth" }, { token: "FW", weapon: "hth" }, { token: "WW", weapon: "hth" }, { token: "VD", weapon: "hth" }, { token: "SL", weapon: "hth" }, { token: "ZZ", weapon: "hth" }, { token: "SW", weapon: "hth" }, { token: "ZP", weapon: "hth" }, { token: "SR", weapon: "hth" }, { token: "VA", weapon: "hth" }, { token: "SP", weapon: "hth" }, { token: "BT", weapon: "hth" }, { token: "SM", weapon: "hth" }, { token: "MM", weapon: "hth" }, { token: "FK", weapon: "1hs" }, { token: "WR", weapon: "hth" }, { token: "GY", weapon: "hth" }, { token: "CR", weapon: "1hs" }, { token: "SD", weapon: "hth" }, { token: "SC", weapon: "hth" }, { token: "CR", weapon: "bow" }, { token: "PN", weapon: "hth" }],
|
||||||
|
4: [{ token: "FA", weapon: "hth" }, { token: "ZM", weapon: "hth" }, { token: "SI", weapon: "hth" }, { token: "YE", weapon: "hth" }, { token: "SK", weapon: "hth" }, { token: "CW", weapon: "hth" }, { token: "SL", weapon: "hth" }, { token: "VM", weapon: "hth" }, { token: "FR", weapon: "hth" }, { token: "CS", weapon: "hth" }, { token: "UM", weapon: "fgt" }, { token: "DM", weapon: "hth" }, { token: "WW", weapon: "hth" }, { token: "BH", weapon: "hth" }, { token: "BT", weapon: "hth" }, { token: "UM", weapon: "hth" }, { token: "SM", weapon: "hth" }, { token: "PN", weapon: "hth" }, { token: "UM", weapon: "mgk" }],
|
||||||
|
5: [{ token: "FA", weapon: "hth" }, { token: "ZM", weapon: "hth" }, { token: "SI", weapon: "hth" }, { token: "YE", weapon: "hth" }, { token: "SK", weapon: "hth" }, { token: "CW", weapon: "hth" }, { token: "XX", weapon: "hth" }, { token: "M5", weapon: "hth" }, { token: "OS", weapon: "hth" }, { token: "IP", weapon: "hth" }, { token: "SK", weapon: "bow" }, { token: "BH", weapon: "hth" }, { token: "OX", weapon: "hth" }, { token: "F0", weapon: "hth" }, { token: "0B", weapon: "hth" }, { token: "L3", weapon: "hth" }, { token: "IO", weapon: "hth" }, { token: "RE", weapon: "hth" }, { token: "0D", weapon: "hth" }, { token: "0F", weapon: "hth" }, { token: "0E", weapon: "hth" }, { token: "45", weapon: "hth" }, { token: "0C", weapon: "hth" }, { token: "XY", weapon: "hth" }, { token: "CR", weapon: "1hs" }, { token: "CR", weapon: "bow" }, { token: "CR", weapon: "2ht" }, { token: "SW", weapon: "hth" }, { token: "SM", weapon: "hth" }, { token: "SP", weapon: "hth" }, { token: "SC", weapon: "hth" }, { token: "UM", weapon: "mgk" }, { token: "DM", weapon: "hth" }],
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the visual token and weapon class for a given monster ID.
|
||||||
*/
|
*/
|
||||||
export function resolveMonsterArtSpec(id: string): MonsterArtSpec | null {
|
export function resolveMonsterArtSpec(id: string): MonsterArtSpec | null {
|
||||||
const spec = MONSTER_ART_MAP[id.toLowerCase()]
|
const lower = id.toLowerCase()
|
||||||
if (spec !== undefined) return spec
|
if (MONSTER_ART_MAP[lower]) {
|
||||||
// Generic fallback if id starts with known prefixes
|
return MONSTER_ART_MAP[lower]
|
||||||
for (const [key, val] of Object.entries(MONSTER_ART_MAP)) {
|
}
|
||||||
const prefix = key.replace(/\d+$/, '')
|
const base = lower.replace(/\d+$/, '')
|
||||||
if (id.toLowerCase().startsWith(prefix)) {
|
for (const [key, spec] of Object.entries(MONSTER_ART_MAP)) {
|
||||||
return val
|
if (key.startsWith(base)) {
|
||||||
|
return spec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,32 @@
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
import { existsSync, readFileSync } from 'node:fs'
|
import { existsSync, readFileSync } from 'node:fs'
|
||||||
import { join } from 'node:path'
|
import { join } from 'node:path'
|
||||||
import { MONSTER_ART_MAP } from '../src/game/monster-mapping.ts'
|
import { ACT_MONSTER_SPECS, MONSTER_ART_MAP } from '../src/game/monster-mapping.ts'
|
||||||
|
|
||||||
describe('offline pre-baked entity atlases (Issue #30)', () => {
|
describe('offline pre-baked entity atlases & full Act 1..5 pack index (Issues #30 & #33)', () => {
|
||||||
const packRoot = join(process.cwd(), 'samples', 'd2-packs')
|
const packRoot = join(process.cwd(), 'samples', 'd2-packs')
|
||||||
const hasPacks = existsSync(join(packRoot, 'act1', 'entities', 'char-so.json'))
|
const indexJsonPath = join(packRoot, 'index.json')
|
||||||
|
const hasPacks = existsSync(indexJsonPath) && existsSync(join(packRoot, 'act1', 'entities', 'char-so.json'))
|
||||||
|
|
||||||
|
it('maps 700+ monster IDs across Acts 1..5 in MONSTER_ART_MAP', () => {
|
||||||
|
expect(Object.keys(MONSTER_ART_MAP).length).toBeGreaterThanOrEqual(700)
|
||||||
|
expect(MONSTER_ART_MAP.fallen).toEqual({ token: 'FA', weapon: 'hth' })
|
||||||
|
expect(MONSTER_ART_MAP.hellbovine).toEqual({ token: 'CW', weapon: 'hth' })
|
||||||
|
expect(MONSTER_ART_MAP.skeleton).toEqual({ token: 'SK', weapon: 'hth' })
|
||||||
|
})
|
||||||
|
|
||||||
|
it.skipIf(!hasPacks)('includes all 5 acts and 350+ map variants in samples/d2-packs/index.json (Issue #33)', () => {
|
||||||
|
const index = JSON.parse(readFileSync(indexJsonPath, 'utf8'))
|
||||||
|
expect(Array.isArray(index.levels)).toBe(true)
|
||||||
|
expect(index.levels.length).toBeGreaterThanOrEqual(350)
|
||||||
|
|
||||||
|
const actsPresent = new Set(index.levels.map((l: { act: number }) => l.act))
|
||||||
|
expect(actsPresent.has(1)).toBe(true)
|
||||||
|
expect(actsPresent.has(2)).toBe(true)
|
||||||
|
expect(actsPresent.has(3)).toBe(true)
|
||||||
|
expect(actsPresent.has(4)).toBe(true)
|
||||||
|
expect(actsPresent.has(5)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
it.skipIf(!hasPacks)('bakes Sorceress char-so PNG + JSON for Acts 1..5', () => {
|
it.skipIf(!hasPacks)('bakes Sorceress char-so PNG + JSON for Acts 1..5', () => {
|
||||||
for (let act = 1; act <= 5; act += 1) {
|
for (let act = 1; act <= 5; act += 1) {
|
||||||
|
|
@ -24,28 +45,22 @@ describe('offline pre-baked entity atlases (Issue #30)', () => {
|
||||||
expect(meta.groups.length).toBe(32) // 16 walk + 16 stand
|
expect(meta.groups.length).toBe(32) // 16 walk + 16 stand
|
||||||
|
|
||||||
const png = readFileSync(pngPath)
|
const png = readFileSync(pngPath)
|
||||||
// Check PNG magic header
|
|
||||||
expect(png[0]).toBe(137)
|
expect(png[0]).toBe(137)
|
||||||
expect(png[1]).toBe(80) // P
|
expect(png[1]).toBe(80) // P
|
||||||
expect(png[2]).toBe(78) // N
|
expect(png[2]).toBe(78) // N
|
||||||
expect(png[3]).toBe(71) // G
|
expect(png[3]).toBe(71) // G
|
||||||
// Read IHDR width and height
|
|
||||||
const view = new DataView(png.buffer, png.byteOffset, png.byteLength)
|
const view = new DataView(png.buffer, png.byteOffset, png.byteLength)
|
||||||
expect(view.getUint32(16)).toBe(meta.width)
|
expect(view.getUint32(16)).toBe(meta.width)
|
||||||
expect(view.getUint32(20)).toBe(meta.height)
|
expect(view.getUint32(20)).toBe(meta.height)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it.skipIf(!hasPacks)('bakes monster PNG + JSON atlases for all mapped monster specs across Acts 1..5', () => {
|
it.skipIf(!hasPacks)('bakes monster PNG + JSON atlases for all per-Act monster specs across Acts 1..5', () => {
|
||||||
const uniqueSpecs = new Map<string, { token: string; weapon: string }>()
|
|
||||||
for (const spec of Object.values(MONSTER_ART_MAP)) {
|
|
||||||
const key = `${spec.token}-${spec.weapon}`
|
|
||||||
if (!uniqueSpecs.has(key)) uniqueSpecs.set(key, spec)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let act = 1; act <= 5; act += 1) {
|
for (let act = 1; act <= 5; act += 1) {
|
||||||
const dir = join(packRoot, `act${act}`, 'entities')
|
const dir = join(packRoot, `act${act}`, 'entities')
|
||||||
for (const spec of uniqueSpecs.values()) {
|
const specs = ACT_MONSTER_SPECS[act] ?? []
|
||||||
|
expect(specs.length).toBeGreaterThan(0)
|
||||||
|
for (const spec of specs) {
|
||||||
const slug = `monster-${spec.token.toLowerCase()}-${spec.weapon.toLowerCase()}`
|
const slug = `monster-${spec.token.toLowerCase()}-${spec.weapon.toLowerCase()}`
|
||||||
const jsonPath = join(dir, `${slug}.json`)
|
const jsonPath = join(dir, `${slug}.json`)
|
||||||
const pngPath = join(dir, `${slug}.png`)
|
const pngPath = join(dir, `${slug}.png`)
|
||||||
|
|
@ -55,10 +70,10 @@ describe('offline pre-baked entity atlases (Issue #30)', () => {
|
||||||
const meta = JSON.parse(readFileSync(jsonPath, 'utf8'))
|
const meta = JSON.parse(readFileSync(jsonPath, 'utf8'))
|
||||||
expect(meta.token).toBe(spec.token)
|
expect(meta.token).toBe(spec.token)
|
||||||
expect(meta.weapon).toBe(spec.weapon)
|
expect(meta.weapon).toBe(spec.weapon)
|
||||||
expect(meta.directions).toBe(8)
|
expect([1, 4, 8, 16]).toContain(meta.directions)
|
||||||
expect(meta.walkOffset).toBe(0)
|
expect(meta.walkOffset).toBe(0)
|
||||||
expect(meta.standOffset).toBe(8)
|
expect(meta.standOffset).toBe(meta.directions)
|
||||||
expect(meta.groups.length).toBe(16) // 8 walk + 8 stand
|
expect(meta.groups.length).toBe(meta.directions * 2)
|
||||||
|
|
||||||
const png = readFileSync(pngPath)
|
const png = readFileSync(pngPath)
|
||||||
const view = new DataView(png.buffer, png.byteOffset, png.byteLength)
|
const view = new DataView(png.buffer, png.byteOffset, png.byteLength)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue