4 Commits

Author SHA1 Message Date
walkpan
2ee8eb097e feat(talent): 引入天赋碎片系统替换通用天赋点
- 新增 TalentFragmentType 枚举和 TalentFragmentInfo 接口定义碎片类型
- 在 SingletonModuleComp 中新增 talent_fragments 字段存储碎片库存,talent_points 改为可选字段以兼容旧存档
- 为每个天赋配置 fragmentType 指定升级所需的具体碎片类型
- 修改 TalentsComp 升级逻辑,从消耗天赋点改为扣除对应类型的碎片
- 重置天赋功能现在返还已消耗的碎片而非天赋点
- 更新界面显示,展示碎片库存摘要和具体消耗
2026-05-08 08:38:01 +08:00
walkpan
a895456974 feat(i18n): 新增四个技能的中英文本地化文本
为技能ID 7009至7012添加对应的中英文名称与描述文本,支持游戏内多语言显示。
2026-05-03 00:13:27 +08:00
walkpan
2eaf85c6f5 feat(英雄属性): 新增暴击伤害属性并支持驻场技能加成
- 在 HeroAttrs 枚举中添加 critical_damage 属性
- 修改 HeroAtkSystem 的暴击伤害计算逻辑,支持基础暴伤和英雄额外暴伤叠加
- 在 Skill 类中设置技能属性时,使用 HeroAttrsComp 的运行时属性获取方法
- 为 FieldSkillSet 添加 HeroFrost、HeroCrit、HeroCritDamage 和 HeroSpeed 驻场技能配置
- 在 HeroAttrsComp 中新增 crit_damage 字段和相关运行时属性计算方法
- 实现驻场技能百分比值统一换算逻辑,支持 0.2 和 20 两种配置写法
- 添加攻速加成机制,通过缩短技能 CD 实现攻击速度提升
2026-05-02 23:50:23 +08:00
walkpan
7a0b3ee74d feat(config): 新增英雄属性相关字段技能类型
- 在 FieldSkillType 枚举中添加 HeroFrost、HeroCrit、HeroCritDamage 和 HeroSpeed 类型,用于支持新的英雄属性加成技能。
- 清理 heros.md 文档中过时的流派设计文档。
2026-05-02 23:50:06 +08:00
11 changed files with 248 additions and 83 deletions

View File

@@ -20,5 +20,13 @@
"role_hp": "HP", "role_hp": "HP",
"role_power": "Power", "role_power": "Power",
"role_physical": "Physical", "role_physical": "Physical",
"role_agile": "Agile" "role_agile": "Agile",
} "fskill_name_7009": "Frost Domain",
"fskill_name_7010": "Deadly Focus",
"fskill_name_7011": "Rending Strike",
"fskill_name_7012": "Gale March",
"fskill_info_7009": "Increase all allied heroes' freeze chance by {0}%",
"fskill_info_7010": "Increase all allied heroes' critical chance by {0}%",
"fskill_info_7011": "Increase all allied heroes' critical damage by {0}%",
"fskill_info_7012": "Increase all allied heroes' attack speed by {0}%"
}

View File

@@ -152,6 +152,10 @@
"fskill_name_7006": "商业大亨", "fskill_name_7006": "商业大亨",
"fskill_name_7007": "神圣恢复", "fskill_name_7007": "神圣恢复",
"fskill_name_7008": "战鼓激昂", "fskill_name_7008": "战鼓激昂",
"fskill_name_7009": "寒霜领域",
"fskill_name_7010": "致命专注",
"fskill_name_7011": "裂伤打击",
"fskill_name_7012": "疾风战歌",
"fskill_info_7001": "场上所有友方召唤触发技能触发次数+{0}", "fskill_info_7001": "场上所有友方召唤触发技能触发次数+{0}",
"fskill_info_7002": "场上所有友方死亡触发技能触发次数+{0}", "fskill_info_7002": "场上所有友方死亡触发技能触发次数+{0}",
@@ -161,6 +165,10 @@
"fskill_info_7006": "卖出英雄时金币收益提升{0}", "fskill_info_7006": "卖出英雄时金币收益提升{0}",
"fskill_info_7007": "战斗结束时全队恢复效果+{0}%", "fskill_info_7007": "战斗结束时全队恢复效果+{0}%",
"fskill_info_7008": "场上所有友方攻击力提升{0}%", "fskill_info_7008": "场上所有友方攻击力提升{0}%",
"fskill_info_7009": "场上所有友方冰冻概率提升{0}%",
"fskill_info_7010": "场上所有友方暴击率提升{0}%",
"fskill_info_7011": "场上所有友方暴击伤害提升{0}%",
"fskill_info_7012": "场上所有友方攻击速度提升{0}%",
"hl_title_CritMaster_1": "初级暴击者", "hl_title_CritMaster_1": "初级暴击者",
"hl_title_CritMaster_2": "暴击大师", "hl_title_CritMaster_2": "暴击大师",
@@ -220,4 +228,4 @@
"hl_title_ThriftyPlayer_4": "一毛不拔", "hl_title_ThriftyPlayer_4": "一毛不拔",
"hl_title_ThriftyPlayer_5": "理财大师", "hl_title_ThriftyPlayer_5": "理财大师",
"hl_desc_ThriftyPlayer": "金币使用率{0}%" "hl_desc_ThriftyPlayer": "金币使用率{0}%"
} }

View File

