feat: 实现无限关卡 Boss 大招系统并重构关卡配置

This commit is contained in:
pan
2026-09-23 10:06:00 +08:00
parent c10e24709d
commit 8f89dd11fa
4 changed files with 301 additions and 439 deletions

View File

@@ -1,17 +1,22 @@
/**
* @file StageSet.ts
* @description 关卡系统配置(章节-关-节三级结构)
* @description 关卡系统配置(无限关卡·单线推进)
*
* 设计要点:
* - 1 章 = 10 关,每关 = 5 小节(一关一局,节间自动推进),共 50 个小节
* - 每关节点固定:1~3 节普通(3 小怪)、第 4 节精英(3 精英)、第 5 节 Boss(1 Boss + 2 小怪)
* - 怪物等级 = 关卡号(1-1 的怪为 Lv1,1-5 的怪为 Lv5)
* - 怪物属性 = 基础 × 开局系数 × (1 + 成长斜率 × (lv-1)) × 类型倍率
* - 无限关卡:从第 1 关起无上限推进,每关 = 5 回合(一关一局,回合间自动推进)
* - 第 1~4 回合为普通怪,属性逐回合轻量增强(SECTION_RAMP_STEP)
* - 第 5 回合为单 Boss 回合(无护卫),Boss 采用超厚血量(BOSS_HP_MUL)
* - 怪物等级 = 关卡号(第 N 关的怪为 LvN,随关卡无限成长)
* - 小节配置按需动态生成,不再依赖章节静态表
*
* 数值基准:
* - 开局系数 0.8(1 级小怪略弱于英雄)
* - 成长斜率 +5%/级(10 级 ≈ 1.45 倍)
* - 精英倍率 × 1.5,Boss 倍率 × 3.0(精英 × 2)
* - 成长斜率 +5%/级(无限线性成长,曲线平缓可控)
* - 回合增强 +12%/回合(第 4 回合 ≈ 1.36 倍)
* - Boss:HP × 12(超厚血量)/ AP × 2.0(攻击克制,避免 Boss 秒杀英雄)
*
* 核心公式:
* 怪物属性 = 基础 × MON_START_COEF × (1 + MON_GROW_SLOPE×(lv-1)) × 回合增强 × 类型倍率
*/
import { MonType } from "./heroSet";
@@ -20,82 +25,54 @@ import { MonType } from "./heroSet";
/** 怪物开局系数:1 级小怪略弱于英雄(0.8 倍) */
export const MON_START_COEF = 0.8;
/** 怪物成长斜率:每级 +5% 基础属性 */
/** 怪物成长斜率:每级 +5% 基础属性(无限线性成长) */
export const MON_GROW_SLOPE = 0.05;
/** 精英属性倍率:同级普通怪的 1.5 倍 */
export const ELITE_MUL = 1.5;
/** Boss 属性倍率:同级普通怪的 3.0 倍(精英 × 2) */
export const BOSS_MUL = 3.0;
/** 每关小节总数(一关一局,节间自动推进) */
/** 回合内轻量增强步长:第 1→4 回合每回合 +12% 属性 */
export const SECTION_RAMP_STEP = 0.12;
/** Boss 血量倍率:同级普通怪的 12 倍(超厚血量,拖长 Boss 战节奏) */
export const BOSS_HP_MUL = 12;
/** Boss 攻击倍率:同级普通怪的 2.0 倍(显著低于血量倍率,避免 Boss 秒杀英雄) */
export const BOSS_AP_MUL = 2.0;
/** 每关回合总数(一关一局,回合间自动推进) */
export const SECTIONS_PER_STAGE = 5;
/** 精英节号(每关第 4 节为精英节) */
export const ELITE_SECTION = 4;
/** Boss 节号(每关第 5 节为 Boss 节,即末节) */
/** Boss 回合号(每关第 5 回合,即末回合) */
export const BOSS_SECTION = 5;
/** 普通节小怪数量(一关一局压缩后单节 3 只) */
/** 普通回合小怪数量 */
export const NORMAL_MON_COUNT = 3;
/** 普通节开局立即刷出数量(3 只全部立即刷出) */
/** 普通回合开局立即刷出数量(3 只全部立即刷出) */
export const NORMAL_FIRST_SPAWN = 3;
/** 普通节后续刷怪间隔(秒):保留配置位(3 只全立即刷,当前无后续批次) */
/** 普通回合后续刷怪间隔(秒):保留配置位(3 只全立即刷,当前无后续批次) */
export const NORMAL_SPAWN_INTERVAL = 2;
/** 精英节精英数量 */
export const ELITE_COUNT = 3;
/** Boss 节护卫小怪数量 */
export const BOSS_ELITE_COUNT = 2;
/** Boss 节 Boss 延迟刷出时间(秒) */
/** Boss 回合 Boss 延迟刷出时间(秒) */
export const BOSS_DELAY = 1;
// ======================== 类型定义 ========================
/** 小节类型 */
/** 回合类型 */
export enum SectionType {
Normal = 0, // 普通节:3 小怪
Elite = 1, // 精英节:3 精英
Boss = 2, // Boss 节:1 Boss + 2 小怪
Normal = 0, // 普通回合:3 小怪(逐回合轻量增强)
Boss = 2, // Boss 回合:1 Boss(超厚血量,无护卫)
}
/** 小节配置 */
/** 回合配置 */
export interface SectionConfig {
/** 小节类型 */
/** 回合类型 */
type: SectionType;
/** 怪物等级(驱动线性成长) */
/** 怪物等级(= 关卡号,驱动线性成长) */
monLv: number;
/** 精英数量(精英关/Boss 关用) */
eliteCount?: number;
/** Boss UUID(Boss 关用,可选,缺省随机) */
bossUuid?: number;
/** 怪物类型池(普通关用,缺省随机) */
/** 回合增强系数(第 n 回合 = 1 + SECTION_RAMP_STEP×(n-1),仅普通回合生效) */
ramp: number;
/** 怪物类型池(普通回合用,缺省随机) */
monTypes?: MonType[];
}
/** 大关配置(1-1 ~ 1-10),每关 SECTIONS_PER_STAGE 个小节 */
export interface StageConfig {
/** 章节号 */
chapter: number;
/** 关卡号 1~10 */
stage: number;
/** 小节列表(长度 = SECTIONS_PER_STAGE) */
sections: SectionConfig[];
}
/** 章节配置 */
export interface ChapterConfig {
/** 章节号 */
chapter: number;
/** 章节名称 */
name: string;
/** 10 个大关 */
stages: StageConfig[];
}
// ======================== 工具函数 ========================
/**
* 计算怪物最终属性
* 计算怪物最终属性(Boss 血/攻倍率分离:超厚血 + 克制攻)
* @param baseHp 基础 HP
* @param baseAp 基础 AP
* @param lv 怪物等级
* @param isElite 是否精英
* @param isBoss 是否 Boss
* @returns 最终 HP/AP
*/
@@ -103,159 +80,72 @@ export function calcMonsterAttrs(
baseHp: number,
baseAp: number,
lv: number,
isElite: boolean = false,
isBoss: boolean = false
): { hp: number; ap: number } {
const growCoef = 1 + MON_GROW_SLOPE * (lv - 1);
let typeMul = 1;
if (isBoss) {
typeMul = BOSS_MUL;
} else if (isElite) {
typeMul = ELITE_MUL;
}
const hp = Math.max(1, Math.round(baseHp * MON_START_COEF * growCoef * typeMul));
const ap = Math.max(1, Math.round(baseAp * MON_START_COEF * growCoef * typeMul));
const hp = Math.max(1, Math.round(baseHp * MON_START_COEF * growCoef * (isBoss ? BOSS_HP_MUL : 1)));
const ap = Math.max(1, Math.round(baseAp * MON_START_COEF * growCoef * (isBoss ? BOSS_AP_MUL : 1)));
return { hp, ap };
}
/**
* 获取指定关卡的怪物等级
* @param stage 关卡号(1~10)
* 获取指定关卡的怪物等级(无限关:等级 = 关卡号,无上限)
* @param stage 关卡号(1~∞)
* @returns 怪物等级
*/
export function getMonsterLv(stage: number): number {
return Math.max(1, Math.min(10, stage));
return Math.max(1, Math.floor(stage));
}
/**
* 判断是否为精英节(每关第 ELITE_SECTION 节)
* @param section 小节号(1~SECTIONS_PER_STAGE)
*/
export function isEliteSection(section: number): boolean {
return section === ELITE_SECTION;
}
/**
* 判断是否为 Boss 节(每关第 BOSS_SECTION 节,即末节)
* @param section 小节号(1~SECTIONS_PER_STAGE)
* 判断是否为 Boss 回合(每关第 BOSS_SECTION 回合,即末回合)
* @param section 回合号(1~SECTIONS_PER_STAGE)
*/
export function isBossSection(section: number): boolean {
return section === BOSS_SECTION;
}
/**
* 获取小节类型(按节号区分:1~3 普通 / 4 精英 / 5 Boss)
* @param section 小节号(1~SECTIONS_PER_STAGE)
* 获取回合类型(按回合号区分:1~4 普通 / 5 Boss)
* @param section 回合号(1~SECTIONS_PER_STAGE)
*/
export function getSectionType(section: number): SectionType {
if (isBossSection(section)) return SectionType.Boss;
if (isEliteSection(section)) return SectionType.Elite;
return SectionType.Normal;
return isBossSection(section) ? SectionType.Boss : SectionType.Normal;
}
// ======================== 第 1 章配置 ========================
/** 生成单个大关的 SECTIONS_PER_STAGE 个小节配置(1~3 普通 / 4 精英 / 5 Boss) */
function makeStageSections(stage: number): SectionConfig[] {
const monLv = getMonsterLv(stage);
const sections: SectionConfig[] = [];
for (let i = 1; i <= SECTIONS_PER_STAGE; i++) {
const type = getSectionType(i);
const section: SectionConfig = { type, monLv };
if (type === SectionType.Elite) {
section.eliteCount = ELITE_COUNT;
} else if (type === SectionType.Boss) {
section.eliteCount = BOSS_ELITE_COUNT;
// bossUuid 缺省随机,由生成引擎从 Boss 池抽取
} else {
// 普通节:近战为主,穿插远程/重型
section.monTypes = [MonType.Melee, MonType.Melee, MonType.Melee, MonType.Heavy, MonType.Long];
}
sections.push(section);
}
return sections;
}
/** 第 1 章配置 */
export const Chapter1: ChapterConfig = {
chapter: 1,
name: "兽人入侵",
stages: [],
};
// 初始化第 1 章 10 个大关
for (let stage = 1; stage <= 10; stage++) {
Chapter1.stages.push({
chapter: 1,
stage,
sections: makeStageSections(stage),
});
}
/** 所有章节配置表(当前仅第 1 章,后续扩展) */
export const ChapterSet: Record<number, ChapterConfig> = {
1: Chapter1,
};
// ======================== 查询接口 ========================
/**
* 获取小节配置
* @param chapter 章节号
* @param stage 关卡号
* @param section 小节号(1~SECTIONS_PER_STAGE)
* @returns 小节配置,不存在时返回 null
* 获取回合配置(无限关:按需动态生成,不再依赖章节静态表)
* @param chapter 章节号(无限关模式已废弃章节概念,参数仅为兼容旧调用方,恒忽略)
* @param stage 关卡号(1~∞)
* @param section 回合号(1~SECTIONS_PER_STAGE)
* @returns 回合配置,回合号越界时返回 null
*/
export function getSectionConfig(chapter: number, stage: number, section: number): SectionConfig | null {
const ch = ChapterSet[chapter];
if (!ch) return null;
const st = ch.stages.find(s => s.stage === stage);
if (!st) return null;
if (section < 1 || section > st.sections.length) return null;
return st.sections[section - 1];
if (section < 1 || section > SECTIONS_PER_STAGE) return null;
const type = getSectionType(section);
const cfg: SectionConfig = {
type,
monLv: getMonsterLv(stage),
ramp: 1 + SECTION_RAMP_STEP * (section - 1),
};
if (type !== SectionType.Boss) {
// 普通回合:近战为主,穿插远程/重型
cfg.monTypes = [MonType.Melee, MonType.Melee, MonType.Melee, MonType.Heavy, MonType.Long];
}
return cfg;
}
/**
* 获取大关配置
* @param chapter 章节号
* 获取当前进度显示文本(无限关:只展示 关-回合,章节号恒忽略)
* @param chapter 章节号(兼容参数,恒忽略)
* @param stage 关卡号
* @returns 大关配置,不存在时返回 null
*/
export function getStageConfig(chapter: number, stage: number): StageConfig | null {
const ch = ChapterSet[chapter];
if (!ch) return null;
return ch.stages.find(s => s.stage === stage) ?? null;
}
/**
* 获取章节配置
* @param chapter 章节号
* @returns 章节配置,不存在时返回 null
*/
export function getChapterConfig(chapter: number): ChapterConfig | null {
return ChapterSet[chapter] ?? null;
}
/**
* 获取当前进度显示文本
* @param chapter 章节号
* @param stage 关卡号
* @param section 小节号
* @returns 格式化文本,如 "1-5-3"
* @param section 回合号
* @returns 格式化文本,如 "5-3"
*/
export function getProgressText(chapter: number, stage: number, section: number): string {
return `${chapter}-${stage}-${section}`;
}
/**
* 获取大关显示文本
* @param chapter 章节号
* @param stage 关卡号
* @returns 格式化文本,如 "1-5"
*/
export function getStageText(chapter: number, stage: number): string {
return `${chapter}-${stage}`;
return `${stage}-${section}`;
}