import { TableNames } from '../../../game/ConfigTableData'; import { MapGrid } from './MapGrid'; /** * 拖英雄推进区域解锁的结果。 * skip:不是解锁格,走原落子。 * blocked:还没轮到、条件没随出来,或英雄类型/等级不符。 * unlocked:英雄符合当前条件,整区可放置文字。 */ export type AreaUnlockDrag = 'skip' | 'blocked' | 'unlocked'; /** 拖上格子的英雄:类型与表等级 */ export interface AreaUnlockHero { /** Hero.herotype */ heroType: number; /** Hero.level */ level: number; } /** * 地图解锁区域。 * 区域 ID 从小到大依次解锁。当前区域从关卡 unlockland1-6 按权重随机一条条件, * 等级加上段位 levelvalue。把符合类型和等级的英雄拖上去后,整区可放置文字。 */ export class MapAreaUnlock { private _ids: number[] = []; private _done: boolean[] = []; private _rolled: boolean[] = []; private _heroTypes: number[] = []; private _needLevels: number[] = []; private _grids: MapGrid[] = []; private _levelId = 0; private _levelValue = 0; private _levelReady = false; private _warnedSprite = false; /** * 本局关卡与段位等级加值。加值来自 Rank.levelvalue。 * @param levelId 关卡表 id * @param levelValue 段位等级加值 */ setLevel(levelId: number, levelValue: number): void { this._levelId = levelId; this._levelValue = levelValue; this._levelReady = true; this._rollCurrent(); } /** * 按当前格子重建区域,并刷新等待解锁的显示。已解锁和已随机的条件按区域 ID 保留。 * @param grids 地图格子 */ sync(grids: MapGrid[]): void { const prevIds = this._ids; const prevDone = this._done; const prevRolled = this._rolled; const prevTypes = this._heroTypes; const prevLevels = this._needLevels; this._grids = grids; this._ids = []; this._done = []; this._rolled = []; this._heroTypes = []; this._needLevels = []; this._warnedSprite = false; for (let i = 0; i < grids.length; i++) { const grid = grids[i]; if (!grid?.isCanBeDig || grid.unlockAreaId <= 0) continue; this._touchArea(grid.unlockAreaId, prevIds, prevDone, prevRolled, prevTypes, prevLevels); } this._sortAreas(); this._rollCurrent(); this.applyVisual(); } /** 当前正在解锁的区域 ID;没有则 0 */ currentId(): number { for (let i = 0; i < this._ids.length; i++) { if (!this._done[i]) return this._ids[i]; } return 0; } /** * 把英雄拖到当前解锁区域。类型 0 表示任意 herotype,等级须达到配置等级加段位加值。 * @param grid 落点格 * @param hero 拖动的英雄;没有对应英雄时传 null */ tryDrag(grid: MapGrid, hero: AreaUnlockHero | null): AreaUnlockDrag { if (!grid?.isAreaUnlock) return 'skip'; const current = this.currentId(); if (current <= 0 || grid.unlockAreaId !== current) return 'blocked'; const index = this._indexOf(current); if (index < 0 || !this._rolled[index]) return 'blocked'; if (!hero) return 'blocked'; const typeOk = this._heroTypes[index] === 0 || hero.heroType === this._heroTypes[index]; const levelOk = hero.level >= this._needLevels[index]; if (!typeOk || !levelOk) return 'blocked'; this._done[index] = true; this._rollCurrent(); return 'unlocked'; } /** * 某区域下挂着该 ID 的格子。 * @param areaId 解锁区域 ID */ gridsOf(areaId: number): MapGrid[] { const out: MapGrid[] = []; for (let i = 0; i < this._grids.length; i++) { const grid = this._grids[i]; if (grid?.unlockAreaId === areaId) out.push(grid); } return out; } /** * 刷新解锁区域皮:当前区域 Wait Unlocked,未轮到 Put,已解锁 Dig(由 MapGrid 按状态选图)。 */ applyVisual(): void { const current = this.currentId(); for (let i = 0; i < this._grids.length; i++) { const grid = this._grids[i]; if (!grid) continue; const wait = grid.isAreaUnlock && grid.unlockAreaId === current; if (wait && !grid.waitUnlockedSprite && !this._warnedSprite) { console.warn('[MapAreaUnlock] 当前解锁区域缺少 Wait Unlocked', current, grid.Index); this._warnedSprite = true; } grid.waitingUnlock = wait; if (!wait && grid.isAreaUnlock) grid.updateGrid(); } } /** 给当前区域随机一条 unlockland 条件 */ private _rollCurrent(): void { if (!this._levelReady) return; const id = this.currentId(); if (id <= 0) return; const index = this._indexOf(id); if (index < 0 || this._rolled[index]) return; const level = gg.data.table.getTableData(TableNames.Level, this._levelId); if (!level) { console.warn('[MapAreaUnlock] 找不到关卡', this._levelId); return; } const raw = this._landRaw(level, id); if (raw == null) return; const picked = this._pickLand(raw, id); if (!picked) return; this._heroTypes[index] = picked.heroType; this._needLevels[index] = picked.level + this._levelValue; this._rolled[index] = true; } /** * 按区域 ID 取 unlockland1-6。超出 1-6 时警告。 * @param level 关卡行 * @param areaId 解锁区域 ID */ private _landRaw(level: ITableLevel, areaId: number): string | null { switch (areaId) { case 1: return level.unlockland1; case 2: return level.unlockland2; case 3: return level.unlockland3; case 4: return level.unlockland4; case 5: return level.unlockland5; case 6: return level.unlockland6; default: console.warn('[MapAreaUnlock] 解锁区域ID只支持 1-6', areaId); return null; } } /** * 按权重随机一条。格式:属性类型,等级,权重|属性类型,等级,权重。 * @param raw 配表原文 * @param areaId 解锁区域 ID */ private _pickLand(raw: string, areaId: number): { heroType: number; level: number } | null { const text = String(raw ?? '').trim(); if (!text) { console.warn('[MapAreaUnlock] 关卡未配置解锁条件', this._levelId, areaId); return null; } const types: number[] = []; const levels: number[] = []; const weights: number[] = []; const parts = text.split('|'); for (let i = 0; i < parts.length; i++) { const seg = parts[i].trim().split(','); if (seg.length !== 3) { console.warn('[MapAreaUnlock] 解锁条件格式应为 类型,等级,权重', areaId, parts[i]); continue; } const heroType = Number(seg[0]); const level = Number(seg[1]); const weight = Number(seg[2]); if (!Number.isFinite(heroType) || !Number.isFinite(level) || !Number.isFinite(weight)) { console.warn('[MapAreaUnlock] 解锁条件含非数字', areaId, parts[i]); continue; } if (weight <= 0) continue; types.push(heroType); levels.push(level); weights.push(weight); } if (types.length === 0) { console.warn('[MapAreaUnlock] 解锁条件没有有效权重', this._levelId, areaId, text); return null; } let total = 0; for (let i = 0; i < weights.length; i++) total += weights[i]; let roll = Math.random() * total; let pick = types.length - 1; for (let i = 0; i < weights.length; i++) { roll -= weights[i]; if (roll < 0) { pick = i; break; } } return { heroType: types[pick], level: levels[pick] }; } /** * @param id 区域 ID * @param prevIds 同步前的区域 ID * @param prevDone 同步前是否已解锁 * @param prevRolled 同步前是否已随机条件 * @param prevTypes 同步前的英雄类型 * @param prevLevels 同步前的需求等级 */ private _touchArea( id: number, prevIds: number[], prevDone: boolean[], prevRolled: boolean[], prevTypes: number[], prevLevels: number[], ): void { if (this._indexOf(id) >= 0) return; const old = this._oldIndex(id, prevIds); this._ids.push(id); this._done.push(old >= 0 ? prevDone[old] : false); this._rolled.push(old >= 0 ? prevRolled[old] : false); this._heroTypes.push(old >= 0 ? prevTypes[old] : 0); this._needLevels.push(old >= 0 ? prevLevels[old] : 0); } /** 区域 ID 从小到大 */ private _sortAreas(): void { for (let i = 1; i < this._ids.length; i++) { const id = this._ids[i]; const done = this._done[i]; const rolled = this._rolled[i]; const heroType = this._heroTypes[i]; const needLevel = this._needLevels[i]; let j = i - 1; while (j >= 0 && this._ids[j] > id) { this._ids[j + 1] = this._ids[j]; this._done[j + 1] = this._done[j]; this._rolled[j + 1] = this._rolled[j]; this._heroTypes[j + 1] = this._heroTypes[j]; this._needLevels[j + 1] = this._needLevels[j]; j--; } this._ids[j + 1] = id; this._done[j + 1] = done; this._rolled[j + 1] = rolled; this._heroTypes[j + 1] = heroType; this._needLevels[j + 1] = needLevel; } } /** * @param id 区域 ID */ private _indexOf(id: number): number { for (let i = 0; i < this._ids.length; i++) { if (this._ids[i] === id) return i; } return -1; } /** * @param id 区域 ID * @param ids 旧列表 */ private _oldIndex(id: number, ids: number[]): number { for (let i = 0; i < ids.length; i++) { if (ids[i] === id) return i; } return -1; } }