feat(肉鸽): 重构为基于波次的刷怪系统
- 废弃动态威胁预算算法,改用确定性的15波配置(每分钟1波) - 引入三阶段节奏设计:构筑期、磨合期、极限期,每波有独立怪物权重池 - 简化刷怪逻辑,移除复杂的预算计算和英雄血量响应机制 - 特殊事件怪物改为队列处理,与波次系统并行运行 - 优化代码结构,移除冗余状态变量和未使用的方法
This commit is contained in:
@@ -5,8 +5,8 @@ import { Monster } from "../hero/Mon";
|
|||||||
import { MonStart } from "../common/config/heroSet";
|
import { MonStart } from "../common/config/heroSet";
|
||||||
import { smc } from "../common/SingletonModuleComp";
|
import { smc } from "../common/SingletonModuleComp";
|
||||||
import { GameEvent } from "../common/config/GameEvent";
|
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 { BuffConf } from "../common/config/SkillSet";
|
||||||
import { IndexSet, FacSet, BoxSet } from "../common/config/GameSet";
|
import { IndexSet, FacSet, BoxSet } from "../common/config/GameSet";
|
||||||
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||||
@@ -17,7 +17,7 @@ const { ccclass, property } = _decorator;
|
|||||||
@ccclass('MissionMonCompComp')
|
@ccclass('MissionMonCompComp')
|
||||||
@ecs.register('MissionMonComp', false)
|
@ecs.register('MissionMonComp', false)
|
||||||
export class MissionMonCompComp extends CCComp {
|
export class MissionMonCompComp extends CCComp {
|
||||||
// 添加刷怪队列 - 使用新的RogueConfig格式
|
// 刷怪队列 (主要用于特殊事件插队)
|
||||||
private MonQueue: Array<{
|
private MonQueue: Array<{
|
||||||
uuid: number,
|
uuid: number,
|
||||||
position: number,
|
position: number,
|
||||||
@@ -25,20 +25,20 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
level: number,
|
level: number,
|
||||||
buffs: BuffConf[]
|
buffs: BuffConf[]
|
||||||
}> = [];
|
}> = [];
|
||||||
private isSpawning: boolean = false;// 是否正在生成怪物
|
|
||||||
private spawnInterval: number = 0.6; // 每个怪物生成间隔时间(减半速度)
|
|
||||||
private spawnTimer: number = 0; // 生成计时器
|
|
||||||
private spawnCount: 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 globalSpawnOrder: number = 0;
|
||||||
|
|
||||||
/** 游戏进行时间(秒) */
|
/** 游戏进行时间(秒) */
|
||||||
private gameTime: number = 0;
|
private gameTime: number = 0;
|
||||||
/** 刷怪逻辑计时器(每秒执行一次) */
|
|
||||||
private spawnLogicTimer: number = 0;
|
|
||||||
|
|
||||||
|
/** 波次刷怪计时器 */
|
||||||
|
private waveTimer: number = 0;
|
||||||
|
|
||||||
|
/** 队列处理计时器 */
|
||||||
|
private queueTimer: number = 0;
|
||||||
|
|
||||||
onLoad(){
|
onLoad(){
|
||||||
this.on(GameEvent.FightReady,this.fight_ready,this)
|
this.on(GameEvent.FightReady,this.fight_ready,this)
|
||||||
@@ -56,8 +56,8 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
if (!args) return;
|
if (!args) return;
|
||||||
console.log(`[MissionMonComp] 收到特殊刷怪指令:`, args);
|
console.log(`[MissionMonComp] 收到特殊刷怪指令:`, args);
|
||||||
|
|
||||||
// 插入队列头部,优先生成
|
// 插入队列
|
||||||
this.MonQueue.unshift({
|
this.MonQueue.push({
|
||||||
uuid: args.uuid,
|
uuid: args.uuid,
|
||||||
position: args.position !== undefined ? args.position : 2, // 默认中间
|
position: args.position !== undefined ? args.position : 2, // 默认中间
|
||||||
type: args.type,
|
type: args.type,
|
||||||
@@ -65,27 +65,24 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
buffs: args.buffs || []
|
buffs: args.buffs || []
|
||||||
});
|
});
|
||||||
|
|
||||||
// 让刷怪计时器立即满足条件,以便尽快生成
|
// 立即触发一次队列检查 (可选,让 update 尽快处理)
|
||||||
// 注意:不直接调用 spawnNextMonster 是为了保持 update 循环的一致性
|
this.queueTimer = 1.0;
|
||||||
const config = getRogueConfig();
|
|
||||||
this.spawnTimer = config.spawnInterval + 0.1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 视图层逻辑代码分离演示 */
|
|
||||||
start() {
|
start() {
|
||||||
// var entity = this.ent as ecs.Entity; // ecs.Entity 可转为当前模块的具体实体对象
|
|
||||||
// this.on(ModuleEvent.Cmd, this.onHandler, this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fight_ready(){
|
fight_ready(){
|
||||||
// console.log("[MissionMonComp]:fight_ready")
|
|
||||||
smc.vmdata.mission_data.mon_num=0
|
smc.vmdata.mission_data.mon_num=0
|
||||||
// 重置生成顺序计数器
|
// 重置生成顺序计数器
|
||||||
this.globalSpawnOrder = 0
|
this.globalSpawnOrder = 0
|
||||||
this.gameTime = 0
|
this.gameTime = 0
|
||||||
this.spawnLogicTimer = 0
|
this.waveTimer = 0
|
||||||
|
this.queueTimer = 0
|
||||||
this.MonQueue = []
|
this.MonQueue = []
|
||||||
this.do_mon_wave()
|
this.spawnCount = 0
|
||||||
|
|
||||||
|
console.log("[MissionMonComp] Starting Wave System (15-min Cycle)");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected update(dt: number): void {
|
protected update(dt: number): void {
|
||||||
@@ -96,137 +93,75 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
|
|
||||||
// 累加游戏时间
|
// 累加游戏时间
|
||||||
this.gameTime += dt;
|
this.gameTime += dt;
|
||||||
const config = getRogueConfig();
|
|
||||||
|
// 获取当前波次配置
|
||||||
// ==========================================
|
const currentWave = getCurrentWave(this.gameTime);
|
||||||
// 新增:每秒执行一次刷怪逻辑 (Threat Budget)
|
|
||||||
// ==========================================
|
// 1. 优先处理特殊怪队列
|
||||||
this.spawnLogicTimer += dt;
|
if (this.MonQueue.length > 0) {
|
||||||
if (this.spawnLogicTimer >= config.spawnLogicInterval) {
|
this.queueTimer += dt;
|
||||||
this.spawnLogicTimer = 0;
|
// 队列出怪速度快于普通波次 (0.5秒一只)
|
||||||
|
if (this.queueTimer >= 0.5) {
|
||||||
// 检查最大怪物数量限制
|
this.spawnNextFromQueue();
|
||||||
if (smc.vmdata.mission_data.mon_num < config.maxMonsterCount) {
|
this.queueTimer = 0;
|
||||||
// 获取英雄血量比例
|
|
||||||
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 || []
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理随机事件
|
// 2. 处理波次自然刷怪
|
||||||
|
this.waveTimer += dt;
|
||||||
|
if (this.waveTimer >= currentWave.spawnInterval) {
|
||||||
// 处理刷怪队列
|
this.waveTimer = 0;
|
||||||
if (this.MonQueue.length > 0 && !this.isSpawning) {
|
|
||||||
this.spawnTimer += dt;
|
|
||||||
|
|
||||||
// 正常召唤间隔
|
// 检查同屏数量限制
|
||||||
if (this.spawnTimer >= config.spawnInterval) {
|
if (smc.vmdata.mission_data.mon_num < currentWave.maxActive) {
|
||||||
this.spawnNextMonster();
|
this.spawnWaveMonster(currentWave);
|
||||||
this.spawnTimer = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
private spawnWaveMonster(wave: WaveConfig) {
|
||||||
// 查询带有 HeroAttrsComp 的实体
|
if (!wave.weights || wave.weights.length === 0) return;
|
||||||
// 注意:这里假设只有一个英雄,且性能允许每秒查询一次
|
|
||||||
const entities = ecs.query(ecs.allOf(HeroAttrsComp));
|
// 权重随机算法
|
||||||
for (const e of entities) {
|
const totalWeight = wave.weights.reduce((sum, item) => sum + item.weight, 0);
|
||||||
const attrs = e.get(HeroAttrsComp);
|
let random = Math.random() * totalWeight;
|
||||||
if (attrs && attrs.fac === FacSet.HERO) {
|
let selectedUuid = wave.weights[0].uuid;
|
||||||
const maxHp = attrs.Attrs[Attrs.HP_MAX] || 1;
|
let selectedType = wave.weights[0].type || MonType.NORMAL;
|
||||||
return attrs.hp / maxHp;
|
|
||||||
|
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;
|
private spawnNextFromQueue() {
|
||||||
|
|
||||||
// 设置怪物总数
|
|
||||||
// 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() {
|
|
||||||
if (this.MonQueue.length === 0) return;
|
if (this.MonQueue.length === 0) return;
|
||||||
|
|
||||||
const monsterData = this.MonQueue.shift();
|
const monsterData = this.MonQueue.shift();
|
||||||
@@ -239,9 +174,7 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
monsterData.buffs,
|
monsterData.buffs,
|
||||||
this.gameTime
|
this.gameTime
|
||||||
);
|
);
|
||||||
// 增加召唤计数
|
|
||||||
this.spawnCount++;
|
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>(Monster);
|
let mon = ecs.getEntity<Monster>(Monster);
|
||||||
let scale = -1;
|
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 y = BoxSet.GAME_LINE;
|
||||||
let lane = 0;
|
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);
|
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);
|
mon.load(pos, scale, uuid, lv, monType, buffs, false, lane, this.globalSpawnOrder, gameTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */
|
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */
|
||||||
reset() {
|
reset() {
|
||||||
// this.node.destroy();
|
// this.node.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,18 @@
|
|||||||
/**
|
/**
|
||||||
* 肉鸽模式配置脚本 - 增强版
|
* 肉鸽模式配置脚本 - 增强版 (Wave System)
|
||||||
*
|
*
|
||||||
* 功能说明:
|
* 功能说明:
|
||||||
* - 提供基础的刷怪配置:刷什么怪,刷多少怪
|
* - 采用 15 个小波次(每分钟 1 波)
|
||||||
* - 支持程序化关卡生成逻辑,每一关的怪物组合、数量和强度应随关卡进度递增而变化
|
* - 整合进 3 个大的节奏阶段:构筑期、磨合期、极限期
|
||||||
* - 支持随机事件系统
|
* - 废弃动态预算,使用确定性波次配置
|
||||||
*
|
|
||||||
*
|
|
||||||
* 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)。防止后期怪物速度过快导致画面瞬移。
|
|
||||||
*
|
*
|
||||||
* @author 游戏开发团队
|
* @author 游戏开发团队
|
||||||
* @version 2.0 增强版
|
* @version 3.0 波次重构版
|
||||||
* @date 2025-10-19
|
* @date 2025-10-19
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { HeroInfo } from "../common/config/heroSet";
|
import { HeroInfo } from "../common/config/heroSet";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 怪物类型枚举
|
* 怪物类型枚举
|
||||||
*/
|
*/
|
||||||
@@ -35,7 +23,7 @@ export enum MonType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 怪物配置接口
|
* 怪物配置接口 (用于生成实例)
|
||||||
*/
|
*/
|
||||||
export interface IMonsConfig {
|
export interface IMonsConfig {
|
||||||
uuid: number; // 怪物ID
|
uuid: number; // 怪物ID
|
||||||
@@ -54,6 +42,8 @@ export interface MonAttrs {
|
|||||||
ap: number;
|
ap: number;
|
||||||
def: number;
|
def: number;
|
||||||
speed: number;
|
speed: number;
|
||||||
|
exp?: number;
|
||||||
|
gold?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,176 +55,218 @@ enum GrowthType {
|
|||||||
LOGARITHMIC = 0.3 // 对数级 - Speed
|
LOGARITHMIC = 0.3 // 对数级 - Speed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 全局刷怪配置接口
|
* 刷怪权重接口
|
||||||
*/
|
*/
|
||||||
export interface IRogueGlobalConfig {
|
export interface SpawnWeight {
|
||||||
/** 场上最大怪物数量限制 */
|
uuid: number;
|
||||||
maxMonsterCount: number;
|
weight: number;
|
||||||
/** 刷怪逻辑执行间隔(秒) - 决定多久计算一次生成 */
|
type?: MonType; // 默认为 NORMAL
|
||||||
spawnLogicInterval: number;
|
|
||||||
/** 单个怪物生成间隔(秒) - 队列中怪物的实际生成频率 */
|
|
||||||
spawnInterval: number;
|
|
||||||
/** 基础威胁预算 - 每秒产生的基础点数 */
|
|
||||||
baseBudget: number;
|
|
||||||
/** 时间难度因子 - 每分钟增加的预算比例 (0.2 = 20%) */
|
|
||||||
timeDifficultyFactor: number;
|
|
||||||
/** 绝地求生阈值 - 英雄血量低于此比例时触发减缓刷怪 */
|
|
||||||
survivalHpThreshold: number;
|
|
||||||
/** 绝地求生预算乘数 - 触发阈值时的预算折扣 */
|
|
||||||
survivalBudgetMultiplier: number;
|
|
||||||
/** 单次逻辑最大生成怪物数限制 - 防止瞬间生成过多 */
|
|
||||||
maxSpawnPerLogic: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认配置
|
* 波次配置接口
|
||||||
*/
|
*/
|
||||||
export const DefaultRogueConfig: IRogueGlobalConfig = {
|
export interface WaveConfig {
|
||||||
maxMonsterCount: 5, // 默认同屏5只 - 降低数量,提高单体质量
|
waveId: number; // 波次ID (1-15)
|
||||||
spawnLogicInterval: 1.0, // 每秒计算一次
|
name: string; // 波次名称
|
||||||
spawnInterval: 2.0, // 队列出怪间隔
|
duration: number; // 持续时间 (秒),通常为60
|
||||||
baseBudget: 1.0, // 基础预算
|
spawnInterval: number; // 刷怪间隔 (秒)
|
||||||
timeDifficultyFactor: 0.5, // 每分钟增加50%预算
|
maxActive: number; // 同屏最大怪物数
|
||||||
survivalHpThreshold: 0.4, // 40%血量触发保护
|
weights: SpawnWeight[]; // 怪物权重池
|
||||||
survivalBudgetMultiplier: 0.7, // 保护时预算打7折
|
}
|
||||||
maxSpawnPerLogic: 2 // 单次最多生成2只
|
|
||||||
|
// 怪物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 = [
|
* 全局波次配置表 (15波)
|
||||||
{ time: 60, uuid: 5601, type: MonType.ELITE, level: 5, desc: "1分钟: 精英自爆兵" },
|
*/
|
||||||
{ time: 180, uuid: 5601, type: MonType.ELITE, level: 10, desc: "3分钟: 精英自爆兵" },
|
export const RogueWaves: WaveConfig[] = [
|
||||||
{ time: 300, uuid: 5701, type: MonType.BOSS, level: 15, desc: "5分钟: 兽人首领" },
|
// --- 第一阶段:构筑期 (0-5min) ---
|
||||||
{ time: 600, uuid: 5701, type: MonType.BOSS, level: 25, desc: "10分钟: 兽人首领" },
|
{
|
||||||
{ time: 900, uuid: 5701, type: MonType.BOSS, level: 30, desc: "15分钟: 最终Boss" }
|
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 }
|
||||||
|
]
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// 当前配置实例
|
// 精英怪和Boss刷新时间配置 (时间单位: 秒)
|
||||||
let currentConfig: IRogueGlobalConfig = { ...DefaultRogueConfig };
|
// 注意:这里的时间点应与波次结束/开始对应
|
||||||
|
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 {
|
export function getCurrentWave(timeInSeconds: number): WaveConfig {
|
||||||
return currentConfig;
|
const waveIndex = Math.min(Math.floor(timeInSeconds / 60), 14);
|
||||||
|
return RogueWaves[waveIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新全局配置
|
* 怪物消耗点数配置 (用于经验/金币计算)
|
||||||
* @param config 部分或全部配置
|
|
||||||
*/
|
*/
|
||||||
export function updateRogueConfig(config: Partial<IRogueGlobalConfig>) {
|
export const MonsterCost: Record<number, number> = {
|
||||||
currentConfig = {
|
5201: 1, // 兽人战士 (Warrior)
|
||||||
...currentConfig,
|
5301: 3, // 兽人斥候 (Assassin)
|
||||||
...config
|
5401: 5, // 兽人卫士 (Tank)
|
||||||
};
|
5501: 4, // 兽人射手 (Remote)
|
||||||
console.log("[RogueConfig] Configuration updated:", currentConfig);
|
5601: 10, // 兽人自爆兵 (Mechanic)
|
||||||
}
|
5602: 8, // 兽人召唤师
|
||||||
|
5603: 6, // 兽人祭司 (Healer)
|
||||||
/**
|
5604: 6, // 兽人图腾师
|
||||||
* 计算当前威胁点数预算
|
5701: 50, // 兽人首领 (Elite/Boss)
|
||||||
* @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<w.weight; i++) pool.push(w.uuid);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (pool.length === 0) return [];
|
|
||||||
|
|
||||||
let attempts = 0;
|
|
||||||
const minCost = Math.min(...weights.map(w => 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计算波次因子
|
* 计算波次因子
|
||||||
* @param stage 当前波次
|
* @param stage 当前波次
|
||||||
* @param timeInSeconds 游戏进行时间(秒)
|
* @param timeInSeconds 游戏进行时间(秒)
|
||||||
* @returns 波次因子 (0-1之间,15分钟时达到最大)
|
* @returns 波次因子 (0-1之间,15分钟时达到最大)
|
||||||
* @returns 波次因子 (0-1之间,15分钟时达到最大)
|
|
||||||
*/
|
*/
|
||||||
function calculateWaveFactor(stage: number, timeInSeconds: number = 0): number {
|
function calculateWaveFactor(stage: number, timeInSeconds: number = 0): number {
|
||||||
const MAX_GAME_TIME = 15 * 60; // 15分钟 = 900秒
|
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);
|
const factor = Math.min(effectiveTime / MAX_GAME_TIME, 1.0);
|
||||||
return factor;
|
return factor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用成长公式到基础属性
|
* 应用成长公式到基础属性
|
||||||
* @param baseStat 基础属性值
|
|
||||||
* @param waveFactor 波次因子 (0-1)
|
|
||||||
* @param growthType 成长类型
|
|
||||||
* @returns 成长后的属性值
|
|
||||||
*/
|
*/
|
||||||
function applyGrowthFormula(baseStat: number, waveFactor: number, growthType: GrowthType): number {
|
function applyGrowthFormula(baseStat: number, waveFactor: number, growthType: GrowthType): number {
|
||||||
// 基础倍率:15分钟成长约 16 倍 (1 + 1.0 * 15)
|
// 基础倍率:15分钟成长约 16 倍 (1 + 1.0 * 15)
|
||||||
// waveFactor 是 0-1 (基于15分钟)
|
|
||||||
const TIME_SCALING = 15;
|
const TIME_SCALING = 15;
|
||||||
const growthMultiplier = Math.pow(1 + waveFactor * TIME_SCALING, growthType);
|
const growthMultiplier = Math.pow(1 + waveFactor * TIME_SCALING, growthType);
|
||||||
return Math.floor(baseStat * growthMultiplier);
|
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 uuid 怪物ID
|
||||||
* @param monType 怪物类型
|
* @param monType 怪物类型
|
||||||
* @param timeInSeconds 游戏进行时间(秒)
|
* @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
|
// 质量系数:数量减至10(原50的1/5),质量x5
|
||||||
const qualityRatio = 5.0;
|
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 {
|
export function getLevelExp(level: number): number {
|
||||||
// 基础经验
|
|
||||||
const baseExp = 100;
|
const baseExp = 100;
|
||||||
// 增长因子 (每级增加20%)
|
|
||||||
const growthFactor = 1.2;
|
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));
|
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 {
|
export function calculateMonsterGold(uuid: number, level: number, type: MonType): number {
|
||||||
const cost = MonsterCost[uuid] || 1;
|
const cost = MonsterCost[uuid] || 1;
|
||||||
|
|
||||||
// 危险值系数: cost越大越危险
|
|
||||||
let danger_ratio = 1 + cost * 0.1;
|
let danger_ratio = 1 + cost * 0.1;
|
||||||
|
|
||||||
let type_ratio = 1;
|
let type_ratio = 1;
|
||||||
if(type == MonType.BOSS) type_ratio = 10;
|
if(type == MonType.BOSS) type_ratio = 10;
|
||||||
else if(type == MonType.ELITE) type_ratio = 3;
|
else if(type == MonType.ELITE) type_ratio = 3;
|
||||||
|
|
||||||
// 公式: 基础(10) * 类型 * 危险值 + 等级加成
|
|
||||||
const baseGold = 10;
|
const baseGold = 10;
|
||||||
// 数量减至1/5,收益倍率提升至 8 (原5) 以保持总产出平衡
|
|
||||||
let gold = Math.floor((baseGold * type_ratio * danger_ratio + level) * 8);
|
let gold = Math.floor((baseGold * type_ratio * danger_ratio + level) * 8);
|
||||||
|
|
||||||
return gold;
|
return gold;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计算怪物经验值
|
* 计算怪物经验值
|
||||||
* 目标:让玩家在13分钟左右升到20级
|
|
||||||
* @param uuid 怪物ID
|
|
||||||
* @param level 怪物等级
|
|
||||||
*/
|
*/
|
||||||
export function calculateMonsterExp(uuid: number, level: number): number {
|
export function calculateMonsterExp(uuid: number, level: number): number {
|
||||||
const cost = MonsterCost[uuid] || 1;
|
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));
|
return Math.max(1, Math.floor(cost * 1.0 * Math.pow(1.15, level - 1) * 8));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 怪物消耗点数配置
|
|
||||||
export const MonsterCost: Record<number, number> = {
|
|
||||||
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 }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user