Files
pixelheros/assets/script/game/map/TalentsComp.ts
panFD 707e25e73f refactor: 重构天赋系统与技能触发逻辑
1.  重构天赋系统架构:将天赋加成与触发修正统一收敛到 HeroAttrsComp.applyTalent
2.  新增天赋触发修正字段:talent_t_num_reduce 与 talent_extra_trigger_chance
3.  重构天赋加点逻辑:统一 1 点/级,新增软前置校验
4.  优化天赋UI组件:改为编辑器静态配置驱动,重构层位展示逻辑
5.  修复技能触发判定:在 SkillTriggerHelper 中应用天赋触发修正
6.  调整天赋配置结构:改为 5 层递进解锁,新增 L4/L5 专属触发修正配置
2026-09-19 11:20:55 +08:00

219 lines
8.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file TalentsComp.ts
* @description 天赋加点页组件(UI 视图层,最小容器)
*
* 职责:
* 1. 页头展示:可用天赋点 / 天赋等级(Lv.x)/ 当前等级经验进度。
* 2. 收集子树内全部 TalentItemComp(编辑器静态摆放),TALENT_UPDATE 事件驱动全量刷新。
* 3. 卡片点击统一校验(层位解锁 → 软前置 → 满级 → 点数),不满足走 smalltip 气泡;
* 满足则弹出 CommonPrompt 确认加点(smc.upgradeTalent)。
* 4. 看广告重置入口:CommonPrompt 二次确认后调 smc.resetTalents() 退还全部点数。
*
* 依赖:
* - TalentSet(TalentTier/isTalentTierUnlocked/isTalentPrereqMet/getTalentTierInvestedPoints)—— 配置与层位校验
* - TalentItemComp —— 单张迷你升级卡(编辑器静态摆放,按 uuid 自驱动渲染)
* - smc —— 天赋等级表/点数/升级/重置
*/
import { _decorator, Label, Node, NodeEventType, ProgressBar } from "cc";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
import {
TalentSet,
TALENT_TIER_UNLOCK_THRESHOLD,
getTalentDisplayText, getTalentLevelExp, getTalentTierInvestedPoints, getTalentUpgradeCost,
isTalentPrereqMet, isTalentTierUnlocked,
} from "../common/config/TalentSet";
import { GameEvent } from "../common/config/GameEvent";
import { UIID } from "../common/config/GameUIConfig";
import { SmallTipComp } from "./SmallTipComp";
import { TalentItemComp } from "./TalentItemComp";
import { smc } from "../common/SingletonModuleComp";
import { oops } from "db://oops-framework/core/Oops";
const { ccclass, property } = _decorator;
/** 层位序号展示文案(提示文案用) */
const TIER_NAMES: Record<number, string> = {
1: "第 1 层", 2: "第 2 层", 3: "第 3 层", 4: "第 4 层", 5: "第 5 层",
};
/** 页签/卡片上的 smalltip 气泡节点名 */
const SMALL_TIP_CHILD = "smalltip";
/** TalentsComp —— 天赋加点页组件(最小容器:页头 + 编辑器静态卡片收集) */
@ccclass('TalentsComp')
@ecs.register('Talents', false)
export class TalentsComp extends CCComp {
@property({ type: Label, tooltip: "页头可用天赋点" })
lbl_points: Label = null!;
@property({ type: Label, tooltip: "天赋等级文案(Lv.x 样式)" })
lbl_lv: Label = null!;
@property({ type: ProgressBar, tooltip: "当前等级经验进度条" })
progress_exp: ProgressBar = null!;
@property({ type: Label, tooltip: "当前等级经验数值(当前经验/升级所需经验)" })
lbl_exp: Label = null!;
@property({ type: Node, tooltip: "看广告重置按钮" })
btn_reset: Node = null!;
/** 页内全部天赋卡(onLoad 时自动收集子树内所有 TalentItemComp,无需编辑器拖引) */
private talent_items: TalentItemComp[] = [];
protected onLoad(): void {
this.talent_items = this.node.getComponentsInChildren(TalentItemComp);
for (const item of this.talent_items) {
item.onTalentTap = (uuid: number) => this.onTalentTap(uuid);
}
this.btn_reset?.on(NodeEventType.TOUCH_END, this.onResetTap, this);
}
protected onEnable(): void {
// 升级/重置/加点变动统一走 TALENT_UPDATE 事件驱动全量刷新
oops.message.on(GameEvent.TALENT_UPDATE, this.refreshUI, this);
this.refreshUI();
}
protected onDisable(): void {
oops.message.off(GameEvent.TALENT_UPDATE, this.refreshUI, this);
}
protected onDestroy(): void {
this.btn_reset?.targetOff(this);
}
/** 全量刷新:页头点数 + 天赋等级/经验 + 全部静态卡片 */
public refreshUI(): void {
const levels = smc.getTalentLevels();
if (this.lbl_points) {
// 剩余/总点数:总点数 = 剩余 + 各层已投入
const remaining = smc.getTalentPoints();
let invested = 0;
for (let tier = 1; tier <= 5; tier++) {
invested += getTalentTierInvestedPoints(levels, tier);
}
this.lbl_points.string = `${remaining}/${remaining + invested}`;
}
// 天赋等级(Lv.x)与当前等级经验进度
const talentLv = smc.getTalentLevel();
if (this.lbl_lv) this.lbl_lv.string = `Lv.${talentLv}`;
if (this.progress_exp) {
const needExp = getTalentLevelExp(talentLv);
// 满级后进度条显示为满
this.progress_exp.progress = needExp > 0
? Math.min(1, smc.getTalentExp() / needExp)
: 1;
}
if (this.lbl_exp) {
const needExp = getTalentLevelExp(talentLv);
// 满级后显示为 0/0,避免分母为 0
this.lbl_exp.string = needExp > 0
? `${smc.getTalentExp()}/${needExp}`
: `0/0`;
}
for (const item of this.talent_items) {
item.updateItem();
}
}
/**
* 获取挂载点下的 smalltip 气泡组件(节点缺失或组件未挂时返回 null 并静默跳过提示)
* @param parent 气泡挂载的父节点
*/
private getSmallTip(parent: Node | null): SmallTipComp | null {
const tipNode = parent?.getChildByName(SMALL_TIP_CHILD);
return tipNode ? (tipNode.getComponent(SmallTipComp) ?? tipNode.addComponent(SmallTipComp)) : null;
}
/**
* 在指定节点下弹出 smalltip 气泡提示
* @param anchor 气泡挂载的父节点
* @param text 提示文案
*/
private showSmallTip(anchor: Node | null, text: string): void {
const tip = this.getSmallTip(anchor);
tip?.show(text);
}
/**
* 按 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 {
const cfg = TalentSet[uuid];
if (!cfg) return;
const levels = smc.getTalentLevels();
const anchor = this.findItemNode(uuid);
// 层位解锁:第 N 层要求第 N-1 层累计投入 ≥ 阈值
if (!isTalentTierUnlocked(levels, cfg.tier)) {
this.showSmallTip(anchor, `${TIER_NAMES[cfg.tier - 1]}投入${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;
}
const lv = smc.getTalentLv(uuid);
if (lv >= cfg.max_lv) {
this.showSmallTip(anchor, "该天赋已满级");
return;
}
const cost = getTalentUpgradeCost(cfg.tier);
if (cost <= 0) return;
if (smc.getTalentPoints() < cost) {
this.showSmallTip(anchor, "天赋点不足");
return;
}
// CommonPrompt 确认加点(UIID.Window)
oops.gui.open(UIID.Window, {
title: "天赋加点",
content: `${cfg.name} Lv.${lv} → Lv.${lv + 1}\n消耗 <color=#E0A93D>${cost}</color> 点天赋点,获得 ${getTalentDisplayText(cfg, 1)}。是否加点?`,
okWord: "加点",
cancelWord: "取消",
needCancel: true,
maskClose: true,
okFunc: () => {
if (!smc.upgradeTalent(uuid)) {
this.showSmallTip(anchor, "天赋点不足");
}
},
});
}
/** 看广告重置入口:CommonPrompt 二次确认(广告接入前先直接确认重置) */
private onResetTap(): void {
oops.gui.open(UIID.Window, {
title: "重置天赋",
content: "观看广告后重置所有天赋等级,并全额退还已消耗的天赋点。是否继续?",
okWord: "重置",
cancelWord: "取消",
needCancel: true,
okFunc: () => smc.resetTalents(),
});
}
/** ECS 组件移除时销毁节点 */
reset() {
this.node.destroy();
}
}