refactor: 重构天赋系统与技能触发逻辑
1. 重构天赋系统架构:将天赋加成与触发修正统一收敛到 HeroAttrsComp.applyTalent 2. 新增天赋触发修正字段:talent_t_num_reduce 与 talent_extra_trigger_chance 3. 重构天赋加点逻辑:统一 1 点/级,新增软前置校验 4. 优化天赋UI组件:改为编辑器静态配置驱动,重构层位展示逻辑 5. 修复技能触发判定:在 SkillTriggerHelper 中应用天赋触发修正 6. 调整天赋配置结构:改为 5 层递进解锁,新增 L4/L5 专属触发修正配置
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -9180,8 +9180,8 @@
|
|||||||
"rawHeight": 52,
|
"rawHeight": 52,
|
||||||
"borderTop": 0,
|
"borderTop": 0,
|
||||||
"borderBottom": 0,
|
"borderBottom": 0,
|
||||||
"borderLeft": 0,
|
"borderLeft": 43.5,
|
||||||
"borderRight": 0,
|
"borderRight": 43.5,
|
||||||
"packable": true,
|
"packable": true,
|
||||||
"pixelsToUnit": 100,
|
"pixelsToUnit": 100,
|
||||||
"pivotX": 0.5,
|
"pivotX": 0.5,
|
||||||
|
|||||||
@@ -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, TALENT_LEVEL_MAX, getTalentLevelExp, getTalentUpgradeCost, isTalentTierUnlocked } from "./config/TalentSet";
|
import { TalentSet, TALENT_LEVEL_MAX, getTalentLevelExp, isTalentPrereqMet, isTalentTierUnlocked } from "./config/TalentSet";
|
||||||
import { ScoreRankList, ScoreTier, getRankByScore } from "./config/ScoreRankSet";
|
import { ScoreRankList, ScoreTier, getRankByScore } from "./config/ScoreRankSet";
|
||||||
import { RANK_REWARDS, STAGE_REWARDS, RewardItem, pickRandomHeroUuid } from "./config/RewardSet";
|
import { RANK_REWARDS, STAGE_REWARDS, RewardItem, pickRandomHeroUuid } from "./config/RewardSet";
|
||||||
|
|
||||||
@@ -694,7 +694,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];
|
||||||
@@ -702,11 +702,13 @@ 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;
|
||||||
// 阶位解锁:二/三阶天赋要求前一阶累计投入点数达标(一阶恒解锁)
|
// 层位解锁:第 N 层天赋要求第 N-1 层累计投入点数达标(第 1 层恒解锁)
|
||||||
if (!isTalentTierUnlocked(this.data.talents, cfg.tier)) return false;
|
if (!isTalentTierUnlocked(this.data.talents, cfg.tier)) return false;
|
||||||
const cost = getTalentUpgradeCost(cfg.tier);
|
// 软前置:前置天赋等级需达标(仅限制加点)
|
||||||
if (cost < 0 || this.getTalentPoints() < cost) return false;
|
if (!isTalentPrereqMet(this.data.talents, cfg)) return false;
|
||||||
this.data.talent_points -= cost;
|
// 统一 1 点/级
|
||||||
|
if (this.getTalentPoints() < 1) return false;
|
||||||
|
this.data.talent_points -= 1;
|
||||||
this.data.talents[uuid] = curLv + 1;
|
this.data.talents[uuid] = curLv + 1;
|
||||||
gameDataSync.markDataDirty();
|
gameDataSync.markDataDirty();
|
||||||
oops.message.dispatchEvent(GameEvent.TALENT_UPDATE);
|
oops.message.dispatchEvent(GameEvent.TALENT_UPDATE);
|
||||||
@@ -715,17 +717,13 @@ export class SingletonModuleComp extends ecs.Comp {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 重置所有天赋等级并退还全部已消耗天赋点(看广告入口)
|
* 重置所有天赋等级并退还全部已消耗天赋点(看广告入口)
|
||||||
* Why: 退还点数按"升到当前等级的累计消耗"计算,与升级消耗表严格对账,避免点数膨胀/缩水。
|
* Why: 统一 1 点/级后退还点数 = 等级总和,与升级消耗严格对账,避免点数膨胀/缩水。
|
||||||
*/
|
*/
|
||||||
resetTalents(): void {
|
resetTalents(): void {
|
||||||
if (!this.data.talents) this.data.talents = {};
|
if (!this.data.talents) this.data.talents = {};
|
||||||
let refund = 0;
|
let refund = 0;
|
||||||
for (const key in this.data.talents) {
|
for (const key in this.data.talents) {
|
||||||
const lv = this.data.talents[key];
|
refund += this.data.talents[key];
|
||||||
for (let i = 0; i < lv; i++) {
|
|
||||||
const c = getTalentUpgradeCost(i);
|
|
||||||
if (c > 0) refund += c;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.data.talents = {};
|
this.data.talents = {};
|
||||||
this.data.talent_points = (this.data.talent_points ?? 0) + refund;
|
this.data.talent_points = (this.data.talent_points ?? 0) + refund;
|
||||||
|
|||||||
@@ -4,13 +4,15 @@ import { HRole } from "./heroSet";
|
|||||||
/**
|
/**
|
||||||
* 天赋系统配置(局外成长:升级即激活,战斗中对英雄属性常驻加成)。
|
* 天赋系统配置(局外成长:升级即激活,战斗中对英雄属性常驻加成)。
|
||||||
*
|
*
|
||||||
* 三阶结构(阶位逐级解锁,门槛见 TALENT_TIER_UNLOCK_THRESHOLD):
|
* 五层递进结构(逐层解锁,门槛见 TALENT_TIER_UNLOCK_THRESHOLD):
|
||||||
* - 一阶·基础天赋(9001-9099):全局生效,对所有英雄叠加;
|
* - 第 1 层(9001-9099):恒解锁,建议放置全局基础属性;
|
||||||
* - 二阶·专精天赋(9101-9199):按 HRole 职业过滤,仅对匹配职业的英雄生效;
|
* - 第 2 层(9101-9199):第 1 层累计投入 ≥5 点解锁;
|
||||||
* - 三阶·超强天赋(9201-9219):全局(9201-9209)+ 职业(9211-9219)混合,数值幅度约为前两阶的 2 倍。
|
* - 第 3 层(9201-9299):第 2 层累计投入 ≥5 点解锁;
|
||||||
|
* - 第 4 层(9301-9399):第 3 层累计投入 ≥5 点解锁;
|
||||||
|
* - 第 5 层(9401-9499):第 4 层累计投入 ≥5 点解锁。
|
||||||
*
|
*
|
||||||
* 解锁规则:一阶恒可升级;二阶需一阶累计投入 ≥10 点;三阶需二阶累计投入 ≥10 点。
|
* 解锁规则:第 1 层恒可升级;第 N 层需第 N-1 层累计投入 ≥5 点。
|
||||||
* 专精/超强职业天赋不再有独立前置天赋,仅受阶位解锁约束。
|
* 前置天赋(prereq_uuid/prereq_lv)为软前置:仅限制加点,不影响已有点数的战斗结算。
|
||||||
*
|
*
|
||||||
* 结算口径(与项目 buff 规范一致):
|
* 结算口径(与项目 buff 规范一致):
|
||||||
* - percent 模式:按英雄基础值(base_ap/base_hp/hero.def)相对放大;
|
* - percent 模式:按英雄基础值(base_ap/base_hp/hero.def)相对放大;
|
||||||
@@ -23,14 +25,18 @@ export enum TalentValueMode {
|
|||||||
Flat = 1,
|
Flat = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 天赋阶位:一阶基础 / 二阶专精 / 三阶超强 */
|
/** 天赋层位:第 1 层 ~ 第 5 层递进解锁 */
|
||||||
export enum TalentTier {
|
export enum TalentTier {
|
||||||
/** 一阶:基础天赋,全局生效 */
|
/** 第 1 层:恒解锁 */
|
||||||
Basic = 1,
|
L1 = 1,
|
||||||
/** 二阶:专精天赋,按职业过滤 */
|
/** 第 2 层:第 1 层投入 5 点解锁 */
|
||||||
Specialized = 2,
|
L2 = 2,
|
||||||
/** 三阶:超强天赋,全局 + 职业混合 */
|
/** 第 3 层:第 2 层投入 5 点解锁 */
|
||||||
Super = 3,
|
L3 = 3,
|
||||||
|
/** 第 4 层:第 3 层投入 5 点解锁 */
|
||||||
|
L4 = 4,
|
||||||
|
/** 第 5 层:第 4 层投入 5 点解锁 */
|
||||||
|
L5 = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 单条属性加成词条 */
|
/** 单条属性加成词条 */
|
||||||
@@ -47,7 +53,7 @@ export interface TalentAttrBonus {
|
|||||||
export interface TalentConfig {
|
export interface TalentConfig {
|
||||||
/** 天赋 uuid(段位见 TalentSet 注释) */
|
/** 天赋 uuid(段位见 TalentSet 注释) */
|
||||||
uuid: number;
|
uuid: number;
|
||||||
/** 所属阶位(升级校验:升下一阶天赋需前一阶累计投入点数达标) */
|
/** 所属层位(升级校验:升下一层天赋需前一层累计投入点数达标) */
|
||||||
tier: TalentTier;
|
tier: TalentTier;
|
||||||
/** 天赋名称 */
|
/** 天赋名称 */
|
||||||
name: string;
|
name: string;
|
||||||
@@ -59,8 +65,16 @@ export interface TalentConfig {
|
|||||||
bonuses: TalentAttrBonus[];
|
bonuses: TalentAttrBonus[];
|
||||||
/** 最大等级 */
|
/** 最大等级 */
|
||||||
max_lv: number;
|
max_lv: number;
|
||||||
/** 专精/超强职业天赋:仅对该职业英雄生效;全局天赋省略此字段 */
|
/** 专精/职业天赋:仅对该职业英雄生效;全局天赋省略此字段 */
|
||||||
role?: HRole;
|
role?: HRole;
|
||||||
|
/** 软前置:前置天赋 uuid(与 prereq_lv 成对出现;缺省视为无前置) */
|
||||||
|
prereq_uuid?: number;
|
||||||
|
/** 软前置:前置天赋要求达到的等级(仅限制加点,不影响已有点数的结算) */
|
||||||
|
prereq_lv?: number;
|
||||||
|
/** L4 触发修正:触发类技能计数阈值减少量(每级,下限 1;与 bonuses 互斥) */
|
||||||
|
t_num_reduce_per_lv?: number;
|
||||||
|
/** L5 触发修正:触发类技能额外触发 1 次的概率(每级百分点;与 bonuses 互斥) */
|
||||||
|
extra_trigger_chance_per_lv?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 单条已解析的加成(等级已乘算) */
|
/** 单条已解析的加成(等级已乘算) */
|
||||||
@@ -84,208 +98,201 @@ export interface ITalentTarget {
|
|||||||
base_ap: number;
|
base_ap: number;
|
||||||
/** 基础血量快照(percent 模式的基数) */
|
/** 基础血量快照(percent 模式的基数) */
|
||||||
base_hp: number;
|
base_hp: number;
|
||||||
|
/** L4 写入:触发类技能计数阈值减少量 */
|
||||||
|
talent_t_num_reduce: number;
|
||||||
|
/** L5 写入:触发类技能额外触发 1 次的概率(百分点) */
|
||||||
|
talent_extra_trigger_chance: number;
|
||||||
/** 防御基础值(percent 模式的基数,等于 hero.def) */
|
/** 防御基础值(percent 模式的基数,等于 hero.def) */
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 天赋配置表。
|
* 天赋配置表。
|
||||||
* uuid 段位:9001-9099 一阶基础(全局);9101-9199 二阶专精(按职业);
|
* uuid 段位:9001-9099 第 1 层;9101-9199 第 2 层;9201-9299 第 3 层;
|
||||||
* 9201-9209 三阶超强·全局;9211-9219 三阶超强·职业。
|
* 9301-9399 第 4 层;9401-9499 第 5 层。
|
||||||
|
* 每层建议放置 4-6 个天赋;前置天赋用 prereq_uuid + prereq_lv 声明(软前置,仅限制加点)。
|
||||||
* icon 复用 SkillSet/uiconsAtlas 既有帧名,后续可在编辑器侧替换为专属图标。
|
* icon 复用 SkillSet/uiconsAtlas 既有帧名,后续可在编辑器侧替换为专属图标。
|
||||||
*/
|
*/
|
||||||
export const TalentSet: Record<number, TalentConfig> = {
|
export const TalentSet: Record<number, TalentConfig> = {
|
||||||
// ==================== 一阶·基础天赋(9001-9099,全局生效) ====================
|
// ==================== 第 1 层(9001-9099,恒解锁,基础属性) ====================
|
||||||
9001: {
|
9001: {
|
||||||
uuid: 9001, tier: TalentTier.Basic, name: "攻击强化", info: "所有英雄攻击 +{value}%", icon: "3-Aggression",
|
uuid: 9001, tier: TalentTier.L1, name: "攻击强化", info: "所有英雄攻击 +{value}%", icon: "3-Aggression",
|
||||||
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, tier: TalentTier.Basic, name: "生命强化", info: "所有英雄生命 +{value}%", icon: "29-Endure",
|
uuid: 9002, tier: TalentTier.L1, name: "生命强化", info: "所有英雄生命 +{value}%", icon: "29-Endure",
|
||||||
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, tier: TalentTier.Basic, name: "防御强化", info: "所有英雄防御 +{value} 点", icon: "46-Iron-Clad",
|
uuid: 9003, tier: TalentTier.L1, name: "防御强化", info: "所有英雄防御 +{value} 点", icon: "46-Iron-Clad",
|
||||||
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, tier: TalentTier.Basic, name: "暴击强化", info: "所有英雄暴击率 +{value}%", icon: "27-Hawkeye",
|
uuid: 9004, tier: TalentTier.L1, name: "反伤荆棘", info: "所有英雄反伤 +{value} 点", icon: "45-Induration",
|
||||||
|
bonuses: [{ attr: Attrs.thorns_dmg, mode: TalentValueMode.Flat, per_lv: 5 }],
|
||||||
|
max_lv: 5,
|
||||||
|
},
|
||||||
|
|
||||||
|
// ==================== 第 2 层(9101-9199,第 1 层投入 5 点解锁,概率属性) ====================
|
||||||
|
9101: {
|
||||||
|
uuid: 9101, tier: TalentTier.L2, name: "暴击强化", info: "所有英雄暴击率 +{value}%", icon: "27-Hawkeye",
|
||||||
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: {
|
9102: {
|
||||||
uuid: 9005, tier: TalentTier.Basic, name: "击晕强化", info: "所有英雄击晕率 +{value}%", icon: "13-Concussion",
|
uuid: 9102, tier: TalentTier.L2, name: "击退强化", info: "所有英雄击退距离 +{value}", icon: "34-Force-Push",
|
||||||
bonuses: [{ attr: Attrs.stun_chance, mode: TalentValueMode.Flat, per_lv: 2 }],
|
|
||||||
max_lv: 5,
|
|
||||||
},
|
|
||||||
9006: {
|
|
||||||
uuid: 9006, tier: TalentTier.Basic, name: "冰冻强化", info: "所有英雄冰冻率 +{value}%", icon: "92-Frost-Bite",
|
|
||||||
bonuses: [{ attr: Attrs.freeze_chance, mode: TalentValueMode.Flat, per_lv: 2 }],
|
|
||||||
max_lv: 5,
|
|
||||||
},
|
|
||||||
9007: {
|
|
||||||
uuid: 9007, tier: TalentTier.Basic, name: "击退强化", info: "所有英雄击退距离 +{value}", icon: "34-Force-Push",
|
|
||||||
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: {
|
|
||||||
uuid: 9008, tier: TalentTier.Basic, name: "风怒强化", info: "所有英雄风怒率 +{value}%", icon: "98-Enchant-Wind",
|
|
||||||
bonuses: [{ attr: Attrs.wfuny, mode: TalentValueMode.Flat, per_lv: 2 }],
|
|
||||||
max_lv: 5,
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
|
|
||||||
// ==================== 二阶·专精天赋(9101-9199,按职业过滤;一阶投入 10 点解锁) ====================
|
|
||||||
9101: {
|
|
||||||
uuid: 9101, tier: TalentTier.Specialized, name: "坦克专精", info: "坦克防御 +{value}%、生命 +{value2}%", icon: "36-Guardian-Stance",
|
|
||||||
bonuses: [
|
|
||||||
{ attr: Attrs.defense, mode: TalentValueMode.Percent, per_lv: 30 },
|
|
||||||
{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 5 },
|
|
||||||
],
|
|
||||||
max_lv: 5, role: HRole.Tank,
|
|
||||||
},
|
|
||||||
9102: {
|
|
||||||
uuid: 9102, tier: TalentTier.Specialized, name: "战士专精", info: "战士生命 +{value}%", icon: "1-Battle-Roar",
|
|
||||||
bonuses: [{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 12 }],
|
|
||||||
max_lv: 5, role: HRole.Warrior,
|
|
||||||
},
|
|
||||||
9103: {
|
9103: {
|
||||||
uuid: 9103, tier: TalentTier.Specialized, name: "刺客专精", info: "刺客攻击 +{value}%、暴击率 +{value2}%", icon: "127-Hide",
|
uuid: 9103, tier: TalentTier.L2, name: "冰冻强化", info: "所有英雄冰冻率 +{value}%", icon: "92-Frost-Bite",
|
||||||
bonuses: [
|
bonuses: [{ attr: Attrs.freeze_chance, mode: TalentValueMode.Flat, per_lv: 2 }],
|
||||||
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 },
|
max_lv: 5,
|
||||||
{ attr: Attrs.critical, mode: TalentValueMode.Flat, per_lv: 2 },
|
|
||||||
],
|
|
||||||
max_lv: 5, role: HRole.Assassin,
|
|
||||||
},
|
},
|
||||||
9104: {
|
9104: {
|
||||||
uuid: 9104, tier: TalentTier.Specialized, name: "射手专精", info: "射手攻击 +{value}%、穿透率 +{value2}%", icon: "56-Pierce-Shot",
|
uuid: 9104, tier: TalentTier.L2, name: "击晕强化", info: "所有英雄击晕率 +{value}%", icon: "13-Concussion",
|
||||||
bonuses: [
|
bonuses: [{ attr: Attrs.stun_chance, mode: TalentValueMode.Flat, per_lv: 2 }],
|
||||||
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 },
|
max_lv: 5,
|
||||||
{ attr: Attrs.puncture_chance, mode: TalentValueMode.Flat, per_lv: 2 },
|
|
||||||
],
|
|
||||||
max_lv: 5, role: HRole.Archer,
|
|
||||||
},
|
},
|
||||||
9105: {
|
9105: {
|
||||||
uuid: 9105, tier: TalentTier.Specialized, name: "法师专精", info: "法师攻击 +{value}%、冰冻率 +{value2}%", icon: "145-Electromancy",
|
uuid: 9105, tier: TalentTier.L2, name: "穿透强化", info: "所有英雄穿透率 +{value}%", icon: "56-Pierce-Shot",
|
||||||
bonuses: [
|
bonuses: [{ attr: Attrs.puncture_chance, mode: TalentValueMode.Flat, per_lv: 2 }],
|
||||||
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 },
|
max_lv: 5,
|
||||||
{ attr: Attrs.freeze_chance, mode: TalentValueMode.Flat, per_lv: 2 },
|
|
||||||
],
|
|
||||||
max_lv: 5, role: HRole.Mage,
|
|
||||||
},
|
},
|
||||||
9106: {
|
9106: {
|
||||||
uuid: 9106, tier: TalentTier.Specialized, name: "辅助专精", info: "辅助治疗量 +{value}%、施加的增益效果 +{value2}%", icon: "38-Healing-Light",
|
uuid: 9106, tier: TalentTier.L2, name: "暴伤强化", info: "所有英雄暴击伤害 +{value}%", icon: "37-Head-Shot",
|
||||||
bonuses: [
|
bonuses: [{ attr: Attrs.critical_damage, mode: TalentValueMode.Flat, per_lv: 5 }],
|
||||||
{ attr: Attrs.heal_power, mode: TalentValueMode.Flat, per_lv: 10 },
|
max_lv: 5,
|
||||||
{ attr: Attrs.buff_power, mode: TalentValueMode.Flat, per_lv: 8 },
|
|
||||||
],
|
|
||||||
max_lv: 5, role: HRole.Support,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// ==================== 三阶·超强天赋(9201-9219,二阶投入 10 点解锁;幅度约为前两阶 2 倍) ====================
|
// ==================== 第 3 层(9201-9299,第 2 层投入 5 点解锁,职业加成) ====================
|
||||||
// ---- 全局超强(9201-9209) ----
|
|
||||||
9201: {
|
9201: {
|
||||||
uuid: 9201, tier: TalentTier.Super, name: "神威降临", info: "所有英雄攻击 +{value}%、生命 +{value2}%", icon: "52-Might-of-Heaven",
|
uuid: 9201, tier: TalentTier.L3, name: "坦克专精", info: "坦克生命 +{value}%、防御 +{value2} 点", icon: "36-Guardian-Stance",
|
||||||
bonuses: [
|
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: "37-Head-Shot",
|
|
||||||
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: "76-Shove-Stab",
|
|
||||||
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: "45-Induration",
|
|
||||||
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: "36-Guardian-Stance",
|
|
||||||
bonuses: [
|
|
||||||
{ attr: Attrs.defense, mode: TalentValueMode.Percent, per_lv: 40 },
|
|
||||||
{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 8 },
|
{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 8 },
|
||||||
|
{ attr: Attrs.defense, mode: TalentValueMode.Flat, per_lv: 10 },
|
||||||
],
|
],
|
||||||
max_lv: 5, role: HRole.Tank,
|
max_lv: 5, role: HRole.Tank,
|
||||||
},
|
},
|
||||||
9212: {
|
9202: {
|
||||||
uuid: 9212, tier: TalentTier.Super, name: "狂血战魂", info: "战士攻击 +{value}%、生命 +{value2}%", icon: "113-Rage",
|
uuid: 9202, tier: TalentTier.L3, name: "战士专精", info: "战士攻击 +{value}%、生命 +{value2}%", icon: "1-Battle-Roar",
|
||||||
bonuses: [
|
bonuses: [
|
||||||
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 10 },
|
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 6 },
|
||||||
{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 8 },
|
{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 6 },
|
||||||
],
|
],
|
||||||
max_lv: 5, role: HRole.Warrior,
|
max_lv: 5, role: HRole.Warrior,
|
||||||
},
|
},
|
||||||
9213: {
|
9203: {
|
||||||
uuid: 9213, tier: TalentTier.Super, name: "影刃绝杀", info: "刺客攻击 +{value}%、暴击伤害 +{value2}%", icon: "136-Heart-Rip",
|
uuid: 9203, tier: TalentTier.L3, name: "刺客专精", info: "刺客攻击 +{value}%、攻速 +{value2}%", icon: "127-Hide",
|
||||||
bonuses: [
|
bonuses: [
|
||||||
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 12 },
|
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 },
|
||||||
{ attr: Attrs.critical_damage, mode: TalentValueMode.Flat, per_lv: 12 },
|
{ attr: Attrs.speed, mode: TalentValueMode.Flat, per_lv: 3 },
|
||||||
],
|
],
|
||||||
max_lv: 5, role: HRole.Assassin,
|
max_lv: 5, role: HRole.Assassin,
|
||||||
},
|
},
|
||||||
9214: {
|
9204: {
|
||||||
uuid: 9214, tier: TalentTier.Super, name: "穿云贯日", info: "射手攻击 +{value}%、穿透伤害 +{value2}%", icon: "37-Head-Shot",
|
uuid: 9204, tier: TalentTier.L3, name: "射手专精", info: "射手攻击 +{value}%、攻速 +{value2}%", icon: "56-Pierce-Shot",
|
||||||
bonuses: [
|
bonuses: [
|
||||||
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 12 },
|
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 },
|
||||||
{ attr: Attrs.puncture_dmg_bonus, mode: TalentValueMode.Flat, per_lv: 8 },
|
{ attr: Attrs.speed, mode: TalentValueMode.Flat, per_lv: 3 },
|
||||||
],
|
],
|
||||||
max_lv: 5, role: HRole.Archer,
|
max_lv: 5, role: HRole.Archer,
|
||||||
},
|
},
|
||||||
9215: {
|
9205: {
|
||||||
uuid: 9215, tier: TalentTier.Super, name: "元素主宰", info: "法师攻击 +{value}%、冰冻率 +{value2}%", icon: "144-Cryomancy",
|
uuid: 9205, tier: TalentTier.L3, name: "法师专精", info: "法师攻击 +{value}%、技能增益效果 +{value2}%", icon: "145-Electromancy",
|
||||||
bonuses: [
|
bonuses: [
|
||||||
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 12 },
|
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 },
|
||||||
{ attr: Attrs.freeze_chance, mode: TalentValueMode.Flat, per_lv: 3 },
|
{ attr: Attrs.buff_power, mode: TalentValueMode.Flat, per_lv: 6 },
|
||||||
],
|
],
|
||||||
max_lv: 5, role: HRole.Mage,
|
max_lv: 5, role: HRole.Mage,
|
||||||
},
|
},
|
||||||
9216: {
|
9206: {
|
||||||
uuid: 9216, tier: TalentTier.Super, name: "圣光普照", info: "辅助治疗量 +{value}%、施加的增益效果 +{value2}%", icon: "40-Holy",
|
uuid: 9206, tier: TalentTier.L3, name: "辅助专精", info: "辅助治疗量 +{value}%、增益效果 +{value2}%", icon: "38-Healing-Light",
|
||||||
bonuses: [
|
bonuses: [
|
||||||
{ attr: Attrs.heal_power, mode: TalentValueMode.Flat, per_lv: 15 },
|
{ attr: Attrs.heal_power, mode: TalentValueMode.Flat, per_lv: 10 },
|
||||||
{ attr: Attrs.buff_power, mode: TalentValueMode.Flat, per_lv: 12 },
|
{ attr: Attrs.buff_power, mode: TalentValueMode.Flat, per_lv: 6 },
|
||||||
],
|
],
|
||||||
max_lv: 5, role: HRole.Support,
|
max_lv: 5, role: HRole.Support,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ==================== 第 4 层(9301-9399,第 3 层投入 5 点解锁,触发条件减少,按职业) ====================
|
||||||
|
9301: {
|
||||||
|
uuid: 9301, tier: TalentTier.L4, name: "坦克·蓄势", info: "坦克触发类技能所需次数 -{value}", icon: "36-Guardian-Stance",
|
||||||
|
bonuses: [], t_num_reduce_per_lv: 1,
|
||||||
|
max_lv: 2, role: HRole.Tank,
|
||||||
|
},
|
||||||
|
9302: {
|
||||||
|
uuid: 9302, tier: TalentTier.L4, name: "战士·蓄势", info: "战士触发类技能所需次数 -{value}", icon: "113-Rage",
|
||||||
|
bonuses: [], t_num_reduce_per_lv: 1,
|
||||||
|
max_lv: 2, role: HRole.Warrior,
|
||||||
|
},
|
||||||
|
9303: {
|
||||||
|
uuid: 9303, tier: TalentTier.L4, name: "刺客·蓄势", info: "刺客触发类技能所需次数 -{value}", icon: "136-Heart-Rip",
|
||||||
|
bonuses: [], t_num_reduce_per_lv: 1,
|
||||||
|
max_lv: 2, role: HRole.Assassin,
|
||||||
|
},
|
||||||
|
9304: {
|
||||||
|
uuid: 9304, tier: TalentTier.L4, name: "射手·蓄势", info: "射手触发类技能所需次数 -{value}", icon: "74-Satisfaction",
|
||||||
|
bonuses: [], t_num_reduce_per_lv: 1,
|
||||||
|
max_lv: 2, role: HRole.Archer,
|
||||||
|
},
|
||||||
|
9305: {
|
||||||
|
uuid: 9305, tier: TalentTier.L4, name: "法师·蓄势", info: "法师触发类技能所需次数 -{value}", icon: "144-Cryomancy",
|
||||||
|
bonuses: [], t_num_reduce_per_lv: 1,
|
||||||
|
max_lv: 2, role: HRole.Mage,
|
||||||
|
},
|
||||||
|
9306: {
|
||||||
|
uuid: 9306, tier: TalentTier.L4, name: "辅助·蓄势", info: "辅助触发类技能所需次数 -{value}", icon: "40-Holy",
|
||||||
|
bonuses: [], t_num_reduce_per_lv: 1,
|
||||||
|
max_lv: 2, role: HRole.Support,
|
||||||
|
},
|
||||||
|
|
||||||
|
// ==================== 第 5 层(9401-9499,第 4 层投入 5 点解锁,概率额外触发,按职业) ====================
|
||||||
|
9401: {
|
||||||
|
uuid: 9401, tier: TalentTier.L5, name: "坦克·余震", info: "坦克触发类技能 +{value}% 概率额外触发 1 次", icon: "36-Guardian-Stance",
|
||||||
|
bonuses: [], extra_trigger_chance_per_lv: 10,
|
||||||
|
max_lv: 5, role: HRole.Tank, prereq_uuid: 9301, prereq_lv: 1,
|
||||||
|
},
|
||||||
|
9402: {
|
||||||
|
uuid: 9402, tier: TalentTier.L5, name: "战士·余威", info: "战士触发类技能 +{value}% 概率额外触发 1 次", icon: "113-Rage",
|
||||||
|
bonuses: [], extra_trigger_chance_per_lv: 10,
|
||||||
|
max_lv: 5, role: HRole.Warrior, prereq_uuid: 9302, prereq_lv: 1,
|
||||||
|
},
|
||||||
|
9403: {
|
||||||
|
uuid: 9403, tier: TalentTier.L5, name: "刺客·残影", info: "刺客触发类技能 +{value}% 概率额外触发 1 次", icon: "136-Heart-Rip",
|
||||||
|
bonuses: [], extra_trigger_chance_per_lv: 10,
|
||||||
|
max_lv: 5, role: HRole.Assassin, prereq_uuid: 9303, prereq_lv: 1,
|
||||||
|
},
|
||||||
|
9404: {
|
||||||
|
uuid: 9404, tier: TalentTier.L5, name: "射手·连珠", info: "射手触发类技能 +{value}% 概率额外触发 1 次", icon: "74-Satisfaction",
|
||||||
|
bonuses: [], extra_trigger_chance_per_lv: 10,
|
||||||
|
max_lv: 5, role: HRole.Archer, prereq_uuid: 9304, prereq_lv: 1,
|
||||||
|
},
|
||||||
|
9405: {
|
||||||
|
uuid: 9405, tier: TalentTier.L5, name: "法师·回响", info: "法师触发类技能 +{value}% 概率额外触发 1 次", icon: "144-Cryomancy",
|
||||||
|
bonuses: [], extra_trigger_chance_per_lv: 10,
|
||||||
|
max_lv: 5, role: HRole.Mage, prereq_uuid: 9305, prereq_lv: 1,
|
||||||
|
},
|
||||||
|
9406: {
|
||||||
|
uuid: 9406, tier: TalentTier.L5, name: "辅助·福音", info: "辅助触发类技能 +{value}% 概率额外触发 1 次", icon: "40-Holy",
|
||||||
|
bonuses: [], extra_trigger_chance_per_lv: 10,
|
||||||
|
max_lv: 5, role: HRole.Support, prereq_uuid: 9306, prereq_lv: 1,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 天赋升级消耗:按阶位统一消耗,同一阶内每级消耗相同 */
|
/** 天赋升级消耗:全部天赋统一 1 点/级 */
|
||||||
export const TALENT_UPGRADE_COST: Record<number, number> = {
|
export const TALENT_UPGRADE_COST = 1;
|
||||||
[TalentTier.Basic]: 1, // 基础天赋:1 点升 1 级
|
|
||||||
[TalentTier.Specialized]: 2, // 专精天赋:2 点升 1 级
|
|
||||||
[TalentTier.Super]: 2, // 传说天赋:2 点升 1 级
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询升级所需天赋点数
|
* 查询升级所需天赋点数
|
||||||
* @param tier 天赋所属阶位
|
* @param _tier 天赋所属层位(保留参数以兼容旧调用;统一点价后不再按层区分)
|
||||||
* @returns 升 1 级所需点数;非法阶位返回 -1
|
* @returns 升 1 级所需点数(恒为 1)
|
||||||
*/
|
*/
|
||||||
export function getTalentUpgradeCost(tier: TalentTier): number {
|
export function getTalentUpgradeCost(_tier: TalentTier): number {
|
||||||
return TALENT_UPGRADE_COST[tier] ?? -1;
|
return TALENT_UPGRADE_COST;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 天赋等级上限(不含):等级从 0 起步,区间 0~29,共 30 级;每升 1 级产出 1 点天赋点 */
|
/** 天赋等级上限(不含):等级从 0 起步,区间 0~29,共 30 级;每升 1 级产出 1 点天赋点 */
|
||||||
@@ -308,42 +315,51 @@ export function getTalentLevelExp(curLv: number): number {
|
|||||||
return TALENT_LEVEL_EXP[curLv];
|
return TALENT_LEVEL_EXP[curLv];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 阶位解锁门槛:前一阶累计投入 ≥ 该点数才能升级下一阶天赋 */
|
/** 层位解锁门槛:前一层累计投入 ≥ 该点数才能升级下一层天赋 */
|
||||||
export const TALENT_TIER_UNLOCK_THRESHOLD = 10;
|
export const TALENT_TIER_UNLOCK_THRESHOLD = 5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计算指定阶位已投入的天赋点数(按该阶统一消耗 × 等级累加)。
|
* 计算指定层位已投入的天赋点数(统一点价 × 等级累加)。
|
||||||
* Why: 阶位解锁按"实际消耗点数"而非"等级总和"口径——与玩家直觉(投入了多少点)
|
* Why: 层位解锁按"实际消耗点数"而非"等级总和"口径——与玩家直觉(投入了多少点)
|
||||||
* 及重置退款口径(resetTalents 同表对账)保持一致。
|
* 及重置退款口径(resetTalents 同表对账)保持一致。
|
||||||
* @param levels 天赋等级表(key 为天赋 uuid,value 为当前等级)
|
* @param levels 天赋等级表(key 为天赋 uuid,value 为当前等级)
|
||||||
* @param tier 目标阶位
|
* @param tier 目标层位
|
||||||
* @returns 该阶位全部天赋已投入的点数总和
|
* @returns 该层位全部天赋已投入的点数总和
|
||||||
*/
|
*/
|
||||||
export function getTalentTierInvestedPoints(levels: Record<number, number>, tier: TalentTier): number {
|
export function getTalentTierInvestedPoints(levels: Record<number, number>, tier: TalentTier): number {
|
||||||
if (!levels) return 0;
|
if (!levels) return 0;
|
||||||
const cost = getTalentUpgradeCost(tier);
|
|
||||||
if (cost <= 0) return 0;
|
|
||||||
let invested = 0;
|
let invested = 0;
|
||||||
for (const key in levels) {
|
for (const key in levels) {
|
||||||
const cfg = TalentSet[key];
|
const cfg = TalentSet[key];
|
||||||
if (!cfg || cfg.tier !== tier) continue;
|
if (!cfg || cfg.tier !== tier) continue;
|
||||||
invested += levels[key] * cost;
|
invested += levels[key] * TALENT_UPGRADE_COST;
|
||||||
}
|
}
|
||||||
return invested;
|
return invested;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断指定阶位是否已解锁(加点入口校验用;战斗结算无需关心阶位——已投入的等级恒生效)。
|
* 判断指定层位是否已解锁(加点入口校验用;战斗结算无需关心层位——已投入的等级恒生效)。
|
||||||
* 规则:一阶恒解锁;二阶要求一阶累计投入 ≥ TALENT_TIER_UNLOCK_THRESHOLD;三阶要求二阶累计投入达标。
|
* 规则:第 1 层恒解锁;第 N 层要求第 N-1 层累计投入 ≥ TALENT_TIER_UNLOCK_THRESHOLD。
|
||||||
* @param levels 天赋等级表
|
* @param levels 天赋等级表
|
||||||
* @param tier 目标阶位
|
* @param tier 目标层位
|
||||||
*/
|
*/
|
||||||
export function isTalentTierUnlocked(levels: Record<number, number>, tier: TalentTier): boolean {
|
export function isTalentTierUnlocked(levels: Record<number, number>, tier: TalentTier): boolean {
|
||||||
if (tier === TalentTier.Basic) return true;
|
if (tier === TalentTier.L1) return true;
|
||||||
const prevTier = (tier - 1) as TalentTier;
|
const prevTier = (tier - 1) as TalentTier;
|
||||||
return getTalentTierInvestedPoints(levels, prevTier) >= TALENT_TIER_UNLOCK_THRESHOLD;
|
return getTalentTierInvestedPoints(levels, prevTier) >= TALENT_TIER_UNLOCK_THRESHOLD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断天赋的软前置是否已满足(加点入口校验用;未满足仅限制加点,不影响已有点数的结算)。
|
||||||
|
* @param levels 天赋等级表
|
||||||
|
* @param cfg 天赋配置
|
||||||
|
* @returns 无前置声明或前置天赋等级达标时返回 true
|
||||||
|
*/
|
||||||
|
export function isTalentPrereqMet(levels: Record<number, number>, cfg: TalentConfig): boolean {
|
||||||
|
if (!cfg.prereq_uuid || !cfg.prereq_lv) return true;
|
||||||
|
return (levels?.[cfg.prereq_uuid] ?? 0) >= cfg.prereq_lv;
|
||||||
|
}
|
||||||
|
|
||||||
/** 天赋属性的卡面显示名(迷你卡空间有限,用短标签) */
|
/** 天赋属性的卡面显示名(迷你卡空间有限,用短标签) */
|
||||||
const TALENT_ATTR_LABELS: Partial<Record<Attrs, string>> = {
|
const TALENT_ATTR_LABELS: Partial<Record<Attrs, string>> = {
|
||||||
[Attrs.ap]: "攻击",
|
[Attrs.ap]: "攻击",
|
||||||
@@ -376,6 +392,15 @@ const TALENT_ATTR_SUFFIX: Partial<Record<Attrs, string>> = {
|
|||||||
*/
|
*/
|
||||||
export function getTalentDisplayText(cfg: TalentConfig, lv: number): string {
|
export function getTalentDisplayText(cfg: TalentConfig, lv: number): string {
|
||||||
const mult = lv > 0 ? lv : 1;
|
const mult = lv > 0 ? lv : 1;
|
||||||
|
// L4/L5 效果词条:不走 bonuses,按专属字段生成文案
|
||||||
|
if (cfg.t_num_reduce_per_lv) {
|
||||||
|
const text = `触发次数 -${cfg.t_num_reduce_per_lv * mult}`;
|
||||||
|
return lv > 0 ? text : `${text}/级`;
|
||||||
|
}
|
||||||
|
if (cfg.extra_trigger_chance_per_lv) {
|
||||||
|
const text = `额外触发 +${cfg.extra_trigger_chance_per_lv * mult}%`;
|
||||||
|
return lv > 0 ? text : `${text}/级`;
|
||||||
|
}
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
for (const b of cfg.bonuses) {
|
for (const b of cfg.bonuses) {
|
||||||
const label = TALENT_ATTR_LABELS[b.attr] ?? String(b.attr);
|
const label = TALENT_ATTR_LABELS[b.attr] ?? String(b.attr);
|
||||||
@@ -388,8 +413,8 @@ export function getTalentDisplayText(cfg: TalentConfig, lv: number): string {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 聚合指定职业英雄生效的全部天赋加成。
|
* 聚合指定职业英雄生效的全部天赋加成。
|
||||||
* 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)
|
||||||
@@ -412,7 +437,47 @@ export function getTalentBonusesForRole(levels: Record<number, number>, role?: H
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将天赋加成写入英雄属性模型(在 Hero.load / MissionCardComp.applyHeroLevel 的属性组装段调用)。
|
* 聚合指定职业英雄的 L4 触发阈值修正量。
|
||||||
|
* @param levels 天赋等级表
|
||||||
|
* @param role 英雄职业
|
||||||
|
* @returns 触发计数阈值的总减少量(SkillTriggerHelper 端再 clamp 下限 1)
|
||||||
|
*/
|
||||||
|
export function getTalentTriggerNumReduce(levels: Record<number, number>, role?: HRole): number {
|
||||||
|
let total = 0;
|
||||||
|
if (!levels) return total;
|
||||||
|
for (const key in levels) {
|
||||||
|
const lv = levels[key];
|
||||||
|
if (lv <= 0) continue;
|
||||||
|
const cfg = TalentSet[key];
|
||||||
|
if (!cfg || !cfg.t_num_reduce_per_lv) continue;
|
||||||
|
if (cfg.role !== undefined && cfg.role !== role) continue;
|
||||||
|
total += cfg.t_num_reduce_per_lv * lv;
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聚合指定职业英雄的 L5 额外触发概率(百分点)。
|
||||||
|
* @param levels 天赋等级表
|
||||||
|
* @param role 英雄职业
|
||||||
|
* @returns 额外触发 1 次的概率(百分点,可超过 100 由消费端自行 clamp)
|
||||||
|
*/
|
||||||
|
export function getTalentExtraTriggerChance(levels: Record<number, number>, role?: HRole): number {
|
||||||
|
let total = 0;
|
||||||
|
if (!levels) return total;
|
||||||
|
for (const key in levels) {
|
||||||
|
const lv = levels[key];
|
||||||
|
if (lv <= 0) continue;
|
||||||
|
const cfg = TalentSet[key];
|
||||||
|
if (!cfg || !cfg.extra_trigger_chance_per_lv) continue;
|
||||||
|
if (cfg.role !== undefined && cfg.role !== role) continue;
|
||||||
|
total += cfg.extra_trigger_chance_per_lv * lv;
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将天赋加成写入英雄属性模型(统一由 HeroAttrsComp.applyTalent 调用)。
|
||||||
*
|
*
|
||||||
* 结算规则:
|
* 结算规则:
|
||||||
* - Percent 模式:以基础快照为基数(ap→base_ap,hp_max→base_hp,defense→def_base),按百分比放大后累加;
|
* - Percent 模式:以基础快照为基数(ap→base_ap,hp_max→base_hp,defense→def_base),按百分比放大后累加;
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { Attrs } from "../common/config/HeroAttrs";
|
|||||||
import { MoveComp } from "./MoveComp";
|
import { MoveComp } from "./MoveComp";
|
||||||
import { mLogger } from "../common/Logger";
|
import { mLogger } from "../common/Logger";
|
||||||
import { FieldSkillType } from "../common/config/SkillSet";
|
import { FieldSkillType } from "../common/config/SkillSet";
|
||||||
import { applyTalentBonuses } from "../common/config/TalentSet";
|
|
||||||
/** 英雄实体:负责英雄节点创建、属性初始化、入场动画与销毁流程 */
|
/** 英雄实体:负责英雄节点创建、属性初始化、入场动画与销毁流程 */
|
||||||
@ecs.register(`Hero`)
|
@ecs.register(`Hero`)
|
||||||
|
|
||||||
@@ -173,16 +172,6 @@ export class Hero extends ecs.Entity {
|
|||||||
model.skills[skill.uuid] = { ...skill, lv: Math.min(FightSet.HERO_SKILL_MAX_LV, Math.max(0, skill.lv + hero_lv - 2)), ccd: 0, overrides: resolveSkillOverrides(skill, hero_lv) };
|
model.skills[skill.uuid] = { ...skill, lv: Math.min(FightSet.HERO_SKILL_MAX_LV, Math.max(0, skill.lv + hero_lv - 2)), ccd: 0, overrides: resolveSkillOverrides(skill, hero_lv) };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按当前英雄等级 resolve 触发技能/光环到运行时缓存,供 SkillTriggerHelper 消费
|
|
||||||
model.runtime_call = resolveTriggerByLv(model.call, hero_lv);
|
|
||||||
model.runtime_dead = resolveTriggerByLv(model.dead, hero_lv);
|
|
||||||
model.runtime_fstart = resolveTriggerByLv(model.fstart, hero_lv);
|
|
||||||
model.runtime_fend = resolveTriggerByLv(model.fend, hero_lv);
|
|
||||||
model.runtime_atking = resolveTriggerByLv(model.atking, hero_lv);
|
|
||||||
model.runtime_atked = resolveTriggerByLv(model.atked, hero_lv);
|
|
||||||
model.runtime_field = resolveFieldByLv(model.field, hero_lv);
|
|
||||||
// 复活已纳入 dead 触发组,无需单独解析 revive
|
|
||||||
|
|
||||||
// 应用额外属性加成(成长类型每级递增 + bonus 档位累加)
|
// 应用额外属性加成(成长类型每级递增 + bonus 档位累加)
|
||||||
// 成长增量:仅英雄生效(model.fac 已为 HERO),按 grow_type 递增曲线累计
|
// 成长增量:仅英雄生效(model.fac 已为 HERO),按 grow_type 递增曲线累计
|
||||||
const grow = calcGrowBonus(hero, hero_lv);
|
const grow = calcGrowBonus(hero, hero_lv);
|
||||||
@@ -211,12 +200,23 @@ export class Hero extends ecs.Entity {
|
|||||||
|
|
||||||
// lv12 职业专项已移除:单项属性强化统一由 bonus 配置(如 attrs: { ap: 50 })按等级注入
|
// lv12 职业专项已移除:单项属性强化统一由 bonus 配置(如 attrs: { ap: 50 })按等级注入
|
||||||
|
|
||||||
// 天赋加成层:全局天赋(一阶基础/三阶超强全局)全局生效,职业天赋(二阶专精/三阶超强职业)按 role 过滤(仅英雄生效,怪物跳过)
|
// 天赋加成层:全局天赋(L1/L2)全局生效,职业天赋(L3-L5)按 role 过滤(仅英雄生效,怪物跳过)
|
||||||
// Why: 放在成长/bonus 之后、光环乘区之后,以基础快照为基数叠加,避免与乘区叠加成乘法膨胀
|
// Why: 放在成长/bonus 之后、runtime_* resolve 之前——触发修正需先写入模型,L4/L5 才能影响阈值判定;
|
||||||
|
// 属性加成以基础快照为基数叠加,避免与光环乘区叠加成乘法膨胀
|
||||||
if (model.fac === FacSet.HERO) {
|
if (model.fac === FacSet.HERO) {
|
||||||
applyTalentBonuses(model, smc.getTalentLevels(), model.role);
|
model.applyTalent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 按当前英雄等级 resolve 触发技能/光环到运行时缓存,供 SkillTriggerHelper 消费
|
||||||
|
model.runtime_call = resolveTriggerByLv(model.call, hero_lv);
|
||||||
|
model.runtime_dead = resolveTriggerByLv(model.dead, hero_lv);
|
||||||
|
model.runtime_fstart = resolveTriggerByLv(model.fstart, hero_lv);
|
||||||
|
model.runtime_fend = resolveTriggerByLv(model.fend, hero_lv);
|
||||||
|
model.runtime_atking = resolveTriggerByLv(model.atking, hero_lv);
|
||||||
|
model.runtime_atked = resolveTriggerByLv(model.atked, hero_lv);
|
||||||
|
model.runtime_field = resolveFieldByLv(model.field, hero_lv);
|
||||||
|
// 复活已纳入 dead 触发组,无需单独解析 revive
|
||||||
|
|
||||||
// 缓存技能射程等派生数据,减少战斗帧内重复计算
|
// 缓存技能射程等派生数据,减少战斗帧内重复计算
|
||||||
model.updateSkillDistanceCache();
|
model.updateSkillDistanceCache();
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { FacSet, FightSet } from "../common/config/GameSet";
|
|||||||
import { FieldSkillSet, FieldSkillType, SkillOverrides } from "../common/config/SkillSet";
|
import { FieldSkillSet, FieldSkillType, SkillOverrides } from "../common/config/SkillSet";
|
||||||
import { smc } from "../common/SingletonModuleComp";
|
import { smc } from "../common/SingletonModuleComp";
|
||||||
import { Attrs } from "../common/config/HeroAttrs";
|
import { Attrs } from "../common/config/HeroAttrs";
|
||||||
|
import { applyTalentBonuses, getTalentExtraTriggerChance, getTalentTriggerNumReduce } from "../common/config/TalentSet";
|
||||||
import { FieldSkillHelper } from "./FieldSkillHelper";
|
import { FieldSkillHelper } from "./FieldSkillHelper";
|
||||||
import { BuffComp } from "./BuffComp";
|
import { BuffComp } from "./BuffComp";
|
||||||
import { BuffList, ModOp } from "../common/config/BuffSet";
|
import { BuffList, ModOp } from "../common/config/BuffSet";
|
||||||
@@ -101,6 +102,10 @@ export class HeroAttrsComp extends ecs.Comp {
|
|||||||
thorns_dmg: number = 0; // 反伤伤害(固定值,受到攻击时对攻击者造成等量伤害)
|
thorns_dmg: number = 0; // 反伤伤害(固定值,受到攻击时对攻击者造成等量伤害)
|
||||||
thorns_rate: number = 0; // 反伤比例(百分点;反伤值 = 实际受到伤害 * thorns_rate / 100 + thorns_dmg)
|
thorns_rate: number = 0; // 反伤比例(百分点;反伤值 = 实际受到伤害 * thorns_rate / 100 + thorns_dmg)
|
||||||
|
|
||||||
|
// ==================== 天赋触发修正(L4/L5 写入,SkillTriggerHelper 消费) ====================
|
||||||
|
talent_t_num_reduce: number = 0; // L4:触发类技能计数阈值减少量(下限 1 由消费端 clamp)
|
||||||
|
talent_extra_trigger_chance: number = 0; // L5:触发类技能额外触发 1 次的概率(百分点)
|
||||||
|
|
||||||
revived_count: number = 0; // 已复活次数(从 dead 触发配置的 revive_count 限制)
|
revived_count: number = 0; // 已复活次数(从 dead 触发配置的 revive_count 限制)
|
||||||
death_count: number = 0; // 本回合死亡次数(用于 dead 触发统计)
|
death_count: number = 0; // 本回合死亡次数(用于 dead 触发统计)
|
||||||
invincible_time: number = 0;// 无敌时间
|
invincible_time: number = 0;// 无敌时间
|
||||||
@@ -154,6 +159,20 @@ export class HeroAttrsComp extends ecs.Comp {
|
|||||||
*/
|
*/
|
||||||
initAttrs() {
|
initAttrs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 天赋统一应用入口:写入属性加成(L1-L3)与触发修正(L4/L5)。
|
||||||
|
* Why: 收敛到属性组件单点调用,Hero.load 与局内升级重算(applyHeroLevel)不再各自 import TalentSet;
|
||||||
|
* 触发修正必须在 runtime_* 触发缓存 resolve 之前写入,才能影响 SkillTriggerHelper 的阈值判定。
|
||||||
|
* 调用前请确保触发修正字段已归零(升级重算路径由调用方归零,保证幂等)。
|
||||||
|
*/
|
||||||
|
applyTalent(): void {
|
||||||
|
if (this.fac !== FacSet.HERO) return;
|
||||||
|
const levels = smc.getTalentLevels();
|
||||||
|
this.talent_t_num_reduce = getTalentTriggerNumReduce(levels, this.role);
|
||||||
|
this.talent_extra_trigger_chance = getTalentExtraTriggerChance(levels, this.role);
|
||||||
|
applyTalentBonuses(this, levels, this.role);
|
||||||
|
}
|
||||||
/*******************基础属性管理********************/
|
/*******************基础属性管理********************/
|
||||||
|
|
||||||
add_hp(value: number) {
|
add_hp(value: number) {
|
||||||
@@ -765,6 +784,8 @@ export class HeroAttrsComp extends ecs.Comp {
|
|||||||
this.buff_power = 0;
|
this.buff_power = 0;
|
||||||
this.thorns_dmg = 0;
|
this.thorns_dmg = 0;
|
||||||
this.thorns_rate = 0;
|
this.thorns_rate = 0;
|
||||||
|
this.talent_t_num_reduce = 0;
|
||||||
|
this.talent_extra_trigger_chance = 0;
|
||||||
this.boom = false;
|
this.boom = false;
|
||||||
|
|
||||||
// 重置技能距离缓存
|
// 重置技能距离缓存
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ export class SkillTriggerHelper {
|
|||||||
/**
|
/**
|
||||||
* 处理攻击附加技能
|
* 处理攻击附加技能
|
||||||
* 判定逻辑:依据配置中的 t_num(触发所需普攻次数),对比当前的普攻累计次数取模判定。
|
* 判定逻辑:依据配置中的 t_num(触发所需普攻次数),对比当前的普攻累计次数取模判定。
|
||||||
|
* 天赋修正:L4 减少 t_num 阈值(下限 1);L5 按概率额外触发 1 次。
|
||||||
*/
|
*/
|
||||||
private static handleAtking(model: HeroAttrsComp, view: HeroViewComp) {
|
private static handleAtking(model: HeroAttrsComp, view: HeroViewComp) {
|
||||||
if (!model.runtime_atking || model.runtime_atking.length === 0) return;
|
if (!model.runtime_atking || model.runtime_atking.length === 0) return;
|
||||||
@@ -107,10 +108,17 @@ export class SkillTriggerHelper {
|
|||||||
triggerCount += FieldSkillHelper.getFieldSkillTotalValue(FieldSkillType.AtkCount);
|
triggerCount += FieldSkillHelper.getFieldSkillTotalValue(FieldSkillType.AtkCount);
|
||||||
}
|
}
|
||||||
triggerCount = Math.max(1, Math.floor(triggerCount));
|
triggerCount = Math.max(1, Math.floor(triggerCount));
|
||||||
|
// L5 天赋:概率额外触发 1 次(与羁绊次数加成独立乘算)
|
||||||
|
if (model.talent_extra_trigger_chance > 0 && Math.random() * 100 < model.talent_extra_trigger_chance) {
|
||||||
|
triggerCount += 1;
|
||||||
|
}
|
||||||
|
// L4 天赋:减少触发所需次数(下限 1)
|
||||||
|
const tNumReduce = model.talent_t_num_reduce;
|
||||||
|
|
||||||
model.runtime_atking.forEach(atkConfig => {
|
model.runtime_atking.forEach(atkConfig => {
|
||||||
// atk_count 代表已进行的普攻次数。当其余数刚好整除配置阈值时触发。
|
// atk_count 代表已进行的普攻次数。当其余数刚好整除配置阈值时触发。
|
||||||
if (model.atk_count > 0 && model.atk_count % atkConfig.t_num === 0) {
|
const tNum = Math.max(1, atkConfig.t_num - tNumReduce);
|
||||||
|
if (model.atk_count > 0 && model.atk_count % tNum === 0) {
|
||||||
for (let i = 0; i < triggerCount; i++) {
|
for (let i = 0; i < triggerCount; i++) {
|
||||||
this.dispatchSingle(atkConfig.s_uuid, model, view, SkillTriggerType.Atking, atkConfig.overrides);
|
this.dispatchSingle(atkConfig.s_uuid, model, view, SkillTriggerType.Atking, atkConfig.overrides);
|
||||||
}
|
}
|
||||||
@@ -121,6 +129,7 @@ export class SkillTriggerHelper {
|
|||||||
/**
|
/**
|
||||||
* 处理受击附加技能
|
* 处理受击附加技能
|
||||||
* 判定逻辑:依据配置中的 t_num(触发所需受击次数),对比当前的受击累计次数取模判定。
|
* 判定逻辑:依据配置中的 t_num(触发所需受击次数),对比当前的受击累计次数取模判定。
|
||||||
|
* 天赋修正:L4 减少 t_num 阈值(下限 1);L5 按概率额外触发 1 次。
|
||||||
*/
|
*/
|
||||||
private static handleAtked(model: HeroAttrsComp, view: HeroViewComp) {
|
private static handleAtked(model: HeroAttrsComp, view: HeroViewComp) {
|
||||||
if (!model.runtime_atked || model.runtime_atked.length === 0) return;
|
if (!model.runtime_atked || model.runtime_atked.length === 0) return;
|
||||||
@@ -130,10 +139,17 @@ export class SkillTriggerHelper {
|
|||||||
triggerCount += FieldSkillHelper.getFieldSkillTotalValue(FieldSkillType.BeAtkCount);
|
triggerCount += FieldSkillHelper.getFieldSkillTotalValue(FieldSkillType.BeAtkCount);
|
||||||
}
|
}
|
||||||
triggerCount = Math.max(1, Math.floor(triggerCount));
|
triggerCount = Math.max(1, Math.floor(triggerCount));
|
||||||
|
// L5 天赋:概率额外触发 1 次(与羁绊次数加成独立乘算)
|
||||||
|
if (model.talent_extra_trigger_chance > 0 && Math.random() * 100 < model.talent_extra_trigger_chance) {
|
||||||
|
triggerCount += 1;
|
||||||
|
}
|
||||||
|
// L4 天赋:减少触发所需次数(下限 1)
|
||||||
|
const tNumReduce = model.talent_t_num_reduce;
|
||||||
|
|
||||||
model.runtime_atked.forEach(atkConfig => {
|
model.runtime_atked.forEach(atkConfig => {
|
||||||
// atked_count 代表已承受的受击次数。当其余数刚好整除配置阈值时触发。
|
// atked_count 代表已承受的受击次数。当其余数刚好整除配置阈值时触发。
|
||||||
if (model.atked_count > 0 && model.atked_count % atkConfig.t_num === 0) {
|
const tNum = Math.max(1, atkConfig.t_num - tNumReduce);
|
||||||
|
if (model.atked_count > 0 && model.atked_count % tNum === 0) {
|
||||||
for (let i = 0; i < triggerCount; i++) {
|
for (let i = 0; i < triggerCount; i++) {
|
||||||
this.dispatchSingle(atkConfig.s_uuid, model, view, SkillTriggerType.Atked, atkConfig.overrides);
|
this.dispatchSingle(atkConfig.s_uuid, model, view, SkillTriggerType.Atked, atkConfig.overrides);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ import { oops } from "db://oops-framework/core/Oops";
|
|||||||
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||||
import { smc } from "../common/SingletonModuleComp";
|
import { smc } from "../common/SingletonModuleComp";
|
||||||
import { HeroInfo, HType, resolveTriggerByLv, resolveFieldByLv, resolveSkillOverrides, calcGrowBonus, getHeroQualityCoef } from "../common/config/heroSet";
|
import { HeroInfo, HType, resolveTriggerByLv, resolveFieldByLv, resolveSkillOverrides, calcGrowBonus, getHeroQualityCoef } from "../common/config/heroSet";
|
||||||
import { applyTalentBonuses } from "../common/config/TalentSet";
|
|
||||||
import { HeroViewComp } from "../hero/HeroViewComp";
|
import { HeroViewComp } from "../hero/HeroViewComp";
|
||||||
import { FacSet, FightSet } from "../common/config/GameSet";
|
import { FacSet, FightSet } from "../common/config/GameSet";
|
||||||
import { MissionEconomy } from "./MissionEconomy";
|
import { MissionEconomy } from "./MissionEconomy";
|
||||||
@@ -2094,13 +2093,16 @@ export class MissionCardComp extends CCComp {
|
|||||||
model.knockback_distance = 0;
|
model.knockback_distance = 0;
|
||||||
model.heal_power = 0;
|
model.heal_power = 0;
|
||||||
model.buff_power = 0;
|
model.buff_power = 0;
|
||||||
applyTalentBonuses(model, smc.getTalentLevels(), model.role);
|
// 天赋触发修正归零(applyTalent 内部重算,保证升级重算幂等)
|
||||||
|
model.talent_t_num_reduce = 0;
|
||||||
|
model.talent_extra_trigger_chance = 0;
|
||||||
|
model.applyTalent();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 血量按当前血线比例映射到新上限,保留升级前战斗状态
|
// 血量按当前血线比例映射到新上限,保留升级前战斗状态
|
||||||
model.hp = Math.max(1, Math.floor(model.hp_max * Math.max(0, Math.min(1, hpRate))));
|
model.hp = Math.max(1, Math.floor(model.hp_max * Math.max(0, Math.min(1, hpRate))));
|
||||||
|
|
||||||
// 重新解析触发技能/驻场光环/复活到运行时缓存(与 Hero.load 对齐)
|
// 重新解析触发技能/驻场光环/复活到运行时缓存(在 applyTalent 之后,确保 L4/L5 触发修正已写入模型)
|
||||||
model.runtime_call = resolveTriggerByLv(model.call, nextLv);
|
model.runtime_call = resolveTriggerByLv(model.call, nextLv);
|
||||||
model.runtime_dead = resolveTriggerByLv(model.dead, nextLv);
|
model.runtime_dead = resolveTriggerByLv(model.dead, nextLv);
|
||||||
model.runtime_fstart = resolveTriggerByLv(model.fstart, nextLv);
|
model.runtime_fstart = resolveTriggerByLv(model.fstart, nextLv);
|
||||||
|
|||||||
@@ -1,40 +1,45 @@
|
|||||||
/**
|
/**
|
||||||
* @file TalentItemComp.ts
|
* @file TalentItemComp.ts
|
||||||
* @description 天赋迷你升级卡组件(UI 视图层)
|
* @description 天赋迷你升级卡组件(UI 视图层,编辑器静态摆放)
|
||||||
*
|
*
|
||||||
* 职责:
|
* 职责:
|
||||||
* 1. 接收 TalentsComp 下发的天赋配置 + 当前等级 + 阶位解锁态,渲染迷你点选卡。
|
* 1. 通过编辑器配置的 uuid 自查询天赋配置 + 当前等级 + 层位解锁态,渲染迷你点选卡。
|
||||||
* 2. 卡面自包含:图标 / 名称 / 当前已生效数值(Lv0 显示每级数值引导)/ 右下「当前/满级」角标。
|
* 2. 卡面自包含:图标 / 名称 / 当前已生效数值(Lv0 显示每级数值引导)/ 右下「当前/满级」角标。
|
||||||
* 3. 阶位背景:普通背景节点下挂 blue / red / yellow 三个颜色子节点,按阶位激活其一(Ⅰ蓝 / Ⅱ红 / Ⅲ金)。
|
* 3. 层位背景:普通背景节点下挂 blue / red / yellow 三个颜色子节点,按层位映射激活其一(L1蓝 / L2红 / L3金,L4/L5 复用金)。
|
||||||
* 4. 状态机:满级(MAX 金角标)/ 未激活(左上锁角标,不再做灰阶处理)。
|
* 4. 状态机:满级(MAX 金角标)/ 未激活(左上锁角标:未加点 / 层位未解锁 / 前置未满足,不做灰阶处理)。
|
||||||
* 5. 点击回调上抛卡体点击,加点确认弹窗与升级校验统一由 TalentsComp 处理(单一决策点)。
|
* 5. 点击回调上抛卡体点击,加点确认弹窗与升级校验统一由 TalentsComp 处理(单一决策点)。
|
||||||
*
|
*
|
||||||
* 依赖:
|
* 依赖:
|
||||||
* - TalentSet.TalentConfig / TalentTier / getTalentDisplayText —— 配置与卡面文案
|
* - TalentSet(TalentConfig/TalentTier/getTalentDisplayText/isTalentTierUnlocked/isTalentPrereqMet)—— 配置与校验
|
||||||
* - 背景三颜色子节点(blue / red / yellow,编辑器侧搭建)
|
* - 背景三颜色子节点(blue / red / yellow,编辑器侧搭建)
|
||||||
* - smc.uiconsAtlas —— 图标解析(缺省回退资源目录)
|
* - smc —— 天赋等级表与图标图集(缺省回退资源目录)
|
||||||
*/
|
*/
|
||||||
import { _decorator, Label, Node, NodeEventType, Sprite, SpriteFrame } from "cc";
|
import { _decorator, CCInteger, Label, Node, NodeEventType, Sprite, SpriteFrame } 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 { TalentConfig, TalentTier, getTalentDisplayText } from "../common/config/TalentSet";
|
import { TalentSet, TalentTier, getTalentDisplayText, isTalentPrereqMet, isTalentTierUnlocked } from "../common/config/TalentSet";
|
||||||
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;
|
||||||
|
|
||||||
/** 阶位 → 背景节点下的颜色子节点名(Ⅰ蓝 / Ⅱ红 / Ⅲ金) */
|
/** 层位 → 背景节点下的颜色子节点名(L1蓝 / L2红 / L3金;L4/L5 复用金) */
|
||||||
const TIER_BG_CHILD_NAMES: Record<number, string> = {
|
const TIER_BG_CHILD_NAMES: Record<number, string> = {
|
||||||
[TalentTier.Basic]: "blue",
|
[TalentTier.L1]: "blue",
|
||||||
[TalentTier.Specialized]: "red",
|
[TalentTier.L2]: "red",
|
||||||
[TalentTier.Super]: "yellow",
|
[TalentTier.L3]: "yellow",
|
||||||
|
[TalentTier.L4]: "yellow",
|
||||||
|
[TalentTier.L5]: "yellow",
|
||||||
};
|
};
|
||||||
|
|
||||||
/** TalentItemComp —— 天赋迷你升级卡组件 */
|
/** TalentItemComp —— 天赋迷你升级卡组件(编辑器静态摆放,按 uuid 自驱动渲染) */
|
||||||
@ccclass('TalentItemComp')
|
@ccclass('TalentItemComp')
|
||||||
@ecs.register('TalentItem', false)
|
@ecs.register('TalentItem', false)
|
||||||
export class TalentItemComp extends CCComp {
|
export class TalentItemComp extends CCComp {
|
||||||
|
|
||||||
|
@property({ type: CCInteger, tooltip: "天赋 uuid(TalentSet key,编辑器侧配置)" })
|
||||||
|
talent_uuid: number = 0;
|
||||||
|
|
||||||
@property({ type: Label, tooltip: "天赋名称" })
|
@property({ type: Label, tooltip: "天赋名称" })
|
||||||
lbl_name: Label = null!;
|
lbl_name: Label = null!;
|
||||||
|
|
||||||
@@ -47,21 +52,18 @@ export class TalentItemComp extends CCComp {
|
|||||||
@property({ type: Sprite, tooltip: "天赋图标" })
|
@property({ type: Sprite, tooltip: "天赋图标" })
|
||||||
icon: Sprite = null!;
|
icon: Sprite = null!;
|
||||||
|
|
||||||
@property({ type: Node, tooltip: "卡牌背景节点(下挂 blue / red / yellow 三颜色子节点,按阶位激活其一)" })
|
@property({ type: Node, tooltip: "卡牌背景节点(下挂 blue / red / yellow 三颜色子节点,按层位激活其一)" })
|
||||||
bg: Node = null!;
|
bg: Node = null!;
|
||||||
|
|
||||||
@property({ type: Node, tooltip: "MAX 满级角标" })
|
@property({ type: Node, tooltip: "MAX 满级角标" })
|
||||||
node_max: Node = null!;
|
node_max: Node = null!;
|
||||||
|
|
||||||
@property({ type: Node, tooltip: "未激活锁遮罩/锁图标(未加点或阶位未解锁)" })
|
@property({ type: Node, tooltip: "未激活锁遮罩/锁图标(未加点、层位未解锁或前置未满足)" })
|
||||||
node_lock: Node = null!;
|
node_lock: Node = null!;
|
||||||
|
|
||||||
/** 点击回调(卡体与 + 徽标同源上抛),由父级统一校验升级 */
|
/** 点击回调(卡体点击上抛),由 TalentsComp 统一校验升级 */
|
||||||
public onTalentTap: ((uuid: number) => void) | null = null;
|
public onTalentTap: ((uuid: number) => void) | null = null;
|
||||||
|
|
||||||
/** 当前展示的天赋 uuid(回调携带) */
|
|
||||||
private curUuid: number = 0;
|
|
||||||
|
|
||||||
protected onLoad(): void {
|
protected onLoad(): void {
|
||||||
this.node.on(NodeEventType.TOUCH_END, this.onTap, this);
|
this.node.on(NodeEventType.TOUCH_END, this.onTap, this);
|
||||||
}
|
}
|
||||||
@@ -70,25 +72,24 @@ export class TalentItemComp extends CCComp {
|
|||||||
this.node.off(NodeEventType.TOUCH_END, this.onTap, this);
|
this.node.off(NodeEventType.TOUCH_END, this.onTap, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 刷新迷你卡展示:按编辑器配置的 uuid 自查询配置与等级,层位/前置校验内部完成 */
|
||||||
* 刷新迷你卡展示
|
public updateItem(): void {
|
||||||
* @param cfg 天赋配置(TalentSet 条目)
|
const cfg = TalentSet[this.talent_uuid];
|
||||||
* @param lv 当前等级
|
|
||||||
* @param tierUnlocked 所属阶位是否已解锁
|
|
||||||
*/
|
|
||||||
public updateItem(cfg: TalentConfig, lv: number, tierUnlocked: boolean): void {
|
|
||||||
if (!cfg) return;
|
if (!cfg) return;
|
||||||
this.curUuid = cfg.uuid;
|
const levels = smc.getTalentLevels();
|
||||||
|
const lv = levels[this.talent_uuid] ?? 0;
|
||||||
|
const tierUnlocked = isTalentTierUnlocked(levels, cfg.tier);
|
||||||
|
const prereqMet = isTalentPrereqMet(levels, cfg);
|
||||||
|
|
||||||
if (this.lbl_name) this.lbl_name.string = cfg.name;
|
if (this.lbl_name) this.lbl_name.string = cfg.name;
|
||||||
// 卡面自包含:已加点显示当前生效数值,未加点显示每级数值引导
|
// 卡面自包含:已加点显示当前生效数值,未加点显示每级数值引导
|
||||||
if (this.lbl_info) this.lbl_info.string = getTalentDisplayText(cfg, lv);
|
if (this.lbl_info) this.lbl_info.string = getTalentDisplayText(cfg, lv);
|
||||||
if (this.lbl_lv) this.lbl_lv.string = `${lv}/${cfg.max_lv}`;
|
if (this.lbl_lv) this.lbl_lv.string = `${lv}/${cfg.max_lv}`;
|
||||||
|
|
||||||
// 未激活(Lv0 或阶位未解锁):仅显示 node_lock 锁遮罩,不做灰阶处理
|
// 未激活(Lv0 / 层位未解锁 / 前置未满足):仅显示 node_lock 锁遮罩,不做灰阶处理
|
||||||
const inactive = lv <= 0 || !tierUnlocked;
|
const inactive = lv <= 0 || !tierUnlocked || !prereqMet;
|
||||||
|
|
||||||
// 阶位背景:激活对应颜色子节点(Ⅰ蓝 / Ⅱ红 / Ⅲ金)
|
// 层位背景:激活对应颜色子节点(L1蓝 / L2红 / L3金)
|
||||||
if (this.bg) this.applyTierBg(cfg.tier);
|
if (this.bg) this.applyTierBg(cfg.tier);
|
||||||
|
|
||||||
// 图标:技能图标图集(gui/ui7)优先,资源目录回退(与 SCardComp 口径一致)
|
// 图标:技能图标图集(gui/ui7)优先,资源目录回退(与 SCardComp 口径一致)
|
||||||
@@ -103,12 +104,12 @@ export class TalentItemComp extends CCComp {
|
|||||||
if (frame) this.icon.spriteFrame = frame;
|
if (frame) this.icon.spriteFrame = frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 状态角标:满级 MAX / 未激活锁角标(未加点或阶位未解锁,置于左上角)
|
// 状态角标:满级 MAX / 未激活锁角标(置于左上角)
|
||||||
if (this.node_max) this.node_max.active = lv >= cfg.max_lv;
|
if (this.node_max) this.node_max.active = lv >= cfg.max_lv;
|
||||||
if (this.node_lock) this.node_lock.active = inactive;
|
if (this.node_lock) this.node_lock.active = inactive;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 按阶位激活 bg 下对应颜色子节点,其余隐藏 */
|
/** 按层位激活 bg 下对应颜色子节点,其余隐藏 */
|
||||||
private applyTierBg(tier: TalentTier): void {
|
private applyTierBg(tier: TalentTier): void {
|
||||||
const curName = TIER_BG_CHILD_NAMES[tier];
|
const curName = TIER_BG_CHILD_NAMES[tier];
|
||||||
for (const [, name] of Object.entries(TIER_BG_CHILD_NAMES)) {
|
for (const [, name] of Object.entries(TIER_BG_CHILD_NAMES)) {
|
||||||
@@ -120,7 +121,7 @@ export class TalentItemComp extends CCComp {
|
|||||||
|
|
||||||
/** 卡体点击上抛 uuid,加点确认与升级校验由 TalentsComp 处理 */
|
/** 卡体点击上抛 uuid,加点确认与升级校验由 TalentsComp 处理 */
|
||||||
private onTap(): void {
|
private onTap(): void {
|
||||||
this.onTalentTap?.(this.curUuid);
|
this.onTalentTap?.(this.talent_uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ECS 组件移除时销毁节点(CCComp 抽象方法实现) */
|
/** ECS 组件移除时销毁节点(CCComp 抽象方法实现) */
|
||||||
|
|||||||
@@ -1,25 +1,27 @@
|
|||||||
/**
|
/**
|
||||||
* @file TalentsComp.ts
|
* @file TalentsComp.ts
|
||||||
* @description 天赋加点页组件(UI 视图层)
|
* @description 天赋加点页组件(UI 视图层,最小容器)
|
||||||
*
|
*
|
||||||
* 职责:
|
* 职责:
|
||||||
* 1. 三阶页签(Ⅰ 基础 / Ⅱ 专精 / Ⅲ 超强)状态机:单屏聚焦当前阶位的迷你天赋卡。
|
* 1. 页头展示:可用天赋点 / 天赋等级(Lv.x)/ 当前等级经验进度。
|
||||||
* 2. 页签常显解锁进度(前阶投入 x/10),点击天赋卡弹出 CommonPrompt 确认加点(smc.upgradeTalent)。
|
* 2. 收集子树内全部 TalentItemComp(编辑器静态摆放),TALENT_UPDATE 事件驱动全量刷新。
|
||||||
* 3. 监听 GameEvent.TALENT_UPDATE 全量重渲染(23 条卡片,无需虚拟列表)。
|
* 3. 卡片点击统一校验(层位解锁 → 软前置 → 满级 → 点数),不满足走 smalltip 气泡;
|
||||||
|
* 满足则弹出 CommonPrompt 确认加点(smc.upgradeTalent)。
|
||||||
* 4. 看广告重置入口:CommonPrompt 二次确认后调 smc.resetTalents() 退还全部点数。
|
* 4. 看广告重置入口:CommonPrompt 二次确认后调 smc.resetTalents() 退还全部点数。
|
||||||
*
|
*
|
||||||
* 依赖:
|
* 依赖:
|
||||||
* - TalentSet(TalentSet/TalentTier/isTalentTierUnlocked/getTalentTierInvestedPoints)—— 配置与阶位校验
|
* - TalentSet(TalentTier/isTalentTierUnlocked/isTalentPrereqMet/getTalentTierInvestedPoints)—— 配置与层位校验
|
||||||
* - TalentItemComp —— 单张迷你升级卡
|
* - TalentItemComp —— 单张迷你升级卡(编辑器静态摆放,按 uuid 自驱动渲染)
|
||||||
* - smc —— 天赋等级表/点数/升级/重置
|
* - smc —— 天赋等级表/点数/升级/重置
|
||||||
*/
|
*/
|
||||||
import { _decorator, instantiate, Label, Node, NodeEventType, Prefab, ProgressBar, UITransform } from "cc";
|
import { _decorator, Label, Node, NodeEventType, ProgressBar } 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 {
|
import {
|
||||||
TalentSet, TalentTier,
|
TalentSet,
|
||||||
TALENT_TIER_UNLOCK_THRESHOLD,
|
TALENT_TIER_UNLOCK_THRESHOLD,
|
||||||
getTalentDisplayText, getTalentLevelExp, getTalentTierInvestedPoints, getTalentUpgradeCost, isTalentTierUnlocked,
|
getTalentDisplayText, getTalentLevelExp, getTalentTierInvestedPoints, getTalentUpgradeCost,
|
||||||
|
isTalentPrereqMet, isTalentTierUnlocked,
|
||||||
} from "../common/config/TalentSet";
|
} from "../common/config/TalentSet";
|
||||||
import { GameEvent } from "../common/config/GameEvent";
|
import { GameEvent } from "../common/config/GameEvent";
|
||||||
import { UIID } from "../common/config/GameUIConfig";
|
import { UIID } from "../common/config/GameUIConfig";
|
||||||
@@ -30,36 +32,19 @@ import { oops } from "db://oops-framework/core/Oops";
|
|||||||
|
|
||||||
const { ccclass, property } = _decorator;
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
/** 阶位页签展示文案(lockName 用于未解锁提示) */
|
/** 层位序号展示文案(提示文案用) */
|
||||||
const TIER_TAB_TEXT: Record<number, { title: string; lockName: string }> = {
|
const TIER_NAMES: Record<number, string> = {
|
||||||
[TalentTier.Basic]: { title: "基础天赋", lockName: "基础天赋" },
|
1: "第 1 层", 2: "第 2 层", 3: "第 3 层", 4: "第 4 层", 5: "第 5 层",
|
||||||
[TalentTier.Specialized]: { title: "专精天赋", lockName: "基础天赋" },
|
|
||||||
[TalentTier.Super]: { title: "传说天赋", lockName: "专精天赋" },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 阶位页签选中高亮子节点名(可选,编辑器侧搭建) */
|
|
||||||
const TAB_SELECTED_CHILD = "sp_on";
|
|
||||||
|
|
||||||
/** 阶位页签锁定图标子节点名(可选;阶位未解锁时显示) */
|
|
||||||
const TAB_LOCKED_CHILD = "locked";
|
|
||||||
|
|
||||||
/** 阶位页签未选中态子节点名(可选;非当前选中页签时显示) */
|
|
||||||
const TAB_INACTIVE_CHILD = "noactive";
|
|
||||||
|
|
||||||
/** 页签/卡片上的 smalltip 气泡节点名 */
|
/** 页签/卡片上的 smalltip 气泡节点名 */
|
||||||
const SMALL_TIP_CHILD = "smalltip";
|
const SMALL_TIP_CHILD = "smalltip";
|
||||||
|
|
||||||
/** TalentsComp —— 天赋加点页组件(阶位 Tab 单屏聚焦) */
|
/** TalentsComp —— 天赋加点页组件(最小容器:页头 + 编辑器静态卡片收集) */
|
||||||
@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: "当前阶位卡片列表容器(ScrollView content,Grid 三列)" })
|
|
||||||
talents_content: Node = null!;
|
|
||||||
|
|
||||||
@property({ type: Prefab, tooltip: "迷你天赋卡预制(TalentItemComp)" })
|
|
||||||
prefab_talent_item: Prefab = null!;
|
|
||||||
|
|
||||||
@property({ type: Label, tooltip: "页头可用天赋点" })
|
@property({ type: Label, tooltip: "页头可用天赋点" })
|
||||||
lbl_points: Label = null!;
|
lbl_points: Label = null!;
|
||||||
|
|
||||||
@@ -72,52 +57,16 @@ export class TalentsComp extends CCComp {
|
|||||||
@property({ type: Label, tooltip: "当前等级经验数值(当前经验/升级所需经验)" })
|
@property({ type: Label, tooltip: "当前等级经验数值(当前经验/升级所需经验)" })
|
||||||
lbl_exp: Label = null!;
|
lbl_exp: Label = null!;
|
||||||
|
|
||||||
@property({ type: Node, tooltip: "一阶页签节点" })
|
|
||||||
tab_basic: Node = null!;
|
|
||||||
|
|
||||||
@property({ type: Node, tooltip: "二阶页签节点" })
|
|
||||||
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: Label, tooltip: "一阶已投入点数" })
|
|
||||||
lbl_invest_basic: Label = null!;
|
|
||||||
|
|
||||||
@property({ type: Label, tooltip: "二阶已投入点数" })
|
|
||||||
lbl_invest_special: Label = null!;
|
|
||||||
|
|
||||||
@property({ type: Label, tooltip: "三阶已投入点数" })
|
|
||||||
lbl_invest_super: Label = null!;
|
|
||||||
|
|
||||||
@property({ type: Node, tooltip: "看广告重置按钮" })
|
@property({ type: Node, tooltip: "看广告重置按钮" })
|
||||||
btn_reset: Node = null!;
|
btn_reset: Node = null!;
|
||||||
|
|
||||||
/** 当前选中的阶位(首次进入默认为已解锁的最高阶,之后由玩家切换) */
|
/** 页内全部天赋卡(onLoad 时自动收集子树内所有 TalentItemComp,无需编辑器拖引) */
|
||||||
private curTier: TalentTier = TalentTier.Basic;
|
private talent_items: TalentItemComp[] = [];
|
||||||
|
|
||||||
/** 是否已完成首次默认阶位选择 */
|
|
||||||
private inited: boolean = false;
|
|
||||||
|
|
||||||
/** 阶位 → 页签节点/文案/投入点数/气泡 映射(onLoad 时装配) */
|
|
||||||
private tabNodes: Map<TalentTier, { node: Node | null; label: Label | null; invest: Label | null; tip: SmallTipComp | null }> = new Map();
|
|
||||||
|
|
||||||
protected onLoad(): void {
|
protected onLoad(): void {
|
||||||
this.tabNodes.set(TalentTier.Basic, { node: this.tab_basic, label: this.lbl_tab_basic, invest: this.lbl_invest_basic, tip: this.getSmallTip(this.tab_basic) });
|
this.talent_items = this.node.getComponentsInChildren(TalentItemComp);
|
||||||
this.tabNodes.set(TalentTier.Specialized, { node: this.tab_special, label: this.lbl_tab_special, invest: this.lbl_invest_special, tip: this.getSmallTip(this.tab_special) });
|
for (const item of this.talent_items) {
|
||||||
this.tabNodes.set(TalentTier.Super, { node: this.tab_super, label: this.lbl_tab_super, invest: this.lbl_invest_super, tip: this.getSmallTip(this.tab_super) });
|
item.onTalentTap = (uuid: number) => this.onTalentTap(uuid);
|
||||||
|
|
||||||
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);
|
this.btn_reset?.on(NodeEventType.TOUCH_END, this.onResetTap, this);
|
||||||
}
|
}
|
||||||
@@ -133,30 +82,20 @@ export class TalentsComp extends CCComp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected onDestroy(): void {
|
protected onDestroy(): void {
|
||||||
for (const [, tab] of this.tabNodes) {
|
|
||||||
tab.node?.targetOff(this);
|
|
||||||
}
|
|
||||||
this.btn_reset?.targetOff(this);
|
this.btn_reset?.targetOff(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 切换阶位页签:锁定阶允许查看(卡片呈锁定态),不弹解锁提示 */
|
/** 全量刷新:页头点数 + 天赋等级/经验 + 全部静态卡片 */
|
||||||
private selectTier(tier: TalentTier): void {
|
|
||||||
if (tier === this.curTier) return;
|
|
||||||
this.curTier = tier;
|
|
||||||
this.refreshUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 全量刷新:页头点数 + 页签解锁进度 + 当前阶位卡片列表 */
|
|
||||||
public refreshUI(): void {
|
public refreshUI(): void {
|
||||||
if (!this.talents_content || !this.prefab_talent_item) return;
|
|
||||||
const levels = smc.getTalentLevels();
|
const levels = smc.getTalentLevels();
|
||||||
|
|
||||||
if (this.lbl_points) {
|
if (this.lbl_points) {
|
||||||
// 剩余/总点数:总点数 = 剩余 + 各阶已投入
|
// 剩余/总点数:总点数 = 剩余 + 各层已投入
|
||||||
const remaining = smc.getTalentPoints();
|
const remaining = smc.getTalentPoints();
|
||||||
const invested = getTalentTierInvestedPoints(levels, TalentTier.Basic)
|
let invested = 0;
|
||||||
+ getTalentTierInvestedPoints(levels, TalentTier.Specialized)
|
for (let tier = 1; tier <= 5; tier++) {
|
||||||
+ getTalentTierInvestedPoints(levels, TalentTier.Super);
|
invested += getTalentTierInvestedPoints(levels, tier);
|
||||||
|
}
|
||||||
this.lbl_points.string = `${remaining}/${remaining + invested}`;
|
this.lbl_points.string = `${remaining}/${remaining + invested}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,66 +117,8 @@ export class TalentsComp extends CCComp {
|
|||||||
: `0/0`;
|
: `0/0`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 仅首次进入时默认选中已解锁的最高阶,后续刷新保持玩家当前选择
|
for (const item of this.talent_items) {
|
||||||
if (!this.inited) {
|
item.updateItem();
|
||||||
this.inited = true;
|
|
||||||
if (isTalentTierUnlocked(levels, TalentTier.Super)) this.curTier = TalentTier.Super;
|
|
||||||
else if (isTalentTierUnlocked(levels, TalentTier.Specialized)) this.curTier = TalentTier.Specialized;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.refreshTabs(levels);
|
|
||||||
this.refreshGrid(levels);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 页签文案 + 各阶已投入点数 + 选中高亮(sp_on)+ 锁定图标(locked)+ 未选中态(noactive) */
|
|
||||||
private refreshTabs(levels: Record<number, number>): void {
|
|
||||||
for (const [tier, tab] of this.tabNodes) {
|
|
||||||
const text = TIER_TAB_TEXT[tier];
|
|
||||||
const unlocked = isTalentTierUnlocked(levels, tier);
|
|
||||||
if (tab.label) tab.label.string = text.title;
|
|
||||||
// 各阶已投入点数(该阶所有天赋等级之和)
|
|
||||||
if (tab.invest) tab.invest.string = `${getTalentTierInvestedPoints(levels, tier)}`;
|
|
||||||
// 锁定图标:该阶未解锁时显示,解锁后隐藏(一阶恒解锁,节点可缺省)
|
|
||||||
const locked = tab.node?.getChildByName(TAB_LOCKED_CHILD);
|
|
||||||
if (locked) locked.active = !unlocked;
|
|
||||||
|
|
||||||
const selected = tier === this.curTier;
|
|
||||||
// 未选中态节点:非当前激活页签时显示(与 sp_on 选中高亮互斥)
|
|
||||||
const inactive = tab.node?.getChildByName(TAB_INACTIVE_CHILD);
|
|
||||||
if (inactive) inactive.active = !selected;
|
|
||||||
|
|
||||||
tab.node?.getChildByName(TAB_SELECTED_CHILD)?.setPosition(0, 0);
|
|
||||||
tab.node?.getChildByName(TAB_SELECTED_CHILD) && (tab.node.getChildByName(TAB_SELECTED_CHILD)!.active = selected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 重建当前阶位的卡片列表(每阶 ≤10 条,直接全量实例化) */
|
|
||||||
private refreshGrid(levels: Record<number, number>): void {
|
|
||||||
this.talents_content.removeAllChildren();
|
|
||||||
const unlocked = isTalentTierUnlocked(levels, this.curTier);
|
|
||||||
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 itemNode = instantiate(this.prefab_talent_item);
|
|
||||||
this.talents_content.addChild(itemNode);
|
|
||||||
const comp = itemNode.getComponent(TalentItemComp);
|
|
||||||
if (!comp) continue;
|
|
||||||
comp.updateItem(cfg, lv, unlocked);
|
|
||||||
comp.onTalentTap = (uuid: number) => this.onTalentTap(uuid);
|
|
||||||
if (!cardHeight) cardHeight = itemNode.getComponent(UITransform)?.height ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
if (uiTransform) uiTransform.height = contentHeight;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,7 +133,7 @@ export class TalentsComp extends CCComp {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 在指定节点下弹出 smalltip 气泡提示
|
* 在指定节点下弹出 smalltip 气泡提示
|
||||||
* @param anchor 气泡挂载的父节点(优先页签,卡片点击时传卡片节点)
|
* @param anchor 气泡挂载的父节点
|
||||||
* @param text 提示文案
|
* @param text 提示文案
|
||||||
*/
|
*/
|
||||||
private showSmallTip(anchor: Node | null, text: string): void {
|
private showSmallTip(anchor: Node | null, text: string): void {
|
||||||
@@ -260,27 +141,45 @@ export class TalentsComp extends CCComp {
|
|||||||
tip?.show(text);
|
tip?.show(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 卡片点击统一入口:未解锁/已满级/点数不足走 smalltip 气泡;其余弹出 CommonPrompt 确认加点,成功后事件驱动刷新 */
|
/**
|
||||||
|
* 按 uuid 找到对应的天赋卡节点(气泡锚点用)
|
||||||
|
* @param uuid 天赋 uuid
|
||||||
|
*/
|
||||||
|
private findItemNode(uuid: number): Node | null {
|
||||||
|
for (const item of this.talent_items) {
|
||||||
|
if (item.talent_uuid === uuid) return item.node;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 卡片点击统一入口:层位未解锁/前置未满足/已满级/点数不足走 smalltip 气泡;其余弹出 CommonPrompt 确认加点,成功后事件驱动刷新 */
|
||||||
private onTalentTap(uuid: number): void {
|
private onTalentTap(uuid: number): void {
|
||||||
const cfg = TalentSet[uuid];
|
const cfg = TalentSet[uuid];
|
||||||
if (!cfg) return;
|
if (!cfg) return;
|
||||||
const levels = smc.getTalentLevels();
|
const levels = smc.getTalentLevels();
|
||||||
|
const anchor = this.findItemNode(uuid);
|
||||||
|
|
||||||
const tab = this.tabNodes.get(cfg.tier);
|
// 层位解锁:第 N 层要求第 N-1 层累计投入 ≥ 阈值
|
||||||
if (!isTalentTierUnlocked(levels, cfg.tier)) {
|
if (!isTalentTierUnlocked(levels, cfg.tier)) {
|
||||||
const invested = getTalentTierInvestedPoints(levels, (cfg.tier - 1) as TalentTier);
|
this.showSmallTip(anchor, `${TIER_NAMES[cfg.tier - 1]}投入${TALENT_TIER_UNLOCK_THRESHOLD} 点解锁`);
|
||||||
this.showSmallTip(tab?.node ?? null, `${TIER_TAB_TEXT[cfg.tier].lockName}投入${TALENT_TIER_UNLOCK_THRESHOLD} 点解锁`);
|
return;
|
||||||
|
}
|
||||||
|
// 软前置:前置天赋等级需达标
|
||||||
|
if (!isTalentPrereqMet(levels, cfg)) {
|
||||||
|
const preCfg = TalentSet[cfg.prereq_uuid!];
|
||||||
|
const preName = preCfg ? preCfg.name : `天赋${cfg.prereq_uuid}`;
|
||||||
|
this.showSmallTip(anchor, `需「${preName}」达到 Lv.${cfg.prereq_lv}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const lv = smc.getTalentLv(uuid);
|
const lv = smc.getTalentLv(uuid);
|
||||||
if (lv >= cfg.max_lv) {
|
if (lv >= cfg.max_lv) {
|
||||||
this.showSmallTip(tab?.node ?? null, "该天赋已满级");
|
this.showSmallTip(anchor, "该天赋已满级");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const cost = getTalentUpgradeCost(cfg.tier);
|
const cost = getTalentUpgradeCost(cfg.tier);
|
||||||
if (cost <= 0) return;
|
if (cost <= 0) return;
|
||||||
if (smc.getTalentPoints() < cost) {
|
if (smc.getTalentPoints() < cost) {
|
||||||
this.showSmallTip(tab?.node ?? null, "天赋点不足");
|
this.showSmallTip(anchor, "天赋点不足");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,7 +193,7 @@ export class TalentsComp extends CCComp {
|
|||||||
maskClose: true,
|
maskClose: true,
|
||||||
okFunc: () => {
|
okFunc: () => {
|
||||||
if (!smc.upgradeTalent(uuid)) {
|
if (!smc.upgradeTalent(uuid)) {
|
||||||
this.showSmallTip(this.tabNodes.get(cfg.tier)?.node ?? null, "天赋点不足");
|
this.showSmallTip(anchor, "天赋点不足");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user