You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
120 lines
4.0 KiB
120 lines
4.0 KiB
import type { WaveDef, WavePortPlan, WaveSpawnSegment } from 'db://assets/mx/module/battle';
|
|
import type { ProjectLevelMonsterConfig } from './LevelMonsterConfig';
|
|
import {
|
|
calcChapterMonsterHp,
|
|
makeMonsterUnitDefId,
|
|
} from './MonsterHpCalculator';
|
|
|
|
export interface ParsedWaveSegment {
|
|
monsterId: number;
|
|
count: number;
|
|
difficultyRow: number;
|
|
}
|
|
|
|
export interface ParsedPortWaves {
|
|
port: number;
|
|
waves: ParsedWaveSegment[][];
|
|
}
|
|
|
|
export interface MergedWave {
|
|
ports: Array<{ port: number; segments: ParsedWaveSegment[] }>;
|
|
}
|
|
|
|
/**
|
|
* 解析 Chapter 出怪串
|
|
* - `|` 分隔波次
|
|
* - 波内 `;` 分隔顺序段(前段全部出完再出下段)
|
|
* - 每段 `id,count,difficultyRow`(逗号分隔,第三列可选,默认 1)
|
|
*/
|
|
export class MonsterWaveParser {
|
|
static parsePort(portStr: string | undefined | null): ParsedWaveSegment[][] {
|
|
if (!portStr || portStr.trim() === '') return [];
|
|
const waveStrs = portStr.split('|');
|
|
return waveStrs.map(w => this.parseWaveSegments(w));
|
|
}
|
|
|
|
static parseWaveSegments(waveStr: string): ParsedWaveSegment[] {
|
|
if (!waveStr || waveStr.trim() === '') return [];
|
|
const segments = waveStr.split(';');
|
|
const out: ParsedWaveSegment[] = [];
|
|
for (const seg of segments) {
|
|
const parsed = this.parseSegment(seg);
|
|
if (parsed) out.push(parsed);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
static parseSegment(seg: string): ParsedWaveSegment | null {
|
|
const parts = seg.split(',').map(s => s.trim()).filter(Boolean);
|
|
if (parts.length < 2) return null;
|
|
const monsterId = parseInt(parts[0], 10);
|
|
const count = parseInt(parts[1], 10);
|
|
const difficultyRow = parts.length >= 3 ? parseInt(parts[2], 10) : 1;
|
|
if (isNaN(monsterId) || isNaN(count) || count <= 0) return null;
|
|
return {
|
|
monsterId,
|
|
count,
|
|
difficultyRow: isNaN(difficultyRow) ? 1 : difficultyRow,
|
|
};
|
|
}
|
|
|
|
/** 三口按波次索引对齐合并,每波内三口并行、口内顺序 */
|
|
static mergePorts(
|
|
port1: ParsedWaveSegment[][],
|
|
port2: ParsedWaveSegment[][],
|
|
port3: ParsedWaveSegment[][],
|
|
): MergedWave[] {
|
|
const max = Math.max(port1.length, port2.length, port3.length);
|
|
const merged: MergedWave[] = [];
|
|
|
|
for (let i = 0; i < max; i++) {
|
|
const ports: MergedWave['ports'] = [];
|
|
const addPort = (port: number, segments: ParsedWaveSegment[] | undefined) => {
|
|
if (segments?.length) ports.push({ port, segments });
|
|
};
|
|
addPort(1, port1[i]);
|
|
addPort(2, port2[i]);
|
|
addPort(3, port3[i]);
|
|
if (ports.length > 0) merged.push({ ports });
|
|
}
|
|
return merged;
|
|
}
|
|
|
|
static toWaveDefs(
|
|
merged: MergedWave[],
|
|
monsterConfig: ProjectLevelMonsterConfig,
|
|
spawnInterval: number,
|
|
firstWaveDelaySec: number,
|
|
subsequentIntervalSec: number,
|
|
waveCoin?: number[],
|
|
): WaveDef[] {
|
|
return merged.map((wave, idx) => {
|
|
const ports: WavePortPlan[] = wave.ports.map(({ port, segments }) => ({
|
|
port,
|
|
segments: segments.map(seg => this.toSpawnSegment(seg, port, monsterConfig, spawnInterval)),
|
|
}));
|
|
|
|
return {
|
|
id: `wave_${idx + 1}`,
|
|
prepTime: idx === 0 ? firstWaveDelaySec : subsequentIntervalSec,
|
|
rewardGold: waveCoin?.[idx] ?? 0,
|
|
ports,
|
|
};
|
|
});
|
|
}
|
|
|
|
private static toSpawnSegment(
|
|
seg: ParsedWaveSegment,
|
|
port: number,
|
|
monsterConfig: ProjectLevelMonsterConfig,
|
|
spawnInterval: number,
|
|
): WaveSpawnSegment {
|
|
return {
|
|
unitDefId: makeMonsterUnitDefId(seg.monsterId, port, seg.difficultyRow),
|
|
count: seg.count,
|
|
interval: spawnInterval,
|
|
difficultyRow: seg.difficultyRow,
|
|
maxHp: calcChapterMonsterHp(seg.monsterId, port, seg.difficultyRow, monsterConfig),
|
|
};
|
|
}
|
|
}
|
|
|