ginka-generator/data/src/auto/auto.ts

281 lines
10 KiB
TypeScript

import { readdir, readFile } from 'fs/promises';
import { parseFloorInfo, parseTowerInfo } from './info';
import { IAutoLabelConfig, IConvertedMapInfo, ITowerInfo } from './types';
import { join } from 'path';
import { Presets, SingleBar } from 'cli-progress';
import { convertTowerMap, runTowerCode } from './tower';
import { MapTileConverter } from './converter';
export interface ILabelResult {
/** 塔信息列表 */
readonly tower: ITowerInfo;
/** 转换后的楼层列表 */
readonly maps: IConvertedMapInfo[];
}
function addIssuePrefix(maxLength: number, path: string, content: string) {
return `${path}: ${' '.repeat(maxLength - path.length)}${content}`;
}
/**
* 自动标注塔地图
* @param towerInfo 所有塔的信息路径,文件包括颜色、标签等
* @param pathList 塔文件路径列表
* @param config 自动标记配置
*/
export async function autoLabelTowers(
towerInfo: string,
pathList: string[],
config: IAutoLabelConfig
) {
const labelResult: ILabelResult[] = [];
// 统计被不同规则过滤掉的楼层
let ignoredFloorsSize = 0;
let ignoredMaxEmptyArea = 0;
let ignoredMaxResourceArea = 0;
let ignoredFloorsEnemy = 0;
let ignoredFloorsWall = 0;
let ignoredFloorsResource = 0;
let ignoredFloorsDoor = 0;
let ignoredFloorsEntry = 0;
let ignoredFloorsCustom = 0;
let ignoredFloorsIdle = 0;
let ignoredFloorsIdleDoor = 0;
let ignoredFloorsIdleEnemy = 0;
let ignoredFloorsUseless = 0;
let ignoredFloorsContinuous = 0;
let ignoredFloorsContinuousDoor = 0;
let ignoredFloorsContinuousEnemy = 0;
const towers = await parseTowerInfo(towerInfo);
const paths: string[] = [];
await Promise.all(
pathList.map(async path => {
const dir = await readdir(path);
paths.push(...dir.map(v => join(path, v)));
})
);
const issues: [string, string][] = [];
const progress = new SingleBar({}, Presets.shades_classic);
progress.start(paths.length, 0);
let i = 0;
for (const path of paths) {
progress.update(++i);
let project: string;
let floors: string;
try {
project = await readFile(
join(path, 'project', 'project.min.js'),
'utf-8'
);
floors = await readFile(
join(path, 'project', 'floors.min.js'),
'utf-8'
);
} catch {
issues.push([path, '读取塔信息失败']);
continue;
}
const result = runTowerCode(project, floors);
if (result.issue.length > 0) {
issues.push(...result.issue.map<[string, string]>(v => [path, v]));
continue;
}
const converter = new MapTileConverter(result, config);
const info = towers.get(result.data.firstData.name);
if (!info) continue;
const customPass = config.customTowerFilter?.(info) ?? true;
if (!customPass) continue;
const convertedMaps: IConvertedMapInfo[] = [];
// 处理每个塔的每个楼层
for (const [name, floor] of Object.entries(result.main.floors)) {
const width = floor.map[0].length;
const height = floor.map.length;
// 尺寸不匹配
const sizePass = config.allowedSize.some(
([w, h]) => w === width && h === height
);
if (!sizePass) {
ignoredFloorsSize++;
continue;
}
// 转换楼层
const converted = convertTowerMap(result, floor, config, converter);
const otherLayers = [];
if (floor.bgmap) {
otherLayers.push(floor.bgmap);
}
if (floor.bg2map) {
otherLayers.push(floor.bg2map);
}
if (floor.fgmap) {
otherLayers.push(floor.fgmap);
}
if (floor.fg2map) {
otherLayers.push(floor.fg2map);
}
const floorInfo = parseFloorInfo(
info,
floor.map,
converted.map,
otherLayers,
config,
converter,
name
);
const floorData: IConvertedMapInfo = {
data: converted,
tower: info,
mapId: name,
info: floorInfo
};
// 配置过滤楼层
if (floorInfo.maxEmptyArea > config.maxEmptyArea) {
ignoredMaxEmptyArea++;
continue;
}
if (floorInfo.maxResourceArea > config.maxResourceArea) {
ignoredMaxResourceArea++;
continue;
}
if (
floorInfo.enemyDensity < config.minEnemyRatio ||
floorInfo.enemyDensity > config.maxEnemyRatio
) {
ignoredFloorsEnemy++;
continue;
}
if (
floorInfo.wallDensity < config.minWallRatio ||
floorInfo.wallDensity > config.maxWallRatio
) {
ignoredFloorsWall++;
continue;
}
if (
floorInfo.resourceDensity < config.minResourceRatio ||
floorInfo.resourceDensity > config.maxResourceRatio
) {
ignoredFloorsResource++;
continue;
}
if (
floorInfo.doorDensity < config.minDoorRatio ||
floorInfo.doorDensity > config.maxDoorRatio
) {
ignoredFloorsDoor++;
continue;
}
if (
floorInfo.entryCount < config.minEntryCount ||
floorInfo.entryCount > config.maxEntryCount
) {
ignoredFloorsEntry++;
continue;
}
const filteredByLargeDoorCluster =
!config.allowLargeDoorCluster && floorInfo.hasLargeDoorCluster;
const filteredByLargeEnemyCluster =
!config.allowLargeEnemyCluster &&
floorInfo.hasLargeEnemyCluster;
if (filteredByLargeDoorCluster) {
ignoredFloorsContinuousDoor++;
}
if (filteredByLargeEnemyCluster) {
ignoredFloorsContinuousEnemy++;
}
if (filteredByLargeDoorCluster || filteredByLargeEnemyCluster) {
ignoredFloorsContinuous++;
continue;
}
const filteredByIdleDoorBranch =
!config.allowIdleBranch && floorInfo.idleDoorBranchCount > 0;
const filteredByRepeatedGuardDoorBranch =
!config.allowRepeatedGuardIdleBranch &&
floorInfo.repeatedGuardDoorBranchCount > 0;
const filteredByIdleEnemyBranch =
!config.allowIdleBranch && floorInfo.idleEnemyBranchCount > 0;
const filteredByRepeatedGuardEnemyBranch =
!config.allowRepeatedGuardIdleBranch &&
floorInfo.repeatedGuardEnemyBranchCount > 0;
if (filteredByIdleDoorBranch || filteredByRepeatedGuardDoorBranch) {
ignoredFloorsIdleDoor++;
}
if (
filteredByIdleEnemyBranch ||
filteredByRepeatedGuardEnemyBranch
) {
ignoredFloorsIdleEnemy++;
}
if (
(!config.allowIdleBranch && floorInfo.hasIdleBranch) ||
(!config.allowRepeatedGuardIdleBranch &&
floorInfo.hasRepeatedGuardIdleBranch)
) {
ignoredFloorsIdle++;
continue;
}
if (!config.allowUselessBranch && floorInfo.hasUselessBranch) {
ignoredFloorsUseless++;
continue;
}
// 自定义过滤楼层
const customPass = config.customFloorFilter?.(floorData) ?? true;
if (!customPass) {
ignoredFloorsCustom++;
continue;
}
// 楼层过滤通过
convertedMaps.push(floorData);
}
labelResult.push({ tower: info, maps: convertedMaps });
}
progress.stop();
if (!config.ignoreIssues) {
const maxLength = Math.max(...issues.map(v => v[0].length));
issues.forEach(v => {
console.log(addIssuePrefix(maxLength, v[0], v[1]));
});
}
const totalFilted =
ignoredFloorsSize +
ignoredFloorsEnemy +
ignoredFloorsWall +
ignoredFloorsResource +
ignoredFloorsDoor +
ignoredFloorsEntry +
ignoredFloorsContinuous +
ignoredFloorsIdle +
ignoredFloorsUseless +
ignoredFloorsCustom;
console.log(
`已处理 ${labelResult.length} 个塔,共 ${labelResult.reduce(
(prev, curr) => prev + curr.maps.length,
0
)} 层,过滤掉 ${totalFilted} 层:`
);
console.log(`尺寸过滤:${ignoredFloorsSize}`);
console.log(`空地过滤:${ignoredMaxEmptyArea}`);
console.log(`资源区域过滤:${ignoredMaxResourceArea}`);
console.log(`怪物过滤:${ignoredFloorsEnemy}`);
console.log(`墙壁过滤:${ignoredFloorsWall}`);
console.log(`资源过滤:${ignoredFloorsResource}`);
console.log(`门过滤:${ignoredFloorsDoor}`);
console.log(`入口过滤:${ignoredFloorsEntry}`);
console.log(`连续门过滤:${ignoredFloorsContinuousDoor}`);
console.log(`连续怪过滤:${ignoredFloorsContinuousEnemy}`);
console.log(`闲置门过滤:${ignoredFloorsIdleDoor}`);
console.log(`闲置怪过滤:${ignoredFloorsIdleEnemy}`);
console.log(`闲置节点过滤:${ignoredFloorsIdle}`);
console.log(`无用节点过滤:${ignoredFloorsUseless}`);
console.log(`自定义过滤:${ignoredFloorsCustom}`);
return labelResult;
}