5 Commits

Author SHA1 Message Date
pan
ccce8a11ea fix(hero): adjust default hero movement speed from 100 to 200
将英雄默认移动速度从100修改为200,调整基础战斗体验参数
2026-08-12 11:16:38 +08:00
pan
762dc91a11 fix(hero&move): 调整英雄基础移速并优化近战逻辑
将默认移速从800改为100,重构近战战斗移动逻辑:
1. 增加敌人存在校验,无敌人时恢复原编队战斗逻辑
2. 新增近战接触距离常量,让英雄贴近敌人后再停下攻击
3. 优化移动速度和停止位置计算,确保近战贴脸输出
2026-08-12 11:16:20 +08:00
pan
4594f673b4 feat(hero): 新增英雄职业定位系统,优化编队站位逻辑
1. 新增HRole枚举与角色定位配置,解耦成长类型与阵容职能
2. 重构移动系统的编队优先级计算,基于职业定位重新排序站位
3. 重写单行编队站位算法,根据队伍人数自动适配紧凑/松散排布
4. 为所有预设英雄配置对应职业定位
2026-08-12 11:04:59 +08:00
pan
426dc24899 feat(config): add official recommended hero combo configurations
新增官方推荐英雄阵容配置,包含6套标准铁三角阵容,支持新手引导、阵容UI展示与自动布阵使用,同时提供低配替代阵容供新手期使用。
2026-08-12 09:45:46 +08:00
pan
1609d3971a refactor(hero desc): 调整技能描述渲染逻辑,适配不同场景显示需求
1. 新增showName参数控制技能名称是否展示,区分英雄弹窗和卡池场景
2. 修正英雄预制体中角色的Y轴偏移量
3. 同步更新函数注释和调用传参
2026-08-12 09:39:24 +08:00
7 changed files with 249 additions and 102 deletions

View File