@@ -8,7 +8,7 @@ import { WxCloudApi } from "../wx_clound_client_api/WxCloudApi";
import { GameEvent } from "./config/GameEvent"; import { GameEvent } from "./config/GameEvent";
import { GameScoreStats } from "./config/HeroAttrs"; import { GameScoreStats } from "./config/HeroAttrs";
import { mLogger } from "./Logger"; import { mLogger } from "./Logger";
import { TalentType } from "./config/TalentSet"; import { TalentFragmentType, TalentType } from "./config/TalentSet";
import { gameDataSync } from "./GameDataSync"; import { gameDataSync } from "./GameDataSync";
import { FightSet } from "./config/GameSet"; import { FightSet } from "./config/GameSet";
@@ -23,7 +23,8 @@ export interface GameDate{
talents: Partial<Record<TalentType, number>>, talents: Partial<Record<TalentType, number>>,
player_level: number, player_level: number,
player_exp: number, player_exp: number,
talent_points: number, talent_fragments: Partial<Record<TalentFragmentType, number>>,
talent_points?: number,
} }
} }
export interface CloudData { export interface CloudData {
@@ -66,12 +67,14 @@ export class SingletonModuleComp extends ecs.Comp {
talents: Partial<Record<TalentType, number>>; talents: Partial<Record<TalentType, number>>;
player_level: number; player_level: number;
player_exp: number; player_exp: number;
talent_points: number; talent_fragments: Partial<Record<TalentFragmentType, number>>;
talent_points?: number;
} = { } = {
talents: {}, // 存储各个天赋的等级: { talent_id: level } talents: {}, // 存储各个天赋的等级: { talent_id: level }
player_level: 1, // 玩家等级 player_level: 1, // 玩家等级
player_exp: 0, // 玩家当前经验 player_exp: 0, // 玩家当前经验
talent_points: 0, // 当前可用天赋点 talent_fragments: {}, // 当前拥有的天赋碎片库存
talent_points: 0, // 兼容旧存档的历史字段
}; };
vmdata: any = { vmdata: any = {
@@ -240,6 +243,7 @@ export class SingletonModuleComp extends ecs.Comp {
if (remoteCol.talents) this.collection.talents = remoteCol.talents; if (remoteCol.talents) this.collection.talents = remoteCol.talents;
if (typeof remoteCol.player_level === 'number') this.collection.player_level = remoteCol.player_level; if (typeof remoteCol.player_level === 'number') this.collection.player_level = remoteCol.player_level;
if (typeof remoteCol.player_exp === 'number') this.collection.player_exp = remoteCol.player_exp; if (typeof remoteCol.player_exp === 'number') this.collection.player_exp = remoteCol.player_exp;
if (remoteCol.talent_fragments) this.collection.talent_fragments = remoteCol.talent_fragments;
if (typeof remoteCol.talent_points === 'number') this.collection.talent_points = remoteCol.talent_points; if (typeof remoteCol.talent_points === 'number') this.collection.talent_points = remoteCol.talent_points;
} }
} }

View File

