diff --git a/assets/resources/gui/anim/bs4.anim b/assets/resources/gui/anim/bs4.anim new file mode 100644 index 00000000..7afa9df4 --- /dev/null +++ b/assets/resources/gui/anim/bs4.anim @@ -0,0 +1,258 @@ +[ + { + "__type__": "cc.AnimationClip", + "_name": "bs4", + "_objFlags": 0, + "__editorExtras__": { + "embeddedPlayerGroups": [] + }, + "_native": "", + "sample": 15, + "speed": 1, + "wrapMode": 2, + "enableTrsBlending": false, + "_duration": 0.6666666666666666, + "_hash": 500763545, + "_tracks": [ + { + "__id__": 1 + } + ], + "_exoticAnimation": null, + "_events": [], + "_embeddedPlayers": [], + "_additiveSettings": { + "__id__": 11 + }, + "_auxiliaryCurveEntries": [] + }, + { + "__type__": "cc.animation.VectorTrack", + "_binding": { + "__type__": "cc.animation.TrackBinding", + "path": { + "__id__": 2 + }, + "proxy": null + }, + "_channels": [ + { + "__id__": 3 + }, + { + "__id__": 5 + }, + { + "__id__": 7 + }, + { + "__id__": 9 + } + ], + "_nComponents": 3 + }, + { + "__type__": "cc.animation.TrackPath", + "_paths": [ + "scale" + ] + }, + { + "__type__": "cc.animation.Channel", + "_curve": { + "__id__": 4 + } + }, + { + "__type__": "cc.RealCurve", + "_times": [ + 0, + 0.3333333333333333, + 0.6666666666666666 + ], + "_values": [ + { + "__type__": "cc.RealKeyframeValue", + "interpolationMode": 0, + "tangentWeightMode": 0, + "value": 1, + "rightTangent": 0, + "rightTangentWeight": 1, + "leftTangent": 0, + "leftTangentWeight": 1, + "easingMethod": 0, + "__editorExtras__": { + "tangentMode": 0 + } + }, + { + "__type__": "cc.RealKeyframeValue", + "interpolationMode": 0, + "tangentWeightMode": 0, + "value": 1.1, + "rightTangent": 0, + "rightTangentWeight": 1, + "leftTangent": 0, + "leftTangentWeight": 1, + "easingMethod": 0, + "__editorExtras__": { + "tangentMode": 0 + } + }, + { + "__type__": "cc.RealKeyframeValue", + "interpolationMode": 0, + "tangentWeightMode": 0, + "value": 1, + "rightTangent": 0, + "rightTangentWeight": 1, + "leftTangent": 0, + "leftTangentWeight": 1, + "easingMethod": 0, + "__editorExtras__": { + "tangentMode": 0 + } + } + ], + "preExtrapolation": 1, + "postExtrapolation": 1 + }, + { + "__type__": "cc.animation.Channel", + "_curve": { + "__id__": 6 + } + }, + { + "__type__": "cc.RealCurve", + "_times": [ + 0, + 0.3333333333333333, + 0.6666666666666666 + ], + "_values": [ + { + "__type__": "cc.RealKeyframeValue", + "interpolationMode": 0, + "tangentWeightMode": 0, + "value": 1, + "rightTangent": 0, + "rightTangentWeight": 1, + "leftTangent": 0, + "leftTangentWeight": 1, + "easingMethod": 0, + "__editorExtras__": { + "tangentMode": 0 + } + }, + { + "__type__": "cc.RealKeyframeValue", + "interpolationMode": 0, + "tangentWeightMode": 0, + "value": 1.1, + "rightTangent": 0, + "rightTangentWeight": 1, + "leftTangent": 0, + "leftTangentWeight": 1, + "easingMethod": 0, + "__editorExtras__": { + "tangentMode": 0 + } + }, + { + "__type__": "cc.RealKeyframeValue", + "interpolationMode": 0, + "tangentWeightMode": 0, + "value": 1, + "rightTangent": 0, + "rightTangentWeight": 1, + "leftTangent": 0, + "leftTangentWeight": 1, + "easingMethod": 0, + "__editorExtras__": { + "tangentMode": 0 + } + } + ], + "preExtrapolation": 1, + "postExtrapolation": 1 + }, + { + "__type__": "cc.animation.Channel", + "_curve": { + "__id__": 8 + } + }, + { + "__type__": "cc.RealCurve", + "_times": [ + 0, + 0.3333333333333333, + 0.6666666666666666 + ], + "_values": [ + { + "__type__": "cc.RealKeyframeValue", + "interpolationMode": 0, + "tangentWeightMode": 0, + "value": 1, + "rightTangent": 0, + "rightTangentWeight": 1, + "leftTangent": 0, + "leftTangentWeight": 1, + "easingMethod": 0, + "__editorExtras__": { + "tangentMode": 0 + } + }, + { + "__type__": "cc.RealKeyframeValue", + "interpolationMode": 0, + "tangentWeightMode": 0, + "value": 1, + "rightTangent": 0, + "rightTangentWeight": 1, + "leftTangent": 0, + "leftTangentWeight": 1, + "easingMethod": 0, + "__editorExtras__": { + "tangentMode": 0 + } + }, + { + "__type__": "cc.RealKeyframeValue", + "interpolationMode": 0, + "tangentWeightMode": 0, + "value": 1, + "rightTangent": 0, + "rightTangentWeight": 1, + "leftTangent": 0, + "leftTangentWeight": 1, + "easingMethod": 0, + "__editorExtras__": { + "tangentMode": 0 + } + } + ], + "preExtrapolation": 1, + "postExtrapolation": 1 + }, + { + "__type__": "cc.animation.Channel", + "_curve": { + "__id__": 10 + } + }, + { + "__type__": "cc.RealCurve", + "_times": [], + "_values": [], + "preExtrapolation": 1, + "postExtrapolation": 1 + }, + { + "__type__": "cc.AnimationClipAdditiveSettings", + "enabled": false, + "refClip": null + } +] \ No newline at end of file diff --git a/assets/resources/gui/anim/bs4.anim.meta b/assets/resources/gui/anim/bs4.anim.meta new file mode 100644 index 00000000..36d420c5 --- /dev/null +++ b/assets/resources/gui/anim/bs4.anim.meta @@ -0,0 +1,13 @@ +{ + "ver": "2.0.3", + "importer": "animation-clip", + "imported": true, + "uuid": "6bb4dd88-c4ab-4519-8295-a64ad4c6f121", + "files": [ + ".cconb" + ], + "subMetas": {}, + "userData": { + "name": "bs4" + } +} diff --git a/assets/script/game/common/SingletonModuleComp.ts b/assets/script/game/common/SingletonModuleComp.ts index ebaa27a8..50d159ce 100644 --- a/assets/script/game/common/SingletonModuleComp.ts +++ b/assets/script/game/common/SingletonModuleComp.ts @@ -121,6 +121,8 @@ export class SingletonModuleComp extends ecs.Comp { score_defense: 0, score_build: 0, score_efficiency: 0, + + achieved_highlights: [], } as GameScoreStats, gold: 0, // 金币数据(MVVM绑定字段) @@ -181,6 +183,7 @@ export class SingletonModuleComp extends ecs.Comp { score_defense: 0, score_build: 0, score_efficiency: 0, + achieved_highlights: [], } as GameScoreStats; } diff --git a/assets/script/game/common/config/HeroAttrs.ts b/assets/script/game/common/config/HeroAttrs.ts index 75bcd55c..fd1b561d 100644 --- a/assets/script/game/common/config/HeroAttrs.ts +++ b/assets/script/game/common/config/HeroAttrs.ts @@ -84,6 +84,9 @@ export interface GameScoreStats { score_defense: number; score_build: number; score_efficiency: number; + + // 已达成的亮点成就记录 + achieved_highlights: any[]; } diff --git a/assets/script/game/common/config/HighlightSet.ts b/assets/script/game/common/config/HighlightSet.ts new file mode 100644 index 00000000..b655a6e5 --- /dev/null +++ b/assets/script/game/common/config/HighlightSet.ts @@ -0,0 +1,131 @@ +/** + * @file HighlightSet.ts + * @description 亮点成就配置文件 + * + * 定义所有亮点成就的等级、触发条件、加分以及称号描述。 + * 每个亮点都包含5个递进等级,奖励分随等级增加。 + */ + +export enum HighlightType { + CritMaster = "CritMaster", // 暴击大师 + DeathExpert = "DeathExpert", // 送死达人 + IronWall = "IronWall", // 铁壁铜墙 + WindStorm = "WindStorm", // 风暴之王 + OneHitKill = "OneHitKill", // 一击必杀 + HealingLight = "HealingLight", // 治愈之光 + PerfectClear = "PerfectClear", // 完美通关 (特殊,只有一档或布尔判定) + LuckyKing = "LuckyKing", // 欧皇附体 + ThriftyPlayer = "ThriftyPlayer" // 节俭玩家 +} + +export interface HighlightLevel { + level: number; // 成就等级 (1-5) + threshold: number; // 达成阈值 (如:暴击40次) + scoreBonus: number; // 额外加分 + title: string; // 成就称号 + desc: string; // 成就描述模板 +} + +export interface HighlightConfig { + type: HighlightType; + icon: string; // 显示前缀icon,如 "🔥" + levels: HighlightLevel[]; +} + +export const HighlightSet: Record = { + [HighlightType.CritMaster]: { + type: HighlightType.CritMaster, + icon: "🔥", + levels: [ + { level: 1, threshold: 20, scoreBonus: 50, title: "初级暴击者", desc: "暴击{0}次" }, + { level: 2, threshold: 40, scoreBonus: 100, title: "暴击大师", desc: "暴击{0}次" }, + { level: 3, threshold: 60, scoreBonus: 150, title: "致命猎手", desc: "暴击{0}次" }, + { level: 4, threshold: 80, scoreBonus: 200, title: "无情处决", desc: "暴击{0}次" }, + { level: 5, threshold: 100, scoreBonus: 300, title: "刀刀烈火", desc: "暴击{0}次" }, + ] + }, + [HighlightType.DeathExpert]: { + type: HighlightType.DeathExpert, + icon: "💀", + levels: [ + { level: 1, threshold: 15, scoreBonus: 50, title: "不怕死", desc: "死亡触发{0}次" }, + { level: 2, threshold: 25, scoreBonus: 100, title: "送死达人", desc: "死亡触发{0}次" }, + { level: 3, threshold: 40, scoreBonus: 150, title: "亡灵舞者", desc: "死亡触发{0}次" }, + { level: 4, threshold: 60, scoreBonus: 200, title: "向死而生", desc: "死亡触发{0}次" }, + { level: 5, threshold: 80, scoreBonus: 300, title: "不死灾厄", desc: "死亡触发{0}次" }, + ] + }, + [HighlightType.IronWall]: { + type: HighlightType.IronWall, + icon: "🛡️", + levels: [ + { level: 1, threshold: 15, scoreBonus: 50, title: "坚固盾牌", desc: "格挡{0}次" }, + { level: 2, threshold: 30, scoreBonus: 100, title: "铁壁铜墙", desc: "格挡{0}次" }, + { level: 3, threshold: 50, scoreBonus: 150, title: "叹息之墙", desc: "格挡{0}次" }, + { level: 4, threshold: 70, scoreBonus: 200, title: "不破之阵", desc: "格挡{0}次" }, + { level: 5, threshold: 100, scoreBonus: 300, title: "绝对防御", desc: "格挡{0}次" }, + ] + }, + [HighlightType.WindStorm]: { + type: HighlightType.WindStorm, + icon: "⚡", + levels: [ + { level: 1, threshold: 10, scoreBonus: 50, title: "迅捷之风", desc: "风怒{0}次" }, + { level: 2, threshold: 20, scoreBonus: 100, title: "风暴之王", desc: "风怒{0}次" }, + { level: 3, threshold: 35, scoreBonus: 150, title: "狂风骤雨", desc: "风怒{0}次" }, + { level: 4, threshold: 50, scoreBonus: 200, title: "无影之手", desc: "风怒{0}次" }, + { level: 5, threshold: 70, scoreBonus: 300, title: "神速幻影", desc: "风怒{0}次" }, + ] + }, + [HighlightType.OneHitKill]: { + type: HighlightType.OneHitKill, + icon: "🎯", + levels: [ + { level: 1, threshold: 100, scoreBonus: 50, title: "重击", desc: "单次伤害{0}" }, + { level: 2, threshold: 200, scoreBonus: 100, title: "一击必杀", desc: "单次伤害{0}" }, + { level: 3, threshold: 400, scoreBonus: 150, title: "毁天灭地", desc: "单次伤害{0}" }, + { level: 4, threshold: 800, scoreBonus: 200, title: "核弹打击", desc: "单次伤害{0}" }, + { level: 5, threshold: 1500, scoreBonus: 300, title: "弑神一击", desc: "单次伤害{0}" }, + ] + }, + [HighlightType.HealingLight]: { + type: HighlightType.HealingLight, + icon: "💊", + levels: [ + { level: 1, threshold: 200, scoreBonus: 50, title: "急救员", desc: "治疗总量{0}" }, + { level: 2, threshold: 500, scoreBonus: 100, title: "治愈之光", desc: "治疗总量{0}" }, + { level: 3, threshold: 1000, scoreBonus: 150, title: "生命之泉", desc: "治疗总量{0}" }, + { level: 4, threshold: 2000, scoreBonus: 200, title: "起死回生", desc: "治疗总量{0}" }, + { level: 5, threshold: 4000, scoreBonus: 300, title: "移动泉水", desc: "治疗总量{0}" }, + ] + }, + [HighlightType.PerfectClear]: { + type: HighlightType.PerfectClear, + icon: "🏆", + levels: [ + { level: 1, threshold: 1, scoreBonus: 500, title: "完美通关", desc: "20回合全胜且全存活" }, + ] + }, + [HighlightType.LuckyKing]: { + type: HighlightType.LuckyKing, + icon: "🎲", + levels: [ + { level: 1, threshold: 0.6, scoreBonus: 50, title: "手气不错", desc: "刷新命中率{0}%" }, + { level: 2, threshold: 0.7, scoreBonus: 100, title: "心想事成", desc: "刷新命中率{0}%" }, + { level: 3, threshold: 0.8, scoreBonus: 150, title: "欧皇附体", desc: "刷新命中率{0}%" }, + { level: 4, threshold: 0.9, scoreBonus: 200, title: "天选之子", desc: "刷新命中率{0}%" }, + { level: 5, threshold: 1.0, scoreBonus: 300, title: "言出法随", desc: "刷新命中率{0}%" }, + ] + }, + [HighlightType.ThriftyPlayer]: { + type: HighlightType.ThriftyPlayer, + icon: "💰", + levels: [ + { level: 1, threshold: 0.75, scoreBonus: 50, title: "精打细算", desc: "金币使用率{0}%" }, + { level: 2, threshold: 0.85, scoreBonus: 100, title: "勤俭持家", desc: "金币使用率{0}%" }, + { level: 3, threshold: 0.95, scoreBonus: 150, title: "节俭玩家", desc: "金币使用率{0}%" }, + { level: 4, threshold: 0.98, scoreBonus: 200, title: "一毛不拔", desc: "金币使用率{0}%" }, + { level: 5, threshold: 1.00, scoreBonus: 300, title: "理财大师", desc: "金币使用率{0}%" }, + ] + } +}; diff --git a/assets/script/game/common/config/HighlightSet.ts.meta b/assets/script/game/common/config/HighlightSet.ts.meta new file mode 100644 index 00000000..af96c329 --- /dev/null +++ b/assets/script/game/common/config/HighlightSet.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "19430536-a842-4669-a112-653747ab59c1", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/script/game/map/VictoryComp.ts b/assets/script/game/map/VictoryComp.ts index a7b790e5..d0afd4cc 100644 --- a/assets/script/game/map/VictoryComp.ts +++ b/assets/script/game/map/VictoryComp.ts @@ -32,6 +32,7 @@ import { Attrs } from "../common/config/HeroAttrs"; import { HeroInfo } from "../common/config/heroSet"; import { CKind } from "../common/config/CardSet"; import { ScoreWeights } from "../common/config/ScoreSet"; +import { HighlightSet, HighlightType, HighlightLevel } from "../common/config/HighlightSet"; import { mLogger } from "../common/Logger"; const { ccclass, property } = _decorator; @@ -349,17 +350,60 @@ export class VictoryComp extends CCComp { }); } - // ======================== 得分计算 ======================== + /** + * 获取满足条件的最高等级的亮点成就 + * @param type 亮点类型 + * @param value 玩家实际达成的值 + * @returns 达成的最高等级配置,未达成返回 null + */ + private getHighestHighlightLevel(type: HighlightType, value: number): HighlightLevel | null { + const config = HighlightSet[type]; + if (!config || !config.levels) return null; + + let highest: HighlightLevel | null = null; + for (const levelConfig of config.levels) { + if (value >= levelConfig.threshold) { + highest = levelConfig; + } + } + return highest; + } + + /** + * 计算并获取所有达成的最高亮点配置数组,用于加分和UI展示 + */ + private getAchievedHighlights(s: any): { type: HighlightType, config: HighlightLevel, value: number }[] { + const achieved: { type: HighlightType, config: HighlightLevel, value: number }[] = []; + + // 计算辅助比例 + const refreshRatio = s.refresh_count > 0 ? (s.refresh_hit_count / s.refresh_count) : 0; + const goldRatio = s.gold_earned > 0 ? (s.gold_spent / s.gold_earned) : 0; + + // 判定表:每个维度对应的值 + const checkList: { type: HighlightType, value: number }[] = [ + { type: HighlightType.CritMaster, value: s.crt_count }, + { type: HighlightType.DeathExpert, value: s.dead_trigger_count }, + { type: HighlightType.IronWall, value: s.shield_block_count }, + { type: HighlightType.WindStorm, value: s.wf_count }, + { type: HighlightType.OneHitKill, value: Math.floor(s.highest_dmg) }, + { type: HighlightType.HealingLight, value: Math.floor(s.heal_total) }, + { type: HighlightType.PerfectClear, value: (s.passed_wave_20 && s.wave_all_alive_count >= 20) ? 1 : 0 }, + { type: HighlightType.LuckyKing, value: refreshRatio }, + { type: HighlightType.ThriftyPlayer, value: goldRatio } + ]; + + for (const item of checkList) { + const levelConfig = this.getHighestHighlightLevel(item.type, item.value); + if (levelConfig) { + achieved.push({ type: item.type, config: levelConfig, value: item.value }); + } + } + + return achieved; + } /** * 计算单局总分并更新到 smc.vmdata.scores.score。 - * - * 得分维度: - * 1. 战斗行为分 —— 暴击次数、连击触发、闪避、格挡、眩晕、冻结 - * 2. 伤害转化分 —— 总伤害、平均伤害、反伤、暴击伤害 - * 3. 击杀得分 —— 近战击杀、远程击杀、精英击杀、Boss 击杀 - * 4. 生存得分 —— 治疗量、吸血量 - * 5. 资源得分 —— 经验获取、金币获取 */ private calculateTotalScore() { const s = smc.vmdata.scores; @@ -389,14 +433,25 @@ export class VictoryComp extends CCComp { const refreshRatio = s.refresh_count > 0 ? (s.refresh_hit_count / s.refresh_count) : 0; s.score_efficiency = Math.floor(goldRatio * 100) + Math.floor(refreshRatio * 50); + // 6. 亮点成就额外加分 (按等级叠加) + const achieved = this.getAchievedHighlights(s); + s.achieved_highlights = achieved; // 记录已达成的亮点信息 + + let highlightBonus = 0; + for (const item of achieved) { + highlightBonus += item.config.scoreBonus; + } + // 取整并存储 - s.score = Math.floor(s.score_combat + s.score_output + s.score_defense + s.score_build + s.score_efficiency); + s.score = Math.floor(s.score_combat + s.score_output + s.score_defense + s.score_build + s.score_efficiency + highlightBonus); mLogger.log(this.debugMode, 'VictoryComp', `[VictoryComp] 结算总分: ${s.score}`, { combat: s.score_combat, output: s.score_output, defense: s.score_defense, build: s.score_build, - efficiency: s.score_efficiency + efficiency: s.score_efficiency, + highlightBonus: highlightBonus, + achievedHighlights: achieved }); } @@ -446,40 +501,26 @@ export class VictoryComp extends CCComp { this.highlights_container.removeAllChildren(); const s = smc.vmdata.scores; - const tags: { name: string, desc: string }[] = []; + // 获取所有已达成的亮点(包含对应等级的信息) + const achievedList = s.achieved_highlights || []; - // 按照 scoring-system.md 设计的触发条件判定 - if (s.crt_count >= 40) tags.push({ name: "🔥 暴击大师", desc: `暴击${s.crt_count}次` }); - if (s.dead_trigger_count >= 25) tags.push({ name: "💀 送死达人", desc: `死亡触发${s.dead_trigger_count}次` }); - if (s.shield_block_count >= 30) tags.push({ name: "🛡️ 铁壁铜墙", desc: `格挡${s.shield_block_count}次` }); - if (s.wf_count >= 20) tags.push({ name: "⚡ 风暴之王", desc: `风怒${s.wf_count}次` }); - if (s.highest_dmg >= 200) tags.push({ name: "🎯 一击必杀", desc: `单次伤害${Math.floor(s.highest_dmg)}` }); - if (s.heal_total >= 500) tags.push({ name: "💊 治愈之光", desc: `治疗总量${Math.floor(s.heal_total)}` }); + // 最多显示前3个亮点(如有优先级需求,可在截取前对 achievedList 进行排序) + const displayTags = achievedList.slice(0, 3); - // 🏆 完美通关:通关且20回合全员存活 - if (s.passed_wave_20 && s.wave_all_alive_count >= 20) { - tags.push({ name: "🏆 完美通关", desc: "20回合全胜" }); - } - - // 效率类判定 - const refreshRatio = s.refresh_count > 0 ? (s.refresh_hit_count / s.refresh_count) : 0; - if (refreshRatio >= 0.8) { - tags.push({ name: "🎲 欧皇附体", desc: `刷新命中率${Math.floor(refreshRatio * 100)}%` }); - } - - const goldRatio = s.gold_earned > 0 ? (s.gold_spent / s.gold_earned) : 0; - if (goldRatio >= 0.95) { - tags.push({ name: "💰 节俭玩家", desc: `金币使用率${Math.floor(goldRatio * 100)}%` }); - } - - // 最多显示前3个亮点 - const displayTags = tags.slice(0, 3); - displayTags.forEach(tag => { + displayTags.forEach(item => { const tagNode = instantiate(this.highlight_prefab); - // 尝试获取自身或者名为 "label" 的子节点上的 Label 组件 const lab = tagNode.getComponent(Label) || tagNode.getChildByName("label")?.getComponent(Label); + if (lab) { - lab.string = `${tag.name} (${tag.desc})`; + // 获取主配置和等级配置 + const typeConfig = HighlightSet[item.type]; + const levelConfig = item.config; + + // 格式化描述(替换占位符 {0} 为实际所需达成的阈值或当前值) + let descStr = levelConfig.desc.replace("{0}", levelConfig.threshold.toString()); + + // 显示:[图标] 称号 (描述) + lab.string = `${typeConfig.icon} ${levelConfig.title} (${descStr})`; } this.highlights_container.addChild(tagNode); });