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.
146 lines
5.3 KiB
146 lines
5.3 KiB
|
3 weeks ago
|
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;
|
||
|
|
}
|
||
|
|
}
|