@@ -4746,7 +4746,7 @@
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 22.263000000000016,
"y": 14.014,
"z": 0
},
"_lrot": {

View File

@@ -1,4 +1,4 @@
/**
/**
* @file HeroSkillDesc.ts
* @description 英雄技能/能力描述的【唯一文字表达标准层】
*
@@ -554,10 +554,11 @@ export function buildEquipRich(entries: FieldEntry[], showName: boolean = false)
* 数据源为运行时 HeroAttrsComplv 取当前英雄等级),逐触发类型逐技能渲染。
* 技能等级取 LvTriggerEntry.s_lv未标注时按档位序号1/2/3...)兜底。
*
* @param source 英雄运行时数据源(含 lv 与触发技能分组)
* @returns 多行富文本串,\n 分隔
* @param source 英雄运行时数据源(含 lv 与触发技能分组)
* @param showName 是否展示「技能名」片段(英雄信息弹窗 true英雄卡池卡片 false
* @returns 多行富文本串,\n 分隔
*/
export function buildSkillDescMoreRich(source: ISkillDescSource): string {
export function buildSkillDescMoreRich(source: ISkillDescSource, showName: boolean = true): string {
const lv = Math.max(1, source.lv ?? 1);
const lines: string[] = [];
@@ -599,7 +600,7 @@ export function buildSkillDescMoreRich(source: ISkillDescSource): string {
? `${trigName}${valueRich(cur.t_num)}`
: trigName;
// 技能名直接展示,不附加档位数字后缀(由 lv 字段控制生效档,玩家无需关注内部档号)
const curHead = `${skillName}」: ${curTrig}, `;
const curHead = showName ? `${skillName}」: ${curTrig}, ` : `${curTrig}, `;
lines.push(effectRich(buildEffectLine(curHead, mergeSkillParams(base, cur.overrides))));
}
}
@@ -609,7 +610,7 @@ export function buildSkillDescMoreRich(source: ISkillDescSource): string {
if (fieldEntries?.length) {
for (const entry of resolveFieldByLv(fieldEntries, lv)) {
const baseName = getTriggerSkillName(SkillTriggerType.Field, entry.uuid);
const line = buildFieldLine("", entry, true, baseName);
const line = buildFieldLine("", entry, showName, baseName);
if (line) lines.push(effectRich(line));
}
}
@@ -618,7 +619,9 @@ export function buildSkillDescMoreRich(source: ISkillDescSource): string {
const revive = resolveReviveByLv(source[SkillTriggerType.Revive] as LvReviveEntry[] | undefined, lv);
if (revive) {
const reviveName = getTriggerSkillName(SkillTriggerType.Revive, revive.s_uuid);
lines.push(`${skillNameRich(reviveName)}: 每回合拥有${valueRich(revive.r_num)}次重生机会, 死亡后以${valueRich(`${Math.round(revive.upr * 100)}%`)}血量原地重生`);
lines.push(showName
? `${skillNameRich(reviveName)}: 每回合拥有${valueRich(revive.r_num)}次重生机会, 死亡后以${valueRich(`${Math.round(revive.upr * 100)}%`)}血量原地重生`
: `每回合拥有${valueRich(revive.r_num)}次重生机会, 死亡后以${valueRich(`${Math.round(revive.upr * 100)}%`)}血量原地重生`);
}
return lines.join("\n");

View File

@@ -128,6 +128,29 @@ export const HERO_GROW_COEF: Record<HGrowType, { hp: number; ap: number }> = {
[HGrowType.Support]: { hp: 1.1, ap: 0.7 },
};
/**
* 英雄位置类型(职业定位)。
* Why: grow_type 仅驱动数值成长曲线,无法表达"这个英雄在阵容里干什么"
* role 面向 UI 展示与阵容推荐(如 HeroComboSet 的铁三角校验),与成长解耦。
*/
export enum HRole {
Tank = 0, // 坦克:前排承伤,受击发动机
Warrior = 1, // 战士:前排输出/献祭位
Assassin = 2, // 刺客近战爆发主C
Archer = 3, // 射手远程物理主C
Mage = 4, // 法师:远程先手增益
Support = 5, // 辅助:光环/治疗
}
export const HRoleName: Record<HRole, string> = {
[HRole.Tank]: "坦克",
[HRole.Warrior]: "战士",
[HRole.Assassin]: "刺客",
[HRole.Archer]: "射手",
[HRole.Mage]: "法师",
[HRole.Support]: "辅助",
};
/**
* 计算英雄在目标等级下的累计属性成长增量(线性:每级加一份"基础属性×系数")。
* 公式:累计 = 基础属性 × 类型系数 × (lv - 1)。
@@ -282,6 +305,7 @@ export interface heroInfo {
card_lv?: number; // 卡牌等级(驱动 bg_node 颜色1=green 2=blue 3=purple 4=red 5=yellow
type: HType; // 攻击定位(近战/中程/远程)
grow_type?: HGrowType; // 成长类型(决定每级 hp/ap 标准增量,默认 Warrior
role?: HRole; // 位置类型/职业定位(英雄专用,怪物用 monType 表达)
monType?: MonType; // 怪物专属类型
hp: number; // 生命值上限
ap: number; // 攻击力
@@ -378,7 +402,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5001 铁壁战士 — atked 护盾标杆原5011
5001: {
uuid: 5001, name: "铁壁战士", path: "hk1", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, hp: 300, ap: 28,
uuid: 5001, name: "铁壁战士", path: "hk1", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, role: HRole.Tank, hp: 300, ap: 28,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
atked: {
6301: [
@@ -391,7 +415,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5002 影袭刺客 — atking 自身攻击
5002: {
uuid: 5002, name: "影袭刺客", path: "hc1", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Melee, grow_type: HGrowType.Assassin, hp: 300, ap: 28,
uuid: 5002, name: "影袭刺客", path: "hc1", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Melee, grow_type: HGrowType.Assassin, role: HRole.Assassin, hp: 300, ap: 28,
skills: { 6003: { uuid: 6003, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast2].cd, ccd: 0 } },
atking: {
6401: [
@@ -404,7 +428,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5003 遗志将军 — dead 全队攻击
5003: {
uuid: 5003, name: "遗志将军", path: "hk2", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, hp: 300, ap: 28,
uuid: 5003, name: "遗志将军", path: "hk2", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, role: HRole.Warrior, hp: 300, ap: 28,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
dead: {
6401: [
@@ -417,7 +441,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5004 破晓先知 — fstart 全队攻击
5004: {
uuid: 5004, name: "破晓先知", path: "hm1", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, hp: 143, ap: 40,
uuid: 5004, name: "破晓先知", path: "hm1", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Mage, hp: 143, ap: 40,
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid3].cd, ccd: 0 } },
fstart: {
6401: [
@@ -430,7 +454,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5005 战歌法师 — field 攻击光环
5005: {
uuid: 5005, name: "战歌法师", path: "hm2", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, hp: 143, ap: 40,
uuid: 5005, name: "战歌法师", path: "hm2", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Support, hp: 143, ap: 40,
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid3].cd, ccd: 0 } },
field: [
{ lv: 1, uuids: [{ uuid: 7002, value: 0.1 }] },
@@ -444,7 +468,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5101 不死斗士 — atked 回血(更高数值)
5101: {
uuid: 5101, name: "不死斗士", path: "hk3", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, hp: 600, ap: 57,
uuid: 5101, name: "不死斗士", path: "hk3", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, role: HRole.Tank, hp: 600, ap: 57,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
atked: {
6302: [
@@ -457,7 +481,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5102 致命杀手 — atking 暴击(更高数值)
5102: {
uuid: 5102, name: "致命杀手", path: "hc2", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Melee, grow_type: HGrowType.Assassin, hp: 600, ap: 57,
uuid: 5102, name: "致命杀手", path: "hc2", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Melee, grow_type: HGrowType.Assassin, role: HRole.Assassin, hp: 600, ap: 57,
skills: { 6003: { uuid: 6003, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast2].cd, ccd: 0 } },
atking: {
6403: [
@@ -470,7 +494,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5103 殉道勇士 — dead 全队护盾(更高数值)
5103: {
uuid: 5103, name: "殉道勇士", path: "hk4", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, hp: 600, ap: 57,
uuid: 5103, name: "殉道勇士", path: "hk4", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, role: HRole.Warrior, hp: 600, ap: 57,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
dead: {
6301: [
@@ -483,7 +507,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5104 圣光祭司 — fstart 全队护盾(更高数值)
5104: {
uuid: 5104, name: "圣光祭司", path: "hm3", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, hp: 287, ap: 80,
uuid: 5104, name: "圣光祭司", path: "hm3", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Mage, hp: 287, ap: 80,
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow1].cd, ccd: 0 } },
fstart: {
6301: [
@@ -496,7 +520,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5105 风怒先知 — field 攻速光环
5105: {
uuid: 5105, name: "风怒先知", path: "hm1", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, hp: 287, ap: 80,
uuid: 5105, name: "风怒先知", path: "hm1", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Support, hp: 287, ap: 80,
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
field: [
{ lv: 1, uuids: [{ uuid: 7006, value: 0.1 }] },
@@ -507,7 +531,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5106 破甲射手 — atking 自身穿透伤害计时buff增强
5106: {
uuid: 5106, name: "破甲射手", path: "ha1", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Long, grow_type: HGrowType.Assassin, hp: 287, ap: 80,
uuid: 5106, name: "破甲射手", path: "ha1", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Long, grow_type: HGrowType.Assassin, role: HRole.Archer, hp: 287, ap: 80,
skills: { 6008: { uuid: 6008, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
atking: {
6512: [
@@ -523,7 +547,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5201 怒血勇士 — atked 全队攻击(范围扩大)
5201: {
uuid: 5201, name: "怒血勇士", path: "hk5", fac: FacSet.HERO, card_lv: 3, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, hp: 900, ap: 85,
uuid: 5201, name: "怒血勇士", path: "hk5", fac: FacSet.HERO, card_lv: 3, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, role: HRole.Tank, hp: 900, ap: 85,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
atked: {
6401: [
@@ -536,7 +560,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5202 援护射手 — atking 队友攻击(范围扩大)
5202: {
uuid: 5202, name: "援护射手", path: "ha1", fac: FacSet.HERO, card_lv: 3, lv: 1, type: HType.Long, grow_type: HGrowType.Assassin, hp: 430, ap: 120,
uuid: 5202, name: "援护射手", path: "ha1", fac: FacSet.HERO, card_lv: 3, lv: 1, type: HType.Long, grow_type: HGrowType.Assassin, role: HRole.Archer, hp: 430, ap: 120,
skills: { 6008: { uuid: 6008, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal2].cd, ccd: 0 } },
atking: {
6401: [
@@ -549,7 +573,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5203 传承卫士 — dead 全队生命(范围扩大)
5203: {
uuid: 5203, name: "传承卫士", path: "hk1", fac: FacSet.HERO, card_lv: 3, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, hp: 900, ap: 85,
uuid: 5203, name: "传承卫士", path: "hk1", fac: FacSet.HERO, card_lv: 3, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, role: HRole.Warrior, hp: 900, ap: 85,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
dead: {
6402: [
@@ -562,7 +586,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5204 晨光贤者 — fstart 全队暴击(范围扩大)
5204: {
uuid: 5204, name: "晨光贤者", path: "hm2", fac: FacSet.HERO, card_lv: 3, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, hp: 430, ap: 120,
uuid: 5204, name: "晨光贤者", path: "hm2", fac: FacSet.HERO, card_lv: 3, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Mage, hp: 430, ap: 120,
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid3].cd, ccd: 0 } },
fstart: {
6403: [
@@ -575,7 +599,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5205 精准术士 — field 暴击光环(范围扩大)
5205: {
uuid: 5205, name: "精准术士", path: "hm3", fac: FacSet.HERO, card_lv: 3, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, hp: 430, ap: 120,
uuid: 5205, name: "精准术士", path: "hm3", fac: FacSet.HERO, card_lv: 3, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Support, hp: 430, ap: 120,
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
field: [
{ lv: 1, uuids: [{ uuid: 7004, value: 0.1 }] },
@@ -589,7 +613,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5301 复仇守护者 — atked 全队暴击(范围+数值)
5301: {
uuid: 5301, name: "复仇守护者", path: "hk2", fac: FacSet.HERO, card_lv: 4, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, hp: 1200, ap: 113,
uuid: 5301, name: "复仇守护者", path: "hk2", fac: FacSet.HERO, card_lv: 4, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, role: HRole.Tank, hp: 1200, ap: 113,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow1].cd, ccd: 0 } },
atked: {
6403: [
@@ -602,7 +626,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5302 破灭游侠 — atking 队友暴伤(范围+数值)
5302: {
uuid: 5302, name: "破灭游侠", path: "ha2", fac: FacSet.HERO, card_lv: 4, lv: 1, type: HType.Long, grow_type: HGrowType.Assassin, hp: 573, ap: 160,
uuid: 5302, name: "破灭游侠", path: "ha2", fac: FacSet.HERO, card_lv: 4, lv: 1, type: HType.Long, grow_type: HGrowType.Assassin, role: HRole.Archer, hp: 573, ap: 160,
skills: { 6008: { uuid: 6008, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal1].cd, ccd: 0 } },
atking: {
6404: [
@@ -615,7 +639,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5303 亡语刺客 — dead 全队暴击+暴伤(双效果)
5303: {
uuid: 5303, name: "亡语刺客", path: "hc3", fac: FacSet.HERO, card_lv: 4, lv: 1, type: HType.Melee, grow_type: HGrowType.Assassin, hp: 1200, ap: 113,
uuid: 5303, name: "亡语刺客", path: "hc3", fac: FacSet.HERO, card_lv: 4, lv: 1, type: HType.Melee, grow_type: HGrowType.Assassin, role: HRole.Assassin, hp: 1200, ap: 113,
skills: { 6003: { uuid: 6003, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast3].cd, ccd: 0 } },
dead: {
6403: [
@@ -633,7 +657,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5304 战吼术士 — fstart 全队攻击+生命(双效果)
5304: {
uuid: 5304, name: "战吼术士", path: "hm1", fac: FacSet.HERO, card_lv: 4, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, hp: 573, ap: 160,
uuid: 5304, name: "战吼术士", path: "hm1", fac: FacSet.HERO, card_lv: 4, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Mage, hp: 573, ap: 160,
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid1].cd, ccd: 0 } },
fstart: {
6401: [
@@ -651,7 +675,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5305 领域贤者 — field 暴伤+攻速光环(双效果)
5305: {
uuid: 5305, name: "领域贤者", path: "hm2", fac: FacSet.HERO, card_lv: 4, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, hp: 573, ap: 160,
uuid: 5305, name: "领域贤者", path: "hm2", fac: FacSet.HERO, card_lv: 4, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Support, hp: 573, ap: 160,
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
field: [
{ lv: 1, uuids: [{ uuid: 7005, value: 0.2 }, { uuid: 7006, value: 0.1 }] },
@@ -665,7 +689,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5401 不灭战魂 — atked 攻击 + revive双技能
5401: {
uuid: 5401, name: "不灭战魂", path: "hk3", fac: FacSet.HERO, card_lv: 5, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, hp: 1500, ap: 142,
uuid: 5401, name: "不灭战魂", path: "hk3", fac: FacSet.HERO, card_lv: 5, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, role: HRole.Tank, hp: 1500, ap: 142,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
atked: {
6401: [
@@ -683,7 +707,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5402 战歌游侠 — atking 攻击 + field 攻速光环(双技能)
5402: {
uuid: 5402, name: "战歌游侠", path: "ha3", fac: FacSet.HERO, card_lv: 5, lv: 1, type: HType.Long, grow_type: HGrowType.Assassin, hp: 717, ap: 200,
uuid: 5402, name: "战歌游侠", path: "ha3", fac: FacSet.HERO, card_lv: 5, lv: 1, type: HType.Long, grow_type: HGrowType.Assassin, role: HRole.Archer, hp: 717, ap: 200,
skills: { 6008: { uuid: 6008, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast3].cd, ccd: 0 } },
atking: {
6401: [
@@ -701,7 +725,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5403 凤凰祭司 — dead 攻击+护盾 + revive双技能
5403: {
uuid: 5403, name: "凤凰祭司", path: "hk4", fac: FacSet.HERO, card_lv: 5, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, hp: 1500, ap: 142,
uuid: 5403, name: "凤凰祭司", path: "hk4", fac: FacSet.HERO, card_lv: 5, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, role: HRole.Warrior, hp: 1500, ap: 142,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
dead: {
6401: [
@@ -724,7 +748,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5404 涅槃贤者 — fstart 护盾+攻击 + revive双技能
5404: {
uuid: 5404, name: "涅槃贤者", path: "hm3", fac: FacSet.HERO, card_lv: 5, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, hp: 717, ap: 200,
uuid: 5404, name: "涅槃贤者", path: "hm3", fac: FacSet.HERO, card_lv: 5, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Mage, hp: 717, ap: 200,
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal3].cd, ccd: 0 } },
fstart: {
6301: [
@@ -747,7 +771,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5405 圣言诗人 — atking 治疗 + field 生命光环(双技能)
5405: {
uuid: 5405, name: "圣言诗人", path: "hh1", fac: FacSet.HERO, card_lv: 5, lv: 1, type: HType.Long, grow_type: HGrowType.Support, hp: 717, ap: 200,
uuid: 5405, name: "圣言诗人", path: "hh1", fac: FacSet.HERO, card_lv: 5, lv: 1, type: HType.Long, grow_type: HGrowType.Support, role: HRole.Support, hp: 717, ap: 200,
skills: { 6015: { uuid: 6015, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal2].cd, ccd: 0 } },
atking: {
6302: [
@@ -853,6 +877,73 @@ export const HeroList: number[] = [
5401, 5402, 5403, 5404, 5405,
];
// ==================== 英雄推荐阵容配置 ====================
/**
* 推荐阵容条目。
* Why: 场上最多 3 个英雄FightSet.HERO_MAX_NUM围绕"发动机 → 增益 → 主C"铁三角结构
* 预置官方配合组合,供抽卡引导、阵容推荐 UI 与 AI 自动布阵消费。
*/
export interface HeroComboInfo {
uuid: number; // 阵容唯一标识8000 段)
name: string; // 阵容名称(体现流派)
heroes: number[]; // 英雄 uuid 列表,固定 3 个;索引 0=前排,其余按站位从近到远
desc: string; // 配合思路说明(信息面板展示)
low_heroes?: number[]; // 低配替代阵容card_lv 1~2供新手期引导
}
/**
* 推荐阵容表。
* 设计原则:
* 1. 铁三角结构1 前排(坦克/献祭)+ 1 增益(光环/先手)+ 1 主C
* 2. 乘区分散:攻击/暴击/暴伤/攻速各占独立乘区,组合优先覆盖不同乘区避免稀释
* 3. 发动机冗余:献祭/反击流的发动机依赖前排挨打或死亡revive 卡5401/5403/5404负责重复点火
*/
export const HeroComboSet: Record<number, HeroComboInfo> = {
// 8001 暴击爆发流 — 暴击率与暴伤双乘区互不稀释,成型最强
8001: {
uuid: 8001, name: "暴击爆发流",
heroes: [5301, 5302, 5402],
desc: "复仇守护者挨打为全队提供暴击率,破灭游侠普攻为队友叠加暴伤,战歌游侠以极速普攻吃满暴击×暴伤双乘区完成收割。",
},
// 8002 献祭复活流 — revive 让死亡发动机循环点火
8002: {
uuid: 8002, name: "献祭复活流",
heroes: [5403, 5303, 5102],
desc: "凤凰祭司死亡为全队加攻击与护盾,复活后可再次献祭;亡语刺客死亡送出暴击+暴伤双增益全部灌给主C致命杀手。",
low_heroes: [5003, 5103, 5002],
},
// 8003 铁桶反击流 — 挨打次数越多增益越多,最稳
8003: {
uuid: 8003, name: "铁桶反击流",
heroes: [5401, 5201, 5405],
desc: "双前排持续挨打:怒血勇士为全队永久叠加攻击,不灭战魂自我强化并以复活兜底;圣言诗人治疗+生命光环保证前排挨得起。",
},
// 8004 先手速攻流 — 开局 buff 窗口内快速收割,短回合最强
8004: {
uuid: 8004, name: "先手速攻流",
heroes: [5304, 5004, 5002],
desc: "战吼术士与破晓先知双重开局战吼直接拉满全队攻击,影袭刺客凭最快攻速与自身攻击滚雪球在增益窗口内结束战斗。",
low_heroes: [5004, 5005, 5002],
},
// 8005 双光环平A流 — 攻击×攻速光环乘算,低配易成型
8005: {
uuid: 8005, name: "双光环平A流",
heroes: [5101, 5005, 5105],
desc: "战歌法师攻击光环与风怒先知攻速光环互相乘算,不死斗士前排自我回血稳站,全队普攻输出被双光环持续放大。",
low_heroes: [5001, 5005, 5105],
},
// 8006 续航消耗流 — 双奶抬血线,克制高血 Boss 的持久战
8006: {
uuid: 8006, name: "续航消耗流",
heroes: [5101, 5405, 5106],
desc: "不死斗士受击回血顶前排,圣言诗人治疗+生命光环抬全队血线,破甲射手以穿透增益磨掉高血 Boss专打持久战。",
},
};
/** 推荐阵容 id 列表(按品质/成型难度排序,供 UI 顺序展示) */
export const HeroComboList: number[] = [8001, 8002, 8003, 8004, 8005, 8006];
// ==================== 英雄强度评分公式 ====================
/** 英雄强度公式调参权重(可在编辑器或运行时热调)

View File

@@ -120,6 +120,7 @@ export class Hero extends ecs.Entity {
model.base_card_lv = hero.card_lv ?? 1;
model.type = hero.type;
model.grow_type = hero.grow_type;
model.role = hero.role;
model.fac = FacSet.HERO;
model.dis = hero.dis ?? 720;
model.posIndex = posIndex;
@@ -155,7 +156,7 @@ export class Hero extends ecs.Entity {
model.hp = model.hp_max = base_hp;
}
model.speed = hero.speed ?? 800;
model.speed = hero.speed ?? 200;
// 构建技能表并注入运行时冷却字段 ccd
model.skills = {};

View File

@@ -1,5 +1,5 @@
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { HeroDisVal, HeroInfo, HSkillInfo, HType, SkillTriggerType, TriggerGrouped, LvFieldEntry, LvReviveEntry, LvBonusEntry, FieldEntry } from "../common/config/heroSet";
import { HeroDisVal, HeroInfo, HSkillInfo, HType, SkillTriggerType, TriggerGrouped, LvFieldEntry, LvReviveEntry, LvBonusEntry, FieldEntry, HRole } from "../common/config/heroSet";
import { mLogger } from "../common/Logger";
import { FacSet, FightSet } from "../common/config/GameSet";
import { FieldSkillSet, FieldSkillType, SkillOverrides } from "../common/config/SkillSet";
@@ -29,6 +29,7 @@ export class HeroAttrsComp extends ecs.Comp {
type: number = 0; // 0近战 1远程 2辅助
fac: number = 0; // 0:hero 1:monster
grow_type?: number; // 成长类型HGrowType驱动每级 hp/ap 线性成长
role?: HRole; // 位置类型/职业定位(驱动编队站位优先级)
// ==================== 基础属性(有初始值) ====================
base_ap: number = 0; // 原始基础攻击(无任何加成)
base_hp: number = 0; // 原始基础血量(无任何加成)
@@ -563,6 +564,8 @@ export class HeroAttrsComp extends ecs.Comp {
this.lv = 1;
this.type = 0;
this.fac = 0;
this.grow_type = undefined;
this.role = undefined;
this.base_ap = 0;
this.base_hp = 0;
this.ap = 0;

View File

@@ -3,7 +3,7 @@ import { HeroViewComp } from "./HeroViewComp";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { smc } from "../common/SingletonModuleComp";
import { BoxSet, FacSet } from "../common/config/GameSet";
import { HeroDisVal, HType } from "../common/config/heroSet";
import { HeroDisVal, HType, HRole } from "../common/config/heroSet";
import { BoxCollider2D, Node } from "cc";
import { MonMoveComp } from "./MonMoveComp";
@@ -46,8 +46,6 @@ interface MoveFacConfig {
retreatBackX: number;
}
import { MissionHeroComp } from "../map/MissionHeroComp";
@ecs.register('MoveSystem')
export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
private readonly heroFrontAnchorX = -200;
@@ -106,7 +104,7 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
update(e: ecs.Entity) {
/** 战斗未开始/暂停时不驱动移动 */
if (!smc.mission.play || smc.mission.pause) return;
const model = e.get(HeroAttrsComp);
const move = e.get(MoveComp);
const view = e.get(HeroViewComp);
@@ -125,55 +123,37 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
move.baseY = slot.targetY;
move.targetX = slot.targetX;
// 战斗阶段不移动
const canMove = !smc.mission.in_fight;
// 2. 平滑 Y 轴换路
// 2. 平滑 Y 轴换路(不分阶段,立即响应编队变化)
let isChangingLane = false;
if (canMove) {
if (Math.abs(view.node.position.y - move.baseY) > 2) {
const currentY = view.node.position.y;
const deltaY = move.baseY - currentY;
const step = 400 * this.dt; // 换路速度
const newY = currentY + Math.sign(deltaY) * Math.min(Math.abs(deltaY), step);
view.node.setPosition(view.node.position.x, newY, 0);
isChangingLane = true;
} else {
view.node.setPosition(view.node.position.x, move.baseY, 0);
}
if (Math.abs(view.node.position.y - move.baseY) > 2) {
const currentY = view.node.position.y;
const deltaY = move.baseY - currentY;
const step = 400 * this.dt; // 换路速度
const newY = currentY + Math.sign(deltaY) * Math.min(Math.abs(deltaY), step);
view.node.setPosition(view.node.position.x, newY, 0);
isChangingLane = true;
} else {
view.node.setPosition(view.node.position.x, move.baseY, 0);
}
// 渲染层级重排
this.updateRenderOrder();
const nearestEnemy = this.findNearestEnemy(e);
if (nearestEnemy) {
/** 有敌人:进入战斗位移逻辑 */
if (canMove) {
this.processCombatLogic(e, move, view, model, nearestEnemy);
} else {
model.is_atking = true; // 战斗中不移动,但保持攻击状态
}
/** 有敌人:进入战斗位移逻辑(立即向编队目标移动) */
this.processCombatLogic(e, move, view, model, nearestEnemy);
this.syncCombatTarget(model, view, nearestEnemy);
} else {
/** 无敌人:清目标并回归编队站位 */
this.clearCombatTarget(model);
if (canMove) {
this.moveToSlot(view, move, model, move.targetX);
}
this.moveToSlot(view, move, model, move.targetX);
model.is_atking = false;
}
if (canMove) {
// 如果只在 Y 轴移动,也要播放 move 动画
if (isChangingLane && view.status !== "move" && view.status !== "atk") {
view.status_change("move");
}
} else {
// 战斗阶段如果不移动,确保不在攻击时恢复 idle 状态
if (!model.is_atking && view.status === "move") {
view.status_change("idle");
}
// 如果只在 Y 轴移动,也要播放 move 动画
if (isChangingLane && view.status !== "move" && view.status !== "atk") {
view.status_change("move");
}
}
@@ -220,8 +200,33 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
}
}
/**
* 近战交战:有怪物时向右推进,贴近到固定接触距离后停下攻击。
* Why: 近战手短需主动逼近;停止判定用固定接触距离(非攻击距离 dis
* 使英雄进入攻击范围后仍继续贴近,直到贴上目标才站稳输出。
* 怪物 y 与英雄同在 GAME_LINE 附近(微调也在攻击范围内),无需管 Y 轴。
*/
private processMeleeLogic(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
this.processFormationCombat(e, move, view, model);
if (!enemy?.node) {
this.processFormationCombat(e, move, view, model);
return;
}
const selfX = view.node.position.x;
const enemyX = enemy.node.position.x;
const dist = Math.abs(selfX - enemyX);
// 已贴近到固定接触距离:停下攻击
if (dist <= MoveSystem.MELEE_ENGAGE_X) {
model.is_atking = true;
if (view.status !== "atk") view.status_change("idle");
return;
}
// 未贴近:向怪物方向推进(攻击范围内也继续移动),于接触距离处停下
const dir = enemyX > selfX ? 1 : -1;
move.direction = dir;
const stopX = enemyX - dir * MoveSystem.MELEE_ENGAGE_X;
const speed = model.speed / 3;
this.moveEntity(view, dir, speed, stopX);
model.is_atking = false;
}
private processMidLogic(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
@@ -252,14 +257,10 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
allAllies.sort((a, b) => {
const attrsA = a.get(HeroAttrsComp);
const attrsB = b.get(HeroAttrsComp);
const priorityA = attrsA ? this.getCombatPriority(attrsA) : 0;
const priorityB = attrsB ? this.getCombatPriority(attrsB) : 0;
const priorityA = attrsA ? this.getFormationPriority(attrsA) : 0;
const priorityB = attrsB ? this.getFormationPriority(attrsB) : 0;
if (priorityA !== priorityB) return priorityB - priorityA;
const deadA = (attrsA?.dead && attrsA.dead.length > 0) ? 1 : 0;
const deadB = (attrsB?.dead && attrsB.dead.length > 0) ? 1 : 0;
if (deadA !== deadB) return deadB - deadA;
const lvA = attrsA?.lv ?? 1;
const lvB = attrsB?.lv ?? 1;
if (lvA !== lvB) return lvB - lvA;
@@ -271,20 +272,50 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
return a.eid - b.eid;
});
const slotIndex = Math.max(0, allAllies.findIndex(entity => entity === self));
// 优先中前(1) -> 上前(0) -> 下前(2) -> 中后(4) -> 上后(3) -> 下后(5)
const slotPriority = [1, 0, 2, 4, 3, 5];
const posIndex = slotPriority[slotIndex % 6];
const pos = MissionHeroComp.HERO_POSITIONS[posIndex];
return { targetX: pos.x, targetY: pos.y };
// allAllies 按优先级降序(索引 0 = 最高优先级 = 最靠右/最先接敌),
// 反转为"从左侧起的排位"后再分配 X最高优先级 → 最右
const priorityRank = Math.max(0, allAllies.findIndex(entity => entity === self));
const rankFromLeft = allAllies.length - 1 - priorityRank;
const targetX = this.calcSlotX(rankFromLeft, allAllies.length);
return { targetX, targetY: BoxSet.GAME_LINE };
}
// ==================== 编队站位(方案 A单行横向分布 ====================
/** 编队左端起点(最靠后/最后接敌) */
private static readonly FORMATION_LEFT_X = -320;
/** 编队右端终点(最靠前/最先接敌) */
private static readonly FORMATION_RIGHT_X = 100;
/** 固定步进间距(人数较少时按此从左侧逐位右排) */
private static readonly FORMATION_STEP_X = 60;
/** 近战贴近怪物的固定接触距离:小于攻击距离 dis使英雄进攻击范围后仍继续贴近到此间距才停 */
private static readonly MELEE_ENGAGE_X = 60;
/**
* 计算某个槽位的目标 X。
* Why: 单行站位避免横版拥挤——人少时按 60 固定步进从 -320 起步逐位右排(保持松散),
* 人多到 60 步进装不下时,改为在 [-320, 100] 区间均分(利用全宽)。
* @param rankFromLeft 从左侧起的排位0=最靠左/最后,越大越靠右/越先接敌)
* @param total 当前存活英雄总数
*/
private calcSlotX(rankFromLeft: number, total: number): number {
const left = MoveSystem.FORMATION_LEFT_X;
const right = MoveSystem.FORMATION_RIGHT_X;
const step = MoveSystem.FORMATION_STEP_X;
if (total <= 1) return left;
// 固定步进能容纳的最大人数:相邻 60且最右不超过 right
const maxStepCount = Math.floor((right - left) / step) + 1;
if (total <= maxStepCount) {
return left + rankFromLeft * step;
}
// 超出后均分整段区间(含左右端点)
return left + (right - left) * (rankFromLeft / (total - 1));
}
private moveToSlot(view: HeroViewComp, move: MoveComp, model: HeroAttrsComp, targetX: number) {
const currentX = view.node.position.x;
const currentY = view.node.position.y;
// 当 X 和 Y 都到达目标时,才算真正到达
if (Math.abs(currentX - targetX) <= 2 && Math.abs(currentY - move.baseY) <= 2) {
view.node.setPosition(targetX, move.baseY, 0);
@@ -307,7 +338,7 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
const moveMaxX = Math.max(cfg.moveBackX, cfg.moveFrontX);
const currentX = view.node.position.x;
const currentY = view.node.position.y;
const delta = speed * this.dt * direction;
let newX = view.node.position.x + delta;
if (currentX < moveMinX && direction < 0) {
@@ -345,12 +376,30 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
view.status_change("move");
}
private getCombatPriority(model: HeroAttrsComp): number {
/** 数值越大越靠前:近战 > 中程 > 远程 */
/**
* 编队站位优先级(数值越大越靠前/越靠右)。
* Why: 阵容职能决定承伤顺序——死亡触发英雄需最先阵亡以触发遗志,
* 其次坦克承伤,再次战士/刺客,远程射手/法师/辅助依次靠后保护。
* 排序档位:死亡触发 > 坦克 > 战士 > 刺客 > 射手 > 法师 > 辅助
*/
private getFormationPriority(model: HeroAttrsComp): number {
// 死亡触发英雄最高优先:需要先死以发动遗志/献祭
if (model.dead && Object.keys(model.dead).length > 0) return 100;
if (model.role !== undefined) {
switch (model.role) {
case HRole.Tank: return 90;
case HRole.Warrior: return 80;
case HRole.Assassin: return 70;
case HRole.Archer: return 60;
case HRole.Mage: return 50;
case HRole.Support: return 40;
}
}
// 兜底:未配置 role 时按攻击定位(近战 > 中程 > 远程)
const rangeType = model.type as HType.Melee | HType.Mid | HType.Long;
if (rangeType === HType.Melee) return 3;
if (rangeType === HType.Mid) return 2;
return 1;
if (rangeType === HType.Melee) return 30;
if (rangeType === HType.Mid) return 20;
return 10;
}
private resolveCombatRange(model: HeroAttrsComp, defaultMin: number, defaultMax: number): [number, number] {
@@ -364,13 +413,13 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
private findNearestEnemy(entity: ecs.Entity): HeroViewComp | null {
const currentView = entity.get(HeroViewComp);
if (!currentView?.node) return null;
const currentPos = currentView.node.position;
const myFac = entity.get(HeroAttrsComp).fac;
let nearest: HeroViewComp | null = null;
let minDis = Infinity;
/** 遍历筛出最近敌人:以 X 轴距离为主Y 轴距离作为同排的决胜权重,使角色优先攻击同路的敌人 */
ecs.query(this.getHeroViewMatcher()).forEach(e => {
const m = e.get(HeroAttrsComp);
@@ -405,7 +454,7 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
const actorView = e.get(HeroViewComp);
if (!attrs || !actorView?.node || attrs.is_dead) return;
if (attrs.fac !== FacSet.HERO && attrs.fac !== FacSet.MON) return;
if (actorView.node.parent !== actorRoot) {
actorView.node.parent = actorRoot;
}

View File

@@ -289,7 +289,7 @@ export class CHeroComp extends CCComp {
// 与英雄信息弹窗/升级确认框同一套文案规则,杜绝手写文案与数值漂移
// outlineRich 为整段富文本包裹描边,提升深色面板可读性
if (this.info_rich) {
const desc = hero ? buildSkillDescMoreRich(this.buildHeroDescSource(hero)) : (this.cardData.info || "");
const desc = hero ? buildSkillDescMoreRich(this.buildHeroDescSource(hero), false) : (this.cardData.info || "");
this.info_rich.string = outlineRich(desc);
this.info_rich.node.active = !!desc;
}