@@ -23,6 +23,7 @@ export enum Attrs {
// ==================== 暴击与命中属性 ==================== // ==================== 暴击与命中属性 ====================
critical = "critical", // 暴击率 critical = "critical", // 暴击率
critical_damage = "critical_damage", // 暴击伤害
// ==================== 特殊效果属性 ==================== // ==================== 特殊效果属性 ====================
freeze_chance = "freeze_chance", // 冰冻概率 freeze_chance = "freeze_chance", // 冰冻概率

View File

@@ -226,6 +226,9 @@ export const SkillSet: Record<number, SkillConfig> = {
RType:RType.bezier,EType:EType.collision,buffs:[],info:t("skill_info_6008", 1, 100), RType:RType.bezier,EType:EType.collision,buffs:[],info:t("skill_info_6008", 1, 100),
}, },
//大招 //大招
6101: { 6101: {
uuid:6101,name:t("skill_name_6101"),sp_name:"atk_fire",icon:"1173",TGroup:TGroup.Enemy,readyAnm:"reds",endAnm:"",act:"max", uuid:6101,name:t("skill_name_6101"),sp_name:"atk_fire",icon:"1173",TGroup:TGroup.Enemy,readyAnm:"reds",endAnm:"",act:"max",
@@ -322,7 +325,7 @@ export const SkillSet: Record<number, SkillConfig> = {
} }
}; };
//***************驻场技能配置***************
export enum FieldSkillType { export enum FieldSkillType {
SummonCount = 1, // 召唤触发技能次数提升 SummonCount = 1, // 召唤触发技能次数提升
DeadCount = 2, // 死亡触发技能次数提升 DeadCount = 2, // 死亡触发技能次数提升
@@ -332,6 +335,10 @@ export enum FieldSkillType {
SellGold = 6, // 卖出英雄金币提升 SellGold = 6, // 卖出英雄金币提升
WaveHeal = 7, // 战斗结束生命回复量提升 WaveHeal = 7, // 战斗结束生命回复量提升
HeroAtk = 8, // 英雄攻击力加成 HeroAtk = 8, // 英雄攻击力加成
HeroFrost = 9, // 英雄冰冻加成
HeroCrit = 10, // 英雄暴击加成
HeroCritDamage = 11, // 英雄暴击伤害加成
HeroSpeed = 12, // 英雄攻击速度加成
} }
export interface FieldSkillConfig { export interface FieldSkillConfig {
@@ -351,5 +358,8 @@ export const FieldSkillSet: Record<number, FieldSkillConfig> = {
7006: { uuid: 7006, name: t("fskill_name_7006"), type: FieldSkillType.SellGold, value: 5, info: t("fskill_info_7006", 5) }, 7006: { uuid: 7006, name: t("fskill_name_7006"), type: FieldSkillType.SellGold, value: 5, info: t("fskill_info_7006", 5) },
7007: { uuid: 7007, name: t("fskill_name_7007"), type: FieldSkillType.WaveHeal, value: 0.3, info: t("fskill_info_7007", 30) }, 7007: { uuid: 7007, name: t("fskill_name_7007"), type: FieldSkillType.WaveHeal, value: 0.3, info: t("fskill_info_7007", 30) },
7008: { uuid: 7008, name: t("fskill_name_7008"), type: FieldSkillType.HeroAtk, value: 0.2, info: t("fskill_info_7008", 20) }, 7008: { uuid: 7008, name: t("fskill_name_7008"), type: FieldSkillType.HeroAtk, value: 0.2, info: t("fskill_info_7008", 20) },
7009: { uuid: 7009, name: t("fskill_name_7009"), type: FieldSkillType.HeroFrost, value: 0.1, info: t("fskill_info_7009", 10) },
7010: { uuid: 7010, name: t("fskill_name_7010"), type: FieldSkillType.HeroCrit, value: 0.1, info: t("fskill_info_7010", 10) },
7011: { uuid: 7011, name: t("fskill_name_7011"), type: FieldSkillType.HeroCritDamage, value: 0.5, info: t("fskill_info_7011", 50) },
7012: { uuid: 7012, name: t("fskill_name_7012"), type: FieldSkillType.HeroSpeed, value: 0.2, info: t("fskill_info_7012", 20) },
}; };

View File

@@ -17,6 +17,28 @@ export enum TalentType {
SellBonus = 11 // 出售补贴 SellBonus = 11 // 出售补贴
} }
export enum TalentFragmentType {
Power = 1, // 力量碎片
Tempest = 2, // 风暴碎片
Vitality = 3, // 生机碎片
Frost = 4, // 寒霜碎片
Tactics = 5, // 谋略碎片
Spirit = 6, // 灵契碎片
Trade = 7, // 商贸碎片
Fate = 8 // 命运碎片
}
export interface TalentFragmentInfo {
/** 碎片 ID */
id: TalentFragmentType;
/** 碎片名称 */
name: string;
/** 碎片图标或标识(可选) */
icon?: string;
/** 该碎片可用于哪些天赋,单个碎片最多映射 3 个天赋 */
talentIds: TalentType[];
}
export interface TalentInfo { export interface TalentInfo {
/** 天赋 ID */ /** 天赋 ID */
id: number; id: number;
@@ -30,8 +52,10 @@ export interface TalentInfo {
maxLevel: number; maxLevel: number;
/** 每一级的加成数值从第1级到最大级 */ /** 每一级的加成数值从第1级到最大级 */
values: number[]; values: number[];
/** 升到每一级所需的消耗点数从第1级到最大级 */ /** 每一级的消耗数量,下标 0 表示升到 1 级 */
costs: number[]; costs: number[];
/** 升级所需的单碎片类型 */
fragmentType: TalentFragmentType;
} }
export const TalentConfig = { export const TalentConfig = {
@@ -41,30 +65,42 @@ export const TalentConfig = {
{ maxLevel: 20, expPerLevel: 150 }, { maxLevel: 20, expPerLevel: 150 },
{ maxLevel: 30, expPerLevel: 200 } { maxLevel: 30, expPerLevel: 200 }
], ],
// 天赋碎片配置:不同天赋可共用同一种碎片,单个碎片最多映射 3 个天赋
fragments: [
{ id: TalentFragmentType.Power, name: "力量碎片", icon: "◆", talentIds: [TalentType.Attack, TalentType.Critical, TalentType.Puncture] },
{ id: TalentFragmentType.Tempest, name: "风暴碎片", icon: "◇", talentIds: [TalentType.WindFury, TalentType.RefreshDiscount] },
{ id: TalentFragmentType.Vitality, name: "生机碎片", icon: "●", talentIds: [TalentType.Hp, TalentType.DeadTrigger, TalentType.Summon] },
{ id: TalentFragmentType.Frost, name: "寒霜碎片", icon: "○", talentIds: [TalentType.Freeze] },
{ id: TalentFragmentType.Tactics, name: "谋略碎片", icon: "■", talentIds: [TalentType.BuyDiscount] },
{ id: TalentFragmentType.Spirit, name: "灵契碎片", icon: "□", talentIds: [TalentType.SellBonus] },
{ id: TalentFragmentType.Trade, name: "商贸碎片", icon: "▲", talentIds: [] },
{ id: TalentFragmentType.Fate, name: "命运碎片", icon: "△", talentIds: [] }
] as TalentFragmentInfo[],
// 所有天赋定义(使用数组维护) // 所有天赋定义(使用数组维护)
talents: [ talents: [
{ id: TalentType.Attack, name: "攻击强化", icon: "⚔️", desc: "所有英雄 ATK +{value}%", { id: TalentType.Attack, name: "攻击强化", icon: "⚔️", desc: "所有英雄 ATK +{value}%",
maxLevel: 5, values: [3, 6, 9, 12, 15], costs: [1, 1, 2, 2, 3] }, maxLevel: 5, values: [3, 6, 9, 12, 15], costs: [1, 1, 2, 2, 3], fragmentType: TalentFragmentType.Power },
{ id: TalentType.Hp, name: "生命强化", icon: "❤️", desc: "所有英雄 HP +{value}%", { id: TalentType.Hp, name: "生命强化", icon: "❤️", desc: "所有英雄 HP +{value}%",
maxLevel: 5, values: [5, 10, 15, 20, 25], costs: [1, 1, 2, 2, 3] }, maxLevel: 5, values: [5, 10, 15, 20, 25], costs: [1, 1, 2, 2, 3], fragmentType: TalentFragmentType.Vitality },
{ id: TalentType.Critical, name: "暴击强化", icon: "🔥", desc: "所有英雄暴击率 +{value}%", { id: TalentType.Critical, name: "暴击强化", icon: "🔥", desc: "所有英雄暴击率 +{value}%",
maxLevel: 5, values: [2, 4, 6, 8, 10], costs: [1, 1, 2, 2, 3] }, maxLevel: 5, values: [2, 4, 6, 8, 10], costs: [1, 1, 2, 2, 3], fragmentType: TalentFragmentType.Power },
{ id: TalentType.WindFury, name: "风怒强化", icon: "⚡", desc: "所有英雄风怒率 +{value}%", { id: TalentType.WindFury, name: "风怒强化", icon: "⚡", desc: "所有英雄风怒率 +{value}%",
maxLevel: 5, values: [2, 4, 6, 8, 10], costs: [1, 1, 2, 2, 3] }, maxLevel: 5, values: [2, 4, 6, 8, 10], costs: [1, 1, 2, 2, 3], fragmentType: TalentFragmentType.Tempest },
{ id: TalentType.Freeze, name: "冰冻强化", icon: "❄️", desc: "所有英雄冰冻率 +{value}%", { id: TalentType.Freeze, name: "冰冻强化", icon: "❄️", desc: "所有英雄冰冻率 +{value}%",
maxLevel: 5, values: [2, 4, 6, 8, 10], costs: [1, 1, 2, 2, 3] }, maxLevel: 5, values: [2, 4, 6, 8, 10], costs: [1, 1, 2, 2, 3], fragmentType: TalentFragmentType.Frost },
{ id: TalentType.Puncture, name: "穿刺强化", icon: "🗡️", desc: "所有英雄穿刺 +{value}", { id: TalentType.Puncture, name: "穿刺强化", icon: "🗡️", desc: "所有英雄穿刺 +{value}",
maxLevel: 5, values: [0.2, 0.4, 0.6, 0.8, 1.0], costs: [1, 1, 2, 2, 3] }, maxLevel: 5, values: [0.2, 0.4, 0.6, 0.8, 1.0], costs: [1, 1, 2, 2, 3], fragmentType: TalentFragmentType.Power },
{ id: TalentType.DeadTrigger, name: "亡语强化", icon: "🛡️", desc: "死亡触发技能额外触发次数+{value}次", { id: TalentType.DeadTrigger, name: "亡语强化", icon: "🛡️", desc: "死亡触发技能额外触发次数+{value}次",
maxLevel: 5, values: [1], costs: [25] }, maxLevel: 1, values: [1], costs: [25], fragmentType: TalentFragmentType.Vitality },
{ id: TalentType.Summon, name: "召唤强化", icon: "🛡️", desc: "召唤触发技能额外触发次数+{value}次", { id: TalentType.Summon, name: "召唤强化", icon: "🛡️", desc: "召唤触发技能额外触发次数+{value}次",
maxLevel: 1, values: [1], costs: [25] }, maxLevel: 1, values: [1], costs: [25], fragmentType: TalentFragmentType.Vitality },
{ id: TalentType.BuyDiscount, name: "采购优惠", icon: "🛒", desc: "购买英雄 -{value}金", { id: TalentType.BuyDiscount, name: "采购优惠", icon: "🛒", desc: "购买英雄 -{value}金",
maxLevel: 1, values: [1], costs: [10] }, maxLevel: 1, values: [1], costs: [10], fragmentType: TalentFragmentType.Tactics },
{ id: TalentType.RefreshDiscount, name: "刷新优惠", icon: "🔄", desc: "刷新重抽 -{value}金", { id: TalentType.RefreshDiscount, name: "刷新优惠", icon: "🔄", desc: "刷新重抽 -{value}金",
maxLevel: 1, values: [1], costs: [10] }, maxLevel: 1, values: [1], costs: [10], fragmentType: TalentFragmentType.Tempest },
{ id: TalentType.SellBonus, name: "出售补贴", icon: "💰", desc: "出售英雄返还 +{value}金币", { id: TalentType.SellBonus, name: "出售补贴", icon: "💰", desc: "出售英雄返还 +{value}金币",
maxLevel: 1, values: [1], costs: [10] } maxLevel: 1, values: [1], costs: [10], fragmentType: TalentFragmentType.Spirit }
] as TalentInfo[] ] as TalentInfo[]
}; };

View File

@@ -85,25 +85,3 @@
怪物和boss 不配置 call fstart fend技能通过skills多技能触发 怪物和boss 不配置 call fstart fend技能通过skills多技能触发
## 4 流派初步 ## 4 流派初步
根据当前战斗核心机制(**双方定点攻击**、30秒回合制、召唤/死亡/受击/攻击触发)与技能池基础设定,可拓展以下 4 种核心流派:
### 4.1 阵地反伤流 (Thorns Build)
* **核心机制**:在定点站桩输出的前提下,前排坦克英雄必然承受集中火力,依赖其高血量与 `atked` (受击触发) 机制。
* **战斗表现**作为定点靶子前排在承受高频攻击时受击多次自动释放近战范围大招如反击震荡波清理靠近的前排怪物或为自身叠加护盾Shield延长阵地存活时间。
* **适用场景**阶段二Boss 2卡点通过前排高承伤和被动反击保护后排定点输出。
### 4.2 狂风骤雨流 (Windfury Build)
* **核心机制**主堆攻速低CD如0.3s)与风怒/多段攻击。利用 `atking` (普攻次数触发) 机制。由于定点攻击无需移动寻找目标,高攻速收益可被完全吃满。
* **战斗表现**:极高频次的普通攻击,快速积攒普攻次数,频繁触发直线的穿透大招,贯穿敌方定点阵型,满屏跳字。
* **适用场景**:阶段三,数值狂飙时的极速割草体验。
### 4.3 献祭召唤流 (Sacrifice Build)
* **核心机制**:利用低星炮灰英雄的 `call` (召唤触发) 和 `dead` (死亡触发) 技能,配合全局 `FieldSkillType.DeadCount` 加成。
* **战斗表现**将炮灰英雄放置在敌方高威胁定点火力的集火位上场即释放群体Buff替死后自爆造成大范围AOE同时为己方后排核心C位加永久攻击力。
* **适用场景**跨越全阶段的策略型玩法对定点阵型的位置站位如避开Boss弹道、吃挡刀位利用率极高。
### 4.4 冰火元素爆发流 (Elemental Burst Build)
* **核心机制**:以极慢攻速法师为主,利用 `fstart` (战斗开始) 或 `field` (驻场) 提供高额暴击crt或冰冻frz概率加成。
* **战斗表现**定点单发核弹AP极高配合必定暴击或群体冰冻控制。由于是定点战斗冰冻可以有效暂停敌方特定高威胁单位的攻击轴CD读条
* **适用场景**阶段四终极考验精准控制Boss或高危怪物的行动并制造巨额单次伤害。

