diff --git a/assets/script/game/map/MissionMonComp.ts b/assets/script/game/map/MissionMonComp.ts index 879e49a6..361cf0e1 100644 --- a/assets/script/game/map/MissionMonComp.ts +++ b/assets/script/game/map/MissionMonComp.ts @@ -5,8 +5,8 @@ import { Monster } from "../hero/Mon"; import { MonStart } from "../common/config/heroSet"; import { smc } from "../common/SingletonModuleComp"; import { GameEvent } from "../common/config/GameEvent"; -// 导入肉鸽配置 -import { getStageMonConfigs, MonType, generateMonstersFromBudget, getRogueConfig } from "./RogueConfig"; +// 导入新的肉鸽配置 +import { getCurrentWave, MonType, WaveConfig } from "./RogueConfig"; import { BuffConf } from "../common/config/SkillSet"; import { IndexSet, FacSet, BoxSet } from "../common/config/GameSet"; import { HeroAttrsComp } from "../hero/HeroAttrsComp"; @@ -17,7 +17,7 @@ const { ccclass, property } = _decorator; @ccclass('MissionMonCompComp') @ecs.register('MissionMonComp', false) export class MissionMonCompComp extends CCComp { - // 添加刷怪队列 - 使用新的RogueConfig格式 + // 刷怪队列 (主要用于特殊事件插队) private MonQueue: Array<{ uuid: number, position: number, @@ -25,20 +25,20 @@ export class MissionMonCompComp extends CCComp { level: number, buffs: BuffConf[] }> = []; - private isSpawning: boolean = false;// 是否正在生成怪物 - private spawnInterval: number = 0.6; // 每个怪物生成间隔时间(减半速度) - private spawnTimer: number = 0; // 生成计时器 + private spawnCount: number = 0; // 召唤计数器 - // private pauseInterval: number = 5.0; // 暂停间隔时间(5秒) - // private isPausing: boolean = false; // 是否正在暂停 - private eventProcessed: boolean = false; // 事件是否已处理 + /** 全局生成顺序计数器,用于层级管理 */ private globalSpawnOrder: number = 0; + /** 游戏进行时间(秒) */ private gameTime: number = 0; - /** 刷怪逻辑计时器(每秒执行一次) */ - private spawnLogicTimer: number = 0; + /** 波次刷怪计时器 */ + private waveTimer: number = 0; + + /** 队列处理计时器 */ + private queueTimer: number = 0; onLoad(){ this.on(GameEvent.FightReady,this.fight_ready,this) @@ -56,8 +56,8 @@ export class MissionMonCompComp extends CCComp { if (!args) return; console.log(`[MissionMonComp] 收到特殊刷怪指令:`, args); - // 插入队列头部,优先生成 - this.MonQueue.unshift({ + // 插入队列 + this.MonQueue.push({ uuid: args.uuid, position: args.position !== undefined ? args.position : 2, // 默认中间 type: args.type, @@ -65,27 +65,24 @@ export class MissionMonCompComp extends CCComp { buffs: args.buffs || [] }); - // 让刷怪计时器立即满足条件,以便尽快生成 - // 注意:不直接调用 spawnNextMonster 是为了保持 update 循环的一致性 - const config = getRogueConfig(); - this.spawnTimer = config.spawnInterval + 0.1; + // 立即触发一次队列检查 (可选,让 update 尽快处理) + this.queueTimer = 1.0; } - /** 视图层逻辑代码分离演示 */ start() { - // var entity = this.ent as ecs.Entity; // ecs.Entity 可转为当前模块的具体实体对象 - // this.on(ModuleEvent.Cmd, this.onHandler, this); } fight_ready(){ - // console.log("[MissionMonComp]:fight_ready") smc.vmdata.mission_data.mon_num=0 // 重置生成顺序计数器 this.globalSpawnOrder = 0 this.gameTime = 0 - this.spawnLogicTimer = 0 + this.waveTimer = 0 + this.queueTimer = 0 this.MonQueue = [] - this.do_mon_wave() + this.spawnCount = 0 + + console.log("[MissionMonComp] Starting Wave System (15-min Cycle)"); } protected update(dt: number): void { @@ -96,137 +93,75 @@ export class MissionMonCompComp extends CCComp { // 累加游戏时间 this.gameTime += dt; - const config = getRogueConfig(); - - // ========================================== - // 新增:每秒执行一次刷怪逻辑 (Threat Budget) - // ========================================== - this.spawnLogicTimer += dt; - if (this.spawnLogicTimer >= config.spawnLogicInterval) { - this.spawnLogicTimer = 0; - - // 检查最大怪物数量限制 - if (smc.vmdata.mission_data.mon_num < config.maxMonsterCount) { - // 获取英雄血量比例 - const hpRatio = this.getHeroHpRatio(); - - // 生成怪物 - const newMonsters = generateMonstersFromBudget(this.gameTime, hpRatio); - - // 添加到队列 - newMonsters.forEach(mon => { - this.addToStageSpawnQueue( - mon.uuid, - mon.position !== undefined ? mon.position : 0, - mon.type, - mon.level, - mon.buffs || [] - ); - }); + + // 获取当前波次配置 + const currentWave = getCurrentWave(this.gameTime); + + // 1. 优先处理特殊怪队列 + if (this.MonQueue.length > 0) { + this.queueTimer += dt; + // 队列出怪速度快于普通波次 (0.5秒一只) + if (this.queueTimer >= 0.5) { + this.spawnNextFromQueue(); + this.queueTimer = 0; } } - // 处理随机事件 - - - // 处理刷怪队列 - if (this.MonQueue.length > 0 && !this.isSpawning) { - this.spawnTimer += dt; + // 2. 处理波次自然刷怪 + this.waveTimer += dt; + if (this.waveTimer >= currentWave.spawnInterval) { + this.waveTimer = 0; - // 正常召唤间隔 - if (this.spawnTimer >= config.spawnInterval) { - this.spawnNextMonster(); - this.spawnTimer = 0; + // 检查同屏数量限制 + if (smc.vmdata.mission_data.mon_num < currentWave.maxActive) { + this.spawnWaveMonster(currentWave); } } } - do_mon_wave(){ - // 重置召唤相关状态 - this.spawnCount = 0; - // this.isPausing = false; - this.spawnTimer = 0; - this.eventProcessed = false; - - // const cStage = smc.data.mission; - // // 使用新的肉鸽关卡配置 - // let level=smc.vmdata.mission_data.level - - - // const monsConf = getStageMonConfigs(cStage); - // // console.log(`[MissionMonComp]:第${cStage}关 - ${stageType}类型,怪物数量: ${monsConf.length}`); - // const monsConfFiltered = monsConf.filter((mon: any, index) => index === 0); - // this.generateMonsters(monsConfFiltered); - console.log("[MissionMonComp] Starting Threat Budget Wave System"); - } - /** - * 获取英雄血量比例 + * 从当前波次配置生成并生成怪物 */ - private getHeroHpRatio(): number { - // 查询带有 HeroAttrsComp 的实体 - // 注意:这里假设只有一个英雄,且性能允许每秒查询一次 - const entities = ecs.query(ecs.allOf(HeroAttrsComp)); - for (const e of entities) { - const attrs = e.get(HeroAttrsComp); - if (attrs && attrs.fac === FacSet.HERO) { - const maxHp = attrs.Attrs[Attrs.HP_MAX] || 1; - return attrs.hp / maxHp; + private spawnWaveMonster(wave: WaveConfig) { + if (!wave.weights || wave.weights.length === 0) return; + + // 权重随机算法 + const totalWeight = wave.weights.reduce((sum, item) => sum + item.weight, 0); + let random = Math.random() * totalWeight; + let selectedUuid = wave.weights[0].uuid; + let selectedType = wave.weights[0].type || MonType.NORMAL; + + for (const item of wave.weights) { + random -= item.weight; + if (random <= 0) { + selectedUuid = item.uuid; + selectedType = item.type || MonType.NORMAL; + break; } } - return 1.0; // 默认满血 + + // 随机位置 (0-4) + const position = Math.floor(Math.random() * 5); + + // 等级随时间增长 (每分钟+1级) + const level = Math.floor(this.gameTime / 60) + 1; + + this.addMonster( + selectedUuid, + position, + selectedType, + level, + [], + this.gameTime + ); + + this.spawnCount++; } - - // 根据新的关卡配置生成怪物 - private generateMonsters(monsConf: any[]) { - const cStage = smc.data.mission; - - // 设置怪物总数 - // console.log("[MissionMonComp] generateMonsters",monsConf) - if (!monsConf || monsConf.length === 0) { - console.warn(`[MissionMonComp]:关卡${cStage}配置中没有怪物信息`); - return; - } - - // 为每个怪物配置生成怪物 - monsConf.forEach((mon: any, index: number) => { - const { uuid, type, level, buffs, position } = mon; - - // 使用配置中的位置,如果没有则使用索引 - const spawnPosition = position !== undefined ? position : (index % 5); - - this.addToStageSpawnQueue( - uuid, - spawnPosition, - type, - level, - buffs - ); - }); - - // console.log(`[MissionMonComp]:关卡${cStage}将生成 ${monsConf.length} 只怪物`); - } - - // 添加到关卡刷怪队列 - 使用新的配置格式 - private addToStageSpawnQueue( - uuid: number, - position: number, - type: MonType = MonType.NORMAL, - level: number = 1, - buffs: BuffConf[] = [] - ) { - this.MonQueue.push({ - uuid: uuid, - position: position, - type: type, - level: level, - buffs: buffs - }); - } - - // 从队列中生成下一个怪物 - 使用新的配置格式 - private spawnNextMonster() { + /** + * 从队列中生成下一个怪物 + */ + private spawnNextFromQueue() { if (this.MonQueue.length === 0) return; const monsterData = this.MonQueue.shift(); @@ -239,9 +174,7 @@ export class MissionMonCompComp extends CCComp { monsterData.buffs, this.gameTime ); - // 增加召唤计数 this.spawnCount++; - // console.log(`[MissionMonComp]: 召唤第${this.spawnCount}只${monsterData.type}怪物,剩余队列: ${this.MonQueue.length}`); } } @@ -255,46 +188,22 @@ export class MissionMonCompComp extends CCComp { ) { let mon = ecs.getEntity(Monster); let scale = -1; - // 使用 MonStart 计算怪物出生位置: - // x 从 START_X 开始,按 START_I 的间隔递增; - // y 在线路之间交替:0->SLINE_1, 1->SLINE_2, 2->SLINE_3, 3->SLINE_4 - const x = MonStart.START_X + Math.floor(i / 4) * MonStart.START_I; + const x = MonStart.START_X + Math.floor(i / 4) * MonStart.START_I; let y = BoxSet.GAME_LINE; let lane = 0; - // switch (i % 4) { - // case 0: - // y = MonStart.SLINE_1; - // lane = 0; - // break; - // case 1: - // y = MonStart.SLINE_2; - // lane = 1; - // break; - // case 2: - // y = MonStart.SLINE_3; - // lane = 2; - // break; - // case 3: - // y = MonStart.SLINE_4; - // lane = 3; - // break; - // } - let pos: Vec3 = v3(x, y, 0); - // 递增全局生成顺序 - 🔥 添加溢出保护 - this.globalSpawnOrder = (this.globalSpawnOrder + 1) % 999; // 防止无限增长,在999处循环重置 + // 递增全局生成顺序 - 溢出保护 + this.globalSpawnOrder = (this.globalSpawnOrder + 1) % 999; - // 生成怪物,传递线路和生成顺序 + // 生成怪物 mon.load(pos, scale, uuid, lv, monType, buffs, false, lane, this.globalSpawnOrder, gameTime); } - - /** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */ reset() { // this.node.destroy(); } -} \ No newline at end of file +} diff --git a/assets/script/game/map/RogueConfig.ts b/assets/script/game/map/RogueConfig.ts index b9283ec7..14424387 100644 --- a/assets/script/game/map/RogueConfig.ts +++ b/assets/script/game/map/RogueConfig.ts @@ -1,30 +1,18 @@ /** - * 肉鸽模式配置脚本 - 增强版 + * 肉鸽模式配置脚本 - 增强版 (Wave System) * * 功能说明: - * - 提供基础的刷怪配置:刷什么怪,刷多少怪 - * - 支持程序化关卡生成逻辑,每一关的怪物组合、数量和强度应随关卡进度递增而变化 - * - 支持随机事件系统 - * - * - * 3. 全局动态 Scaling 算法既然是 15 分钟单局,属性不应一成不变。 - * 建议引入 Wave_Factor (波次因子): - * 公式建议: Current_Stat = Base_Stat * (1 + (time / 60) * 0.15) ^ Growth_Type - * •HP 成长: 设为指数级 (Growth_Type = 1.2)。后期怪物血量会呈几何倍数增加,适配『神装英雄』。 - * •AP 成长: 设为线性 (Growth_Type = 1.0)。保证怪物能击穿后期护盾,但不会一击必杀。 - * •Speed 成长: 设为对数级 (Growth_Type = 0.5)。防止后期怪物速度过快导致画面瞬移。 + * - 采用 15 个小波次(每分钟 1 波) + * - 整合进 3 个大的节奏阶段:构筑期、磨合期、极限期 + * - 废弃动态预算,使用确定性波次配置 * * @author 游戏开发团队 - * @version 2.0 增强版 + * @version 3.0 波次重构版 * @date 2025-10-19 */ import { HeroInfo } from "../common/config/heroSet"; - - - - /** * 怪物类型枚举 */ @@ -35,7 +23,7 @@ export enum MonType { } /** - * 怪物配置接口 + * 怪物配置接口 (用于生成实例) */ export interface IMonsConfig { uuid: number; // 怪物ID @@ -54,6 +42,8 @@ export interface MonAttrs { ap: number; def: number; speed: number; + exp?: number; + gold?: number; } /** @@ -65,176 +55,218 @@ enum GrowthType { LOGARITHMIC = 0.3 // 对数级 - Speed } - /** - * 全局刷怪配置接口 + * 刷怪权重接口 */ -export interface IRogueGlobalConfig { - /** 场上最大怪物数量限制 */ - maxMonsterCount: number; - /** 刷怪逻辑执行间隔(秒) - 决定多久计算一次生成 */ - spawnLogicInterval: number; - /** 单个怪物生成间隔(秒) - 队列中怪物的实际生成频率 */ - spawnInterval: number; - /** 基础威胁预算 - 每秒产生的基础点数 */ - baseBudget: number; - /** 时间难度因子 - 每分钟增加的预算比例 (0.2 = 20%) */ - timeDifficultyFactor: number; - /** 绝地求生阈值 - 英雄血量低于此比例时触发减缓刷怪 */ - survivalHpThreshold: number; - /** 绝地求生预算乘数 - 触发阈值时的预算折扣 */ - survivalBudgetMultiplier: number; - /** 单次逻辑最大生成怪物数限制 - 防止瞬间生成过多 */ - maxSpawnPerLogic: number; +export interface SpawnWeight { + uuid: number; + weight: number; + type?: MonType; // 默认为 NORMAL } /** - * 默认配置 + * 波次配置接口 */ -export const DefaultRogueConfig: IRogueGlobalConfig = { - maxMonsterCount: 5, // 默认同屏5只 - 降低数量,提高单体质量 - spawnLogicInterval: 1.0, // 每秒计算一次 - spawnInterval: 2.0, // 队列出怪间隔 - baseBudget: 1.0, // 基础预算 - timeDifficultyFactor: 0.5, // 每分钟增加50%预算 - survivalHpThreshold: 0.4, // 40%血量触发保护 - survivalBudgetMultiplier: 0.7, // 保护时预算打7折 - maxSpawnPerLogic: 2 // 单次最多生成2只 +export interface WaveConfig { + waveId: number; // 波次ID (1-15) + name: string; // 波次名称 + duration: number; // 持续时间 (秒),通常为60 + spawnInterval: number; // 刷怪间隔 (秒) + maxActive: number; // 同屏最大怪物数 + weights: SpawnWeight[]; // 怪物权重池 +} + +// 怪物ID映射 (方便阅读) +const MON_IDS = { + WARRIOR: 5201, // 战士 + ASSASSIN: 5301, // 斥候 + TANK: 5401, // 卫士 + ARCHER: 5501, // 射手 + BOMBER: 5601, // 自爆兵 + SUMMONER: 5602, // 召唤师 + HEALER: 5603, // 祭司 + TOTEM: 5604, // 图腾师 + BOSS: 5701 // 首领 }; -// 精英怪和Boss刷新时间配置 (时间单位: 秒) -export const SpecialMonsterSchedule = [ - { time: 60, uuid: 5601, type: MonType.ELITE, level: 5, desc: "1分钟: 精英自爆兵" }, - { time: 180, uuid: 5601, type: MonType.ELITE, level: 10, desc: "3分钟: 精英自爆兵" }, - { time: 300, uuid: 5701, type: MonType.BOSS, level: 15, desc: "5分钟: 兽人首领" }, - { time: 600, uuid: 5701, type: MonType.BOSS, level: 25, desc: "10分钟: 兽人首领" }, - { time: 900, uuid: 5701, type: MonType.BOSS, level: 30, desc: "15分钟: 最终Boss" } +/** + * 全局波次配置表 (15波) + */ +export const RogueWaves: WaveConfig[] = [ + // --- 第一阶段:构筑期 (0-5min) --- + { + waveId: 1, name: "热身", duration: 60, spawnInterval: 2.0, maxActive: 5, + weights: [{ uuid: MON_IDS.WARRIOR, weight: 100 }] + }, + { + waveId: 2, name: "加速", duration: 60, spawnInterval: 1.8, maxActive: 6, + weights: [ + { uuid: MON_IDS.WARRIOR, weight: 80 }, + { uuid: MON_IDS.ASSASSIN, weight: 20 } + ] + }, + { + waveId: 3, name: "堆叠", duration: 60, spawnInterval: 1.6, maxActive: 7, + weights: [ + { uuid: MON_IDS.WARRIOR, weight: 60 }, + { uuid: MON_IDS.ASSASSIN, weight: 40 } + ] + }, + { + waveId: 4, name: "硬度测试", duration: 60, spawnInterval: 1.5, maxActive: 8, + weights: [ + { uuid: MON_IDS.WARRIOR, weight: 50 }, + { uuid: MON_IDS.ASSASSIN, weight: 30 }, + { uuid: MON_IDS.TANK, weight: 20 } + ] + }, + { + waveId: 5, name: "精英首秀", duration: 60, spawnInterval: 1.5, maxActive: 8, + weights: [ + { uuid: MON_IDS.WARRIOR, weight: 40 }, + { uuid: MON_IDS.ASSASSIN, weight: 40 }, + { uuid: MON_IDS.TANK, weight: 20 } + // 注意:第5分钟会触发固定事件刷精英怪,这里只配普通怪 + ] + }, + + // --- 第二阶段:磨合期 (5-10min) --- + { + waveId: 6, name: "远程威胁", duration: 60, spawnInterval: 1.4, maxActive: 10, + weights: [ + { uuid: MON_IDS.TANK, weight: 30 }, + { uuid: MON_IDS.ARCHER, weight: 40 }, + { uuid: MON_IDS.WARRIOR, weight: 30 } + ] + }, + { + waveId: 7, name: "铁桶阵", duration: 60, spawnInterval: 1.3, maxActive: 10, + weights: [ + { uuid: MON_IDS.TANK, weight: 50 }, + { uuid: MON_IDS.ARCHER, weight: 50 } + ] + }, + { + waveId: 8, name: "续航干扰", duration: 60, spawnInterval: 1.2, maxActive: 12, + weights: [ + { uuid: MON_IDS.WARRIOR, weight: 30 }, + { uuid: MON_IDS.TANK, weight: 20 }, + { uuid: MON_IDS.ARCHER, weight: 30 }, + { uuid: MON_IDS.HEALER, weight: 20 } + ] + }, + { + waveId: 9, name: "走位测试", duration: 60, spawnInterval: 1.2, maxActive: 12, + weights: [ + { uuid: MON_IDS.WARRIOR, weight: 40 }, + { uuid: MON_IDS.BOMBER, weight: 30 }, // 自爆兵 + { uuid: MON_IDS.ARCHER, weight: 30 } + ] + }, + { + waveId: 10, name: "中场Boss", duration: 60, spawnInterval: 5.0, maxActive: 3, + // Boss战期间,只刷少量护卫,Boss由事件触发 + weights: [ + { uuid: MON_IDS.TANK, weight: 100 } + ] + }, + + // --- 第三阶段:极限期 (10-15min) --- + { + waveId: 11, name: "混乱开端", duration: 60, spawnInterval: 1.0, maxActive: 15, + weights: [ + { uuid: MON_IDS.SUMMONER, weight: 20 }, + { uuid: MON_IDS.TOTEM, weight: 20 }, + { uuid: MON_IDS.WARRIOR, weight: 30 }, + { uuid: MON_IDS.ARCHER, weight: 30 } + ] + }, + { + waveId: 12, name: "全家桶", duration: 60, spawnInterval: 0.9, maxActive: 18, + weights: [ + { uuid: MON_IDS.WARRIOR, weight: 15 }, + { uuid: MON_IDS.ASSASSIN, weight: 15 }, + { uuid: MON_IDS.TANK, weight: 15 }, + { uuid: MON_IDS.ARCHER, weight: 15 }, + { uuid: MON_IDS.BOMBER, weight: 15 }, + { uuid: MON_IDS.HEALER, weight: 10 }, + { uuid: MON_IDS.SUMMONER, weight: 15 } + ] + }, + { + waveId: 13, name: "精英小队", duration: 60, spawnInterval: 1.0, maxActive: 15, + weights: [ + { uuid: MON_IDS.TANK, weight: 40 }, + { uuid: MON_IDS.ARCHER, weight: 40 }, + { uuid: MON_IDS.WARRIOR, weight: 20, type: MonType.ELITE } // 尝试混入精英 + ] + }, + { + waveId: 14, name: "绝地求生", duration: 60, spawnInterval: 0.6, maxActive: 20, + weights: [ + { uuid: MON_IDS.ASSASSIN, weight: 50 }, + { uuid: MON_IDS.BOMBER, weight: 50 } + ] + }, + { + waveId: 15, name: "终局", duration: 60, spawnInterval: 3.0, maxActive: 5, + // 最终Boss战,只刷少量精英护卫 + weights: [ + { uuid: MON_IDS.TANK, weight: 100, type: MonType.ELITE } + ] + } ]; -// 当前配置实例 -let currentConfig: IRogueGlobalConfig = { ...DefaultRogueConfig }; +// 精英怪和Boss刷新时间配置 (时间单位: 秒) +// 注意:这里的时间点应与波次结束/开始对应 +export const SpecialMonsterSchedule = [ + { time: 4 * 60 + 50, uuid: MON_IDS.WARRIOR, type: MonType.ELITE, level: 5, desc: "5分钟前夕: 精英战士" }, + { time: 9 * 60 + 55, uuid: MON_IDS.BOSS, type: MonType.BOSS, level: 15, desc: "10分钟: 兽人首领" }, + { time: 14 * 60 + 55, uuid: MON_IDS.BOSS, type: MonType.BOSS, level: 30, desc: "15分钟: 最终Boss" } +]; /** - * 获取当前全局配置 + * 获取当前时间的波次配置 + * @param timeInSeconds 游戏时间 (秒) */ -export function getRogueConfig(): IRogueGlobalConfig { - return currentConfig; +export function getCurrentWave(timeInSeconds: number): WaveConfig { + const waveIndex = Math.min(Math.floor(timeInSeconds / 60), 14); + return RogueWaves[waveIndex]; } /** - * 更新全局配置 - * @param config 部分或全部配置 + * 怪物消耗点数配置 (用于经验/金币计算) */ -export function updateRogueConfig(config: Partial) { - currentConfig = { - ...currentConfig, - ...config - }; - console.log("[RogueConfig] Configuration updated:", currentConfig); -} - -/** - * 计算当前威胁点数预算 - * @param timeInSeconds 游戏时间(秒) - * @param heroHpRatio 英雄血量比例 (0-1) - */ -export function calculateBudget(timeInSeconds: number, heroHpRatio: number = 1.0): number { - const config = getRogueConfig(); - - // 基础预算 - const Base_Budget = config.baseBudget; - - // 时间因子:随时间增加难度 - const timeFactor = 1 + (timeInSeconds / 60) * config.timeDifficultyFactor; - - // 强度乘数 (DDA):根据英雄状态调整 - let intensityMultiplier = 1.0; - if (heroHpRatio < config.survivalHpThreshold) { - // 绝地求生:血量低于阈值时预算缩减 - intensityMultiplier = config.survivalBudgetMultiplier; - } - - // 公式: Budget(t) = Base_Budget * (1 + t/60 * Factor) * Intensity_Multiplier - return Math.floor(Base_Budget * timeFactor * intensityMultiplier); -} - -/** - * 根据预算生成怪物列表 - * @param timeInSeconds 游戏时间(秒) - * @param heroHpRatio 英雄血量比例 - */ -export function generateMonstersFromBudget(timeInSeconds: number, heroHpRatio: number = 1.0): IMonsConfig[] { - const config = getRogueConfig(); - - const budget = calculateBudget(timeInSeconds, heroHpRatio); - const weights = getSpawnWeights(timeInSeconds); - const monsters: IMonsConfig[] = []; - let currentBudget = budget; - - // 构建权重池 - const pool: number[] = []; - weights.forEach(w => { - for(let i=0; i MonsterCost[w.uuid] || 1)); - - // 限制单次生成最大数量 - while (currentBudget >= minCost && attempts < 50 && monsters.length < config.maxSpawnPerLogic) { - attempts++; - - const uuid = pool[Math.floor(Math.random() * pool.length)]; - const cost = MonsterCost[uuid] || 1; - - if (currentBudget >= cost) { - currentBudget -= cost; - - // 随机刷怪只生成普通怪,精英和Boss由固定时间控制 - let type = MonType.NORMAL; - // 即使随机到了高Cost怪,在这里也只按普通怪处理,或者在配置中彻底移除高Cost怪 - - monsters.push({ - uuid: uuid, - type: type, - level: Math.floor(timeInSeconds / 60) + 1, - position: Math.floor(Math.random() * 5) - }); - } - } - - return monsters; -} - - - +export const MonsterCost: Record = { + 5201: 1, // 兽人战士 (Warrior) + 5301: 3, // 兽人斥候 (Assassin) + 5401: 5, // 兽人卫士 (Tank) + 5501: 4, // 兽人射手 (Remote) + 5601: 10, // 兽人自爆兵 (Mechanic) + 5602: 8, // 兽人召唤师 + 5603: 6, // 兽人祭司 (Healer) + 5604: 6, // 兽人图腾师 + 5701: 50, // 兽人首领 (Elite/Boss) +}; /** * 计算波次因子 * @param stage 当前波次 * @param timeInSeconds 游戏进行时间(秒) * @returns 波次因子 (0-1之间,15分钟时达到最大) - * @returns 波次因子 (0-1之间,15分钟时达到最大) */ function calculateWaveFactor(stage: number, timeInSeconds: number = 0): number { const MAX_GAME_TIME = 15 * 60; // 15分钟 = 900秒 - const effectiveTime = timeInSeconds || (stage * 30); // 如果没有时间数据,用波次估算(每波30秒) + const effectiveTime = timeInSeconds || (stage * 60); const factor = Math.min(effectiveTime / MAX_GAME_TIME, 1.0); return factor; } /** * 应用成长公式到基础属性 - * @param baseStat 基础属性值 - * @param waveFactor 波次因子 (0-1) - * @param growthType 成长类型 - * @returns 成长后的属性值 */ function applyGrowthFormula(baseStat: number, waveFactor: number, growthType: GrowthType): number { // 基础倍率:15分钟成长约 16 倍 (1 + 1.0 * 15) - // waveFactor 是 0-1 (基于15分钟) const TIME_SCALING = 15; const growthMultiplier = Math.pow(1 + waveFactor * TIME_SCALING, growthType); return Math.floor(baseStat * growthMultiplier); @@ -242,7 +274,7 @@ function applyGrowthFormula(baseStat: number, waveFactor: number, growthType: Gr /** * 获取怪物动态成长属性 - * @param stage 当前波次 + * @param stage 当前波次 (这里复用为等级或忽略) * @param uuid 怪物ID * @param monType 怪物类型 * @param timeInSeconds 游戏进行时间(秒) @@ -256,7 +288,7 @@ export function getMonAttr(stage: number, uuid: number, monType: MonType = MonTy } // 计算波次因子 - const waveFactor = calculateWaveFactor(stage, timeInSeconds); + const waveFactor = calculateWaveFactor(0, timeInSeconds); // 质量系数:数量减至10(原50的1/5),质量x5 const qualityRatio = 5.0; @@ -287,163 +319,35 @@ export function getMonAttr(stage: number, uuid: number, monType: MonType = MonTy }; } -/** - * 根据波次生成怪物配置 - * @param stage 当前波次 - * @returns IMonsConfig数组 - */ -export function getStageMonConfigs(stage: number): IMonsConfig[] { - const monsterConfigs: IMonsConfig[] = []; - - // 基础怪物列表(从heroset.ts中获取) - const normalMons = [5201, 5301, 5401, 5501, 5601, 5602, 5603, 5604]; - const eliteMons = [5701]; - - // 根据波次生成怪物配置 - // 波次越高,怪物数量越多,精英怪物出现概率越高 - const baseCount = 5 + Math.floor(stage / 2); // 基础数量每2波增加1 - const eliteChance = Math.min(stage * 0.05, 0.3); // 精英怪概率最高30% - const bossWave = stage % 10 === 0; // 每10波出Boss - - if (bossWave && stage > 0) { - // Boss波 - monsterConfigs.push({ - uuid: 5701, - type: MonType.BOSS, - level: stage - }); - } else { - // 普通波 - for (let i = 0; i < baseCount; i++) { - // 随机决定是否生成精英怪 - const isElite = Math.random() < eliteChance; - const monList = isElite ? eliteMons : normalMons; - const randomUuid = monList[Math.floor(Math.random() * monList.length)]; - - monsterConfigs.push({ - uuid: randomUuid, - type: isElite ? MonType.ELITE : MonType.NORMAL, - level: stage, - position: i % 5 - }); - } - } - - return monsterConfigs; -} - /** * 无限等级经验配置 - * @param level 当前等级 - * @returns 升级所需经验值 */ export function getLevelExp(level: number): number { - // 基础经验 const baseExp = 100; - // 增长因子 (每级增加20%) const growthFactor = 1.2; - - // 公式: Exp = Base * (Factor ^ (Level - 1)) - // 1级: 100 - // 2级: 120 - // 3级: 144 - // 10级: ~515 - // 20级: ~3194 return Math.floor(baseExp * Math.pow(growthFactor, level - 1)); } /** * 计算怪物掉落金币 - * @param uuid 怪物ID - * @param level 怪物等级 - * @param type 怪物类型 */ export function calculateMonsterGold(uuid: number, level: number, type: MonType): number { const cost = MonsterCost[uuid] || 1; - - // 危险值系数: cost越大越危险 let danger_ratio = 1 + cost * 0.1; let type_ratio = 1; if(type == MonType.BOSS) type_ratio = 10; else if(type == MonType.ELITE) type_ratio = 3; - // 公式: 基础(10) * 类型 * 危险值 + 等级加成 const baseGold = 10; - // 数量减至1/5,收益倍率提升至 8 (原5) 以保持总产出平衡 let gold = Math.floor((baseGold * type_ratio * danger_ratio + level) * 8); - return gold; } /** * 计算怪物经验值 - * 目标:让玩家在13分钟左右升到20级 - * @param uuid 怪物ID - * @param level 怪物等级 */ export function calculateMonsterExp(uuid: number, level: number): number { const cost = MonsterCost[uuid] || 1; - // 基础系数 1.0 (原0.8),成长因子 1.15 (原1.1) - // 这样设计是为了对抗升级所需经验的指数增长 (1.2^L) - // 同时也补偿了因为最大同屏数量减少(10->5)导致的怪物总量减少 - // 新公式下,13分钟大约能产出 19000 经验,满足升到 20 级所需的 15464 经验 - - // 数量大幅减少,单体收益倍率提升至 8 (原5) return Math.max(1, Math.floor(cost * 1.0 * Math.pow(1.15, level - 1) * 8)); } - -// 怪物消耗点数配置 -export const MonsterCost: Record = { - 5201: 1, // 兽人战士 (Warrior) - 5301: 3, // 兽人斥候 (Assassin) - 5401: 5, // 兽人卫士 (Tank) - 5501: 4, // 兽人射手 (Remote) - 5601: 10, // 兽人自爆兵 (Mechanic) - 5602: 8, // 兽人召唤师 - 5603: 6, // 兽人祭司 (Healer) - 5604: 6, // 兽人图腾师 - 5701: 50, // 兽人首领 (Elite/Boss) -}; - -// 刷怪权重接口 -interface SpawnWeight { - uuid: number; - weight: number; -} - -/** - * 根据游戏时间获取刷怪权重 - * @param timeInSeconds 游戏时间(秒) - */ -function getSpawnWeights(timeInSeconds: number): SpawnWeight[] { - const minutes = timeInSeconds / 60; - - if (minutes < 3) { - // 0-3min: 匀速群落 - 100% 战士 - return [{ uuid: 5201, weight: 100 }]; - } else if (minutes < 8) { - // 3-8min: 快速干扰 - 70% 战士, 30% 刺客 - return [ - { uuid: 5201, weight: 70 }, - { uuid: 5301, weight: 30 } - ]; - } else if (minutes < 14) { - // 8-14min: 阵地博弈 - 移除精英怪,只保留普通怪 - return [ - { uuid: 5201, weight: 40 }, - { uuid: 5301, weight: 30 }, - { uuid: 5401, weight: 15 }, - { uuid: 5603, weight: 15 } - ]; - } else { - // 15min+: 混合兵种,Boss由固定时间控制 - return [ - { uuid: 5201, weight: 30 }, - { uuid: 5301, weight: 30 }, - { uuid: 5401, weight: 20 }, - { uuid: 5603, weight: 20 } - ]; - } -} -