feat: 重构英雄成长系统,支持差异化成长曲线与无限升级

1. 新增英雄成长类型枚举与系数表,实现坦克/战士/刺客等差异化成长
2. 替换旧的固定指数成长逻辑,改用线性累计成长公式
3. 移除英雄等级上限,改为技能等级封顶,调整升级/出售金币计算逻辑
4. 重构属性计算与UI展示逻辑,统一成长计算入口
5. 优化技能文案展示与英雄配置格式
This commit is contained in:
panFD
2026-08-10 23:33:06 +08:00
parent d3ee664b00
commit 4eba1c8ad8
14 changed files with 3085 additions and 2624 deletions

View File

@@ -32,6 +32,8 @@ export enum FightSet {
HERO_MAX_NUM = 3,//英雄最大数量
/** 英雄可升级到的最高等级(通过升级卡提升) */
HERO_MAX_LV = 6,
/** 英雄技能等级上限(无限升级下技能 5 级封顶,超出等级只长属性) */
HERO_SKILL_MAX_LV = 5,
/** 英雄属性随等级成长的倍率ap/hp = base * MULTIPLIER^(lv-1) */
HERO_LV_MULTIPLIER = 2,
// BACK_RANG=30,//后退范围

View File

@@ -1,4 +1,4 @@
/**
/**
* @file HeroSkillDesc.ts
* @description 英雄技能/能力描述的【唯一文字表达标准层】
*
@@ -28,7 +28,7 @@
* - SkillDescSet:结构化描述模板
* - FieldSkillSet驻场技能、BuffList计时 buff 默认值兜底、Attrs属性枚举
*/
import { SkillTriggerName, SkillTriggerType, TriggerGrouped, LvTriggerEntry, LvFieldEntry, LvReviveEntry, LvBonusEntry, resolveTriggerByLv, resolveFieldByLv, resolveReviveByLv, getTriggerSkillName } from "./heroSet";
import { SkillTriggerName, SkillTriggerType, TriggerGrouped, LvTriggerEntry, LvFieldEntry, LvReviveEntry, LvBonusEntry, resolveTriggerByLv, resolveFieldByLv, resolveReviveByLv, getTriggerSkillName, HGrowType, calcGrowBonus } from "./heroSet";
import { FieldSkillSet, mergeSkillParams, SkillConfig, SkillKind, SkillOverrides, SkillSet, TGroup } from "./SkillSet";
import { FieldDescSet, FieldValueFmt, SkillDescSet } from "./SkillDescSet";
import { BuffList } from "./BuffSet";
@@ -53,6 +53,14 @@ export interface ISkillDescSource {
/** 额外攻击/生命加成(按 lv 累加,用于成长文案) */
ap_bonus?: LvBonusEntry[];
hp_bonus?: LvBonusEntry[];
/** 成长类型(决定每级 hp/ap 标准递增增量) */
grow_type?: HGrowType;
/** 基础攻击/生命(成长基数,线性公式 = 基础 × 系数 × (lv-1) */
ap?: number;
hp?: number;
/** 原始基础攻击/生命HeroAttrsComp 场景优先用作成长基数,避免运行时 hp/ap 重复计成长) */
base_ap?: number;
base_hp?: number;
}
/**
@@ -315,11 +323,16 @@ function buildReviveLines(source: ISkillDescSource, showName: boolean = true, sh
return lines;
}
/** 额外属性加成ap/hp累计结构化行高亮累计数值 */
/** 额外属性加成ap/hp累计结构化行成长递增 + 一次性奖励,高亮累计数值 */
function buildEvolveBonusLines(source: ISkillDescSource): ISkillLine[] {
const lv = Math.max(1, source.lv ?? 1);
let apTotal = 0;
let hpTotal = 0;
// 成长线性增量优先用原始基础值base_*HeroAttrsComp 场景),否则用配置 hp/ap
// 缺基础值时按 0 处理(如纯触发技能描述场景),避免运行时 hp/ap 重复计成长
const baseHp = source.base_hp ?? source.hp ?? 0;
const baseAp = source.base_ap ?? source.ap ?? 0;
const grow = calcGrowBonus({ hp: baseHp, ap: baseAp, grow_type: source.grow_type }, lv);
let apTotal = grow.ap;
let hpTotal = grow.hp;
if (source.ap_bonus) {
for (const e of source.ap_bonus) { if (e.lv <= lv) apTotal += e.value; }
}
@@ -327,8 +340,8 @@ function buildEvolveBonusLines(source: ISkillDescSource): ISkillLine[] {
for (const e of source.hp_bonus) { if (e.lv <= lv) hpTotal += e.value; }
}
const lines: ISkillLine[] = [];
if (apTotal !== 0) lines.push({ prefix: "成长:额外", value: `+${apTotal}`, suffix: ATTR_NAME[Attrs.ap] ?? "攻击力" });
if (hpTotal !== 0) lines.push({ prefix: "成长:额外", value: `+${hpTotal}`, suffix: ATTR_NAME[Attrs.hp_max] ?? "最大生命" });
if (apTotal !== 0) lines.push({ prefix: "成长:", value: `+${apTotal}`, suffix: ATTR_NAME[Attrs.ap] ?? "攻击力" });
if (hpTotal !== 0) lines.push({ prefix: "成长:", value: `+${hpTotal}`, suffix: ATTR_NAME[Attrs.hp_max] ?? "最大生命" });
return lines;
}

View File

@@ -107,6 +107,45 @@ export enum MonStart {
START_I = 90, //x轴间隔
}
/**
* 英雄成长类型(决定每级 hp/ap 标准增量系数)。
* Why: 不同定位英雄的成长曲线差异化,避免"一刀切"导致坦克/刺客同强度。
*/
export enum HGrowType {
Tank = 0, // 坦克:高 hp 低 ap
Warrior = 1, // 战士:均衡
Assassin = 2, // 刺客:低 hp 高 ap
Mage = 3, // 法师:中 hp 高 ap
Support = 4, // 辅助:中 hp 低 ap
}
/** 成长类型系数表(每级增量 = 英雄基础属性 × 对应系数) */
export const HERO_GROW_COEF: Record<HGrowType, { hp: number; ap: number }> = {
[HGrowType.Tank]: { hp: 1.5, ap: 0.6 },
[HGrowType.Warrior]: { hp: 1.0, ap: 1.0 },
[HGrowType.Assassin]: { hp: 0.7, ap: 1.4 },
[HGrowType.Mage]: { hp: 0.9, ap: 1.3 },
[HGrowType.Support]: { hp: 1.1, ap: 0.7 },
};
/**
* 计算英雄在目标等级下的累计属性成长增量(线性:每级加一份"基础属性×系数")。
* 公式:累计 = 基础属性 × 类型系数 × (lv - 1)。
* Why: 以英雄自身基础值为基数,自动适配低/高费卡的成长占比,避免全局固定值失衡;
* 线性曲线在无限升级下不会指数爆炸,强度平滑可控。
* @param hero 英雄配置(取 hp/ap 基础值与 grow_type
* @param targetLv 目标等级≥1
* @returns 累计 hp/ap 增量(已取整)
*/
export function calcGrowBonus(hero: Pick<heroInfo, "hp" | "ap" | "grow_type">, targetLv: number): { hp: number; ap: number } {
const coef = HERO_GROW_COEF[hero.grow_type ?? HGrowType.Warrior];
const steps = Math.max(0, Math.floor(targetLv) - 1);
return {
hp: Math.floor(hero.hp * coef.hp * steps),
ap: Math.floor(hero.ap * coef.ap * steps),
};
}
/**
@@ -248,6 +287,7 @@ export interface heroInfo {
lv: number; // 英雄等级
card_lv?: number; // 卡牌等级(驱动 bg_node 颜色1=green 2=blue 3=purple 4=red 5=yellow
type: HType; // 攻击定位(近战/中程/远程)
grow_type?: HGrowType; // 成长类型(决定每级 hp/ap 标准增量,默认 Warrior
monType?: MonType; // 怪物专属类型
hp: number; // 生命值上限
ap: number; // 攻击力
@@ -340,19 +380,18 @@ export interface LvBonusEntry {
export const HeroInfo: Record<number, heroInfo> = {
// ========== atked 类(战士 · 自身强化) ==========
5011: {
uuid: 5011, name: "小铁卫", path: "hk1", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Melee, hp: 300, ap: 28,
uuid: 5011, name: "铁壁", path: "hk1", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, hp: 300, ap: 28,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
// 成长主线:护盾层数 4→5→6等效减伤 57%→67%
// 成长主线:受击5次凝聚护盾层数 2→3→4→5每级按 Tank 成长类型递增 hp/ap
atked: {
6301: [
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 3, overrides: { TGroup: TGroup.Self, ap: 4 } },
{ lv: 2, s_lv: SkillLv.Lv2, t_num: 3, overrides: { TGroup: TGroup.Self, ap: 5 } },
{ lv: 4, s_lv: SkillLv.Lv3, t_num: 3, overrides: { TGroup: TGroup.Self, ap: 6 } },
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 5, overrides: { TGroup: TGroup.Self, ap: 2 } },
{ lv: 3, s_lv: SkillLv.Lv2, t_num: 5, overrides: { TGroup: TGroup.Self, ap: 3 } },
{ lv: 5, s_lv: SkillLv.Lv3, t_num: 5, overrides: { TGroup: TGroup.Self, ap: 4 } },
{ lv: 6, s_lv: SkillLv.Lv4, t_num: 5, overrides: { TGroup: TGroup.Self, ap: 5 } },
],
},
hp_bonus: [{ lv: 3, value: 150 }, { lv: 5, value: 150 }],
ap_bonus: [{ lv: 5, value: 10 }],
info: "<color=#27AE60>✧</color>受伤获得护盾",
info: "<color=#27AE60>✧</color>受击5次凝聚护盾",
},
5012: {
uuid: 5012, name: "不死小强", path: "hk2", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Melee, hp: 600, ap: 57,
@@ -368,7 +407,6 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 5, overrides: { TGroup: TGroup.Self, ap: 2 } },
],
},
hp_bonus: [{ lv: 3, value: 150 }],
info: "<color=#27AE60>✧</color>受伤恢复生命",
},
5013: {
@@ -385,7 +423,6 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 5, overrides: { TGroup: TGroup.Self, ap: 2 } },
],
},
hp_bonus: [{ lv: 3, value: 150 }],
info: "<color=#27AE60>✧</color>受伤获得护盾与生命",
},
5014: {
@@ -402,7 +439,6 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 4, s_lv: SkillLv.Lv3, t_num: 5, overrides: { TGroup: TGroup.Self, timed_buff_id: 7012, buff_value: 10, buff_duration: 6 } },
],
},
hp_bonus: [{ lv: 3, value: 150 }],
info: "<color=#27AE60>✧</color>受伤提升攻击与暴击",
},
5015: {
@@ -421,7 +457,6 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 4, s_lv: SkillLv.Lv3, t_num: 5, overrides: { TGroup: TGroup.Self, timed_buff_id: 7020, buff_value: 10, buff_duration: 6 } },
],
},
hp_bonus: [{ lv: 3, value: 200 }],
info: "<color=#27AE60>✧</color>受伤获得护盾与暴伤",
},
5016: {
@@ -438,8 +473,6 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 4, s_lv: SkillLv.Lv3, t_num: 5, overrides: { TGroup: TGroup.Self, timed_buff_id: 7017, buff_value: 10, buff_duration: 6 } },
],
},
ap_bonus: [{ lv: 3, value: 20 }],
hp_bonus: [{ lv: 3, value: 150 }],
info: "<color=#27AE60>✧</color>受伤提升攻击与击晕",
},
// 以下英雄暂时注释,保留待后续扩展
@@ -481,8 +514,6 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 5, s_lv: SkillLv.Lv2, t_num: 4, overrides: { TGroup: TGroup.Self, ap: 1 } },
],
},
ap_bonus: [{ lv: 3, value: 20 }],
hp_bonus: [{ lv: 3, value: 80 }],
info: "<color=#27AE60>✧</color>攻击永久提升攻击",
},
5022: {
@@ -499,8 +530,6 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 7, overrides: { TGroup: TGroup.Self, ap: 1 } },
],
},
ap_bonus: [{ lv: 3, value: 20 }],
hp_bonus: [{ lv: 3, value: 80 }],
info: "<color=#27AE60>✧</color>攻击限时提升暴击",
},
5023: {
@@ -517,7 +546,6 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 4, s_lv: SkillLv.Lv3, t_num: 5, overrides: { TGroup: TGroup.Self, timed_buff_id: 7012, buff_value: 10, buff_duration: 6 } },
],
},
hp_bonus: [{ lv: 3, value: 100 }],
info: "<color=#27AE60>✧</color>攻击提升攻击与暴击",
},
// 5022: {
@@ -550,7 +578,6 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 5, s_lv: SkillLv.Lv2, t_num: 5, overrides: { TGroup: TGroup.Team, hit_count: 2, ap: 1 } },
],
},
hp_bonus: [{ lv: 3, value: 60 }],
info: "<color=#27AE60>✧</color>攻击提升队友攻击",
},
5032: {
@@ -568,7 +595,6 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 4, s_lv: SkillLv.Lv2, t_num: 7, overrides: { TGroup: TGroup.Team, hit_count: 4, ap: 1 } },
],
},
hp_bonus: [{ lv: 3, value: 80 }],
info: "<color=#27AE60>✧</color>攻击限时提升队友暴击",
},
// 5032: {
@@ -592,7 +618,6 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 4, s_lv: SkillLv.Lv3, t_num: 7, overrides: { TGroup: TGroup.Team, hit_count: 4, timed_buff_id: 7020, buff_value: 10, buff_duration: 6 } },
],
},
hp_bonus: [{ lv: 3, value: 100 }],
info: "<color=#27AE60>✧</color>攻击提升队友攻击与暴伤",
},
// 5033: {
@@ -659,7 +684,6 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 5, s_lv: SkillLv.Lv4, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 6 } },
],
},
hp_bonus: [{ lv: 3, value: 100 }],
info: "<color=#27AE60>✧</color>死亡全队获得护盾",
},
5042: {
@@ -697,7 +721,6 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 5, s_lv: SkillLv.Lv3, t_num: 1, overrides: { TGroup: TGroup.Team, timed_buff_id: 7012, buff_value: 10, buff_duration: 6 } },
],
},
hp_bonus: [{ lv: 3, value: 100 }],
info: "<color=#27AE60>✧</color>死亡全队击晕与暴击",
},
5044: {
@@ -715,7 +738,6 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 5, s_lv: SkillLv.Lv3, t_num: 1, overrides: { TGroup: TGroup.Team, timed_buff_id: 7020, buff_value: 10, buff_duration: 6 } },
],
},
hp_bonus: [{ lv: 3, value: 150 }],
info: "<color=#27AE60>✧</color>死亡全队暴击与暴伤",
},
@@ -759,7 +781,6 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 5, s_lv: SkillLv.Lv3, t_num: 1, overrides: { TGroup: TGroup.Team, timed_buff_id: 7111, buff_value: 90, buff_duration: 10 } },
],
},
hp_bonus: [{ lv: 2, value: 80 }, { lv: 4, value: 80 }],
info: "<color=#27AE60>✧</color>回合开始全队提升攻击",
},
5052: {
@@ -776,7 +797,6 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 2 } },
],
},
hp_bonus: [{ lv: 3, value: 80 }],
info: "<color=#27AE60>✧</color>回合开始全队获得护盾",
},
// 5052: {
@@ -832,7 +852,6 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 2, uuids: [7202, 7006] },
{ lv: 4, uuids: [7202, 7206] },
],
hp_bonus: [{ lv: 3, value: 80 }],
info: "<color=#27AE60>✧</color>光环全队提升攻击\n<color=#9B59B6>✦</color>追加提升攻速",
},
@@ -849,7 +868,6 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 5, s_lv: SkillLv.Lv4, t_num: 5, overrides: { TGroup: TGroup.Team, hit_count: 2, ap: 300 } },
],
},
hp_bonus: [{ lv: 3, value: 80 }],
info: "<color=#27AE60>✧</color>攻击治疗低血队友",
},
// 5072: {
@@ -1058,11 +1076,13 @@ export function resolveReviveByLv(revive: LvReviveEntry[] | undefined, lv: numbe
return pick ? { s_uuid: pick.s_uuid, r_num: pick.r_num, upr: pick.upr } : undefined;
}
/** 计算目标等级下额外属性加成hp_bonus/ap_bonus 按 lv 累加 */
/** 计算目标等级下额外属性加成hp_bonus/ap_bonus 一次性奖励 + 成长类型每级递增 */
function getEvolveBonuses(hero: heroInfo, targetLv: number): { hp: number, ap: number } {
let hp = 0;
let ap = 0;
const lv = Math.max(1, targetLv);
// 成长类型线性增量(仅英雄生效,怪物无 grow_type 时走 Warrior 基准但不影响平衡,因怪物通常不填)
const grow = hero.fac === FacSet.HERO ? calcGrowBonus(hero, lv) : { hp: 0, ap: 0 };
let hp = grow.hp;
let ap = grow.ap;
if (hero.hp_bonus) {
for (const e of hero.hp_bonus) {
if (e.lv <= lv) hp += e.value;
@@ -1076,14 +1096,12 @@ function getEvolveBonuses(hero: heroInfo, targetLv: number): { hp: number, ap: n
return { hp, ap };
}
/** 计算目标等级下的有效 HP/AP含等级倍率与额外加成) */
/** 计算目标等级下的有效 HP/AP固定基础值 + 成长递增与一次性加成) */
function getEffectiveStats(hero: heroInfo, targetLv: number): { hp: number, ap: number } {
const bonuses = getEvolveBonuses(hero, targetLv);
const lv = Math.max(1, targetLv);
const multiplier = Math.pow(FightSet.HERO_LV_MULTIPLIER, lv - 1);
return {
hp: (hero.hp + bonuses.hp) * multiplier,
ap: (hero.ap + bonuses.ap) * multiplier,
hp: hero.hp + bonuses.hp,
ap: hero.ap + bonuses.ap,
};
}

View File

@@ -5,7 +5,7 @@ import { smc } from "../common/SingletonModuleComp";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { HeroViewComp } from "./HeroViewComp";
import { BoxSet, FacSet, FightSet, IndexSet } from "../common/config/GameSet";
import { HeroInfo, HeroPos, resolveFormationTargetX, resolveTriggerByLv, resolveFieldByLv, resolveReviveByLv } from "../common/config/heroSet";
import { HeroInfo, HeroPos, resolveFormationTargetX, resolveTriggerByLv, resolveFieldByLv, resolveReviveByLv, calcGrowBonus } from "../common/config/heroSet";
import { GameEvent } from "../common/config/GameEvent";
import { SkillTriggerType } from "../common/config/heroSet";
import { SkillTriggerHelper } from "./SkillTriggerHelper";
@@ -119,6 +119,7 @@ export class Hero extends ecs.Entity {
model.card_lv = card_lv;
model.base_card_lv = hero.card_lv ?? 1;
model.type = hero.type;
model.grow_type = hero.grow_type;
model.fac = FacSet.HERO;
model.dis = hero.dis ?? 720;
model.posIndex = posIndex;
@@ -138,10 +139,9 @@ export class Hero extends ecs.Entity {
model.ap_bonus = hero.ap_bonus;
model.hp_bonus = hero.hp_bonus;
// 基础属性按等级倍率初始化
// 使用指数增长公式:等级 2 时为原来的 HERO_LV_MULTIPLIER 倍,等级 3 时为 HERO_LV_MULTIPLIER^2 倍
let base_ap = hero.ap * Math.pow(FightSet.HERO_LV_MULTIPLIER, model.lv - 1);
let base_hp = hero.hp * Math.pow(FightSet.HERO_LV_MULTIPLIER, model.lv - 1);
// 基础属性:固定基础值,成长完全由 grow_type 指数递增提供(替代旧 ×2^(lv-1) 翻倍)
let base_ap = hero.ap;
let base_hp = hero.hp;
model.base_ap = base_ap;
model.base_hp = base_hp;
@@ -162,8 +162,8 @@ export class Hero extends ecs.Entity {
for (const key in hero.skills) {
const skill = hero.skills[key];
if (!skill) continue;
// 最终技能等级 = 初始技能等级 + 英雄等级增量,下限 0
model.skills[skill.uuid] = { ...skill, lv: Math.max(0, skill.lv + hero_lv - 2), ccd: 0 };
// 最终技能等级 = 初始技能等级 + 英雄等级增量,下限 0、上限封顶(无限升级下技能不涨过上限)
model.skills[skill.uuid] = { ...skill, lv: Math.min(FightSet.HERO_SKILL_MAX_LV, Math.max(0, skill.lv + hero_lv - 2)), ccd: 0 };
}
// 按当前英雄等级 resolve 触发技能/光环/复活到运行时缓存,供 SkillTriggerHelper 消费
@@ -176,7 +176,11 @@ export class Hero extends ecs.Entity {
model.runtime_field = resolveFieldByLv(model.field, hero_lv);
model.runtime_revive = resolveReviveByLv(model.revive, hero_lv);
// 应用额外属性加成hp_bonus/ap_bonus 按 ≤英雄等级 累加)
// 应用额外属性加成(成长类型每级递增 + hp_bonus/ap_bonus 一次性奖励累加)
// 成长增量仅英雄生效model.fac 已为 HERO按 grow_type 递增曲线累计
const grow = calcGrowBonus(hero, hero_lv);
model.hp += grow.hp; model.hp_max += grow.hp;
model.ap += grow.ap;
if (model.hp_bonus) {
let hpExtra = 0;
for (const e of model.hp_bonus) { if (e.lv <= hero_lv) hpExtra += e.value; }

View File

@@ -28,6 +28,7 @@ export class HeroAttrsComp extends ecs.Comp {
base_card_lv: number = 1;
type: number = 0; // 0近战 1远程 2辅助
fac: number = 0; // 0:hero 1:monster
grow_type?: number; // 成长类型HGrowType驱动每级 hp/ap 线性成长
// ==================== 基础属性(有初始值) ====================
base_ap: number = 0; // 原始基础攻击(无任何加成)
base_hp: number = 0; // 原始基础血量(无任何加成)

View File

@@ -483,6 +483,9 @@ export class CHeroComp extends CCComp {
atked: hero.atked,
field: hero.field,
revive: hero.revive,
grow_type: hero.grow_type,
ap: hero.ap,
hp: hero.hp,
};
}

View File

@@ -18,7 +18,7 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
import { oops } from "db://oops-framework/core/Oops";
import { GameEvent } from "../common/config/GameEvent";
import { HeroInfo } from "../common/config/heroSet";
import { HeroInfo, calcGrowBonus } from "../common/config/heroSet";
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { Hero } from "../hero/Hero";
import { FacSet, FightSet, getLvColor, NumberFormatter } from "../common/config/GameSet";
@@ -165,11 +165,7 @@ export class HeroBoxComp extends CCComp {
private onUpgradeHeroClick() {
if (!this.eid || !this.model) return;
const heroLv = Math.max(1, Math.floor(this.model.lv ?? 1));
// 已满级:气泡提示,复用英雄栏 smalltip 挂载点
if (heroLv >= FightSet.HERO_MAX_LV) {
oops.message.dispatchEvent(GameEvent.ShowSmallTip, { type: "hero_full", text: "已达最高等级" });
return;
}
// 无限升级:不再拦截满级
oops.audio.playEffect("music/button");
const nextLv = heroLv + 1;
@@ -226,13 +222,16 @@ export class HeroBoxComp extends CCComp {
atked: hero.atked,
field: hero.field,
revive: hero.revive,
grow_type: hero.grow_type,
ap: hero.ap,
hp: hero.hp,
});
// ---- 属性成长(按 HERO_LV_MULTIPLIER 幂成长 + 等级额外加成) ----
// ---- 属性成长(固定基础值 + 成长类型指数递增 + 一次性加成) ----
const attrLine = (lv: number): string => {
const mult = Math.pow(FightSet.HERO_LV_MULTIPLIER, lv - 1);
const ap = Math.floor(hero.ap * mult + HeroBoxComp.sumBonus(hero.ap_bonus, lv));
const hp = Math.floor(hero.hp * mult + HeroBoxComp.sumBonus(hero.hp_bonus, lv));
const grow = calcGrowBonus(hero, lv);
const ap = Math.floor(hero.ap + grow.ap + HeroBoxComp.sumBonus(hero.ap_bonus, lv));
const hp = Math.floor(hero.hp + grow.hp + HeroBoxComp.sumBonus(hero.hp_bonus, lv));
return `攻击 <color=#27AE60>${ap}</color> 生命 <color=#27AE60>${hp}</color>`;
};
@@ -548,17 +547,13 @@ export class HeroBoxComp extends CCComp {
this.sell_price_label.string = `${MissionEconomy.getSellGold(lv)}`;
}
// ---- 升级费用(满级隐藏;金币不足时标红提示) ----
// ---- 升级费用(无限升级:始终显示;金币不足时标红提示) ----
if (this.upgrade_price_label && this.upgrade_price_label.isValid) {
const lv = Math.max(1, Math.floor(this.model.lv ?? 1));
if (lv >= FightSet.HERO_MAX_LV) {
this.upgrade_price_label.string = "";
} else {
const cost = MissionEconomy.getUpgradeCost(lv);
this.upgrade_price_label.string = `${cost}`;
// 金币不足标红,足够恢复默认色,给玩家直观的可升级反馈
this.upgrade_price_label.color = MissionEconomy.getCoin() >= cost ? Color.WHITE : HeroBoxComp.INSUFFICIENT_COLOR;
}
const cost = MissionEconomy.getUpgradeCost(lv);
this.upgrade_price_label.string = `${cost}`;
// 金币不足标红,足够恢复默认色,给玩家直观的可升级反馈
this.upgrade_price_label.color = MissionEconomy.getCoin() >= cost ? Color.WHITE : HeroBoxComp.INSUFFICIENT_COLOR;
}
// ---- AP / HP ----

View File

@@ -52,7 +52,7 @@ import { MissSkillsComp } from "./MissSkillsComp";
import { oops } from "db://oops-framework/core/Oops";
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { smc } from "../common/SingletonModuleComp";
import { HeroInfo, HType, resolveTriggerByLv, resolveFieldByLv, resolveReviveByLv } from "../common/config/heroSet";
import { HeroInfo, HType, resolveTriggerByLv, resolveFieldByLv, resolveReviveByLv, calcGrowBonus } from "../common/config/heroSet";
import { HeroViewComp } from "../hero/HeroViewComp";
import { FacSet, FightSet } from "../common/config/GameSet";
import { MissionEconomy } from "./MissionEconomy";
@@ -771,10 +771,7 @@ export class MissionCardComp extends CCComp {
if (!eid) return;
const actor = this.queryAliveHeroActors().find(item => item.eid === eid);
if (!actor) return;
if (actor.model.lv >= FightSet.HERO_MAX_LV) {
oops.gui.toast(`已达最高等级`);
return;
}
// 无限升级:不再拦截满级,仅校验金币
// 统一经济管理入口:按当前等级扣费
if (!MissionEconomy.executeUpgradeCost(actor.model.lv)) {
oops.gui.toast(`金币不足`);
@@ -2192,8 +2189,7 @@ export class MissionCardComp extends CCComp {
if (!heroEid) return false;
const actor = this.queryAliveHeroActors().find(item => item.eid === heroEid);
if (!actor) return false;
if (actor.model.lv >= FightSet.HERO_MAX_LV) return false;
const nextLv = Math.min(FightSet.HERO_MAX_LV, actor.model.lv + 1);
const nextLv = actor.model.lv + 1;
this.applyHeroLevel(actor.model, nextLv);
if (actor.view) {
actor.view.playBuff("buff_lvup");
@@ -2202,8 +2198,8 @@ export class MissionCardComp extends CCComp {
}
/**
* 应用英雄等级变化(属性按 HERO_LV_MULTIPLIER 重新计算)。
* 沿用旧版本 SkillLvUp 的技能等级映射规则。
* 应用英雄等级变化(属性 = 固定基础值 + grow_type 指数递增重新计算)。
* 沿用旧版本 SkillLvUp 的技能等级映射规则(技能按 HERO_SKILL_MAX_LV 封顶)
*
* 与 Hero.load 保持一致:
* 1. 同步刷新 base_ap/base_hp 快照,下游 UI/复活读到当前 lv 的基础值
@@ -2214,13 +2210,13 @@ export class MissionCardComp extends CCComp {
public applyHeroLevel(model: HeroAttrsComp, targetLv: number) {
const hero = HeroInfo[model.hero_uuid];
if (!hero) return;
const nextLv = Math.max(1, Math.min(FightSet.HERO_MAX_LV, Math.floor(targetLv)));
const nextLv = Math.max(1, Math.floor(targetLv));
const hpRate = model.hp_max > 0 ? model.hp / model.hp_max : 1;
model.lv = nextLv;
// 同步 base 快照(与 Hero.load 同公式),保证 UI/复活等下游读到当前 lv 的基础值
const base_ap = hero.ap * Math.pow(FightSet.HERO_LV_MULTIPLIER, nextLv - 1);
const base_hp = hero.hp * Math.pow(FightSet.HERO_LV_MULTIPLIER, nextLv - 1);
// 同步 base 快照(与 Hero.load 同公式:固定基础值,成长由 grow_type 指数递增提供)
const base_ap = hero.ap;
const base_hp = hero.hp;
model.base_ap = base_ap;
model.base_hp = base_hp;
@@ -2233,7 +2229,10 @@ export class MissionCardComp extends CCComp {
model.hp_max = base_hp;
}
// 按 ≤nextLv 累加 hp_bonus/ap_bonus沿用 Hero.load 的加法区规则)
// 按 ≤nextLv 累加:成长类型每级递增 + hp_bonus/ap_bonus 一次性奖励(沿用 Hero.load 的加法区规则)
const grow = calcGrowBonus(hero, nextLv);
model.hp_max += grow.hp;
model.ap += grow.ap;
if (model.hp_bonus) {
let hpExtra = 0;
for (const e of model.hp_bonus) { if (e.lv <= nextLv) hpExtra += e.value; }
@@ -2262,7 +2261,7 @@ export class MissionCardComp extends CCComp {
for (const key in hero.skills) {
const skill = hero.skills[key];
if (!skill) continue;
model.skills[skill.uuid] = { ...skill, lv: Math.max(0, skill.lv + nextLv - 2), ccd: 0 };
model.skills[skill.uuid] = { ...skill, lv: Math.min(FightSet.HERO_SKILL_MAX_LV, Math.max(0, skill.lv + nextLv - 2)), ccd: 0 };
}
model.updateSkillDistanceCache();
model.dirty_hp = true;

View File

@@ -110,12 +110,12 @@ export class MissionEconomy {
}
/**
* 计算英雄升级金币消耗(按当前等级线性缩放Lv×5
* 计算英雄升级金币消耗(指数递增5×2^(lv-1),无限升级下指数控速
* @param heroLevel 当前英雄等级(升级前)
*/
static getUpgradeCost(heroLevel: number = 1): number {
const lv = Math.max(1, Math.floor(heroLevel));
return lv * 5;
return Math.floor(5 * Math.pow(2, lv - 1));
}
/**
@@ -128,11 +128,14 @@ export class MissionEconomy {
}
/**
* 计算英雄出售金币(按英雄等级缩放)
* 计算英雄出售金币(按累计升级投入的 50% 折算,无限升级自动缩放)
* 累计投入 = 等比数列求和 Σ5×2^(i-1)i=1..lv-1= 5×(2^(lv-1) - 1)。
*/
static getSellGold(heroLevel: number = 1): number {
const sellByLevel: Record<number, number> = { 1: 3, 2: 10, 3: 25 };
const baseSellGold = sellByLevel[heroLevel] || 3;
const lv = Math.max(1, Math.floor(heroLevel));
// 基础出售价 3保底招募残值+ 累计升级投入的 50%
const investTotal = 5 * (Math.pow(2, lv - 1) - 1);
const baseSellGold = 3 + Math.floor(investTotal * 0.5);
// 驻场英雄带来的"出售金币"加成(包含原 SellBonus 语义,统一在 SellGold 中)
const goldBoost = FieldSkillHelper.getFieldSkillTotalValue(FieldSkillType.SellGold);
const totalSellGold = baseSellGold + goldBoost;