import { GEvent } from 'db://assets/mx/module/event/GEvent'; import { createMembershipSet, membershipAdd, membershipFromArray, membershipDedupeArray, type MembershipSet, type ReadonlyMembershipSet } from '../../../../mx/tools/MembershipSet'; import MTools from 'db://assets/mx/tools/MTools'; import { ChapterNewPlayTypeEnum, IParatroopsProperty } from '../../../game/ChapterPlayTypes'; import { BossWarnType } from '../../../ui/BattleGame/BossWarnSpine'; import { cloneParatroopsConfig } from '../MechanismConfigClone'; import type { IBattleMechanism, MechanismContext } from '../IBattleMechanism'; import { calcMiddleRowRange } from '../MechanismSpawnArea'; import { isCombatPhase, spawnParatroopAtGrid } from '../MechanismServices'; import type { MapGrid } from '../../map/legacy/MapGrid'; /** * 飞禽空投(Playmode 14 / 19) * * 配表语义(Playmode.par1): * - `monsterId,numMin,numMax` 可重复多组 * - 每次空投:每组各自随机数量并全部生成(不是只随机一种怪) */ export class ParatroopsMechanism implements IBattleMechanism { readonly typeId: ChapterNewPlayTypeEnum; private _ctx: MechanismContext | null = null; private _config: IParatroopsProperty | null = null; private _eventIndex = 0; private _checkTimer = 0; constructor(typeId: ChapterNewPlayTypeEnum = ChapterNewPlayTypeEnum.Paratroops) { this.typeId = typeId; } init(ctx: MechanismContext, config: unknown): void { this._ctx = ctx; this._config = cloneParatroopsConfig(config as IParatroopsProperty); this._eventIndex = 0; this._checkTimer = 0; const cfg = this._config; } tick(dt: number): void { if (!this._ctx || !this._config || !isCombatPhase(this._ctx)) return; this._checkTimer += dt; if (this._checkTimer < 1) return; this._checkTimer = 0; this._tryLaunch(); } destroy(): void { this._ctx = null; this._config = null; } private _tryLaunch(): void { const ctx = this._ctx; const cfg = this._config; if (!ctx || !cfg) return; while (this._eventIndex < cfg.timeArr.length && ctx.battleTime >= cfg.timeArr[this._eventIndex]) { if (!this._launchOnce(this._eventIndex)) { // 落点暂不可用:保留 eventIndex,下一秒重试 break; } this._eventIndex++; } } private _launchOnce(eventIndex: number): boolean { const ctx = this._ctx; const cfg = this._config; if (!ctx || !cfg) return false; const options = cfg.monsterOptions ?? []; if (options.length === 0) { console.error('[ParatroopsMechanism] monsterOptions 为空,跳过 event=', eventIndex); return true; } const map = ctx.runtime.Map; if (!map) return false; // 每组怪物各自 roll 数量(策划:两个 id + 两个数量区间都要生效) const batches: { monsterId: number; count: number }[] = []; let total = 0; for (const opt of options) { const id = Number(opt.monsterId) || 0; if (id <= 0) continue; const lo = Math.max(0, Number(opt.numMin) || 0); const hi = Math.max(lo, Number(opt.numMax) || lo); const count = MTools.getRandomValue(lo, hi); if (count <= 0) continue; batches.push({ monsterId: id, count }); total += count; } if (total <= 0) { console.log('[ParatroopsMechanism] 本波 roll 数量为 0,跳过 event=', eventIndex); return true; } const rowRange = calcMiddleRowRange(map.mapRows); // 飞禽空投:仍用全图随机可行走落点,不参与「怪物最多区域」规则 let grids = map.getParatroopLandingGrids(total, ctx.session, rowRange); if (grids.length < total) { const used = membershipFromArray(grids.map(g => `${g.Row},${g.Col}`)); const extra = map.getParatroopLandingGrids(total - grids.length, ctx.session); for (const g of extra) { const key = `${g.Row},${g.Col}`; if (used.has(key)) continue; membershipAdd(used, key); grids.push(g); if (grids.length >= total) break; } } if (grids.length === 0) { console.log( '[ParatroopsMechanism] 无落点 event=', eventIndex, 'battleTime=', ctx.battleTime, ); return false; } GEvent.Ins.emit(BossWarnType.Paratroops); let spawned = 0; let gridCursor = 0; const pickGrid = (): MapGrid => { if (gridCursor < grids.length) return grids[gridCursor++]; return grids[Math.floor(Math.random() * grids.length)]; }; for (const batch of batches) { for (let i = 0; i < batch.count; i++) { if (spawnParatroopAtGrid(ctx, pickGrid(), batch.monsterId) != null) { spawned++; } } } if (spawned <= 0) { console.log('[ParatroopsMechanism] 全部生成失败 event=', eventIndex); return false; } return true; } }