feat(packer): 提取可交互 NPC 的地图定位并独立存储

将场景数据生成时对 objectType 的判定粒度细化。保留对普通怪物的忽略过滤,对于包含 baseIsMonsters 标识的命名 NPC 记录,将类型标识拆分为 'npc' 并单独放入 scene.json 的 npcs 数组中打包。
This commit is contained in:
troytt 2026-09-14 13:07:25 +00:00
parent e3ca14c357
commit 11e0688930
1 changed files with 19 additions and 8 deletions

View File

@ -344,14 +344,19 @@ for (const row of objectsTable.rows) {
/** Object members grouped by token and mode, built once. */
const objectMembers = new Map<string, Map<string, string[]>>()
const monsterMembers = new Map<string, Map<string, string[]>>()
for (const name of allNames) {
if (!name.toLowerCase().startsWith(OBJECT_PREFIX.toLowerCase())) continue
const rest = name.slice(OBJECT_PREFIX.length).split('\\')
const isObj = name.toLowerCase().startsWith(OBJECT_PREFIX.toLowerCase())
const isMon = name.toLowerCase().startsWith('data\\global\\monsters\\')
if (!isObj && !isMon) continue
const prefixLen = isObj ? OBJECT_PREFIX.length : 22 // 'data\\global\\monsters\\'.length
const rest = name.slice(prefixLen).split('\\')
if (rest.length < 3) continue
const token = (rest[0] ?? '').toUpperCase()
const mode = (rest[1] ?? '').toLowerCase()
if (!objectMembers.has(token)) objectMembers.set(token, new Map())
const modes = objectMembers.get(token)!
const map = isObj ? objectMembers : monsterMembers
if (!map.has(token)) map.set(token, new Map())
const modes = map.get(token)!
if (!modes.has(mode)) modes.set(mode, [])
modes.get(mode)!.push(name)
}
@ -378,8 +383,9 @@ function compRank(path: string): number {
* @param modeToken - animation mode token the engine places the object in (`NU`/`OP`/…).
* @returns the member name and how many candidates there were.
*/
function pickObjectMember(token: string, modeToken: string): { member: string; candidates: number } | null {
const dirs = objectMembers.get(token)
function pickObjectMember(token: string, modeToken: string, baseIsMonsters = false): { member: string; candidates: number } | null {
const map = baseIsMonsters ? monsterMembers : objectMembers
const dirs = map.get(token)
if (dirs === undefined) return null
const all = [...dirs.values()].flat().filter(name => {
const lower = name.toLowerCase()
@ -641,6 +647,7 @@ async function bakeDs1Variant(
const objectPages = new PageBuilder(palette)
const objects: unknown[] = []
const npcs: unknown[] = []
const missingObjects: string[] = []
let objectsWithArt = 0
const placementByMember = new Map<string, { placement: Placement; offsetX: number; offsetY: number }>()
@ -655,7 +662,7 @@ async function bakeDs1Variant(
}
if (resolved.kind === 'monster') continue
const row = resolved.row
const pick = resolved.token === '' ? null : pickObjectMember(resolved.token, resolved.mode)
const pick = resolved.token === '' ? null : pickObjectMember(resolved.token, resolved.mode, resolved.kind === 'npc')
const orthoX = (object.x - object.y) * ORTHO_SUB_TILE_WIDTH + scene.originX
const orthoY = (object.x + object.y) * ORTHO_SUB_TILE_HEIGHT + scene.originY
@ -723,7 +730,8 @@ async function bakeDs1Variant(
}
}
objects.push({
const list = resolved.kind === 'npc' ? npcs : objects
list.push({
id: object.id,
type: object.type,
name: row?.name ?? resolved.token,
@ -793,6 +801,7 @@ async function bakeDs1Variant(
walls: scene.walls.map(draw => [draw.frameIndex, draw.x, draw.y, draw.cellX, draw.cellY]),
roofs: scene.roofs.map(draw => [draw.frameIndex, draw.x, draw.y, draw.cellX, draw.cellY]),
objects,
npcs,
collision: { width: scene.gridWidth, height: scene.gridHeight, runs },
spawn: spawn === null ? null : [Math.round(spawn.x), Math.round(spawn.y)],
stats: {
@ -804,6 +813,7 @@ async function bakeDs1Variant(
missingRefs: scene.missingRefs,
walkable: 1 - [...scene.blocked].reduce((sum, value) => sum + value, 0) / scene.blocked.length,
objects: objects.length,
npcs: npcs.length,
objectsWithArt,
objectsUnresolved: missingObjects,
objectsArtPending: (objects as any[]).filter(o => o.member !== null && o.frame === null).length,
@ -840,6 +850,7 @@ async function bakeDs1Variant(
cells: `${String(scene.cellsX)}x${String(scene.cellsY)}`,
frames: scene.frames.length,
objects: objects.length,
npcs: npcs.length,
pages: pageFiles.length,
objectPages: objectFiles.length,
missingTiles: scene.missingTiles,