Compare commits
6 Commits
16854ab1b8
...
4900911cfc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4900911cfc | ||
|
|
45e3e8712b | ||
|
|
f4150bd33c | ||
|
|
707e25e73f | ||
|
|
f53c4293df | ||
|
|
f67f8a4aa0 |
@@ -1781,7 +1781,7 @@
|
|||||||
"propertyPath": [
|
"propertyPath": [
|
||||||
"_active"
|
"_active"
|
||||||
],
|
],
|
||||||
"value": true
|
"value": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"__type__": "cc.TargetInfo",
|
"__type__": "cc.TargetInfo",
|
||||||
@@ -1800,7 +1800,7 @@
|
|||||||
"value": {
|
"value": {
|
||||||
"__type__": "cc.Vec3",
|
"__type__": "cc.Vec3",
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": -30,
|
"y": 0,
|
||||||
"z": 0
|
"z": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1821,7 +1821,7 @@
|
|||||||
"value": {
|
"value": {
|
||||||
"__type__": "cc.Size",
|
"__type__": "cc.Size",
|
||||||
"width": 150,
|
"width": 150,
|
||||||
"height": 95.5
|
"height": 35.5
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1838,7 +1838,7 @@
|
|||||||
"propertyPath": [
|
"propertyPath": [
|
||||||
"_active"
|
"_active"
|
||||||
],
|
],
|
||||||
"value": true
|
"value": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"__type__": "cc.TargetInfo",
|
"__type__": "cc.TargetInfo",
|
||||||
@@ -1869,7 +1869,7 @@
|
|||||||
"propertyPath": [
|
"propertyPath": [
|
||||||
"_top"
|
"_top"
|
||||||
],
|
],
|
||||||
"value": -8.2625
|
"value": 6.737500000000001
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"__type__": "cc.TargetInfo",
|
"__type__": "cc.TargetInfo",
|
||||||
@@ -1885,7 +1885,7 @@
|
|||||||
"propertyPath": [
|
"propertyPath": [
|
||||||
"_bottom"
|
"_bottom"
|
||||||
],
|
],
|
||||||
"value": -7.237500000000001
|
"value": 7.762499999999999
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"__type__": "CCPropertyOverrideInfo",
|
"__type__": "CCPropertyOverrideInfo",
|
||||||
@@ -1933,7 +1933,7 @@
|
|||||||
"value": {
|
"value": {
|
||||||
"__type__": "cc.Vec3",
|
"__type__": "cc.Vec3",
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 2.75,
|
"y": 17.75,
|
||||||
"z": 0
|
"z": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3659,7 +3659,7 @@
|
|||||||
"_lpos": {
|
"_lpos": {
|
||||||
"__type__": "cc.Vec3",
|
"__type__": "cc.Vec3",
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": -30,
|
"y": 0,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"_lrot": {
|
"_lrot": {
|
||||||
@@ -3766,7 +3766,7 @@
|
|||||||
"__id__": 202
|
"__id__": 202
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"_active": true,
|
"_active": false,
|
||||||
"_components": [
|
"_components": [
|
||||||
{
|
{
|
||||||
"__id__": 208
|
"__id__": 208
|
||||||
@@ -4202,7 +4202,7 @@
|
|||||||
"__id__": 220
|
"__id__": 220
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"_active": true,
|
"_active": false,
|
||||||
"_components": [
|
"_components": [
|
||||||
{
|
{
|
||||||
"__id__": 226
|
"__id__": 226
|
||||||
@@ -4217,7 +4217,7 @@
|
|||||||
"_lpos": {
|
"_lpos": {
|
||||||
"__type__": "cc.Vec3",
|
"__type__": "cc.Vec3",
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 2.75,
|
"y": 17.75,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"_lrot": {
|
"_lrot": {
|
||||||
@@ -4635,7 +4635,7 @@
|
|||||||
"_contentSize": {
|
"_contentSize": {
|
||||||
"__type__": "cc.Size",
|
"__type__": "cc.Size",
|
||||||
"width": 150,
|
"width": 150,
|
||||||
"height": 95.5
|
"height": 35.5
|
||||||
},
|
},
|
||||||
"_anchorPoint": {
|
"_anchorPoint": {
|
||||||
"__type__": "cc.Vec2",
|
"__type__": "cc.Vec2",
|
||||||
@@ -4664,8 +4664,8 @@
|
|||||||
"_target": null,
|
"_target": null,
|
||||||
"_left": 0,
|
"_left": 0,
|
||||||
"_right": 0,
|
"_right": 0,
|
||||||
"_top": -8.2625,
|
"_top": 21.7375,
|
||||||
"_bottom": -7.237500000000001,
|
"_bottom": 22.7625,
|
||||||
"_horizontalCenter": 0,
|
"_horizontalCenter": 0,
|
||||||
"_verticalCenter": 0,
|
"_verticalCenter": 0,
|
||||||
"_isAbsLeft": true,
|
"_isAbsLeft": true,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
3813
assets/resources/gui/element/TIBox.prefab
Normal file
3813
assets/resources/gui/element/TIBox.prefab
Normal file
File diff suppressed because it is too large
Load Diff
13
assets/resources/gui/element/TIBox.prefab.meta
Normal file
13
assets/resources/gui/element/TIBox.prefab.meta
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"ver": "1.1.50",
|
||||||
|
"importer": "prefab",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "e2394eb1-27f0-4b7c-ace3-cfb3453eef99",
|
||||||
|
"files": [
|
||||||
|
".json"
|
||||||
|
],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {
|
||||||
|
"syncNodeName": "TIBox"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1083,7 +1083,7 @@
|
|||||||
"_lpos": {
|
"_lpos": {
|
||||||
"__type__": "cc.Vec3",
|
"__type__": "cc.Vec3",
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 103.54399999999998,
|
"y": 96.661,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"_lrot": {
|
"_lrot": {
|
||||||
@@ -1314,8 +1314,8 @@
|
|||||||
"_target": null,
|
"_target": null,
|
||||||
"_left": 15,
|
"_left": 15,
|
||||||
"_right": 15,
|
"_right": 15,
|
||||||
"_top": 11.456000000000017,
|
"_top": 18.339,
|
||||||
"_bottom": 58.54399999999999,
|
"_bottom": 51.66100000000001,
|
||||||
"_horizontalCenter": 0,
|
"_horizontalCenter": 0,
|
||||||
"_verticalCenter": 0,
|
"_verticalCenter": 0,
|
||||||
"_isAbsLeft": true,
|
"_isAbsLeft": true,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 962 KiB After Width: | Height: | Size: 992 KiB |
@@ -116,8 +116,8 @@
|
|||||||
"height": 54,
|
"height": 54,
|
||||||
"rawWidth": 89,
|
"rawWidth": 89,
|
||||||
"rawHeight": 54,
|
"rawHeight": 54,
|
||||||
"borderTop": 0,
|
"borderTop": 26,
|
||||||
"borderBottom": 0,
|
"borderBottom": 26,
|
||||||
"borderLeft": 44,
|
"borderLeft": 44,
|
||||||
"borderRight": 44,
|
"borderRight": 44,
|
||||||
"packable": true,
|
"packable": true,
|
||||||
@@ -576,8 +576,8 @@
|
|||||||
"height": 45,
|
"height": 45,
|
||||||
"rawWidth": 52,
|
"rawWidth": 52,
|
||||||
"rawHeight": 45,
|
"rawHeight": 45,
|
||||||
"borderTop": 0,
|
"borderTop": 22,
|
||||||
"borderBottom": 0,
|
"borderBottom": 23,
|
||||||
"borderLeft": 26,
|
"borderLeft": 26,
|
||||||
"borderRight": 26,
|
"borderRight": 26,
|
||||||
"packable": true,
|
"packable": true,
|
||||||
@@ -4992,8 +4992,8 @@
|
|||||||
"height": 45,
|
"height": 45,
|
||||||
"rawWidth": 81,
|
"rawWidth": 81,
|
||||||
"rawHeight": 45,
|
"rawHeight": 45,
|
||||||
"borderTop": 0,
|
"borderTop": 22.5,
|
||||||
"borderBottom": 0,
|
"borderBottom": 22.5,
|
||||||
"borderLeft": 40,
|
"borderLeft": 40,
|
||||||
"borderRight": 40,
|
"borderRight": 40,
|
||||||
"packable": true,
|
"packable": true,
|
||||||
@@ -6096,10 +6096,10 @@
|
|||||||
"height": 39,
|
"height": 39,
|
||||||
"rawWidth": 37,
|
"rawWidth": 37,
|
||||||
"rawHeight": 39,
|
"rawHeight": 39,
|
||||||
"borderTop": 0,
|
"borderTop": 19,
|
||||||
"borderBottom": 0,
|
"borderBottom": 18,
|
||||||
"borderLeft": 0,
|
"borderLeft": 19,
|
||||||
"borderRight": 0,
|
"borderRight": 18,
|
||||||
"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;
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ export enum UIID {
|
|||||||
SInfo,
|
SInfo,
|
||||||
/** 英雄详情弹窗(图鉴卡片点击弹出:含属性/技能/升级/前后切换) */
|
/** 英雄详情弹窗(图鉴卡片点击弹出:含属性/技能/升级/前后切换) */
|
||||||
HeroInfo,
|
HeroInfo,
|
||||||
|
/** 天赋详情弹窗(天赋迷你卡点击弹出:图标/等级/前置/条幅说明/加点按钮) */
|
||||||
|
TIBox,
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 打开界面方式的配置数据 */
|
/** 打开界面方式的配置数据 */
|
||||||
@@ -44,5 +46,6 @@ export var UIConfigData: { [key: number]: UIConfig } = {
|
|||||||
[UIID.Mission]: { layer: LayerType.UI, prefab: "gui/element/mission" },
|
[UIID.Mission]: { layer: LayerType.UI, prefab: "gui/element/mission" },
|
||||||
[UIID.SInfo]: { layer: LayerType.PopUp, prefab: "gui/element/SInfoBox" },
|
[UIID.SInfo]: { layer: LayerType.PopUp, prefab: "gui/element/SInfoBox" },
|
||||||
[UIID.HeroInfo]: { layer: LayerType.PopUp, prefab: "gui/element/heroInfo", bundle: "resources" },
|
[UIID.HeroInfo]: { layer: LayerType.PopUp, prefab: "gui/element/heroInfo", bundle: "resources" },
|
||||||
|
[UIID.TIBox]: { layer: LayerType.PopUp, prefab: "gui/element/TIBox", bundle: "resources" },
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* 1. 展示单个英雄的详情:idle 动画、名称、AP / DEF / HP / CD、技能描述、
|
* 1. 展示单个英雄的详情:idle 动画、名称、AP / DEF / HP / CD、技能描述、
|
||||||
* 稀有度底框、局外等级、职业、碎片数量、升级按钮、技能图标列表。
|
* 稀有度底框、局外等级、职业、碎片数量、升级按钮、技能图标列表。
|
||||||
* 2. 提供升级操作(消耗碎片提升局外等级,成功后播放升级特效)。
|
* 2. 提供升级操作(消耗碎片提升局外等级,成功后播放升级特效)。
|
||||||
* 3. 提供前后英雄切换按钮,按图鉴排序顺序循环切换(getSortedHeroList)。
|
* 3. 提供前后英雄切换按钮,按图鉴排序顺序循环切换(品质降序 → uuid 升序)。
|
||||||
* 4. 提供关闭按钮 / 遮罩点击关闭弹窗。
|
* 4. 提供关闭按钮 / 遮罩点击关闭弹窗。
|
||||||
*
|
*
|
||||||
* 使用(oops.gui 模块):
|
* 使用(oops.gui 模块):
|
||||||
@@ -15,12 +15,11 @@
|
|||||||
* 关键设计:
|
* 关键设计:
|
||||||
* - 详情渲染逻辑与原 HerosListComp 详情面板同源(已迁移至此),
|
* - 详情渲染逻辑与原 HerosListComp 详情面板同源(已迁移至此),
|
||||||
* HerosListComp 仅保留卡片列表,点击卡片改为打开本弹窗。
|
* HerosListComp 仅保留卡片列表,点击卡片改为打开本弹窗。
|
||||||
* - 前后切换按 getSortedHeroList 计算的顺序循环(与图鉴页面卡片顺序一致)。
|
* - 前后切换按品质降序 → uuid 升序循环(与图鉴页面卡片顺序一致)。
|
||||||
* - hero_icon 使用 Animation + iconVisualToken 机制防止异步加载竞态。
|
* - hero_icon 使用 Animation + iconVisualToken 机制防止异步加载竞态。
|
||||||
*
|
*
|
||||||
* 依赖:
|
* 依赖:
|
||||||
* - HeroInfo / heroSet —— 英雄静态配置
|
* - HeroInfo / HeroList(heroSet)—— 英雄静态配置与全量 UUID 列表
|
||||||
* - getSortedHeroList(heroUpSet)—— 图鉴排序
|
|
||||||
* - smc.getHeroGrowth / smc.getHeroOuterLv / smc.upgradeHero —— 成长数据
|
* - smc.getHeroGrowth / smc.getHeroOuterLv / smc.upgradeHero —— 成长数据
|
||||||
* - buildHeroSkillDesc(HeroSkillText)—— 技能描述生成器
|
* - buildHeroSkillDesc(HeroSkillText)—— 技能描述生成器
|
||||||
* - SSkillBoxComp / ChipComp / SmallTipComp —— 子组件
|
* - SSkillBoxComp / ChipComp / SmallTipComp —— 子组件
|
||||||
@@ -30,12 +29,11 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec
|
|||||||
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
||||||
import { oops } from "db://oops-framework/core/Oops";
|
import { oops } from "db://oops-framework/core/Oops";
|
||||||
import { mLogger } from "../common/Logger";
|
import { mLogger } from "../common/Logger";
|
||||||
import { HeroInfo, SkillTriggerType, LvFieldEntry, LvTriggerEntry, TriggerGrouped, HSkillInfo, getHeroDisplayStats, LvBonusAttrsEntry } from "../common/config/heroSet";
|
import { HeroInfo, HeroList, SkillTriggerType, LvFieldEntry, LvTriggerEntry, TriggerGrouped, HSkillInfo, getHeroDisplayStats, LvBonusAttrsEntry } from "../common/config/heroSet";
|
||||||
import { buildHeroSkillDesc } from "../common/HeroSkillText";
|
import { buildHeroSkillDesc } from "../common/HeroSkillText";
|
||||||
import { outlineRich } from "../common/RichOutline";
|
import { outlineRich } from "../common/RichOutline";
|
||||||
import { GameEvent } from "../common/config/GameEvent";
|
import { GameEvent } from "../common/config/GameEvent";
|
||||||
import { HERO_UPGRADE_NEED_FRAGMENTS, RARITY_BG_COLORS } from "../common/config/GameSet";
|
import { HERO_UPGRADE_NEED_FRAGMENTS, RARITY_BG_COLORS } from "../common/config/GameSet";
|
||||||
import { getSortedHeroList } from "../common/config/heroUpSet";
|
|
||||||
import { ChipComp } from "./ChipComp";
|
import { ChipComp } from "./ChipComp";
|
||||||
import { SmallTipComp } from "./SmallTipComp";
|
import { SmallTipComp } from "./SmallTipComp";
|
||||||
import { SSkillBoxComp } from "./SSkillBoxComp";
|
import { SSkillBoxComp } from "./SSkillBoxComp";
|
||||||
@@ -249,7 +247,13 @@ export class HeroInfoComp extends CCComp {
|
|||||||
* @param delta 1 下一个,-1 上一个
|
* @param delta 1 下一个,-1 上一个
|
||||||
*/
|
*/
|
||||||
private switchHero(delta: 1 | -1) {
|
private switchHero(delta: 1 | -1) {
|
||||||
const sorted = getSortedHeroList(uuid => smc.getHeroGrowth(uuid));
|
// 与图鉴页面排序同源:仅按品质降序 → uuid 升序
|
||||||
|
const sorted = [...HeroList].sort((a, b) => {
|
||||||
|
const ra = HeroInfo[a]?.rarity ?? 1;
|
||||||
|
const rb = HeroInfo[b]?.rarity ?? 1;
|
||||||
|
if (ra !== rb) return rb - ra;
|
||||||
|
return a - b;
|
||||||
|
});
|
||||||
if (sorted.length === 0) return;
|
if (sorted.length === 0) return;
|
||||||
const idx = sorted.indexOf(this.huuid);
|
const idx = sorted.indexOf(this.huuid);
|
||||||
// 当前 uuid 不在列表中(异常)时回退到第一个
|
// 当前 uuid 不在列表中(异常)时回退到第一个
|
||||||
|
|||||||
@@ -5,15 +5,16 @@
|
|||||||
* 职责:
|
* 职责:
|
||||||
* 1. 以卡片列表形式展示所有可用英雄(使用 CardLiteComp 预制体)。
|
* 1. 以卡片列表形式展示所有可用英雄(使用 CardLiteComp 预制体)。
|
||||||
* 2. 点击卡片打开英雄详情弹窗(UIID.HeroInfo),详情/升级在 HeroInfoComp 中处理。
|
* 2. 点击卡片打开英雄详情弹窗(UIID.HeroInfo),详情/升级在 HeroInfoComp 中处理。
|
||||||
* 3. 卡片排序:可升级优先 → 现有等级降序 → 品质降序(getSortedHeroList)。
|
* 3. 卡片排序:仅按品质降序 → uuid 升序(与养成进度解耦,保持展示稳定)。
|
||||||
*
|
*
|
||||||
* 关键设计:
|
* 关键设计:
|
||||||
* - cards_node 为卡片容器,通过 instantiate(card_lite_prefab) 动态生成卡片。
|
* - cards_node 为卡片容器,通过 instantiate(card_lite_prefab) 动态生成卡片。
|
||||||
* - 升级/碎片变动后通过 HERO_GROWTH_UPDATE 事件统一刷新所有卡片的可升级标识。
|
* - 升级/碎片变动后通过 HERO_GROWTH_UPDATE 事件统一刷新所有卡片的可升级标识。
|
||||||
* - 详情面板已从本组件剥离,由 HeroInfoComp 承载。
|
* - 详情面板已从本组件剥离,由 HeroInfoComp 承载。
|
||||||
|
* - 排序不依赖 smc,仅读取静态配置(HeroInfo.rarity)。
|
||||||
*
|
*
|
||||||
* 依赖:
|
* 依赖:
|
||||||
* - getSortedHeroList(heroUpSet)—— 图鉴排序
|
* - HeroInfo / HeroList(heroSet)—— 英雄静态配置与全量 UUID 列表
|
||||||
* - CardLiteComp —— 轻量卡片组件
|
* - CardLiteComp —— 轻量卡片组件
|
||||||
* - UIID.HeroInfo(oops.gui)—— 英雄详情弹窗
|
* - UIID.HeroInfo(oops.gui)—— 英雄详情弹窗
|
||||||
*/
|
*/
|
||||||
@@ -24,10 +25,8 @@ import { oops } from "db://oops-framework/core/Oops";
|
|||||||
import { mLogger } from "../common/Logger";
|
import { mLogger } from "../common/Logger";
|
||||||
import { HeroInfo, HeroList } from "../common/config/heroSet";
|
import { HeroInfo, HeroList } from "../common/config/heroSet";
|
||||||
import { GameEvent } from "../common/config/GameEvent";
|
import { GameEvent } from "../common/config/GameEvent";
|
||||||
import { getSortedHeroList } from "../common/config/heroUpSet";
|
|
||||||
import { UIID } from "../common/config/GameUIConfig";
|
import { UIID } from "../common/config/GameUIConfig";
|
||||||
import { CardLiteComp } from "./CardLiteComp";
|
import { CardLiteComp } from "./CardLiteComp";
|
||||||
import { smc } from "../common/SingletonModuleComp";
|
|
||||||
|
|
||||||
const { property, ccclass } = _decorator;
|
const { property, ccclass } = _decorator;
|
||||||
|
|
||||||
@@ -46,14 +45,30 @@ export class HerosListComp extends CCComp {
|
|||||||
@property
|
@property
|
||||||
card_w = 120
|
card_w = 120
|
||||||
|
|
||||||
|
/** 卡片高度 */
|
||||||
|
@property
|
||||||
|
card_h = 200
|
||||||
|
|
||||||
/** 卡片横向间隔 */
|
/** 卡片横向间隔 */
|
||||||
@property
|
@property
|
||||||
spacing_x = 10
|
spacing_x = 10
|
||||||
|
|
||||||
|
/** 卡片纵向间隔 */
|
||||||
|
@property
|
||||||
|
spacing_y = 10
|
||||||
|
|
||||||
|
/** 每行卡片数(竖向滚动时按列排列) */
|
||||||
|
@property
|
||||||
|
cols = 4
|
||||||
|
|
||||||
/** 卡片左右边距 */
|
/** 卡片左右边距 */
|
||||||
@property
|
@property
|
||||||
margin_x = 15
|
margin_x = 15
|
||||||
|
|
||||||
|
/** 卡片上下边距 */
|
||||||
|
@property
|
||||||
|
margin_y = 50
|
||||||
|
|
||||||
// ======================== 运行时状态 ========================
|
// ======================== 运行时状态 ========================
|
||||||
|
|
||||||
debugMode: boolean = false
|
debugMode: boolean = false
|
||||||
@@ -95,8 +110,13 @@ export class HerosListComp extends CCComp {
|
|||||||
|
|
||||||
this.cards_node.removeAllChildren()
|
this.cards_node.removeAllChildren()
|
||||||
|
|
||||||
// 排序与详情面板"前后切换"同源(getSortedHeroList),保证两个入口顺序一致
|
// 图鉴排序:仅按品质降序 → uuid 升序(与养成进度解耦,保持展示稳定)
|
||||||
const sorted = getSortedHeroList(uuid => smc.getHeroGrowth(uuid))
|
const sorted = [...HeroList].sort((a, b) => {
|
||||||
|
const ra = HeroInfo[a]?.rarity ?? 1;
|
||||||
|
const rb = HeroInfo[b]?.rarity ?? 1;
|
||||||
|
if (ra !== rb) return rb - ra;
|
||||||
|
return a - b;
|
||||||
|
})
|
||||||
|
|
||||||
for (const uuid of sorted) {
|
for (const uuid of sorted) {
|
||||||
const hero = HeroInfo[uuid]
|
const hero = HeroInfo[uuid]
|
||||||
@@ -127,16 +147,18 @@ export class HerosListComp extends CCComp {
|
|||||||
const total = this.cards_node.children.length
|
const total = this.cards_node.children.length
|
||||||
if (total === 0) return
|
if (total === 0) return
|
||||||
|
|
||||||
// 宽度 = 卡片总宽 + 卡片间隔 + 左右边距
|
// 竖向滚动:宽度固定(每行 cols 列),高度按行数累计
|
||||||
const contentW = total * this.card_w
|
const rows = Math.ceil(total / this.cols)
|
||||||
+ (total - 1) * this.spacing_x
|
|
||||||
|
// 宽度 = 每行卡片总宽 + 横向间隔 + 左右边距
|
||||||
|
const contentW = this.cols * this.card_w
|
||||||
|
+ (this.cols - 1) * this.spacing_x
|
||||||
+ this.margin_x * 2
|
+ this.margin_x * 2
|
||||||
|
|
||||||
const cols = 4
|
// 高度 = 行数 × 卡片高 + 纵向间隔 + 上下边距
|
||||||
const rows = Math.ceil(total / cols)
|
const contentH = rows * this.card_h
|
||||||
const cardH = 200
|
+ (rows - 1) * this.spacing_y
|
||||||
const spacingY = 10
|
+ this.margin_y * 2
|
||||||
const contentH = Math.max(1000, rows * (cardH + spacingY) + 50)
|
|
||||||
|
|
||||||
const uiTrans = this.cards_node.getComponent(UITransform)
|
const uiTrans = this.cards_node.getComponent(UITransform)
|
||||||
if (uiTrans) {
|
if (uiTrans) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
214
assets/script/game/map/TIBoxComp.ts
Normal file
214
assets/script/game/map/TIBoxComp.ts
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
/**
|
||||||
|
* @file TIBoxComp.ts
|
||||||
|
* @description 天赋详情弹窗组件(UI 视图层,TIBox.prefab 载体)
|
||||||
|
*
|
||||||
|
* 职责:
|
||||||
|
* 1. 展示天赋图标 / 名称 / 当前等级 / 条幅说明(getTalentDisplayText)。
|
||||||
|
* 2. 展示软前置行:前置天赋名称、需求等级、前置已达成勾图标(达成亮 On,未达成亮 Off)。
|
||||||
|
* 3. 加点按钮:仅在可加点(层位已解锁 + 前置满足 + 未满级 + 点数足够)时显示;
|
||||||
|
* 不满足条件或已满级时隐藏,弹窗仍可正常打开查看。
|
||||||
|
* 4. 加点成功后通过 TALENT_UPDATE 事件驱动 TalentsComp 全量刷新,并同步刷新本弹窗。
|
||||||
|
*
|
||||||
|
* 使用(oops.gui 模块):
|
||||||
|
* oops.gui.open(UIID.TIBox, { uuid: 9001 });
|
||||||
|
*
|
||||||
|
* 依赖:
|
||||||
|
* - TalentSet(TalentConfig 配置与校验:isTalentTierUnlocked/isTalentPrereqMet/getTalentUpgradeCost)
|
||||||
|
* - smc —— 天赋等级表 / 点数 / upgradeTalent / 图标图集
|
||||||
|
* - outlineRich —— RichText 条幅描边
|
||||||
|
*/
|
||||||
|
import { _decorator, EventTouch, Label, Node, NodeEventType, RichText, Sprite, SpriteFrame } 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, TalentConfig, getTalentUpgradeCost, isTalentPrereqMet, isTalentTierUnlocked } from "../common/config/TalentSet";
|
||||||
|
import { GameEvent } from "../common/config/GameEvent";
|
||||||
|
import { HRoleName } from "../common/config/heroSet";
|
||||||
|
import { outlineRich } from "../common/RichOutline";
|
||||||
|
import { smc } from "../common/SingletonModuleComp";
|
||||||
|
import { oops } from "db://oops-framework/core/Oops";
|
||||||
|
|
||||||
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
|
/** TIBox 弹窗初始化参数 */
|
||||||
|
export interface TIBoxParams {
|
||||||
|
/** 天赋 uuid(TalentSet key) */
|
||||||
|
uuid: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** TIBoxComp —— 天赋详情弹窗(任何状态均可点开,不可加点时隐藏加点按钮) */
|
||||||
|
@ccclass('TIBoxComp')
|
||||||
|
@ecs.register('TIBoxComp', false)
|
||||||
|
export class TIBoxComp extends CCComp {
|
||||||
|
|
||||||
|
@property({ type: Sprite, tooltip: "天赋图标" })
|
||||||
|
icon: Sprite = null!;
|
||||||
|
|
||||||
|
@property({ type: Label, tooltip: "天赋名称" })
|
||||||
|
lbl_name: Label = null!;
|
||||||
|
|
||||||
|
@property({ type: Label, tooltip: "当前天赋等级(当前/满级)" })
|
||||||
|
lbl_level: Label = null!;
|
||||||
|
|
||||||
|
@property({ type: Label, tooltip: "前置天赋名称(无前置时隐藏前置行)" })
|
||||||
|
lbl_prereq_name: Label = null!;
|
||||||
|
|
||||||
|
@property({ type: Label, tooltip: "前置天赋需求等级" })
|
||||||
|
lbl_prereq_lv: Label = null!;
|
||||||
|
|
||||||
|
@property({ type: Node, tooltip: "前置已达成勾图标(On)" })
|
||||||
|
node_prereq_check_on: Node = null!;
|
||||||
|
|
||||||
|
@property({ type: Node, tooltip: "前置未达成勾图标(Off)" })
|
||||||
|
node_prereq_check_off: Node = null!;
|
||||||
|
|
||||||
|
@property({ type: Node, tooltip: "前置行容器(无前置天赋时整体隐藏)" })
|
||||||
|
node_prereq: Node = null!;
|
||||||
|
|
||||||
|
@property({ type: RichText, tooltip: "条幅说明(当前已生效数值 / 每级引导)" })
|
||||||
|
richtext_info: RichText = null!;
|
||||||
|
|
||||||
|
@property({ type: Node, tooltip: "加点按钮(不可加点或满级时隐藏)" })
|
||||||
|
node_add: Node = null!;
|
||||||
|
|
||||||
|
@property({ type: Node, tooltip: "关闭遮罩/关闭按钮(点击关闭弹窗)" })
|
||||||
|
node_close: Node = null!;
|
||||||
|
|
||||||
|
/** 当前展示的天赋 uuid */
|
||||||
|
private talent_uuid: number = 0;
|
||||||
|
|
||||||
|
protected onLoad(): void {
|
||||||
|
this.node_add?.on(NodeEventType.TOUCH_END, this.onAddTap, this);
|
||||||
|
this.node_close?.on(NodeEventType.TOUCH_END, this.onCloseTap, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onDestroy(): void {
|
||||||
|
this.node_add?.targetOff(this);
|
||||||
|
this.node_close?.targetOff(this);
|
||||||
|
oops.message.off(GameEvent.TALENT_UPDATE, this.refresh, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* oops.gui 添加到舞台回调(框架约定入口)
|
||||||
|
* @param params 见 TIBoxParams
|
||||||
|
*/
|
||||||
|
onAdded(params: TIBoxParams): void {
|
||||||
|
this.talent_uuid = params?.uuid ?? 0;
|
||||||
|
// 加点成功后同步刷新本弹窗(等级/按钮状态),与 TalentsComp 同一事件源
|
||||||
|
oops.message.on(GameEvent.TALENT_UPDATE, this.refresh, this);
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 全量刷新弹窗内容 */
|
||||||
|
private refresh(): void {
|
||||||
|
const cfg = TalentSet[this.talent_uuid];
|
||||||
|
if (!cfg) return;
|
||||||
|
const levels = smc.getTalentLevels();
|
||||||
|
const lv = levels[this.talent_uuid] ?? 0;
|
||||||
|
|
||||||
|
// 图标:技能图标图集(gui/ui7)优先,资源目录回退(与 TalentItemComp 口径一致)
|
||||||
|
if (this.icon && cfg.icon) {
|
||||||
|
let frame: SpriteFrame | null = null;
|
||||||
|
if (smc.skillIconsAtlas) {
|
||||||
|
frame = smc.skillIconsAtlas.getSpriteFrame(cfg.icon);
|
||||||
|
}
|
||||||
|
if (!frame) {
|
||||||
|
frame = oops.res.get("game/heros/cards/" + cfg.icon, SpriteFrame) as SpriteFrame;
|
||||||
|
}
|
||||||
|
if (frame) this.icon.spriteFrame = frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.lbl_name) this.lbl_name.string = cfg.name;
|
||||||
|
if (this.lbl_level) this.lbl_level.string = `${lv}/${cfg.max_lv}`;
|
||||||
|
// 条幅说明:已加点显示当前生效数值,未加点显示每级引导
|
||||||
|
if (this.richtext_info) this.richtext_info.string = outlineRich(this.buildDetailText(cfg, lv));
|
||||||
|
|
||||||
|
// 软前置行:无前置整体隐藏;有前置展示名称/需求等级/达成勾
|
||||||
|
const hasPrereq = !!(cfg.prereq_uuid && cfg.prereq_lv);
|
||||||
|
if (this.node_prereq) this.node_prereq.active = hasPrereq;
|
||||||
|
if (hasPrereq) {
|
||||||
|
const preCfg = TalentSet[cfg.prereq_uuid!];
|
||||||
|
const preMet = isTalentPrereqMet(levels, cfg);
|
||||||
|
if (this.lbl_prereq_name) this.lbl_prereq_name.string = preCfg ? preCfg.name : `天赋${cfg.prereq_uuid}`;
|
||||||
|
if (this.lbl_prereq_lv) this.lbl_prereq_lv.string = `${cfg.prereq_lv}`;
|
||||||
|
if (this.node_prereq_check_on) this.node_prereq_check_on.active = preMet;
|
||||||
|
if (this.node_prereq_check_off) this.node_prereq_check_off.active = !preMet;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加点按钮:层位解锁 + 前置满足 + 未满级 + 点数足够时才显示
|
||||||
|
if (this.node_add) this.node_add.active = this.canUpgrade(lv, cfg.tier, levels);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按等级精准渲染天赋 info 模板({value}/{value2} 占位替换为实际数值)。
|
||||||
|
* Lv0 时按 1 级数值预览(引导加点)。
|
||||||
|
* @param cfg 天赋配置
|
||||||
|
* @param lv 当前等级
|
||||||
|
* @returns 替换占位后的完整描述文本
|
||||||
|
*/
|
||||||
|
private buildPreciseText(cfg: TalentConfig, lv: number): string {
|
||||||
|
const mult = lv > 0 ? lv : 1;
|
||||||
|
const v1 = cfg.bonuses[0]?.per_lv ?? cfg.t_num_reduce_per_lv ?? cfg.extra_trigger_chance_per_lv ?? 0;
|
||||||
|
const v2 = cfg.bonuses[1]?.per_lv ?? 0;
|
||||||
|
return cfg.info
|
||||||
|
.replace("{value}", `${v1 * mult}`)
|
||||||
|
.replace("{value2}", `${v2 * mult}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建天赋详情条幅文本:精准描述 + 下一级预览 + 满级提示
|
||||||
|
* @param cfg 天赋配置
|
||||||
|
* @param lv 当前等级
|
||||||
|
*/
|
||||||
|
private buildDetailText(cfg: TalentConfig, lv: number): string {
|
||||||
|
const lines: string[] = [];
|
||||||
|
// 精准效果描述:使用 cfg.info 模板按当前等级替换数值占位
|
||||||
|
lines.push(this.buildPreciseText(cfg, lv));
|
||||||
|
// 未满级:追加下一级预览
|
||||||
|
if (lv < cfg.max_lv) {
|
||||||
|
lines.push(`<color=#E0A93D>下一级:${this.buildPreciseText(cfg, lv + 1)}</color>`);
|
||||||
|
} else {
|
||||||
|
lines.push(`<color=#FFD700>已满级</color>`);
|
||||||
|
}
|
||||||
|
// 职业限定提示
|
||||||
|
if (cfg.role !== undefined) {
|
||||||
|
lines.push(`<color=#AAAAAA>仅${HRoleName[cfg.role]}生效</color>`);
|
||||||
|
}
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前是否可加点(与 smc.upgradeTalent 校验口径一致,仅做 UI 预判定)
|
||||||
|
* @param lv 当前等级
|
||||||
|
* @param tier 所属层位
|
||||||
|
* @param levels 天赋等级表
|
||||||
|
*/
|
||||||
|
private canUpgrade(lv: number, tier: number, levels: Record<number, number>): boolean {
|
||||||
|
const cfg = TalentSet[this.talent_uuid];
|
||||||
|
if (!cfg) return false;
|
||||||
|
if (lv >= cfg.max_lv) return false;
|
||||||
|
if (!isTalentTierUnlocked(levels, tier)) return false;
|
||||||
|
if (!isTalentPrereqMet(levels, cfg)) return false;
|
||||||
|
return smc.getTalentPoints() >= getTalentUpgradeCost(tier);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 加点按钮点击:升级成功后由 TALENT_UPDATE 事件驱动刷新(含本弹窗) */
|
||||||
|
private onAddTap(event?: EventTouch): void {
|
||||||
|
if (event) event.propagationStopped = true;
|
||||||
|
oops.audio.playEffect("music/button");
|
||||||
|
smc.upgradeTalent(this.talent_uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 点击关闭 */
|
||||||
|
private onCloseTap(event?: EventTouch): void {
|
||||||
|
if (event) event.propagationStopped = true;
|
||||||
|
oops.audio.playEffect("music/button");
|
||||||
|
oops.gui.removeByNode(this.node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ECS 组件移除时销毁节点(CCComp 抽象方法实现) */
|
||||||
|
reset(): void {
|
||||||
|
if (this.node && this.node.isValid) {
|
||||||
|
this.node.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
assets/script/game/map/TIBoxComp.ts.meta
Normal file
9
assets/script/game/map/TIBoxComp.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.24",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "33249e41-30b1-42e3-a12e-f525b5580c37",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
@@ -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,19 @@ 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 +73,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 挂锁
|
||||||
const inactive = lv <= 0 || !tierUnlocked;
|
const locked = !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 +105,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 = locked;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 按阶位激活 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 +122,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,65 +1,39 @@
|
|||||||
/**
|
/**
|
||||||
* @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. 卡片点击统一打开 TIBox 天赋详情弹窗;加点校验(层位/前置/满级/点数)由 TIBoxComp 内聚,
|
||||||
|
* 不满足条件时弹窗隐藏加点按钮,仅作详情查看。
|
||||||
* 4. 看广告重置入口:CommonPrompt 二次确认后调 smc.resetTalents() 退还全部点数。
|
* 4. 看广告重置入口:CommonPrompt 二次确认后调 smc.resetTalents() 退还全部点数。
|
||||||
*
|
*
|
||||||
* 依赖:
|
* 依赖:
|
||||||
* - TalentSet(TalentSet/TalentTier/isTalentTierUnlocked/getTalentTierInvestedPoints)—— 配置与阶位校验
|
* - TalentSet(getTalentTierInvestedPoints/getTalentLevelExp)—— 配置与统计
|
||||||
* - TalentItemComp —— 单张迷你升级卡
|
* - TalentItemComp —— 单张迷你升级卡(编辑器静态摆放,按 uuid 自驱动渲染)
|
||||||
|
* - TIBoxComp —— 天赋详情弹窗(UIID.TIBox)
|
||||||
* - 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,
|
getTalentLevelExp, getTalentTierInvestedPoints,
|
||||||
TALENT_TIER_UNLOCK_THRESHOLD,
|
|
||||||
getTalentDisplayText, getTalentLevelExp, getTalentTierInvestedPoints, getTalentUpgradeCost, 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";
|
||||||
import { SmallTipComp } from "./SmallTipComp";
|
|
||||||
import { TalentItemComp } from "./TalentItemComp";
|
import { TalentItemComp } from "./TalentItemComp";
|
||||||
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;
|
||||||
|
|
||||||
/** 阶位页签展示文案(lockName 用于未解锁提示) */
|
/** 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";
|
|
||||||
|
|
||||||
/** 阶位页签锁定图标子节点名(可选;阶位未解锁时显示) */
|
|
||||||
const TAB_LOCKED_CHILD = "locked";
|
|
||||||
|
|
||||||
/** 阶位页签未选中态子节点名(可选;非当前选中页签时显示) */
|
|
||||||
const TAB_INACTIVE_CHILD = "noactive";
|
|
||||||
|
|
||||||
/** 页签/卡片上的 smalltip 气泡节点名 */
|
|
||||||
const SMALL_TIP_CHILD = "smalltip";
|
|
||||||
|
|
||||||
/** 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: "当前阶位卡片列表容器(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!;
|
||||||
|
|
||||||
@@ -69,52 +43,19 @@ export class TalentsComp extends CCComp {
|
|||||||
@property({ type: ProgressBar, tooltip: "当前等级经验进度条" })
|
@property({ type: ProgressBar, tooltip: "当前等级经验进度条" })
|
||||||
progress_exp: ProgressBar = null!;
|
progress_exp: ProgressBar = null!;
|
||||||
|
|
||||||
@property({ type: Node, tooltip: "一阶页签节点" })
|
@property({ type: Label, tooltip: "当前等级经验数值(当前经验/升级所需经验)" })
|
||||||
tab_basic: Node = null!;
|
lbl_exp: Label = 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);
|
||||||
}
|
}
|
||||||
@@ -130,25 +71,22 @@ 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) this.lbl_points.string = `${smc.getTalentPoints()}`;
|
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)与当前等级经验进度
|
// 天赋等级(Lv.x)与当前等级经验进度
|
||||||
const talentLv = smc.getTalentLevel();
|
const talentLv = smc.getTalentLevel();
|
||||||
@@ -160,127 +98,25 @@ export class TalentsComp extends CCComp {
|
|||||||
? Math.min(1, smc.getTalentExp() / needExp)
|
? Math.min(1, smc.getTalentExp() / needExp)
|
||||||
: 1;
|
: 1;
|
||||||
}
|
}
|
||||||
|
if (this.lbl_exp) {
|
||||||
// 仅首次进入时默认选中已解锁的最高阶,后续刷新保持玩家当前选择
|
const needExp = getTalentLevelExp(talentLv);
|
||||||
if (!this.inited) {
|
// 满级后显示为 0/0,避免分母为 0
|
||||||
this.inited = true;
|
this.lbl_exp.string = needExp > 0
|
||||||
if (isTalentTierUnlocked(levels, TalentTier.Super)) this.curTier = TalentTier.Super;
|
? `${smc.getTalentExp()}/${needExp}`
|
||||||
else if (isTalentTierUnlocked(levels, TalentTier.Specialized)) this.curTier = TalentTier.Specialized;
|
: `0/0`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.refreshTabs(levels);
|
for (const item of this.talent_items) {
|
||||||
this.refreshGrid(levels);
|
item.updateItem();
|
||||||
}
|
|
||||||
|
|
||||||
/** 页签文案 + 各阶已投入点数 + 选中高亮(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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取挂载点下的 smalltip 气泡组件(节点缺失或组件未挂时返回 null 并静默跳过提示)
|
* 卡片点击统一入口:任何状态均打开 TIBox 天赋详情弹窗,加点按钮显隐由 TIBoxComp 校验
|
||||||
* @param parent 气泡挂载的父节点
|
* @param uuid 天赋 uuid
|
||||||
*/
|
*/
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 卡片点击统一入口:未解锁/已满级/点数不足走 smalltip 气泡;其余弹出 CommonPrompt 确认加点,成功后事件驱动刷新 */
|
|
||||||
private onTalentTap(uuid: number): void {
|
private onTalentTap(uuid: number): void {
|
||||||
const cfg = TalentSet[uuid];
|
oops.gui.open(UIID.TIBox, { uuid });
|
||||||
if (!cfg) return;
|
|
||||||
const levels = smc.getTalentLevels();
|
|
||||||
|
|
||||||
const tab = this.tabNodes.get(cfg.tier);
|
|
||||||
if (!isTalentTierUnlocked(levels, cfg.tier)) {
|
|
||||||
const invested = getTalentTierInvestedPoints(levels, (cfg.tier - 1) as TalentTier);
|
|
||||||
this.showSmallTip(tab?.node ?? null, `${TIER_TAB_TEXT[cfg.tier].lockName}投入${TALENT_TIER_UNLOCK_THRESHOLD} 点解锁`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const lv = smc.getTalentLv(uuid);
|
|
||||||
if (lv >= cfg.max_lv) {
|
|
||||||
this.showSmallTip(tab?.node ?? null, "该天赋已满级");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const cost = getTalentUpgradeCost(cfg.tier);
|
|
||||||
if (cost <= 0) return;
|
|
||||||
if (smc.getTalentPoints() < cost) {
|
|
||||||
this.showSmallTip(tab?.node ?? null, "天赋点不足");
|
|
||||||
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(this.tabNodes.get(cfg.tier)?.node ?? null, "天赋点不足");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 看广告重置入口:CommonPrompt 二次确认(广告接入前先直接确认重置) */
|
/** 看广告重置入口:CommonPrompt 二次确认(广告接入前先直接确认重置) */
|
||||||
|
|||||||
Reference in New Issue
Block a user