feat(talent): 重构天赋系统为三阶结构并实现全新UI
1. 重构天赋系统为一阶基础/二阶专精/三阶超强三阶结构,移除前置天赋要求改为阶位累计点数解锁 2. 新增阶位解锁校验逻辑与天赋点投入统计 3. 重做天赋UI为三阶Tab页签+迷你升级卡样式,支持状态展示与一键升级 4. 补充新的天赋配置与属性加成,更新天赋系统文档 5. 修复属性计算问题,新增暴击伤害与穿刺伤害加成支持
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -167,3 +167,6 @@ $RECYCLE.BIN/
|
|||||||
*.msm
|
*.msm
|
||||||
*.msp
|
*.msp
|
||||||
*.lnk
|
*.lnk
|
||||||
|
|
||||||
|
# brainstorm visual companion
|
||||||
|
.superpowers/
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3246,7 +3246,7 @@
|
|||||||
"_fillStart": 0,
|
"_fillStart": 0,
|
||||||
"_fillRange": 0,
|
"_fillRange": 0,
|
||||||
"_isTrimmedMode": true,
|
"_isTrimmedMode": true,
|
||||||
"_useGrayscale": false,
|
"_useGrayscale": true,
|
||||||
"_atlas": null,
|
"_atlas": null,
|
||||||
"_id": ""
|
"_id": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1537,6 +1537,14 @@
|
|||||||
"__uuid__": "a0cdb31c-0abb-4e12-9dfe-0bd9e8cd7272",
|
"__uuid__": "a0cdb31c-0abb-4e12-9dfe-0bd9e8cd7272",
|
||||||
"__expectedType__": "cc.Prefab"
|
"__expectedType__": "cc.Prefab"
|
||||||
},
|
},
|
||||||
|
"lbl_points": null,
|
||||||
|
"tab_basic": null,
|
||||||
|
"tab_special": null,
|
||||||
|
"tab_super": null,
|
||||||
|
"lbl_tab_basic": null,
|
||||||
|
"lbl_tab_special": null,
|
||||||
|
"lbl_tab_super": null,
|
||||||
|
"btn_reset": null,
|
||||||
"_id": ""
|
"_id": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { GameScoreStats } from "./config/HeroAttrs";
|
|||||||
import { mLogger } from "./Logger";
|
import { mLogger } from "./Logger";
|
||||||
import { gameDataSync } from "./GameDataSync";
|
import { gameDataSync } from "./GameDataSync";
|
||||||
import { HERO_UPGRADE_NEED_FRAGMENTS } from "./config/GameSet";
|
import { HERO_UPGRADE_NEED_FRAGMENTS } from "./config/GameSet";
|
||||||
import { TalentSet, getTalentUpgradeCost } from "./config/TalentSet";
|
import { TalentSet, getTalentUpgradeCost, isTalentTierUnlocked } from "./config/TalentSet";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用远程数据覆盖本地数据(统一方法)
|
* 用远程数据覆盖本地数据(统一方法)
|
||||||
@@ -418,7 +418,7 @@ export class SingletonModuleComp extends ecs.Comp {
|
|||||||
/**
|
/**
|
||||||
* 消耗天赋点升级天赋(升级即激活,下一局生效)
|
* 消耗天赋点升级天赋(升级即激活,下一局生效)
|
||||||
* @param uuid 天赋 uuid
|
* @param uuid 天赋 uuid
|
||||||
* @returns 是否升级成功(天赋不存在/已满级/前置未满足/点数不足时返回 false)
|
* @returns 是否升级成功(天赋不存在/已满级/阶位未解锁/点数不足时返回 false)
|
||||||
*/
|
*/
|
||||||
upgradeTalent(uuid: number): boolean {
|
upgradeTalent(uuid: number): boolean {
|
||||||
const cfg = TalentSet[uuid];
|
const cfg = TalentSet[uuid];
|
||||||
@@ -426,12 +426,8 @@ export class SingletonModuleComp extends ecs.Comp {
|
|||||||
if (!this.data.talents) this.data.talents = {};
|
if (!this.data.talents) this.data.talents = {};
|
||||||
const curLv = this.getTalentLv(uuid);
|
const curLv = this.getTalentLv(uuid);
|
||||||
if (curLv >= cfg.max_lv) return false;
|
if (curLv >= cfg.max_lv) return false;
|
||||||
// 专精天赋的前置要求:前置天赋等级不足则禁止升级
|
// 阶位解锁:二/三阶天赋要求前一阶累计投入点数达标(一阶恒解锁)
|
||||||
if (cfg.requires) {
|
if (!isTalentTierUnlocked(this.data.talents, cfg.tier)) return false;
|
||||||
for (const req of cfg.requires) {
|
|
||||||
if (this.getTalentLv(req.uuid) < req.lv) return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const cost = getTalentUpgradeCost(curLv);
|
const cost = getTalentUpgradeCost(curLv);
|
||||||
if (cost < 0 || this.getTalentPoints() < cost) return false;
|
if (cost < 0 || this.getTalentPoints() < cost) return false;
|
||||||
this.data.talent_points -= cost;
|
this.data.talent_points -= cost;
|
||||||
|
|||||||
@@ -4,9 +4,13 @@ import { HRole } from "./heroSet";
|
|||||||
/**
|
/**
|
||||||
* 天赋系统配置(局外成长:升级即激活,战斗中对英雄属性常驻加成)。
|
* 天赋系统配置(局外成长:升级即激活,战斗中对英雄属性常驻加成)。
|
||||||
*
|
*
|
||||||
* 双层结构:
|
* 三阶结构(阶位逐级解锁,门槛见 TALENT_TIER_UNLOCK_THRESHOLD):
|
||||||
* - 基础天赋:全局生效,对所有英雄叠加;
|
* - 一阶·基础天赋(9001-9099):全局生效,对所有英雄叠加;
|
||||||
* - 专精天赋:按 HRole 职业过滤,仅对匹配职业的英雄生效,且要求前置天赋达到指定等级。
|
* - 二阶·专精天赋(9101-9199):按 HRole 职业过滤,仅对匹配职业的英雄生效;
|
||||||
|
* - 三阶·超强天赋(9201-9219):全局(9201-9209)+ 职业(9211-9219)混合,数值幅度约为前两阶的 2 倍。
|
||||||
|
*
|
||||||
|
* 解锁规则:一阶恒可升级;二阶需一阶累计投入 ≥10 点;三阶需二阶累计投入 ≥10 点。
|
||||||
|
* 专精/超强职业天赋不再有独立前置天赋,仅受阶位解锁约束。
|
||||||
*
|
*
|
||||||
* 结算口径(与项目 buff 规范一致):
|
* 结算口径(与项目 buff 规范一致):
|
||||||
* - percent 模式:按英雄基础值(base_ap/base_hp/hero.def)相对放大;
|
* - percent 模式:按英雄基础值(base_ap/base_hp/hero.def)相对放大;
|
||||||
@@ -19,6 +23,16 @@ export enum TalentValueMode {
|
|||||||
Flat = 1,
|
Flat = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 天赋阶位:一阶基础 / 二阶专精 / 三阶超强 */
|
||||||
|
export enum TalentTier {
|
||||||
|
/** 一阶:基础天赋,全局生效 */
|
||||||
|
Basic = 1,
|
||||||
|
/** 二阶:专精天赋,按职业过滤 */
|
||||||
|
Specialized = 2,
|
||||||
|
/** 三阶:超强天赋,全局 + 职业混合 */
|
||||||
|
Super = 3,
|
||||||
|
}
|
||||||
|
|
||||||
/** 单条属性加成词条 */
|
/** 单条属性加成词条 */
|
||||||
export interface TalentAttrBonus {
|
export interface TalentAttrBonus {
|
||||||
/** 目标属性(Attrs 枚举键) */
|
/** 目标属性(Attrs 枚举键) */
|
||||||
@@ -33,18 +47,20 @@ export interface TalentAttrBonus {
|
|||||||
export interface TalentConfig {
|
export interface TalentConfig {
|
||||||
/** 天赋 uuid(段位见 TalentSet 注释) */
|
/** 天赋 uuid(段位见 TalentSet 注释) */
|
||||||
uuid: number;
|
uuid: number;
|
||||||
|
/** 所属阶位(升级校验:升下一阶天赋需前一阶累计投入点数达标) */
|
||||||
|
tier: TalentTier;
|
||||||
/** 天赋名称 */
|
/** 天赋名称 */
|
||||||
name: string;
|
name: string;
|
||||||
/** 描述(UI 展示) */
|
/** 描述(UI 展示,{value}/{value2} 为按当前等级计算后的数值占位) */
|
||||||
info: string;
|
info: string;
|
||||||
|
/** 图标(uiconsAtlas 帧名,缺省时 UI 端按资源目录回退) */
|
||||||
|
icon?: string;
|
||||||
/** 属性加成词条列表(每级叠加) */
|
/** 属性加成词条列表(每级叠加) */
|
||||||
bonuses: TalentAttrBonus[];
|
bonuses: TalentAttrBonus[];
|
||||||
/** 最大等级 */
|
/** 最大等级 */
|
||||||
max_lv: number;
|
max_lv: number;
|
||||||
/** 专精天赋:仅对该职业英雄生效;基础天赋省略此字段 */
|
/** 专精/超强职业天赋:仅对该职业英雄生效;全局天赋省略此字段 */
|
||||||
role?: HRole;
|
role?: HRole;
|
||||||
/** 专精天赋:前置要求(前置天赋 uuid + 所需等级),全部满足才生效 */
|
|
||||||
requires?: { uuid: number; lv: number }[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 单条已解析的加成(等级已乘算) */
|
/** 单条已解析的加成(等级已乘算) */
|
||||||
@@ -74,111 +90,185 @@ export interface ITalentTarget {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 天赋配置表。
|
* 天赋配置表。
|
||||||
* uuid 段位:9001-9099 基础天赋(全局);9101-9199 专精天赋(按职业过滤)。
|
* uuid 段位:9001-9099 一阶基础(全局);9101-9199 二阶专精(按职业);
|
||||||
|
* 9201-9209 三阶超强·全局;9211-9219 三阶超强·职业。
|
||||||
|
* icon 复用 SkillSet/uiconsAtlas 既有帧名,后续可在编辑器侧替换为专属图标。
|
||||||
*/
|
*/
|
||||||
export const TalentSet: Record<number, TalentConfig> = {
|
export const TalentSet: Record<number, TalentConfig> = {
|
||||||
// ==================== 基础天赋(9001-9099,全局生效) ====================
|
// ==================== 一阶·基础天赋(9001-9099,全局生效) ====================
|
||||||
9001: {
|
9001: {
|
||||||
uuid: 9001, name: "攻击强化", info: "所有英雄攻击 +{value}%",
|
uuid: 9001, tier: TalentTier.Basic, name: "攻击强化", info: "所有英雄攻击 +{value}%", icon: "Stat_Attack_03",
|
||||||
bonuses: [{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 3 }],
|
bonuses: [{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 3 }],
|
||||||
max_lv: 5,
|
max_lv: 5,
|
||||||
},
|
},
|
||||||
9002: {
|
9002: {
|
||||||
uuid: 9002, name: "生命强化", info: "所有英雄生命 +{value}%",
|
uuid: 9002, tier: TalentTier.Basic, name: "生命强化", info: "所有英雄生命 +{value}%", icon: "Stat_Hp_02",
|
||||||
bonuses: [{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 5 }],
|
bonuses: [{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 5 }],
|
||||||
max_lv: 5,
|
max_lv: 5,
|
||||||
},
|
},
|
||||||
9003: {
|
9003: {
|
||||||
uuid: 9003, name: "防御强化", info: "所有英雄防御 +{value} 点",
|
uuid: 9003, tier: TalentTier.Basic, name: "防御强化", info: "所有英雄防御 +{value} 点", icon: "Stat_Defense",
|
||||||
bonuses: [{ attr: Attrs.defense, mode: TalentValueMode.Flat, per_lv: 8 }],
|
bonuses: [{ attr: Attrs.defense, mode: TalentValueMode.Flat, per_lv: 8 }],
|
||||||
max_lv: 5,
|
max_lv: 5,
|
||||||
},
|
},
|
||||||
9004: {
|
9004: {
|
||||||
uuid: 9004, name: "暴击强化", info: "所有英雄暴击率 +{value}%",
|
uuid: 9004, tier: TalentTier.Basic, name: "暴击强化", info: "所有英雄暴击率 +{value}%", icon: "Stat_CriticalChance_02",
|
||||||
bonuses: [{ attr: Attrs.critical, mode: TalentValueMode.Flat, per_lv: 2 }],
|
bonuses: [{ attr: Attrs.critical, mode: TalentValueMode.Flat, per_lv: 2 }],
|
||||||
max_lv: 5,
|
max_lv: 5,
|
||||||
},
|
},
|
||||||
9005: {
|
9005: {
|
||||||
uuid: 9005, name: "击晕强化", info: "所有英雄击晕率 +{value}%",
|
uuid: 9005, tier: TalentTier.Basic, name: "击晕强化", info: "所有英雄击晕率 +{value}%", icon: "Stat_Stun_01",
|
||||||
bonuses: [{ attr: Attrs.stun_chance, mode: TalentValueMode.Flat, per_lv: 2 }],
|
bonuses: [{ attr: Attrs.stun_chance, mode: TalentValueMode.Flat, per_lv: 2 }],
|
||||||
max_lv: 5,
|
max_lv: 5,
|
||||||
},
|
},
|
||||||
9006: {
|
9006: {
|
||||||
uuid: 9006, name: "冰冻强化", info: "所有英雄冰冻率 +{value}%",
|
uuid: 9006, tier: TalentTier.Basic, name: "冰冻强化", info: "所有英雄冰冻率 +{value}%", icon: "Stat_Freeze",
|
||||||
bonuses: [{ attr: Attrs.freeze_chance, mode: TalentValueMode.Flat, per_lv: 2 }],
|
bonuses: [{ attr: Attrs.freeze_chance, mode: TalentValueMode.Flat, per_lv: 2 }],
|
||||||
max_lv: 5,
|
max_lv: 5,
|
||||||
},
|
},
|
||||||
9007: {
|
9007: {
|
||||||
uuid: 9007, name: "击退强化", info: "所有英雄击退距离 +{value}",
|
uuid: 9007, tier: TalentTier.Basic, name: "击退强化", info: "所有英雄击退距离 +{value}", icon: "Stat_Stun_01",
|
||||||
bonuses: [{ attr: Attrs.knockback_distance, mode: TalentValueMode.Flat, per_lv: 10 }],
|
bonuses: [{ attr: Attrs.knockback_distance, mode: TalentValueMode.Flat, per_lv: 10 }],
|
||||||
max_lv: 5,
|
max_lv: 5,
|
||||||
},
|
},
|
||||||
// 风怒机制已停用: 9008 风怒强化天赋已移除
|
// 风怒机制已停用: 9008 风怒强化天赋已移除
|
||||||
/*
|
/*
|
||||||
9008: {
|
9008: {
|
||||||
uuid: 9008, name: "风怒强化", info: "所有英雄风怒率 +{value}%",
|
uuid: 9008, tier: TalentTier.Basic, name: "风怒强化", info: "所有英雄风怒率 +{value}%", icon: "Stat_CriticalComboChance",
|
||||||
bonuses: [{ attr: Attrs.wfuny, mode: TalentValueMode.Flat, per_lv: 2 }],
|
bonuses: [{ attr: Attrs.wfuny, mode: TalentValueMode.Flat, per_lv: 2 }],
|
||||||
max_lv: 5,
|
max_lv: 5,
|
||||||
},
|
},
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// ==================== 专精天赋(9101-9199,按职业过滤 + 前置要求) ====================
|
// ==================== 二阶·专精天赋(9101-9199,按职业过滤;一阶投入 10 点解锁) ====================
|
||||||
// 坦克专精:前置 防御强化 Lv3 → 坦克大量防御 + 生命
|
|
||||||
9101: {
|
9101: {
|
||||||
uuid: 9101, name: "坦克专精", info: "坦克防御 +{value}%、生命 +{value2}%",
|
uuid: 9101, tier: TalentTier.Specialized, name: "坦克专精", info: "坦克防御 +{value}%、生命 +{value2}%", icon: "Stat_Armor_01",
|
||||||
bonuses: [
|
bonuses: [
|
||||||
{ attr: Attrs.defense, mode: TalentValueMode.Percent, per_lv: 30 },
|
{ attr: Attrs.defense, mode: TalentValueMode.Percent, per_lv: 30 },
|
||||||
{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 5 },
|
{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 5 },
|
||||||
],
|
],
|
||||||
max_lv: 5, role: HRole.Tank,
|
max_lv: 5, role: HRole.Tank,
|
||||||
requires: [{ uuid: 9003, lv: 3 }],
|
|
||||||
},
|
},
|
||||||
// 战士专精:前置 生命强化 Lv3 → 战士大量生命
|
|
||||||
9102: {
|
9102: {
|
||||||
uuid: 9102, name: "战士专精", info: "战士生命 +{value}%",
|
uuid: 9102, tier: TalentTier.Specialized, name: "战士专精", info: "战士生命 +{value}%", icon: "Sword-FA_WP_Main_Sword_003_PurpleSilver",
|
||||||
bonuses: [{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 12 }],
|
bonuses: [{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 12 }],
|
||||||
max_lv: 5, role: HRole.Warrior,
|
max_lv: 5, role: HRole.Warrior,
|
||||||
requires: [{ uuid: 9002, lv: 3 }],
|
|
||||||
},
|
},
|
||||||
// 刺客专精:前置 暴击强化 Lv3 → 刺客攻击 + 暴击
|
|
||||||
9103: {
|
9103: {
|
||||||
uuid: 9103, name: "刺客专精", info: "刺客攻击 +{value}%、暴击率 +{value2}%",
|
uuid: 9103, tier: TalentTier.Specialized, name: "刺客专精", info: "刺客攻击 +{value}%、暴击率 +{value2}%", icon: "Sword-FA_WP_Main_Sword_019_WoodSilver",
|
||||||
bonuses: [
|
bonuses: [
|
||||||
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 },
|
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 },
|
||||||
{ attr: Attrs.critical, mode: TalentValueMode.Flat, per_lv: 2 },
|
{ attr: Attrs.critical, mode: TalentValueMode.Flat, per_lv: 2 },
|
||||||
],
|
],
|
||||||
max_lv: 5, role: HRole.Assassin,
|
max_lv: 5, role: HRole.Assassin,
|
||||||
requires: [{ uuid: 9004, lv: 3 }],
|
|
||||||
},
|
},
|
||||||
// 射手专精:前置 攻击强化 Lv3 → 射手攻击 + 穿透
|
|
||||||
9104: {
|
9104: {
|
||||||
uuid: 9104, name: "射手专精", info: "射手攻击 +{value}%、穿透率 +{value2}%",
|
uuid: 9104, tier: TalentTier.Specialized, name: "射手专精", info: "射手攻击 +{value}%、穿透率 +{value2}%", icon: "Stat_RangedAttack",
|
||||||
bonuses: [
|
bonuses: [
|
||||||
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 },
|
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 },
|
||||||
{ attr: Attrs.puncture_chance, mode: TalentValueMode.Flat, per_lv: 2 },
|
{ attr: Attrs.puncture_chance, mode: TalentValueMode.Flat, per_lv: 2 },
|
||||||
],
|
],
|
||||||
max_lv: 5, role: HRole.Archer,
|
max_lv: 5, role: HRole.Archer,
|
||||||
requires: [{ uuid: 9001, lv: 3 }],
|
|
||||||
},
|
},
|
||||||
// 法师专精:前置 冰冻强化 Lv3 → 法师攻击 + 冰冻率
|
|
||||||
9105: {
|
9105: {
|
||||||
uuid: 9105, name: "法师专精", info: "法师攻击 +{value}%、冰冻率 +{value2}%",
|
uuid: 9105, tier: TalentTier.Specialized, name: "法师专精", info: "法师攻击 +{value}%、冰冻率 +{value2}%", icon: "Stat_Mana",
|
||||||
bonuses: [
|
bonuses: [
|
||||||
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 },
|
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 },
|
||||||
{ attr: Attrs.freeze_chance, mode: TalentValueMode.Flat, per_lv: 2 },
|
{ attr: Attrs.freeze_chance, mode: TalentValueMode.Flat, per_lv: 2 },
|
||||||
],
|
],
|
||||||
max_lv: 5, role: HRole.Mage,
|
max_lv: 5, role: HRole.Mage,
|
||||||
requires: [{ uuid: 9006, lv: 3 }],
|
|
||||||
},
|
},
|
||||||
// 辅助专精:前置 生命强化 Lv3 → 治疗量提升 + 施加的友方 buff 效果放大
|
|
||||||
9106: {
|
9106: {
|
||||||
uuid: 9106, name: "辅助专精", info: "辅助治疗量 +{value}%、施加的增益效果 +{value2}%",
|
uuid: 9106, tier: TalentTier.Specialized, name: "辅助专精", info: "辅助治疗量 +{value}%、施加的增益效果 +{value2}%", icon: "Stat_PotionBoost",
|
||||||
bonuses: [
|
bonuses: [
|
||||||
{ attr: Attrs.heal_power, mode: TalentValueMode.Flat, per_lv: 10 },
|
{ attr: Attrs.heal_power, mode: TalentValueMode.Flat, per_lv: 10 },
|
||||||
{ attr: Attrs.buff_power, mode: TalentValueMode.Flat, per_lv: 8 },
|
{ attr: Attrs.buff_power, mode: TalentValueMode.Flat, per_lv: 8 },
|
||||||
],
|
],
|
||||||
max_lv: 5, role: HRole.Support,
|
max_lv: 5, role: HRole.Support,
|
||||||
requires: [{ uuid: 9002, lv: 3 }],
|
},
|
||||||
|
|
||||||
|
// ==================== 三阶·超强天赋(9201-9219,二阶投入 10 点解锁;幅度约为前两阶 2 倍) ====================
|
||||||
|
// ---- 全局超强(9201-9209) ----
|
||||||
|
9201: {
|
||||||
|
uuid: 9201, tier: TalentTier.Super, name: "神威降临", info: "所有英雄攻击 +{value}%、生命 +{value2}%", icon: "Stat_HolyDamage",
|
||||||
|
bonuses: [
|
||||||
|
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 },
|
||||||
|
{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 8 },
|
||||||
|
],
|
||||||
|
max_lv: 5,
|
||||||
|
},
|
||||||
|
9202: {
|
||||||
|
uuid: 9202, tier: TalentTier.Super, name: "致命终结", info: "所有英雄暴击率 +{value}%、暴击伤害 +{value2}%", icon: "Stat_Critical_01",
|
||||||
|
bonuses: [
|
||||||
|
{ attr: Attrs.critical, mode: TalentValueMode.Flat, per_lv: 3 },
|
||||||
|
{ attr: Attrs.critical_damage, mode: TalentValueMode.Flat, per_lv: 15 },
|
||||||
|
],
|
||||||
|
max_lv: 5,
|
||||||
|
},
|
||||||
|
9203: {
|
||||||
|
uuid: 9203, tier: TalentTier.Super, name: "破甲重创", info: "所有英雄穿透率 +{value}%、穿透伤害 +{value2}%", icon: "Stat_Tripleshot",
|
||||||
|
bonuses: [
|
||||||
|
{ attr: Attrs.puncture_chance, mode: TalentValueMode.Flat, per_lv: 3 },
|
||||||
|
{ attr: Attrs.puncture_dmg_bonus, mode: TalentValueMode.Flat, per_lv: 10 },
|
||||||
|
],
|
||||||
|
max_lv: 5,
|
||||||
|
},
|
||||||
|
9204: {
|
||||||
|
uuid: 9204, tier: TalentTier.Super, name: "钢铁之躯", info: "所有英雄防御 +{value}%、生命 +{value2}%", icon: "Stat_Defense",
|
||||||
|
bonuses: [
|
||||||
|
{ attr: Attrs.defense, mode: TalentValueMode.Percent, per_lv: 20 },
|
||||||
|
{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 6 },
|
||||||
|
],
|
||||||
|
max_lv: 5,
|
||||||
|
},
|
||||||
|
// ---- 职业超强(9211-9219,按 HRole 过滤) ----
|
||||||
|
9211: {
|
||||||
|
uuid: 9211, tier: TalentTier.Super, name: "不朽壁垒", info: "坦克防御 +{value}%、生命 +{value2}%", icon: "Stat_Armor_01",
|
||||||
|
bonuses: [
|
||||||
|
{ attr: Attrs.defense, mode: TalentValueMode.Percent, per_lv: 40 },
|
||||||
|
{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 8 },
|
||||||
|
],
|
||||||
|
max_lv: 5, role: HRole.Tank,
|
||||||
|
},
|
||||||
|
9212: {
|
||||||
|
uuid: 9212, tier: TalentTier.Super, name: "狂血战魂", info: "战士攻击 +{value}%、生命 +{value2}%", icon: "Stat_Attack_03",
|
||||||
|
bonuses: [
|
||||||
|
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 10 },
|
||||||
|
{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 8 },
|
||||||
|
],
|
||||||
|
max_lv: 5, role: HRole.Warrior,
|
||||||
|
},
|
||||||
|
9213: {
|
||||||
|
uuid: 9213, tier: TalentTier.Super, name: "影刃绝杀", info: "刺客攻击 +{value}%、暴击伤害 +{value2}%", icon: "Sword-FA_WP_Main_Sword_019_WoodSilver",
|
||||||
|
bonuses: [
|
||||||
|
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 12 },
|
||||||
|
{ attr: Attrs.critical_damage, mode: TalentValueMode.Flat, per_lv: 12 },
|
||||||
|
],
|
||||||
|
max_lv: 5, role: HRole.Assassin,
|
||||||
|
},
|
||||||
|
9214: {
|
||||||
|
uuid: 9214, tier: TalentTier.Super, name: "穿云贯日", info: "射手攻击 +{value}%、穿透伤害 +{value2}%", icon: "Stat_RangedAttack",
|
||||||
|
bonuses: [
|
||||||
|
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 12 },
|
||||||
|
{ attr: Attrs.puncture_dmg_bonus, mode: TalentValueMode.Flat, per_lv: 8 },
|
||||||
|
],
|
||||||
|
max_lv: 5, role: HRole.Archer,
|
||||||
|
},
|
||||||
|
9215: {
|
||||||
|
uuid: 9215, tier: TalentTier.Super, name: "元素主宰", info: "法师攻击 +{value}%、冰冻率 +{value2}%", icon: "Sub_Item-FA_WP_Sub_MagicBall_001_Blue",
|
||||||
|
bonuses: [
|
||||||
|
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 12 },
|
||||||
|
{ attr: Attrs.freeze_chance, mode: TalentValueMode.Flat, per_lv: 3 },
|
||||||
|
],
|
||||||
|
max_lv: 5, role: HRole.Mage,
|
||||||
|
},
|
||||||
|
9216: {
|
||||||
|
uuid: 9216, tier: TalentTier.Super, name: "圣光普照", info: "辅助治疗量 +{value}%、施加的增益效果 +{value2}%", icon: "Stat_HolyDamage",
|
||||||
|
bonuses: [
|
||||||
|
{ attr: Attrs.heal_power, mode: TalentValueMode.Flat, per_lv: 15 },
|
||||||
|
{ attr: Attrs.buff_power, mode: TalentValueMode.Flat, per_lv: 12 },
|
||||||
|
],
|
||||||
|
max_lv: 5, role: HRole.Support,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -195,12 +285,91 @@ export function getTalentUpgradeCost(curLv: number): number {
|
|||||||
return TALENT_UPGRADE_COST[curLv];
|
return TALENT_UPGRADE_COST[curLv];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 阶位解锁门槛:前一阶累计投入 ≥ 该点数才能升级下一阶天赋 */
|
||||||
|
export const TALENT_TIER_UNLOCK_THRESHOLD = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算指定阶位已投入的天赋点数(按升级消耗表逐级累加)。
|
||||||
|
* Why: 阶位解锁按"实际消耗点数"而非"等级总和"口径——与玩家直觉(投入了多少点)
|
||||||
|
* 及重置退款口径(resetTalents 同表对账)保持一致。
|
||||||
|
* @param levels 天赋等级表(key 为天赋 uuid,value 为当前等级)
|
||||||
|
* @param tier 目标阶位
|
||||||
|
* @returns 该阶位全部天赋已投入的点数总和
|
||||||
|
*/
|
||||||
|
export function getTalentTierInvestedPoints(levels: Record<number, number>, tier: TalentTier): number {
|
||||||
|
if (!levels) return 0;
|
||||||
|
let invested = 0;
|
||||||
|
for (const key in levels) {
|
||||||
|
const cfg = TalentSet[key];
|
||||||
|
if (!cfg || cfg.tier !== tier) continue;
|
||||||
|
for (let i = 0; i < levels[key]; i++) {
|
||||||
|
const c = getTalentUpgradeCost(i);
|
||||||
|
if (c > 0) invested += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return invested;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断指定阶位是否已解锁(加点入口校验用;战斗结算无需关心阶位——已投入的等级恒生效)。
|
||||||
|
* 规则:一阶恒解锁;二阶要求一阶累计投入 ≥ TALENT_TIER_UNLOCK_THRESHOLD;三阶要求二阶累计投入达标。
|
||||||
|
* @param levels 天赋等级表
|
||||||
|
* @param tier 目标阶位
|
||||||
|
*/
|
||||||
|
export function isTalentTierUnlocked(levels: Record<number, number>, tier: TalentTier): boolean {
|
||||||
|
if (tier === TalentTier.Basic) return true;
|
||||||
|
const prevTier = (tier - 1) as TalentTier;
|
||||||
|
return getTalentTierInvestedPoints(levels, prevTier) >= TALENT_TIER_UNLOCK_THRESHOLD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 天赋属性的卡面显示名(迷你卡空间有限,用短标签) */
|
||||||
|
const TALENT_ATTR_LABELS: Partial<Record<Attrs, string>> = {
|
||||||
|
[Attrs.ap]: "攻击",
|
||||||
|
[Attrs.hp_max]: "生命",
|
||||||
|
[Attrs.defense]: "防御",
|
||||||
|
[Attrs.critical]: "暴击",
|
||||||
|
[Attrs.critical_damage]: "暴伤",
|
||||||
|
[Attrs.stun_chance]: "击晕",
|
||||||
|
[Attrs.freeze_chance]: "冰冻",
|
||||||
|
[Attrs.knockback_distance]: "击退",
|
||||||
|
[Attrs.puncture_chance]: "穿透",
|
||||||
|
[Attrs.puncture_dmg_bonus]: "穿透伤",
|
||||||
|
[Attrs.heal_power]: "治疗",
|
||||||
|
[Attrs.buff_power]: "增益",
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 天赋属性的单位后缀:概率/治疗/增益类为百分点(%),防御为"点",击退距离为纯数值 */
|
||||||
|
const TALENT_ATTR_SUFFIX: Partial<Record<Attrs, string>> = {
|
||||||
|
[Attrs.defense]: "点",
|
||||||
|
[Attrs.knockback_distance]: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成天赋迷你卡的卡面文案(自包含:不显示满级效果)。
|
||||||
|
* 规则:已加点(lv>0)显示当前已生效数值,如「攻击 +9%」;
|
||||||
|
* 未加点(lv=0)显示每级数值引导,如「攻击 +3%/级」。
|
||||||
|
* Why: 迷你点选卡没有二级详情入口,文案必须一眼看懂当前收益。
|
||||||
|
* @param cfg 天赋配置
|
||||||
|
* @param lv 当前等级
|
||||||
|
*/
|
||||||
|
export function getTalentDisplayText(cfg: TalentConfig, lv: number): string {
|
||||||
|
const mult = lv > 0 ? lv : 1;
|
||||||
|
const parts: string[] = [];
|
||||||
|
for (const b of cfg.bonuses) {
|
||||||
|
const label = TALENT_ATTR_LABELS[b.attr] ?? String(b.attr);
|
||||||
|
const suffix = TALENT_ATTR_SUFFIX[b.attr] ?? "%";
|
||||||
|
parts.push(`${label} +${b.per_lv * mult}${suffix}`);
|
||||||
|
}
|
||||||
|
const text = parts.join("、");
|
||||||
|
return lv > 0 ? text : `${text}/级`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 聚合指定职业英雄生效的全部天赋加成。
|
* 聚合指定职业英雄生效的全部天赋加成。
|
||||||
* Why: 基础天赋全局生效,专精天赋按 role 过滤且要求前置天赋达标;
|
* Why: 基础/超强全局天赋全局生效,专精与超强职业天赋按 role 过滤;
|
||||||
* 战斗加载时(Hero.load / applyHeroLevel)调用本函数一次性取出本英雄的天赋加成区。
|
* 阶位解锁只约束加点阶段,战斗加载时(Hero.load / applyHeroLevel)按当前等级直接取加成。
|
||||||
* @param levels 天赋等级表(key 为天赋 uuid,value 为当前等级)
|
* @param levels 天赋等级表(key 为天赋 uuid,value 为当前等级)
|
||||||
* @param role 英雄职业(专精过滤依据;无 role 时只应用基础天赋)
|
* @param role 英雄职业(职业过滤依据;无 role 时只应用全局天赋)
|
||||||
* @returns 已解析的加成列表(等级已乘算进 value)
|
* @returns 已解析的加成列表(等级已乘算进 value)
|
||||||
*/
|
*/
|
||||||
export function getTalentBonusesForRole(levels: Record<number, number>, role?: HRole): TalentResolvedBonus[] {
|
export function getTalentBonusesForRole(levels: Record<number, number>, role?: HRole): TalentResolvedBonus[] {
|
||||||
@@ -211,16 +380,8 @@ export function getTalentBonusesForRole(levels: Record<number, number>, role?: H
|
|||||||
if (lv <= 0) continue;
|
if (lv <= 0) continue;
|
||||||
const cfg = TalentSet[key];
|
const cfg = TalentSet[key];
|
||||||
if (!cfg) continue;
|
if (!cfg) continue;
|
||||||
// 专精天赋:职业不匹配则跳过
|
// 职业天赋:职业不匹配则跳过
|
||||||
if (cfg.role !== undefined && cfg.role !== role) continue;
|
if (cfg.role !== undefined && cfg.role !== role) continue;
|
||||||
// 前置要求:任一前置天赋等级不足则不生效
|
|
||||||
if (cfg.requires) {
|
|
||||||
let ok = true;
|
|
||||||
for (const req of cfg.requires) {
|
|
||||||
if ((levels[req.uuid] ?? 0) < req.lv) { ok = false; break; }
|
|
||||||
}
|
|
||||||
if (!ok) continue;
|
|
||||||
}
|
|
||||||
for (const b of cfg.bonuses) {
|
for (const b of cfg.bonuses) {
|
||||||
result.push({ attr: b.attr, mode: b.mode, value: b.per_lv * lv });
|
result.push({ attr: b.attr, mode: b.mode, value: b.per_lv * lv });
|
||||||
}
|
}
|
||||||
@@ -240,7 +401,7 @@ export function getTalentBonusesForRole(levels: Record<number, number>, role?: H
|
|||||||
*
|
*
|
||||||
* @param model 英雄属性模型(满足 ITalentTarget 结构)
|
* @param model 英雄属性模型(满足 ITalentTarget 结构)
|
||||||
* @param levels 天赋等级表(smc.getTalentLevels())
|
* @param levels 天赋等级表(smc.getTalentLevels())
|
||||||
* @param role 英雄职业(专精过滤;无 role 的怪物由调用方跳过)
|
* @param role 英雄职业(职业过滤;无 role 的怪物由调用方跳过)
|
||||||
*/
|
*/
|
||||||
export function applyTalentBonuses(model: ITalentTarget, levels: Record<number, number>, role?: HRole): void {
|
export function applyTalentBonuses(model: ITalentTarget, levels: Record<number, number>, role?: HRole): void {
|
||||||
const bonuses = getTalentBonusesForRole(levels, role);
|
const bonuses = getTalentBonusesForRole(levels, role);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# 天赋系统设计 — Draftmaster Arena
|
# 天赋系统设计 — Draftmaster Arena
|
||||||
|
|
||||||
*Created: 2026-04-26*
|
*Created: 2026-04-26*
|
||||||
*Updated: 2026-04-26*
|
*Updated: 2026-08-25*
|
||||||
*Status: Draft*
|
*Status: Draft*
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -10,7 +10,15 @@
|
|||||||
|
|
||||||
局外成长系统。通过每局获取的经验值提升玩家等级,获得天赋点数,用于升级天赋。升级即激活,无需单独激活步骤。看广告可重置所有天赋等级并退还天赋点。
|
局外成长系统。通过每局获取的经验值提升玩家等级,获得天赋点数,用于升级天赋。升级即激活,无需单独激活步骤。看广告可重置所有天赋等级并退还天赋点。
|
||||||
|
|
||||||
**设计原则**:天赋提供小幅加成,不改变核心策略体验。高评分 = 更多经验 = 更快升级,但天赋不是胜利的决定性因素。
|
**三阶结构**:
|
||||||
|
|
||||||
|
| 阶位 | 名称 | 生效范围 | uuid 段位 |
|
||||||
|
|------|------|---------|----------|
|
||||||
|
| 一阶 | 基础天赋 | 全局(所有英雄) | 9001-9099 |
|
||||||
|
| 二阶 | 专精天赋 | 按职业过滤 | 9101-9199 |
|
||||||
|
| 三阶 | 超强天赋 | 全局 + 职业混合 | 9201-9219 |
|
||||||
|
|
||||||
|
**设计原则**:天赋提供小幅加成,不改变核心策略体验。高评分 = 更多经验 = 更快升级,但天赋不是胜利的决定性因素。三阶超强天赋幅度约为前两阶的 2 倍,作为长线成长的目标感奖励。
|
||||||
|
|
||||||
**成长闭环**:评分 → 经验 → 等级 → 天赋点 → 天赋升级 → 下一局更强 → 更高评分
|
**成长闭环**:评分 → 经验 → 等级 → 天赋点 → 天赋升级 → 下一局更强 → 更高评分
|
||||||
|
|
||||||
@@ -52,25 +60,54 @@
|
|||||||
|
|
||||||
## 天赋列表
|
## 天赋列表
|
||||||
|
|
||||||
### 战斗属性类(全局加成)
|
### 一阶·基础天赋(全局加成,9001-9099)
|
||||||
|
|
||||||
| 天赋 | 每级效果 | 5级总效果 |
|
| 天赋 | 每级效果 | 5级总效果 |
|
||||||
|------|---------|----------|
|
|------|---------|----------|
|
||||||
| **攻击强化** | 所有英雄 ATK +3% | +15% |
|
| **攻击强化** | 所有英雄 ATK +3% | +15% |
|
||||||
| **生命强化** | 所有英雄 HP +5% | +25% |
|
| **生命强化** | 所有英雄 HP +5% | +25% |
|
||||||
|
| **防御强化** | 所有英雄防御 +8 点 | +40 点 |
|
||||||
| **暴击强化** | 所有英雄暴击率 +2% | +10% |
|
| **暴击强化** | 所有英雄暴击率 +2% | +10% |
|
||||||
| **风怒强化** | 所有英雄风怒率 +2% | +10% |
|
| **击晕强化** | 所有英雄击晕率 +2% | +10% |
|
||||||
| **冰冻强化** | 所有英雄冰冻率 +2% | +10% |
|
| **冰冻强化** | 所有英雄冰冻率 +2% | +10% |
|
||||||
| **穿刺强化** | 所有英雄穿刺 +0.2 | +1 |
|
| **击退强化** | 所有英雄击退距离 +10 | +50 |
|
||||||
| **护盾强化** | 所有护盾效果 +5% | +25% |
|
|
||||||
|
|
||||||
### 经济类(金币减免/补贴)
|
> 风怒强化(原 9008)已随风怒机制停用移除。
|
||||||
|
|
||||||
|
### 二阶·专精天赋(按职业过滤,9101-9199)
|
||||||
|
|
||||||
|
| 天赋 | 职业 | 每级效果 | 5级总效果 |
|
||||||
|
|------|------|---------|----------|
|
||||||
|
| **坦克专精** | 坦克 | 防御 +30%、生命 +5% | +150%、+25% |
|
||||||
|
| **战士专精** | 战士 | 生命 +12% | +60% |
|
||||||
|
| **刺客专精** | 刺客 | ATK +8%、暴击率 +2% | +40%、+10% |
|
||||||
|
| **射手专精** | 射手 | ATK +8%、穿透率 +2% | +40%、+10% |
|
||||||
|
| **法师专精** | 法师 | ATK +8%、冰冻率 +2% | +40%、+10% |
|
||||||
|
| **辅助专精** | 辅助 | 治疗量 +10%、增益效果 +8% | +50%、+40% |
|
||||||
|
|
||||||
|
> 专精天赋不再有独立前置天赋,仅受阶位解锁约束(见核心规则)。
|
||||||
|
|
||||||
|
### 三阶·超强天赋(全局 + 职业混合,9201-9219)
|
||||||
|
|
||||||
|
**全局超强(9201-9204)**:
|
||||||
|
|
||||||
| 天赋 | 每级效果 | 5级总效果 |
|
| 天赋 | 每级效果 | 5级总效果 |
|
||||||
|------|---------|----------|
|
|------|---------|----------|
|
||||||
| **采购优惠** | 购买英雄 -1金 | -5金(10→5) |
|
| **神威降临** | 所有英雄 ATK +8%、HP +8% | +40%、+40% |
|
||||||
| **刷新优惠** | 刷新重抽 -0.5金 | -2.5金(3→0.5) |
|
| **致命终结** | 所有英雄暴击率 +3%、暴击伤害 +15% | +15%、+75% |
|
||||||
| **出售补贴** | 出售英雄返还 +10%金币 | +50% |
|
| **破甲重创** | 所有英雄穿透率 +3%、穿透伤害 +10% | +15%、+50% |
|
||||||
|
| **钢铁之躯** | 所有英雄防御 +20%、HP +6% | +100%、+30% |
|
||||||
|
|
||||||
|
**职业超强(9211-9216)**:
|
||||||
|
|
||||||
|
| 天赋 | 职业 | 每级效果 | 5级总效果 |
|
||||||
|
|------|------|---------|----------|
|
||||||
|
| **不朽壁垒** | 坦克 | 防御 +40%、生命 +8% | +200%、+40% |
|
||||||
|
| **狂血战魂** | 战士 | ATK +10%、HP +8% | +50%、+40% |
|
||||||
|
| **影刃绝杀** | 刺客 | ATK +12%、暴击伤害 +12% | +60%、+60% |
|
||||||
|
| **穿云贯日** | 射手 | ATK +12%、穿透伤害 +8% | +60%、+40% |
|
||||||
|
| **元素主宰** | 法师 | ATK +12%、冰冻率 +3% | +60%、+15% |
|
||||||
|
| **圣光普照** | 辅助 | 治疗量 +15%、增益效果 +12% | +75%、+60% |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -84,17 +121,20 @@
|
|||||||
| 4级 | 2点 | 6 |
|
| 4级 | 2点 | 6 |
|
||||||
| 5级 | 3点 | 9 |
|
| 5级 | 3点 | 9 |
|
||||||
|
|
||||||
**一个天赋升满 = 9点**
|
**一个天赋升满 = 9点**(各阶位升级费用相同)
|
||||||
|
|
||||||
30个天赋点的分配选择:
|
|
||||||
- 专精:升满3个天赋(27点)+ 3点分散
|
|
||||||
- 均衡:6-7个天赋各1-2级
|
|
||||||
- 经济:采购+刷新+出售全满(27点)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 核心规则
|
## 核心规则
|
||||||
|
|
||||||
|
### 阶位解锁
|
||||||
|
|
||||||
|
- **一阶**:恒可升级
|
||||||
|
- **二阶**:一阶天赋累计投入 **≥10 点** 后解锁
|
||||||
|
- **三阶**:二阶天赋累计投入 **≥10 点** 后解锁
|
||||||
|
- 投入点数按实际消耗计算(非等级总和),与重置退款口径一致
|
||||||
|
- 专精/超强职业天赋没有独立前置天赋,仅受阶位解锁约束
|
||||||
|
|
||||||
### 升级即激活
|
### 升级即激活
|
||||||
|
|
||||||
- 消耗天赋点升级天赋 → 立即生效
|
- 消耗天赋点升级天赋 → 立即生效
|
||||||
@@ -104,7 +144,7 @@
|
|||||||
|
|
||||||
### 重置(看广告)
|
### 重置(看广告)
|
||||||
|
|
||||||
- 观看广告 → **所有天赋等级归零**
|
- 观看广告 → **所有天赋等级归零**(阶位解锁状态随之重置)
|
||||||
- **退还全部天赋点**(30点内已消耗的)
|
- **退还全部天赋点**(30点内已消耗的)
|
||||||
- 玩家等级不变(经验永久累积)
|
- 玩家等级不变(经验永久累积)
|
||||||
- 重新自由分配天赋点
|
- 重新自由分配天赋点
|
||||||
@@ -114,67 +154,83 @@
|
|||||||
|
|
||||||
## 分配策略示例
|
## 分配策略示例
|
||||||
|
|
||||||
**暴击流专精**(27点):
|
30 点总预算下:10 点开二阶 + 10 点开三阶 = 20 点门槛,剩余约 10 点自由分配。
|
||||||
|
|
||||||
|
**满配输出流**(30点):
|
||||||
```
|
```
|
||||||
暴击强化 Lv5(9) + 攻击强化 Lv5(9) + 采购优惠 Lv5(9) = 27点
|
一阶: 攻击强化 Lv4(6) + 暴击强化 Lv2(2) + 生命强化 Lv2(2) = 10点 → 解锁二阶
|
||||||
→ 全局暴击+10%, ATK+15%, 买卡-5金
|
二阶: 刺客专精 Lv4(6) + 战士专精 Lv2(2) + 坦克专精 Lv1(1) + 射手专精 Lv1(1) = 10点 → 解锁三阶
|
||||||
|
三阶: 致命终结 Lv4(6) + 影刃绝杀 Lv1(1) = 7点 + 3点备用
|
||||||
|
→ 全局暴击+15%/暴伤+75%,刺客 ATK+40%+60%、暴伤+60%+12%
|
||||||
```
|
```
|
||||||
|
|
||||||
**护盾流专精**(27点):
|
**生存流**(29点):
|
||||||
```
|
```
|
||||||
护盾强化 Lv5(9) + 生命强化 Lv5(9) + 冰冻强化 Lv5(9) = 27点
|
一阶: 生命强化 Lv5(9) + 防御强化 Lv1(1) = 10点
|
||||||
→ 护盾+25%, HP+25%, 冰冻+10%
|
二阶: 坦克专精 Lv5(9) + 战士专精 Lv1(1) = 10点
|
||||||
|
三阶: 钢铁之躯 Lv5(9) = 9点
|
||||||
|
→ 全局 HP+55%/防御+100%,坦克 HP+65%/防御+250%
|
||||||
```
|
```
|
||||||
|
|
||||||
**经济流**(27点):
|
**均衡新手**(20点):
|
||||||
```
|
```
|
||||||
采购优惠 Lv5(9) + 刷新优惠 Lv5(9) + 出售补贴 Lv5(9) = 27点
|
一阶: 攻击 Lv3(4) + 生命 Lv3(4) + 暴击 Lv1(1) + 击晕 Lv1(1) = 10点
|
||||||
→ 买卡5金, 刷新0.5金, 出售+50%返还
|
二阶: 按主力阵容选 2 个专精各 Lv3(8) + 1 个 Lv1(1) ≈ 10点(刚开三阶)
|
||||||
```
|
三阶: 留待后续点数
|
||||||
|
|
||||||
**均衡新手**(26点):
|
|
||||||
```
|
|
||||||
攻击 Lv3(4) + 生命 Lv3(4) + 采购 Lv3(4) +
|
|
||||||
暴击 Lv2(2) + 护盾 Lv2(2) + 刷新 Lv2(2) +
|
|
||||||
风怒 Lv2(2) + 冰冻 Lv2(2) = 22点 + 4点备用
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 界面布局
|
## 界面设计(阶位 Tab 单屏聚焦 · 迷你点选卡)
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────┐
|
┌─────────────────────────────────┐
|
||||||
│ Lv.12 ████████░░ 150/200│
|
│ 天赋点: 4 │
|
||||||
│ 天赋点: 4/30 │
|
│ [Ⅰ 基础] [Ⅱ 专精 6/10🔒] [Ⅲ 超强 0/10🔒] │
|
||||||
├─────────────────────────┤
|
├─────────────────────────────────┤
|
||||||
│ │
|
│ ┌──────┐ ┌──────┐ ┌──────┐ │
|
||||||
│ ⚔️ 攻击强化 Lv.3 ███░░ │
|
│ │ ⚔️ │ │ ❤️ │ │ 🛡️ │ │
|
||||||
│ ATK +9% [+1点] │
|
│ │ 攻击强化│ │ 生命强化│ │ 防御强化│ │
|
||||||
│ │
|
│ │攻击 +9%│ │生命+10%│ │防御+8点│ │
|
||||||
│ ❤️ 生命强化 Lv.2 ██░░░ │
|
│ │ 3/5│ │ 2/5│ │ 1/5│ │
|
||||||
│ HP +10% [+2点] │
|
│ │ [+]│ │ [+]│ │ [+]│ │
|
||||||
│ │
|
│ └──────┘ └──────┘ └──────┘ │
|
||||||
│ 🔥 暴击强化 Lv.1 █░░░░ │
|
│ ……(Grid 三列,单屏聚焦当前阶) │
|
||||||
│ 暴击 +2% [+1点] │
|
|
||||||
│ │
|
|
||||||
│ ⚡ 风怒强化 Lv.0 ░░░░░ │
|
|
||||||
│ 风怒 +0% [+1点] │
|
|
||||||
│ │
|
|
||||||
│ ❄️ 冰冻强化 Lv.0 ░░░░░ │
|
|
||||||
│ 冰冻 +0% [+1点] │
|
|
||||||
│ │
|
|
||||||
│ 🛡️ 护盾强化 Lv.0 ░░░░░ │
|
|
||||||
│ 🗡️ 穿刺强化 Lv.0 ░░░░░ │
|
|
||||||
│ 🛒 采购优惠 Lv.2 ██░░░ │
|
|
||||||
│ 🔄 刷新优惠 Lv.0 ░░░░░ │
|
|
||||||
│ 💰 出售补贴 Lv.0 ░░░░░ │
|
|
||||||
│ │
|
│ │
|
||||||
│ [🎬 看广告重置天赋] │
|
│ [🎬 看广告重置天赋] │
|
||||||
│ [← 返回] │
|
└─────────────────────────────────┘
|
||||||
└─────────────────────────┘
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 卡片规格(迷你点选卡,自包含)
|
||||||
|
|
||||||
|
- **布局**:图标 / 名称 / 当前已生效数值 / 右下「当前/满级」数字角标;右上 `+` 升级徽标
|
||||||
|
- **卡面数值**:已加点显示当前生效值(如「攻击 +9%」);Lv0 显示每级引导(如「攻击 +3%/级」);**不显示满级效果**(`getTalentDisplayText`)
|
||||||
|
- **阶位底框**:复用 cardbg.prefab + CardBgComp,Ⅰ→蓝(2) / Ⅱ→紫(3) / Ⅲ→金(4)
|
||||||
|
- **状态机**:可升级(`+` 徽标绿光呼吸)|满级(MAX 金角标)|阶未解锁(半透明 110 + 🔒,无 `+`)
|
||||||
|
|
||||||
|
### 交互规则
|
||||||
|
|
||||||
|
- **点击 `+` 徽标 / 卡体** → 统一上抛 uuid,由 TalentsComp 校验:
|
||||||
|
- 阶未解锁 → toast「一阶投入 x/10 点解锁本阶」
|
||||||
|
- 已满级 → toast「该天赋已满级」
|
||||||
|
- 点数不足 → toast「天赋点不足」
|
||||||
|
- 校验通过 → `smc.upgradeTalent(uuid)`,成功由 `TALENT_UPDATE` 事件驱动全量刷新
|
||||||
|
- **阶位 Tab**:三 Tab 常显;锁定 Tab 显示前阶投入进度(`x/10`);点击锁定 Tab 允许查看(卡片呈锁定态)并 toast 解锁条件;首次进入默认选中已解锁的最高阶
|
||||||
|
- **重置**:底部按钮 → CommonPrompt 二次确认 → `smc.resetTalents()`(广告 SDK 接入前先直接确认重置)
|
||||||
|
|
||||||
|
### 编辑器搭建清单(prefab)
|
||||||
|
|
||||||
|
- `talents.prefab`(复用现有骨架改造):ScrollView + Grid 三列 content 挂 `talents_content`;新增页头 `lbl_points`、三个 Tab 节点(挂 `tab_basic/tab_special/tab_super` + 各自 Label、可选子节点 `sp_on` 选中高亮)、底部 `btn_reset`
|
||||||
|
- `prefab_talent_item.prefab`(复用改造):`icon` / `lbl_name` / `lbl_desc`→`lbl_info` / `lbl_level`→`lbl_lv` 四个既有节点绑定沿用;新增 `node_plus`(+ 徽标)、`node_max`(MAX 角标)、`node_lock`(锁遮罩)
|
||||||
|
- 图标:TalentSet 配置 `icon` 字段(uiconsAtlas 帧名),暂复用技能图标素材
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 实现映射
|
||||||
|
|
||||||
|
- 配置与工具函数:`TalentSet.ts`(`TalentTier` 阶位枚举、`isTalentTierUnlocked` / `getTalentTierInvestedPoints` 解锁校验、`getTalentDisplayText` 卡面文案、`applyTalentBonuses` 战斗注入)
|
||||||
|
- 存储/升级/重置:`SingletonModuleComp.ts`(`upgradeTalent` 内做阶位解锁校验)
|
||||||
|
- 战斗注入点:`Hero.load`(召唤加载)与 `MissionCardComp.applyHeroLevel`(局内升级重算,累加语义字段先归零再注入)
|
||||||
|
- 加点 UI:`TalentsComp.ts`(阶位 Tab 页容器)+ `TalentItemComp.ts`(迷你升级卡),挂主界面现有"天赋"页签(MissionHomeComp.showPage 切换)
|
||||||
|
|
||||||
*与评分系统联动:评分 → 经验 → 等级 → 天赋点 → 天赋 → 下一局更强 → 更高评分 → 更多经验*
|
*与评分系统联动:评分 → 经验 → 等级 → 天赋点 → 天赋 → 下一局更强 → 更高评分 → 更多经验*
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ export class Hero extends ecs.Entity {
|
|||||||
model.ap += apExtra;
|
model.ap += apExtra;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 天赋加成层:基础天赋全局生效,专精天赋按职业过滤(仅英雄生效,怪物跳过)
|
// 天赋加成层:全局天赋(一阶基础/三阶超强全局)全局生效,职业天赋(二阶专精/三阶超强职业)按 role 过滤(仅英雄生效,怪物跳过)
|
||||||
// Why: 放在成长/bonus 之后、光环乘区之后,以基础快照为基数叠加,避免与乘区叠加成乘法膨胀
|
// Why: 放在成长/bonus 之后、光环乘区之后,以基础快照为基数叠加,避免与乘区叠加成乘法膨胀
|
||||||
if (model.fac === FacSet.HERO) {
|
if (model.fac === FacSet.HERO) {
|
||||||
applyTalentBonuses(model, smc.getTalentLevels(), model.role);
|
applyTalentBonuses(model, smc.getTalentLevels(), model.role);
|
||||||
|
|||||||
@@ -2189,11 +2189,13 @@ export class MissionCardComp extends CCComp {
|
|||||||
if (model.fac === FacSet.HERO) {
|
if (model.fac === FacSet.HERO) {
|
||||||
model.defense = hero.def;
|
model.defense = hero.def;
|
||||||
model.critical = 0;
|
model.critical = 0;
|
||||||
|
model.critical_damage = 0;
|
||||||
model.stun_chance = 0;
|
model.stun_chance = 0;
|
||||||
model.freeze_chance = 0;
|
model.freeze_chance = 0;
|
||||||
// 风怒机制已停用: 升级重算不再重置风怒
|
// 风怒机制已停用: 升级重算不再重置风怒
|
||||||
// model.wfuny = 0;
|
// model.wfuny = 0;
|
||||||
model.puncture_chance = 0;
|
model.puncture_chance = 0;
|
||||||
|
model.puncture_dmg_bonus = 0;
|
||||||
model.knockback_distance = 0;
|
model.knockback_distance = 0;
|
||||||
model.heal_power = 0;
|
model.heal_power = 0;
|
||||||
model.buff_power = 0;
|
model.buff_power = 0;
|
||||||
|
|||||||
@@ -1,28 +1,36 @@
|
|||||||
/**
|
/**
|
||||||
* @file TalentItemComp.ts
|
* @file TalentItemComp.ts
|
||||||
* @description 天赋图鉴项组件(UI 视图层)
|
* @description 天赋迷你升级卡组件(UI 视图层)
|
||||||
*
|
*
|
||||||
* 职责:
|
* 职责:
|
||||||
* 1. 接收 TalentsComp 下发的天赋卡配置(技能卡)。
|
* 1. 接收 TalentsComp 下发的天赋配置 + 当前等级 + 阶位解锁态,渲染迷你点选卡。
|
||||||
* 2. 渲染名称、描述、图标和背景。
|
* 2. 卡面自包含:图标 / 名称 / 当前已生效数值(Lv0 显示每级数值引导)/ 右下「当前/满级」角标。
|
||||||
* 3. 背景颜色根据 wave 映射为对应的 poolLv。
|
* 3. 状态机:可升级(右上 + 徽标,绿光呼吸)/ 满级(MAX 金角标)/ 阶未解锁(半透明 + 锁)。
|
||||||
|
* 4. 点击回调上抛(含 + 徽标与卡体),升级校验与 toast 统一由 TalentsComp 处理(单一决策点)。
|
||||||
*
|
*
|
||||||
* 依赖:
|
* 依赖:
|
||||||
* - CardSet.CardConfig —— 单条卡牌配置
|
* - TalentSet.TalentConfig / TalentTier / getTalentDisplayText —— 配置与卡面文案
|
||||||
* - SkillSet —— 获取技能图标
|
* - CardBgComp —— 阶位品质底框(Ⅰ蓝 / Ⅱ紫 / Ⅲ金)
|
||||||
|
* - smc.uiconsAtlas —— 图标解析(缺省回退资源目录)
|
||||||
*/
|
*/
|
||||||
import { _decorator, Label, Sprite, SpriteFrame } from "cc";
|
import { _decorator, Label, Node, NodeEventType, Sprite, SpriteFrame, UIOpacity } from "cc";
|
||||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||||
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
||||||
import { CardConfig } from "../common/config/CardSet";
|
import { TalentConfig, TalentTier, getTalentDisplayText } from "../common/config/TalentSet";
|
||||||
import { FieldSkillSet, SkillSet } from "../common/config/SkillSet";
|
|
||||||
import { CardBgComp } from "./CardBgComp";
|
import { CardBgComp } from "./CardBgComp";
|
||||||
import { smc } from "../common/SingletonModuleComp";
|
import { smc } from "../common/SingletonModuleComp";
|
||||||
import { oops } from "db://oops-framework/core/Oops";
|
import { oops } from "db://oops-framework/core/Oops";
|
||||||
|
|
||||||
const { ccclass, property } = _decorator;
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
/** TalentItemComp —— 天赋图鉴项组件 */
|
/** 阶位 → 品质底框 card_lv(CARD_LV_BG_COLORS:2=blue、3=purple、4=yellow) */
|
||||||
|
const TIER_TO_BG_LV: Record<number, number> = {
|
||||||
|
[TalentTier.Basic]: 2,
|
||||||
|
[TalentTier.Specialized]: 3,
|
||||||
|
[TalentTier.Super]: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** TalentItemComp —— 天赋迷你升级卡组件 */
|
||||||
@ccclass('TalentItemComp')
|
@ccclass('TalentItemComp')
|
||||||
@ecs.register('TalentItem', false)
|
@ecs.register('TalentItem', false)
|
||||||
export class TalentItemComp extends CCComp {
|
export class TalentItemComp extends CCComp {
|
||||||
@@ -30,53 +38,86 @@ export class TalentItemComp extends CCComp {
|
|||||||
@property({ type: Label, tooltip: "天赋名称" })
|
@property({ type: Label, tooltip: "天赋名称" })
|
||||||
lbl_name: Label = null!;
|
lbl_name: Label = null!;
|
||||||
|
|
||||||
@property({ type: Label, tooltip: "天赋说明" })
|
@property({ type: Label, tooltip: "当前已生效数值(Lv0 显示每级引导)" })
|
||||||
lbl_info: Label = null!;
|
lbl_info: Label = null!;
|
||||||
|
|
||||||
|
@property({ type: Label, tooltip: "等级角标(当前/满级)" })
|
||||||
|
lbl_lv: Label = null!;
|
||||||
|
|
||||||
@property({ type: Sprite, tooltip: "天赋图标" })
|
@property({ type: Sprite, tooltip: "天赋图标" })
|
||||||
icon: Sprite = null!;
|
icon: Sprite = null!;
|
||||||
|
|
||||||
@property({ type: CardBgComp, tooltip: "卡牌背景组件" })
|
@property({ type: CardBgComp, tooltip: "卡牌背景组件(阶位品质色)" })
|
||||||
bg: CardBgComp = null!;
|
bg: CardBgComp = null!;
|
||||||
|
|
||||||
|
@property({ type: Node, tooltip: "右上 + 升级徽标(可升级时显示)" })
|
||||||
|
node_plus: Node = null!;
|
||||||
|
|
||||||
|
@property({ type: Node, tooltip: "MAX 满级角标" })
|
||||||
|
node_max: Node = null!;
|
||||||
|
|
||||||
|
@property({ type: Node, tooltip: "阶位未解锁遮罩/锁图标" })
|
||||||
|
node_lock: Node = null!;
|
||||||
|
|
||||||
|
/** 点击回调(卡体与 + 徽标同源上抛),由父级统一校验升级 */
|
||||||
|
public onTalentTap: ((uuid: number) => void) | null = null;
|
||||||
|
|
||||||
|
/** 当前展示的天赋 uuid(回调携带) */
|
||||||
|
private curUuid: number = 0;
|
||||||
|
|
||||||
|
protected onLoad(): void {
|
||||||
|
this.node.on(NodeEventType.TOUCH_END, this.onTap, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onDestroy(): void {
|
||||||
|
this.node.off(NodeEventType.TOUCH_END, this.onTap, this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 刷新单条天赋展示
|
* 刷新迷你卡展示
|
||||||
* @param config CardPoolList 中的天赋卡(技能卡)配置
|
* @param cfg 天赋配置(TalentSet 条目)
|
||||||
|
* @param lv 当前等级
|
||||||
|
* @param tierUnlocked 所属阶位是否已解锁
|
||||||
|
* @param canUp 父级预判的"可升级"(阶位解锁 + 未满级 + 点数足够)
|
||||||
*/
|
*/
|
||||||
public updateItem(config: CardConfig): void {
|
public updateItem(cfg: TalentConfig, lv: number, tierUnlocked: boolean, canUp: boolean): void {
|
||||||
if (!config) return;
|
if (!cfg) return;
|
||||||
|
this.curUuid = cfg.uuid;
|
||||||
|
|
||||||
if (this.lbl_name) this.lbl_name.string = config.name ?? "";
|
if (this.lbl_name) this.lbl_name.string = cfg.name;
|
||||||
if (this.lbl_info) this.lbl_info.string = config.info ?? "";
|
// 卡面自包含:已加点显示当前生效数值,未加点显示每级数值引导
|
||||||
|
if (this.lbl_info) this.lbl_info.string = getTalentDisplayText(cfg, lv);
|
||||||
|
if (this.lbl_lv) this.lbl_lv.string = `${lv}/${cfg.max_lv}`;
|
||||||
|
|
||||||
// 直接复用 CardSet 中已映射好的 card_lv,保证与技能卡牌背景一致
|
// 阶位品质底框:Ⅰ蓝 / Ⅱ紫 / Ⅲ金
|
||||||
// CardSet 通过 waveToCardLv[wave] 由 SKILL_CARD_WAVES 索引推导(wave 1→1, 5→2, 8→3)
|
if (this.bg) this.bg.apply(TIER_TO_BG_LV[cfg.tier] ?? 1);
|
||||||
if (this.bg) {
|
|
||||||
this.bg.apply(config.card_lv || 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置图标:驻场卡(skill=undefined 但有 field)走 FieldSkillSet,否则走 SkillSet
|
// 图标:uiconsAtlas 优先,资源目录回退(与 TalentItemComp 旧版/SCardComp 口径一致)
|
||||||
// 与 SCardComp 保持一致,避免驻场卡无 icon 显示
|
if (this.icon && cfg.icon) {
|
||||||
if (this.icon) {
|
let frame: SpriteFrame | null = null;
|
||||||
let iconId: string | undefined;
|
|
||||||
if (!config.skill && config.field && config.field.length > 0) {
|
|
||||||
// 驻场卡:用 FieldSkillSet[field[0]].icon
|
|
||||||
const fieldUuid = config.field[0].uuid;
|
|
||||||
iconId = FieldSkillSet[fieldUuid]?.icon || `${fieldUuid}`;
|
|
||||||
} else if (config.skill) {
|
|
||||||
// 技能卡:用 SkillSet[skill].icon
|
|
||||||
iconId = SkillSet[config.skill]?.icon;
|
|
||||||
}
|
|
||||||
if (iconId) {
|
|
||||||
if (smc.uiconsAtlas) {
|
if (smc.uiconsAtlas) {
|
||||||
const frame = smc.uiconsAtlas.getSpriteFrame(iconId);
|
frame = smc.uiconsAtlas.getSpriteFrame(cfg.icon);
|
||||||
|
}
|
||||||
|
if (!frame) {
|
||||||
|
frame = oops.res.get("game/heros/cards/" + cfg.icon, SpriteFrame) as SpriteFrame;
|
||||||
|
}
|
||||||
if (frame) this.icon.spriteFrame = frame;
|
if (frame) this.icon.spriteFrame = frame;
|
||||||
} else {
|
|
||||||
const sf = oops.res.get("game/heros/cards/" + iconId, SpriteFrame) as SpriteFrame;
|
|
||||||
if (sf) this.icon.spriteFrame = sf;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 状态徽标:满级 MAX / 可升级 + / 阶位锁定 🔒
|
||||||
|
const maxed = lv >= cfg.max_lv;
|
||||||
|
if (this.node_max) this.node_max.active = maxed;
|
||||||
|
if (this.node_plus) this.node_plus.active = !maxed && tierUnlocked && canUp;
|
||||||
|
if (this.node_lock) this.node_lock.active = !tierUnlocked;
|
||||||
|
|
||||||
|
// 阶位锁定整卡压暗(UIOpacity:Cocos 3.x 节点透明度组件)
|
||||||
|
const uo = this.node.getComponent(UIOpacity) ?? this.node.addComponent(UIOpacity);
|
||||||
|
uo.opacity = tierUnlocked ? 255 : 110;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 卡体/+ 徽标点击:上抛 uuid,升级校验与提示由 TalentsComp 统一处理 */
|
||||||
|
private onTap(): void {
|
||||||
|
this.onTalentTap?.(this.curUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ECS 组件移除时销毁节点(CCComp 抽象方法实现) */
|
/** ECS 组件移除时销毁节点(CCComp 抽象方法实现) */
|
||||||
|
|||||||
@@ -1,106 +1,233 @@
|
|||||||
/**
|
/**
|
||||||
* @file TalentsComp.ts
|
* @file TalentsComp.ts
|
||||||
* @description 天赋图鉴页组件(UI 视图层)
|
* @description 天赋加点页组件(UI 视图层)
|
||||||
*
|
*
|
||||||
* 职责:
|
* 职责:
|
||||||
* 1. 展示当前所有天赋卡(技能卡)的图鉴。
|
* 1. 三阶页签(Ⅰ 基础 / Ⅱ 专精 / Ⅲ 超强)状态机:单屏聚焦当前阶位的迷你天赋卡。
|
||||||
* 2. 从 CardPoolList 获取所有的 type=Skill 卡牌。
|
* 2. 页签常显解锁进度(前阶投入 x/10),点击卡/+ 徽标升级(smc.upgradeTalent)。
|
||||||
* 3. 兼容旧的 `@ecs.register('Talents')` 资源引用。
|
* 3. 监听 GameEvent.TALENT_UPDATE 全量重渲染(23 条卡片,无需虚拟列表)。
|
||||||
|
* 4. 看广告重置入口:CommonPrompt 二次确认后调 smc.resetTalents() 退还全部点数。
|
||||||
*
|
*
|
||||||
* 依赖:
|
* 依赖:
|
||||||
* - CardPoolList(CardSet)—— 获取技能卡配置
|
* - TalentSet(TalentSet/TalentTier/isTalentTierUnlocked/getTalentTierInvestedPoints)—— 配置与阶位校验
|
||||||
* - TalentItemComp —— 单条天赋图鉴项视图
|
* - TalentItemComp —— 单张迷你升级卡
|
||||||
|
* - smc —— 天赋等级表/点数/升级/重置
|
||||||
*/
|
*/
|
||||||
import { _decorator, instantiate, Node, Prefab, UITransform } from "cc";
|
import { _decorator, instantiate, Label, Node, NodeEventType, Prefab, UITransform } from "cc";
|
||||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||||
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
||||||
import { mLogger } from "../common/Logger";
|
import {
|
||||||
import { CardConfig } from "../common/config/CardSet";
|
TalentSet, TalentTier,
|
||||||
import { SkillCardList } from "../common/config/SCardSet";
|
TALENT_TIER_UNLOCK_THRESHOLD,
|
||||||
|
getTalentTierInvestedPoints, getTalentUpgradeCost, isTalentTierUnlocked,
|
||||||
|
} from "../common/config/TalentSet";
|
||||||
|
import { GameEvent } from "../common/config/GameEvent";
|
||||||
|
import { UIID } from "../common/config/GameUIConfig";
|
||||||
import { TalentItemComp } from "./TalentItemComp";
|
import { TalentItemComp } from "./TalentItemComp";
|
||||||
|
import { smc } from "../common/SingletonModuleComp";
|
||||||
|
import { oops } from "db://oops-framework/core/Oops";
|
||||||
|
|
||||||
const { ccclass, property } = _decorator;
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
/** TalentsComp —— 天赋图鉴信息页组件 */
|
/** 阶位页签展示文案(锁定态追加投入进度) */
|
||||||
|
const TIER_TAB_TEXT: Record<number, { title: string; lockName: string }> = {
|
||||||
|
[TalentTier.Basic]: { title: "Ⅰ 基础", lockName: "一阶" },
|
||||||
|
[TalentTier.Specialized]: { title: "Ⅱ 专精", lockName: "一阶" },
|
||||||
|
[TalentTier.Super]: { title: "Ⅲ 超强", lockName: "二阶" },
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 阶位页签选中高亮子节点名(可选,编辑器侧搭建) */
|
||||||
|
const TAB_SELECTED_CHILD = "sp_on";
|
||||||
|
|
||||||
|
/** TalentsComp —— 天赋加点页组件(阶位 Tab 单屏聚焦) */
|
||||||
@ccclass('TalentsComp')
|
@ccclass('TalentsComp')
|
||||||
@ecs.register('Talents', false)
|
@ecs.register('Talents', false)
|
||||||
export class TalentsComp extends CCComp {
|
export class TalentsComp extends CCComp {
|
||||||
|
|
||||||
@property({ type: Node, tooltip: "天赋技能列表容器" })
|
@property({ type: Node, tooltip: "当前阶位卡片列表容器(ScrollView content,Grid 三列)" })
|
||||||
talents_content: Node = null!;
|
talents_content: Node = null!;
|
||||||
|
|
||||||
@property({ type: Prefab, tooltip: "单条天赋项预制" })
|
@property({ type: Prefab, tooltip: "迷你天赋卡预制(TalentItemComp)" })
|
||||||
prefab_talent_item: Prefab = null!;
|
prefab_talent_item: Prefab = null!;
|
||||||
|
|
||||||
/** 调试日志开关 */
|
@property({ type: Label, tooltip: "页头可用天赋点" })
|
||||||
debugMode: boolean = false;
|
lbl_points: Label = null!;
|
||||||
|
|
||||||
/** 首次实例化缓存 */
|
@property({ type: Node, tooltip: "一阶页签节点" })
|
||||||
private rendered: boolean = false;
|
tab_basic: Node = null!;
|
||||||
|
|
||||||
/** 缓存的稳定配置顺序,避免重复渲染时列表抖动 */
|
@property({ type: Node, tooltip: "二阶页签节点" })
|
||||||
private cachedConfigs: CardConfig[] = [];
|
tab_special: Node = null!;
|
||||||
|
|
||||||
|
@property({ type: Node, tooltip: "三阶页签节点" })
|
||||||
|
tab_super: Node = null!;
|
||||||
|
|
||||||
|
@property({ type: Label, tooltip: "一阶页签文案" })
|
||||||
|
lbl_tab_basic: Label = null!;
|
||||||
|
|
||||||
|
@property({ type: Label, tooltip: "二阶页签文案" })
|
||||||
|
lbl_tab_special: Label = null!;
|
||||||
|
|
||||||
|
@property({ type: Label, tooltip: "三阶页签文案" })
|
||||||
|
lbl_tab_super: Label = null!;
|
||||||
|
|
||||||
|
@property({ type: Node, tooltip: "看广告重置按钮" })
|
||||||
|
btn_reset: Node = null!;
|
||||||
|
|
||||||
|
/** 当前选中的阶位(首次进入默认为已解锁的最高阶,之后由玩家切换) */
|
||||||
|
private curTier: TalentTier = TalentTier.Basic;
|
||||||
|
|
||||||
|
/** 是否已完成首次默认阶位选择 */
|
||||||
|
private inited: boolean = false;
|
||||||
|
|
||||||
|
/** 阶位 → 页签节点/文案 映射(onLoad 时装配) */
|
||||||
|
private tabNodes: Map<TalentTier, { node: Node | null; label: Label | null }> = new Map();
|
||||||
|
|
||||||
|
protected onLoad(): void {
|
||||||
|
this.tabNodes.set(TalentTier.Basic, { node: this.tab_basic, label: this.lbl_tab_basic });
|
||||||
|
this.tabNodes.set(TalentTier.Specialized, { node: this.tab_special, label: this.lbl_tab_special });
|
||||||
|
this.tabNodes.set(TalentTier.Super, { node: this.tab_super, label: this.lbl_tab_super });
|
||||||
|
|
||||||
|
for (const [tier, tab] of this.tabNodes) {
|
||||||
|
tab.node?.on(NodeEventType.TOUCH_END, () => this.selectTier(tier), this);
|
||||||
|
}
|
||||||
|
this.btn_reset?.on(NodeEventType.TOUCH_END, this.onResetTap, this);
|
||||||
|
}
|
||||||
|
|
||||||
protected onEnable(): void {
|
protected onEnable(): void {
|
||||||
|
// 升级/重置/加点变动统一走 TALENT_UPDATE 事件驱动全量刷新
|
||||||
|
oops.message.on(GameEvent.TALENT_UPDATE, this.refreshUI, this);
|
||||||
this.refreshUI();
|
this.refreshUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重新拉取最新数据并刷新所有子项 */
|
protected onDisable(): void {
|
||||||
|
oops.message.off(GameEvent.TALENT_UPDATE, this.refreshUI, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onDestroy(): void {
|
||||||
|
for (const [, tab] of this.tabNodes) {
|
||||||
|
tab.node?.targetOff(this);
|
||||||
|
}
|
||||||
|
this.btn_reset?.targetOff(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 切换阶位页签:锁定阶允许查看(卡片呈锁定态),并提示解锁条件 */
|
||||||
|
private selectTier(tier: TalentTier): void {
|
||||||
|
if (tier === this.curTier) return;
|
||||||
|
this.curTier = tier;
|
||||||
|
const levels = smc.getTalentLevels();
|
||||||
|
if (!isTalentTierUnlocked(levels, tier)) {
|
||||||
|
const prev = tier - 1;
|
||||||
|
const invested = getTalentTierInvestedPoints(levels, prev);
|
||||||
|
oops.gui.toast(`${TIER_TAB_TEXT[tier].lockName}投入 ${invested}/${TALENT_TIER_UNLOCK_THRESHOLD} 点解锁本阶`);
|
||||||
|
}
|
||||||
|
this.refreshUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 全量刷新:页头点数 + 页签解锁进度 + 当前阶位卡片列表 */
|
||||||
public refreshUI(): void {
|
public refreshUI(): void {
|
||||||
if (!this.talents_content || !this.prefab_talent_item) return;
|
if (!this.talents_content || !this.prefab_talent_item) return;
|
||||||
|
const levels = smc.getTalentLevels();
|
||||||
|
|
||||||
// 第一次:实例化所有子节点
|
if (this.lbl_points) this.lbl_points.string = `天赋点 ${smc.getTalentPoints()}`;
|
||||||
if (!this.rendered) {
|
|
||||||
// 获取所有天赋(技能卡)并按 wave 和 uuid 排序
|
// 仅首次进入时默认选中已解锁的最高阶,后续刷新保持玩家当前选择
|
||||||
this.cachedConfigs = [...SkillCardList]
|
if (!this.inited) {
|
||||||
.sort((a, b) => {
|
this.inited = true;
|
||||||
const waveA = a.wave || 1;
|
if (isTalentTierUnlocked(levels, TalentTier.Super)) this.curTier = TalentTier.Super;
|
||||||
const waveB = b.wave || 1;
|
else if (isTalentTierUnlocked(levels, TalentTier.Specialized)) this.curTier = TalentTier.Specialized;
|
||||||
if (waveA !== waveB) return waveA - waveB;
|
}
|
||||||
return a.uuid - b.uuid;
|
|
||||||
});
|
this.refreshTabs(levels);
|
||||||
|
this.refreshGrid(levels);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 页签文案 + 解锁进度 + 选中高亮(sp_on 子节点可选) */
|
||||||
|
private refreshTabs(levels: Record<number, number>): void {
|
||||||
|
for (const [tier, tab] of this.tabNodes) {
|
||||||
|
const text = TIER_TAB_TEXT[tier];
|
||||||
|
if (tab.label) {
|
||||||
|
if (tier === TalentTier.Basic) {
|
||||||
|
tab.label.string = text.title;
|
||||||
|
} else {
|
||||||
|
const unlocked = isTalentTierUnlocked(levels, tier);
|
||||||
|
const invested = getTalentTierInvestedPoints(levels, (tier - 1) as TalentTier);
|
||||||
|
tab.label.string = unlocked ? text.title : `${text.title} ${invested}/${TALENT_TIER_UNLOCK_THRESHOLD}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tab.node?.getChildByName(TAB_SELECTED_CHILD)?.setPosition(0, 0);
|
||||||
|
tab.node?.getChildByName(TAB_SELECTED_CHILD) && (tab.node.getChildByName(TAB_SELECTED_CHILD)!.active = tier === this.curTier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重建当前阶位的卡片列表(每阶 ≤10 条,直接全量实例化) */
|
||||||
|
private refreshGrid(levels: Record<number, number>): void {
|
||||||
|
this.talents_content.removeAllChildren();
|
||||||
|
const unlocked = isTalentTierUnlocked(levels, this.curTier);
|
||||||
|
const points = smc.getTalentPoints();
|
||||||
|
const cfgs = Object.values(TalentSet)
|
||||||
|
.filter(cfg => cfg.tier === this.curTier)
|
||||||
|
.sort((a, b) => a.uuid - b.uuid);
|
||||||
|
|
||||||
|
let cardHeight = 0;
|
||||||
|
for (const cfg of cfgs) {
|
||||||
|
const lv = levels[cfg.uuid] ?? 0;
|
||||||
|
const cost = getTalentUpgradeCost(lv);
|
||||||
|
// 可升级 = 阶位解锁 + 未满级 + 点数足够(+ 徽标显示依据)
|
||||||
|
const canUp = unlocked && lv < cfg.max_lv && cost > 0 && points >= cost;
|
||||||
|
|
||||||
this.cachedConfigs.forEach((cfg) => {
|
|
||||||
const itemNode = instantiate(this.prefab_talent_item);
|
const itemNode = instantiate(this.prefab_talent_item);
|
||||||
this.talents_content.addChild(itemNode);
|
this.talents_content.addChild(itemNode);
|
||||||
const comp = itemNode.getComponent(TalentItemComp);
|
const comp = itemNode.getComponent(TalentItemComp);
|
||||||
if (comp) {
|
if (!comp) continue;
|
||||||
comp.updateItem(cfg);
|
comp.updateItem(cfg, lv, unlocked, canUp);
|
||||||
|
comp.onTalentTap = (uuid: number) => this.onTalentTap(uuid);
|
||||||
|
if (!cardHeight) cardHeight = itemNode.getComponent(UITransform)?.height ?? 0;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
this.rendered = true;
|
|
||||||
|
|
||||||
// 动态调整容器高度
|
|
||||||
// 默认底高 1000,每行 3 个天赋信息卡,每张卡高 270,行间距 15
|
|
||||||
const totalCount = this.cachedConfigs.length;
|
|
||||||
const rows = Math.ceil(totalCount / 3);
|
|
||||||
const contentHeight = Math.max(1000, rows * 270 + (rows > 0 ? (rows - 1) * 15 : 0 + 50));
|
|
||||||
|
|
||||||
|
// Grid 三列手算容器高度(与旧版图鉴页口径一致,spacing 15 / 上下 padding 15)
|
||||||
|
const rows = Math.ceil(cfgs.length / 3);
|
||||||
|
if (cardHeight > 0 && rows > 0) {
|
||||||
|
const contentHeight = rows * cardHeight + (rows - 1) * 15 + 30;
|
||||||
const uiTransform = this.talents_content.getComponent(UITransform);
|
const uiTransform = this.talents_content.getComponent(UITransform);
|
||||||
if (uiTransform) {
|
if (uiTransform) uiTransform.height = contentHeight;
|
||||||
uiTransform.height = contentHeight;
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
// 如果已渲染,则仅更新数据(图鉴一般不会变动,这里做个兜底更新)
|
|
||||||
this.cachedConfigs.forEach((cfg, index) => {
|
/** 卡片点击统一入口:阶位未解锁/已满级/点数不足分别 toast,成功升级由事件驱动刷新 */
|
||||||
const child = this.talents_content.children[index];
|
private onTalentTap(uuid: number): void {
|
||||||
if (!child) return;
|
const cfg = TalentSet[uuid];
|
||||||
const comp = child.getComponent(TalentItemComp);
|
if (!cfg) return;
|
||||||
if (!comp) return;
|
const levels = smc.getTalentLevels();
|
||||||
comp.updateItem(cfg);
|
|
||||||
|
if (!isTalentTierUnlocked(levels, cfg.tier)) {
|
||||||
|
const invested = getTalentTierInvestedPoints(levels, (cfg.tier - 1) as TalentTier);
|
||||||
|
oops.gui.toast(`${TIER_TAB_TEXT[cfg.tier].lockName}投入 ${invested}/${TALENT_TIER_UNLOCK_THRESHOLD} 点解锁本阶`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (smc.getTalentLv(uuid) >= cfg.max_lv) {
|
||||||
|
oops.gui.toast("该天赋已满级");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!smc.upgradeTalent(uuid)) {
|
||||||
|
oops.gui.toast("天赋点不足");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 看广告重置入口:CommonPrompt 二次确认(广告接入前先直接确认重置) */
|
||||||
|
private onResetTap(): void {
|
||||||
|
oops.gui.open(UIID.Window, {
|
||||||
|
title: "重置天赋",
|
||||||
|
content: "观看广告后重置所有天赋等级,并全额退还已消耗的天赋点。是否继续?",
|
||||||
|
okWord: "重置",
|
||||||
|
cancelWord: "取消",
|
||||||
|
needCancel: true,
|
||||||
|
okFunc: () => smc.resetTalents(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/** ECS 组件移除时销毁节点 */
|
/** ECS 组件移除时销毁节点 */
|
||||||
reset() {
|
reset() {
|
||||||
this.rendered = false;
|
|
||||||
this.cachedConfigs = [];
|
|
||||||
this.node.destroy();
|
this.node.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onDestroy(): void {
|
|
||||||
super.onDestroy();
|
|
||||||
mLogger.log(this.debugMode, 'TalentsComp', "释放界面");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user