'use strict'; const TOOLS = ['paint', 'box', 'picker', 'eraser', 'fill']; const TOOL_LABELS = { paint: '画笔:在网格上点击或拖拽绘制(需先在右侧选瓦片)', box: '框选:拖拽矩形区域,松开后用当前瓦片填满', picker: '吸管:点击已有格子,吸取瓦片为当前画笔', eraser: '橡皮擦:点击或拖拽删除当前层的格子', fill: '油漆桶:填充相连的同类型格子(需先选瓦片)', }; const TOOL_CURSORS = { paint: 'crosshair', box: 'crosshair', picker: 'copy', eraser: 'not-allowed', fill: 'cell', }; function layerMap(state) { return state.editLayer === 'ground' ? state.config.ground : state.config.border; } function getCell(state, key) { const map = layerMap(state); return map[key]; } function hasCell(state, key) { return getCell(state, key) !== undefined; } function removeCell(state, key) { if (state.editLayer === 'ground') delete state.config.ground[key]; else delete state.config.border[key]; } function setCell(state, key, tileKey) { if (state.editLayer === 'ground') { state.config.ground[key] = tileKey === 'JumpBlock' ? 'JumpBlock' : 'Baseblock'; } else { const s = typeof tileKey === 'string' ? tileKey.trim() : ''; state.config.border[key] = (!s || s === 'Baseblock' || s === 'JumpBlock') ? 'WallBlock' : s; } } function canPaintBrush(state) { return state.brush && state.brush.layer === state.editLayer; } function fillRectangle(state, x0, y0, x1, y1, tileKey) { const minX = Math.min(x0, x1); const maxX = Math.max(x0, x1); const minY = Math.min(y0, y1); const maxY = Math.max(y0, y1); let n = 0; for (let x = minX; x <= maxX; x++) { for (let y = minY; y <= maxY; y++) { setCell(state, `${x},${y}`, tileKey); n++; } } return n; } function eraseRectangle(state, x0, y0, x1, y1) { const minX = Math.min(x0, x1); const maxX = Math.max(x0, x1); const minY = Math.min(y0, y1); const maxY = Math.max(y0, y1); let n = 0; for (let x = minX; x <= maxX; x++) { for (let y = minY; y <= maxY; y++) { const key = `${x},${y}`; if (hasCell(state, key)) { removeCell(state, key); n++; } } } return n; } /** 四连通洪水填充(仅当前编辑层) */ function floodFill(state, startKey, fillTileKey) { const map = layerMap(state); const target = map[startKey]; if (target === fillTileKey) return 0; const q = [startKey]; const seen = new Set([startKey]); let n = 0; while (q.length) { const key = q.shift(); map[key] = fillTileKey; n++; const [xs, ys] = key.split(','); const x = parseInt(xs, 10); const y = parseInt(ys, 10); for (const [nx, ny] of [[x + 1, y], [x - 1, y], [x, y + 1], [x, y - 1]]) { const nk = `${nx},${ny}`; if (seen.has(nk)) continue; const v = map[nk]; const same = target === undefined ? v === undefined : v === target; if (!same) continue; seen.add(nk); q.push(nk); } } return n; } /** 洪水清除(橡皮擦油漆桶:删相连同值区域) */ function floodErase(state, startKey) { const map = layerMap(state); if (map[startKey] === undefined) return 0; const target = map[startKey]; const q = [startKey]; const seen = new Set([startKey]); let n = 0; while (q.length) { const key = q.shift(); delete map[key]; n++; const [xs, ys] = key.split(','); const x = parseInt(xs, 10); const y = parseInt(ys, 10); for (const [nx, ny] of [[x + 1, y], [x - 1, y], [x, y + 1], [x, y - 1]]) { const nk = `${nx},${ny}`; if (seen.has(nk) || map[nk] !== target) continue; seen.add(nk); q.push(nk); } } return n; } function findPaletteTile(state, tileKey, layer) { return state.tiles.find((t) => t.tileKey === tileKey && t.layer === layer) || null; } /** Ground 瓦片中文名 */ function groundTileLabel(tileKey) { if (tileKey === 'JumpBlock') return '跳跃砖(JumpBlock)'; return '可移动砖块(Baseblock)'; } /** Border 瓦片中文名 */ function borderTileLabel(tileKey) { const s = tileKey === true || tileKey === 1 ? 'WallBlock' : String(tileKey || 'WallBlock').trim(); if (!s || s === 'WallBlock') return '墙砖(WallBlock)'; return `墙饰(${s})`; } /** * 汇总格子上全部内容(两层瓦片 + 实体),用于状态栏悬停提示。 * @param {object} state * @param {number} x * @param {number} y * @param {{ kind: string, propPlacement?: string }[]} [spawnHits] * @param {Record} [spawnKindLabels] */ function describeCellContents(state, x, y, spawnHits, spawnKindLabels) { const key = `${x},${y}`; const parts = []; const ground = state.config?.ground?.[key]; if (ground !== undefined) parts.push(groundTileLabel(ground)); const border = state.config?.border?.[key]; if (border !== undefined) parts.push(borderTileLabel(border)); if (Array.isArray(spawnHits)) { for (const h of spawnHits) { let label = (spawnKindLabels && spawnKindLabels[h.kind]) || h.kind; if (h.kind === 'player') label = '人物'; if (h.kind === 'prop' && h.propPlacement === 'ground') label = `${label}(空地)`; parts.push(label); } } return parts.length ? parts.join(' + ') : '空'; } module.exports = { TOOLS, TOOL_LABELS, TOOL_CURSORS, layerMap, getCell, hasCell, removeCell, setCell, canPaintBrush, fillRectangle, eraseRectangle, floodFill, floodErase, findPaletteTile, groundTileLabel, borderTileLabel, describeCellContents, };