View File

@@ -142,7 +142,8 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd
mLogger.log(this.debugMode, 'HeroAtkSystem', " dmgCount",damage) mLogger.log(this.debugMode, 'HeroAtkSystem', " dmgCount",damage)
if (isCrit) { if (isCrit) {
damage = Math.floor(damage * (1 + FightSet.CRIT_DAMAGE / 100)); const critDamageBonus = damageEvent.Attrs[Attrs.critical_damage] || 0;
damage = Math.floor(damage * (1 + (FightSet.CRIT_DAMAGE + critDamageBonus) / 100));
reDate.isCrit=true; reDate.isCrit=true;
if (damageEvent.Attrs.fac === FacSet.HERO) { if (damageEvent.Attrs.fac === FacSet.HERO) {
// 【评分系统 - 输出分】统计暴击次数与暴击造成的总伤害 // 【评分系统 - 输出分】统计暴击次数与暴击造成的总伤害

View File

@@ -9,6 +9,8 @@ import { TalentConfig, TalentType } from "../common/config/TalentSet";
@ecs.register('HeroAttrs') @ecs.register('HeroAttrs')
export class HeroAttrsComp extends ecs.Comp { export class HeroAttrsComp extends ecs.Comp {
public debugMode: boolean = false; public debugMode: boolean = false;
private static readonly percentRateThreshold = 1;
private static readonly minAttackCd = 0.05;
Ebus:any=null! Ebus:any=null!
// ==================== 角色基础信息 ==================== // ==================== 角色基础信息 ====================
@@ -41,6 +43,7 @@ export class HeroAttrsComp extends ecs.Comp {
// ==================== 特殊属性 ==================== // ==================== 特殊属性 ====================
critical: number = 0; // 暴击率 critical: number = 0; // 暴击率
freeze_chance: number = 0; // 冰冻概率 freeze_chance: number = 0; // 冰冻概率
crit_damage: number = 0; // 额外暴击伤害
puncture: number = 0; // 穿刺次数 puncture: number = 0; // 穿刺次数
wfuny: number = 0; // 风怒 wfuny: number = 0; // 风怒
@@ -137,15 +140,16 @@ export class HeroAttrsComp extends ecs.Comp {
for (const key in this.skills) { for (const key in this.skills) {
const skill = this.skills[key]; const skill = this.skills[key];
if (!skill) continue; if (!skill) continue;
if (skill.cd <= 0) { const actualCd = this.getEffectiveSkillCd(skill.uuid);
if (actualCd <= 0) {
skill.ccd = 0; skill.ccd = 0;
continue; continue;
} }
if (skill.ccd >= skill.cd) { if (skill.ccd >= actualCd) {
skill.ccd = skill.cd; skill.ccd = actualCd;
continue; continue;
} }
skill.ccd = Math.min(skill.cd, skill.ccd + dt); skill.ccd = Math.min(actualCd, skill.ccd + dt);
} }
} }
isFrost(): boolean { isFrost(): boolean {
@@ -164,8 +168,9 @@ export class HeroAttrsComp extends ecs.Comp {
if (!skillId) return false; if (!skillId) return false;
const skill = this.skills[skillId]; const skill = this.skills[skillId];
if (!skill) return false; if (!skill) return false;
if (skill.cd <= 0) return true; const actualCd = this.getEffectiveSkillCd(skillId);
return skill.ccd >= skill.cd; if (actualCd <= 0) return true;
return skill.ccd >= actualCd;
} }
triggerSkillCD(skillId: number) { triggerSkillCD(skillId: number) {
@@ -178,8 +183,9 @@ export class HeroAttrsComp extends ecs.Comp {
getSkillCdProgress(skillId: number): number { getSkillCdProgress(skillId: number): number {
if (!skillId) return 1; if (!skillId) return 1;
const skill = this.skills[skillId]; const skill = this.skills[skillId];
if (!skill || skill.cd <= 0) return 1; const actualCd = this.getEffectiveSkillCd(skillId);
return Math.max(0, Math.min(1, skill.ccd / skill.cd)); if (!skill || actualCd <= 0) return 1;
return Math.max(0, Math.min(1, skill.ccd / actualCd));
} }
getDisplaySkillCdProgress(): number { getDisplaySkillCdProgress(): number {
@@ -188,6 +194,50 @@ export class HeroAttrsComp extends ecs.Comp {
return this.getSkillCdProgress(displaySkillId); return this.getSkillCdProgress(displaySkillId);
} }
/** 将驻场配置值统一换算成百分比数值,兼容 0.2 和 20 两种写法。 */
private getFieldPercentValue(type: FieldSkillType): number {
const rawValue = HeroAttrsComp.getFieldSkillTotalValue(type);
if (Math.abs(rawValue) <= HeroAttrsComp.percentRateThreshold) {
return rawValue * 100;
}
return rawValue;
}
/** 英雄实时暴击率 = 基础暴击率 + 驻场暴击率。 */
public getRuntimeCritical(): number {
if (this.fac !== FacSet.HERO) return this.critical;
return this.critical + this.getFieldPercentValue(FieldSkillType.HeroCrit);
}
/** 英雄实时冰冻率 = 基础冰冻率 + 驻场冰冻率。 */
public getRuntimeFreezeChance(): number {
if (this.fac !== FacSet.HERO) return this.freeze_chance;
return this.freeze_chance + this.getFieldPercentValue(FieldSkillType.HeroFrost);
}
/** 英雄实时暴击伤害 = 基础额外暴伤 + 驻场暴伤。 */
public getRuntimeCritDamageBonus(): number {
if (this.fac !== FacSet.HERO) return this.crit_damage;
return this.crit_damage + this.getFieldPercentValue(FieldSkillType.HeroCritDamage);
}
/** 攻速加成通过缩短普通攻击技能 CD 生效,正值越高,攻击越快。 */
public getRuntimeAttackSpeedBonus(): number {
if (this.fac !== FacSet.HERO) return 0;
return this.getFieldPercentValue(FieldSkillType.HeroSpeed);
}
/** 根据攻速加成换算实际攻击间隔,避免直接改写配置里的基础 CD。 */
public getEffectiveSkillCd(skillId: number): number {
const skill = this.skills[skillId];
if (!skill) return 0;
if (skill.cd <= 0) return 0;
const speedBonus = this.getRuntimeAttackSpeedBonus();
if (speedBonus <= 0) return skill.cd;
const speedRate = 1 + speedBonus / 100;
return Math.max(HeroAttrsComp.minAttackCd, skill.cd / speedRate);
}
// ==================== 技能距离缓存管理 ==================== // ==================== 技能距离缓存管理 ====================
@@ -245,6 +295,7 @@ export class HeroAttrsComp extends ecs.Comp {
this.revive = undefined; this.revive = undefined;
this.critical = 0; this.critical = 0;
this.freeze_chance = 0; this.freeze_chance = 0;
this.crit_damage = 0;
this.revived_count = 0; this.revived_count = 0;
this.invincible_time = 0; this.invincible_time = 0;
this.puncture = 0; this.puncture = 0;

View File

@@ -24,7 +24,7 @@ import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/modu
import { mLogger } from "../common/Logger"; import { mLogger } from "../common/Logger";
import { smc } from "../common/SingletonModuleComp"; import { smc } from "../common/SingletonModuleComp";
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops"; import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
import { TalentConfig, TalentInfo, TalentType } from "../common/config/TalentSet"; import { TalentConfig, TalentFragmentInfo, TalentFragmentType, TalentInfo, TalentType } from "../common/config/TalentSet";
const { ccclass, property } = _decorator; const { ccclass, property } = _decorator;
@@ -32,9 +32,9 @@ const { ccclass, property } = _decorator;
* TalentsComp —— 天赋系统界面组件 * TalentsComp —— 天赋系统界面组件
* *
* 职责: * 职责:
* 1. 展示玩家等级、当前经验、进度条、可用天赋点 * 1. 展示玩家等级、当前经验、进度条、碎片库存
* 2. 展示天赋列表及每个天赋的当前等级。 * 2. 展示天赋列表及每个天赋的当前等级。
* 3. 处理天赋升级点击事件,扣除天赋点并保存。 * 3. 处理天赋升级点击事件,扣除碎片并保存。
* 4. 处理重置天赋(看广告)功能。 * 4. 处理重置天赋(看广告)功能。
*/ */
@ccclass('TalentsComp') @ccclass('TalentsComp')
@@ -53,7 +53,7 @@ export class TalentsComp extends CCComp {
@property({ type: ProgressBar, tooltip: "经验进度条" }) @property({ type: ProgressBar, tooltip: "经验进度条" })
pb_exp: ProgressBar = null!; pb_exp: ProgressBar = null!;
@property({ type: Label, tooltip: "当前可用天赋点数文本,例如 '4/30'" }) @property({ type: Label, tooltip: "当前碎片库存摘要文本" })
lbl_points: Label = null!; lbl_points: Label = null!;
@property({ type: Node, tooltip: "天赋列表容器,用于动态添加天赋项" }) @property({ type: Node, tooltip: "天赋列表容器,用于动态添加天赋项" })
@@ -96,12 +96,12 @@ export class TalentsComp extends CCComp {
this.updateTalentList(); this.updateTalentList();
} }
/** 更新玩家等级、经验、天赋点信息 */ /** 更新玩家等级、经验、碎片信息 */
private updatePlayerInfo() { private updatePlayerInfo() {
const collection = smc.collection; const collection = smc.collection;
let level = collection.player_level || 1; let level = collection.player_level || 1;
let exp = collection.player_exp || 0; let exp = collection.player_exp || 0;
let points = collection.talent_points || 0; const fragmentSummary = this.getFragmentSummaryText();
// 限制最大等级 // 限制最大等级
if (level > this.MAX_PLAYER_LEVEL) { if (level > this.MAX_PLAYER_LEVEL) {
@@ -109,7 +109,7 @@ export class TalentsComp extends CCComp {
} }
if (this.lbl_level) this.lbl_level.string = `Lv.${level}`; if (this.lbl_level) this.lbl_level.string = `Lv.${level}`;
if (this.lbl_points) this.lbl_points.string = `天赋点: ${points}/${this.MAX_PLAYER_LEVEL}`; if (this.lbl_points) this.lbl_points.string = fragmentSummary;
// 计算当前等级升级所需经验 // 计算当前等级升级所需经验
let expRequired = this.getExpRequirement(level); let expRequired = this.getExpRequirement(level);
@@ -179,31 +179,46 @@ export class TalentsComp extends CCComp {
if (lblLevel) lblLevel.string = `Lv.${currentLevel}`; if (lblLevel) lblLevel.string = `Lv.${currentLevel}`;
let isMax = currentLevel >= talentInfo.maxLevel; let isMax = currentLevel >= talentInfo.maxLevel;
let cost = isMax ? 0 : talentInfo.costs[currentLevel]; const levelCost = isMax ? null : this.getLevelFragmentCost(talentInfo, currentLevel);
let points = smc.collection.talent_points || 0; const canUpgrade = !!levelCost && this.hasEnoughFragments(levelCost);
if (lblCost) { if (lblCost) {
lblCost.string = isMax ? "已满级" : `消耗: ${cost}`; lblCost.string = isMax ? "已满级" : `消耗: ${this.buildFragmentCostText(levelCost!)}`;
} }
if (btnUpgrade) { if (btnUpgrade) {
btnUpgrade.interactable = !isMax && points >= cost; btnUpgrade.interactable = !isMax && canUpgrade;
// 清除旧的监听,避免重复绑定 // 清除旧的监听,避免重复绑定
btnUpgradeNode?.off(Button.EventType.CLICK); btnUpgradeNode?.off(Button.EventType.CLICK);
btnUpgradeNode?.on(Button.EventType.CLICK, () => { btnUpgradeNode?.on(Button.EventType.CLICK, () => {
this.onUpgradeClicked(talentInfo.id, currentLevel, cost); this.onUpgradeClicked(talentInfo.id, currentLevel);
}, this); }, this);
} }
} }
/** 点击升级按钮 */ /** 点击升级按钮 */
private onUpgradeClicked(talentId: TalentType, currentLevel: number, cost: number) { private onUpgradeClicked(talentId: TalentType, currentLevel: number) {
const collection = smc.collection; const collection = smc.collection;
let points = collection.talent_points || 0; const talentInfo = TalentConfig.talents.find(t => t.id === talentId);
if (!talentInfo) {
oops.gui.toast("天赋配置不存在");
return;
}
if (points >= cost && currentLevel < 5) { if (currentLevel >= talentInfo.maxLevel) {
// 1. 扣除消耗 oops.gui.toast("该天赋已满级");
collection.talent_points -= cost; return;
}
const levelCost = this.getLevelFragmentCost(talentInfo, currentLevel);
if (!levelCost) {
oops.gui.toast("天赋消耗配置异常");
return;
}
if (this.hasEnoughFragments(levelCost)) {
// 1. 扣除单碎片消耗
this.consumeFragments(levelCost);
// 2. 更新等级 // 2. 更新等级
collection.talents[talentId] = currentLevel + 1; collection.talents[talentId] = currentLevel + 1;
@@ -215,7 +230,7 @@ export class TalentsComp extends CCComp {
oops.gui.toast("天赋升级成功"); oops.gui.toast("天赋升级成功");
} else { } else {
oops.gui.toast("天赋点不足或已满级"); oops.gui.toast("碎片不足或已满级");
} }
} }
@@ -225,26 +240,32 @@ export class TalentsComp extends CCComp {
this.watch_ad().then(success => { this.watch_ad().then(success => {
if (success) { if (success) {
const collection = smc.collection; const collection = smc.collection;
// 计算已消耗的天赋点总和 // 计算已消耗碎片并返还
let refundedPoints = 0; const refundedFragments: Partial<Record<TalentFragmentType, number>> = {};
for (let id in collection.talents) { for (let id in collection.talents) {
let talentId = Number(id) as TalentType; let talentId = Number(id) as TalentType;
let level = collection.talents[talentId] || 0; let level = collection.talents[talentId] || 0;
let talentInfo = TalentConfig.talents.find(t => t.id === talentId); let talentInfo = TalentConfig.talents.find(t => t.id === talentId);
if (talentInfo) { if (talentInfo) {
for (let i = 0; i < level; i++) { for (let i = 0; i < level; i++) {
refundedPoints += talentInfo.costs[i]; const levelCost = this.getLevelFragmentCost(talentInfo, i);
if (!levelCost) continue;
refundedFragments[levelCost.type] = (refundedFragments[levelCost.type] || 0) + levelCost.amount;
} }
} }
} }
// 重置天赋等级并返还点数 // 重置天赋等级并返还碎片
collection.talents = {}; collection.talents = {};
collection.talent_points += refundedPoints; if (!collection.talent_fragments) {
collection.talent_fragments = {};
// 限制不超过最大点数(30) }
if (collection.talent_points > this.MAX_PLAYER_LEVEL) { for (const fragmentType in refundedFragments) {
collection.talent_points = this.MAX_PLAYER_LEVEL; const key = Number(fragmentType) as TalentFragmentType;
const amount = refundedFragments[key] || 0;
if (amount > 0) {
collection.talent_fragments[key] = (collection.talent_fragments[key] || 0) + amount;
}
} }
// 同步到云端 // 同步到云端
@@ -252,13 +273,59 @@ export class TalentsComp extends CCComp {
// 刷新界面 // 刷新界面
this.refreshUI(); this.refreshUI();
oops.gui.toast("天赋已重置,点数已返还"); oops.gui.toast("天赋已重置,碎片已返还");
} else { } else {
oops.gui.toast("广告观看失败,无法重置"); oops.gui.toast("广告观看失败,无法重置");
} }
}); });
} }
/** 获取升级到下一等级所需的单碎片消耗 */
private getLevelFragmentCost(talentInfo: TalentInfo, currentLevel: number): { type: TalentFragmentType; amount: number } | null {
if (talentInfo.fragmentType === undefined) {
return null;
}
return {
type: talentInfo.fragmentType,
amount: talentInfo.costs[currentLevel] ?? 0
};
}
/** 判断当前库存是否满足一次升级 */
private hasEnoughFragments(cost: { type: TalentFragmentType; amount: number }): boolean {
const bag = smc.collection.talent_fragments || {};
return (bag[cost.type] || 0) >= cost.amount;
}
/** 执行碎片扣除 */
private consumeFragments(cost: { type: TalentFragmentType; amount: number }) {
const bag = smc.collection.talent_fragments || (smc.collection.talent_fragments = {});
bag[cost.type] = Math.max(0, (bag[cost.type] || 0) - cost.amount);
}
/** 生成单次升级消耗文案 */
private buildFragmentCostText(cost: { type: TalentFragmentType; amount: number }): string {
const info = this.getFragmentInfo(cost.type);
const own = smc.collection.talent_fragments?.[cost.type] || 0;
const fragmentName = info ? info.name : `碎片${cost.type}`;
return `${fragmentName} ${own}/${cost.amount}`;
}
/** 顶部碎片库存摘要 */
private getFragmentSummaryText(): string {
const bag = smc.collection.talent_fragments || {};
const summary = TalentConfig.fragments
.filter(fragment => (bag[fragment.id] || 0) > 0)
.map(fragment => `${fragment.name}:${bag[fragment.id] || 0}`)
.join(" ");
return summary ? `碎片: ${summary}` : "碎片: 暂无";
}
/** 按碎片类型查找碎片配置 */
private getFragmentInfo(type: TalentFragmentType): TalentFragmentInfo | undefined {
return TalentConfig.fragments.find(fragment => fragment.id === type);
}
/** 模拟看广告回调实际项目中需要替换为真实的广告SDK调用 */ /** 模拟看广告回调实际项目中需要替换为真实的广告SDK调用 */
private watch_ad(): Promise<boolean> { private watch_ad(): Promise<boolean> {
return new Promise((resolve) => { return new Promise((resolve) => {
@@ -282,4 +349,4 @@ export class TalentsComp extends CCComp {
reset() { reset() {
this.node.destroy(); this.node.destroy();
} }
} }

View File

@@ -213,8 +213,9 @@ export class Skill extends ecs.Entity {
const sAp =config.ap+(SUp.ap*skill_lv); const sAp =config.ap+(SUp.ap*skill_lv);
const sHit=config.hit_count+(SUp.hit_count*skill_lv) + cAttrsComp.puncture const sHit=config.hit_count+(SUp.hit_count*skill_lv) + cAttrsComp.puncture
sDataCom.Attrs[Attrs.ap] = Math.floor(cAttrsComp.ap*sAp/100); //技能的ap是百分值 需要/100 而且需要再最终计算总ap时再/100不然会出现ap为90%变0 sDataCom.Attrs[Attrs.ap] = Math.floor(cAttrsComp.ap*sAp/100); //技能的ap是百分值 需要/100 而且需要再最终计算总ap时再/100不然会出现ap为90%变0
sDataCom.Attrs[Attrs.critical] = cAttrsComp.critical + sCrt; sDataCom.Attrs[Attrs.critical] = cAttrsComp.getRuntimeCritical() + sCrt;
sDataCom.Attrs[Attrs.freeze_chance] = cAttrsComp.freeze_chance + sFrz; sDataCom.Attrs[Attrs.critical_damage] = cAttrsComp.getRuntimeCritDamageBonus();
sDataCom.Attrs[Attrs.freeze_chance] = cAttrsComp.getRuntimeFreezeChance() + sFrz;
sDataCom.s_uuid=s_uuid sDataCom.s_uuid=s_uuid
sDataCom.skill_lv = Math.max(0, skill_lv); sDataCom.skill_lv = Math.max(0, skill_lv);
sDataCom.fac=cAttrsComp.fac sDataCom.fac=cAttrsComp.fac