From da2caa6576e112f01d4595d95c77802c962a3ae9 Mon Sep 17 00:00:00 2001 From: panFD Date: Sun, 13 Sep 2026 10:28:55 +0800 Subject: [PATCH] =?UTF-8?q?refactor(mission):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E5=85=B3=E5=8D=A1=E7=BB=93=E7=AE=97=E4=B8=8E=E5=88=B7=E6=80=AA?= =?UTF-8?q?=E7=BB=9F=E8=AE=A1=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 新增整局跨回合刷怪统计与重置能力,替换原小节统计 2. 修改奖励配置为整局5回合累计结算逻辑 3. 调整失败奖励判定为整局维度统计 4. 重构胜利/失败奖励计算逻辑,适配多回合关卡 --- assets/script/game/common/config/GameSet.ts | 28 ++++++------ assets/script/game/map/MissionComp.ts | 29 +++++++----- assets/script/game/map/MissionMonComp.ts | 32 ++++++-------- assets/script/game/map/VictoryComp.ts | 49 ++++++++++++++------- 4 files changed, 79 insertions(+), 59 deletions(-) diff --git a/assets/script/game/common/config/GameSet.ts b/assets/script/game/common/config/GameSet.ts index e4296355..45aacfa9 100644 --- a/assets/script/game/common/config/GameSet.ts +++ b/assets/script/game/common/config/GameSet.ts @@ -219,38 +219,40 @@ export function getLvColor(lv: number): Color { export const RARITY_BG_COLORS: string[] = ["green", "blue", "purple", "yellow", "red"]; /** - * 战斗结算奖励配置(每节结束后发放:英雄碎片 + 金币 + 天赋经验)。 - * 胜利奖励随关卡进度(怪物等级 = 大关号)线性增长,整体乘以 victory_multiplier。 + * 战斗结算奖励配置(一关 5 回合累计,整关结算时一次性发放:英雄碎片 + 金币 + 天赋经验)。 + * 通关奖励 = 每回合奖励 × 胜利回合数(wave_win_count),全员存活回合再 × per_wave 加成。 + * Why: 一关一局 5 回合后,通关不再是单场,改按"5 回合累计"发放, + * 每回合基础奖励随关卡进度(怪物等级 = 大关号)增长。 */ export const VICTORY_REWARD_SET = { - /** 金币:base + monLv × per_lv */ + /** 金币:每回合 base + monLv × per_lv,× 胜利回合数累计 */ gold: { base: 100, per_lv: 50 }, - /** 天赋经验:base + monLv × per_lv */ + /** 天赋经验:每回合 base + monLv × per_lv,× 胜利回合数累计 */ talent_exp: { base: 20, per_lv: 10 }, - /** 每个出场英雄保底碎片数 */ + /** 每个出场英雄保底碎片数(× 胜利回合数累计) */ played_fragment: 1, - /** 随机英雄碎片数量(发放给随机抽中的 1 个英雄) */ + /** 随机英雄碎片数量(发放给随机抽中的 1 个英雄,× 胜利回合数累计) */ random_fragment: 2, - /** 银币:固定发放,不分输赢 */ + /** 银币:固定发放,不分输赢(通关时 × 胜利回合数累计) */ silver: 3, - /** 钻石:按输赢在 [min, max] 闭区间内随机(不参与 victory_multiplier) */ + /** 钻石:按输赢在 [min, max] 闭区间内随机(通关时 × 胜利回合数累计,不参与 per_wave 加成) */ diamond: { victory: { min: 5, max: 10 }, defeat: { min: 1, max: 3 }, }, - /** 胜利加成系数(失败 1.0) */ - victory_multiplier: 1.2, + /** 全员存活回合加成系数:每个全员存活回合额外 +20%(乘法叠乘于累计基础之上) */ + per_wave_bonus: 0.2, /** * 失败奖励限制(不影响胜利): - * 坚持 < defeat_time_threshold 秒或本节一只怪未杀 → 无任何奖励; - * 达标后 = 坚持奖励(固定)+ 击杀比例奖励(基数 × 本节击杀比例) + * 坚持 < defeat_time_threshold 秒或本回合一只怪未杀 → 无任何奖励; + * 达标后 = 坚持奖励(固定)+ 击杀比例奖励(基数 × 本回合击杀比例) */ defeat: { /** 坚持时间门槛(秒) */ time_threshold: 15, /** 超门槛固定奖励("超 15 秒奖励") */ survive: { gold: 50, talent_exp: 10 }, - /** 击杀比例奖励基数(本节怪物全灭时全额发放) */ + /** 击杀比例奖励基数(本回合怪物全灭时全额发放) */ kill: { gold: 150, talent_exp: 30 }, }, }; diff --git a/assets/script/game/map/MissionComp.ts b/assets/script/game/map/MissionComp.ts index 6c5d3320..7a04a8d0 100644 --- a/assets/script/game/map/MissionComp.ts +++ b/assets/script/game/map/MissionComp.ts @@ -629,11 +629,8 @@ export class MissionComp extends CCComp { settle_title: this._settleTitle, // 关卡难度度量(= 大关号,驱动胜利奖励随进度增长) mon_lv: getMonsterLv(this.currentStage), - // 失败奖励判定数据:本关坚持时间 + 击杀统计(kills=已刷出-存活) - defeat_stats: { - clear_time: this.clearTime, - ...this.querySectionKillStats(), - }, + // 失败奖励判定数据:整局坚持时间 + 整局击杀统计(kills=跨回合累计击杀,total=整局已刷出) + defeat_stats: this.queryMissionKillStats(), }); } else { // play 已为 false(fight_end 强制结束)→ 直接清理 @@ -838,6 +835,8 @@ export class MissionComp extends CCComp { // 重置所有的战局得分数据,防止上一局的数据污染 smc.resetScores(); + // 重置整局刷怪统计(跨回合累计的已刷出总数),回合间不重置 + ecs.query(ecs.allOf(MissionMonCompComp)).forEach(e => e.get(MissionMonCompComp).resetMissionStats()); smc.vmdata.mission_data.coin = Math.max(0, Math.floor(FightSet.INIT_COIN)); // 银币为局外持久货币,每局不重置,仅通过日常积攒,无需初始化 @@ -1023,14 +1022,24 @@ export class MissionComp extends CCComp { } /** - * 查询本节击杀统计(转交结算弹窗做失败奖励判定) - * @returns { kills, total },查询不到 MissionMonComp 时返回 0/0 + * 查询整局(整关 5 回合)击杀统计与坚持时间,供结算弹窗做失败奖励判定。 + * Why: 一关一局后,失败奖励按整局累计口径计算,不再只看当前回合—— + * clear_time 用整关战斗总时长 fight_time(跨回合累计,整关重置一次); + * kills 用 scores 跨回合累计的分类击杀数之和(melee/remote/elite/boss); + * total 用整局已刷出怪物总数(MissionMonComp 跨回合累计)。 */ - private querySectionKillStats(): { kills: number; total: number } { + private queryMissionKillStats(): { clear_time: number; kills: number; total: number } { + const s = smc.vmdata.scores; + const kills = (s.melee_kill_count || 0) + (s.remote_kill_count || 0) + + (s.elite_kill_count || 0) + (s.boss_kill_count || 0); const entities = ecs.query(ecs.allOf(MissionMonCompComp)); const comp = entities[0]?.get(MissionMonCompComp); - if (!comp) return { kills: 0, total: 0 }; - return comp.getSectionKillStats(); + const total = comp ? comp.getTotalSpawnedCount() : kills; + return { + clear_time: smc.vmdata.mission_data.fight_time, + kills, + total: Math.max(total, kills), + }; } /** diff --git a/assets/script/game/map/MissionMonComp.ts b/assets/script/game/map/MissionMonComp.ts index 2fb59a8d..f8cd6be3 100644 --- a/assets/script/game/map/MissionMonComp.ts +++ b/assets/script/game/map/MissionMonComp.ts @@ -85,6 +85,8 @@ export class MissionMonCompComp extends CCComp { private sectionTargetCount: number = 0; /** 当前小节已刷出怪物数量 */ private sectionSpawnedCount: number = 0; + /** 整局(整关 5 回合)累计刷出怪物总数:跨回合累计,仅在整关重置(由 MissionComp.data_init 触发) */ + private missionSpawnedCount: number = 0; /** 待刷出队列(batch=1 的怪物) */ private pendingQueue: GeneratedMonster[] = []; /** 刷怪定时器已累积时间 */ @@ -241,6 +243,17 @@ export class MissionMonCompComp extends CCComp { } this.sectionSpawnedCount++; + this.missionSpawnedCount++; + } + + /** 整局累计已刷出怪物总数(跨回合,整关重置):供 MissionComp 整局击杀统计 */ + public getTotalSpawnedCount(): number { + return this.missionSpawnedCount; + } + + /** 整关重置刷怪统计(由 MissionComp.data_init 在整关开始/重开时调用,回合间不重置) */ + public resetMissionStats(): void { + this.missionSpawnedCount = 0; } /** @@ -257,25 +270,6 @@ export class MissionMonCompComp extends CCComp { return FightSet.MON_ROW_BACK_X; } - /** - * 本节击杀统计(结算奖励消费): - * kills = 已刷出数 - 场上存活数(实时查询,不依赖 MissionComp 的 0.2s 降频同步) - * total = 本节目标怪物总数(含未刷出) - */ - public getSectionKillStats(): { kills: number; total: number } { - let alive = 0; - ecs.query(ecs.allOf(HeroAttrsComp)).forEach(entity => { - const attrs = entity.get(HeroAttrsComp); - if (attrs && !attrs.is_dead && attrs.fac === FacSet.MON) { - alive++; - } - }); - return { - kills: Math.max(0, this.sectionSpawnedCount - alive), - total: this.sectionTargetCount, - }; - } - /** ECS 组件移除时触发 */ reset() { } } diff --git a/assets/script/game/map/VictoryComp.ts b/assets/script/game/map/VictoryComp.ts index dbe4f11b..ff29987a 100644 --- a/assets/script/game/map/VictoryComp.ts +++ b/assets/script/game/map/VictoryComp.ts @@ -177,7 +177,7 @@ export class VictoryComp extends CCComp { /** 是否胜利场(驱动主按钮文案与行为) */ private isVictory: boolean = false; - /** 失败奖励判定数据(MissionComp 传入:本节坚持时间 + 击杀统计) */ + /** 失败奖励判定数据(MissionComp 传入:整局坚持时间 + 整局击杀统计) */ private defeatStats: { clear_time: number; kills: number; total: number } | null = null; /** 本节怪物等级(= 大关号,驱动胜利奖励随关卡进度增长) */ @@ -585,12 +585,14 @@ export class VictoryComp extends CCComp { /** * 计算并发放结算奖励,结果存入 _rewardResult。 - * 胜利:随关卡进度增长(base + monLv × per_lv,整体 ×1.2)。 - * Why: 无回合概念后 wave_win_count 每节被 resetScores 清零恒为 1, - * 旧的 per_wave 增长失效,改挂怪物等级(= 大关号)随关卡推进线性增长。 + * 通关:按 5 回合累计发放—— + * 每回合基础奖励(随 monLv 增长)× 胜利回合数(wave_win_count), + * 再按全员存活回合数 × per_wave_bonus 加成。 + * Why: 一关一局 5 回合后,通关奖励不再是单场,改按"5 回合累计"发放; + * scores 统计在整关间保留(data_init 仅在整关开始/重开重置),故可直接累计。 * 失败:受"坚持时间 + 击杀比例"限制—— - * - 坚持 < 15s 或本节一只怪未杀 → 无任何奖励; - * - 达标 → 坚持奖励(固定)+ 击杀比例奖励(基数 × 本节击杀比例), + * - 坚持 < 15s 或本回合一只怪未杀 → 无任何奖励; + * - 达标 → 坚持奖励(固定)+ 击杀比例奖励(基数 × 本回合击杀比例), * 银币/钻石/碎片同样受此门槛限制(不达标不发,达标正常发放)。 * @param isVictory 是否胜利 */ @@ -609,13 +611,19 @@ export class VictoryComp extends CCComp { killRatio = total > 0 ? Math.min(1, kills / total) : 0; } + // 5 回合累计:胜利回合数与全员存活回合数(scores 跨回合累计,整关重置) + const winWaves = Math.max(1, Math.floor(smc.vmdata.scores.wave_win_count || 0)); + const aliveWaves = Math.max(0, Math.floor(smc.vmdata.scores.wave_all_alive_count || 0)); + // 全员存活回合加成:每个全员存活回合额外 +per_wave_bonus(线性叠加于累计基础) + const aliveBonus = 1 + aliveWaves * cfg.per_wave_bonus; + // 1. 金币 / 天赋经验 let gold: number; let talentExp: number; if (isVictory) { - const mult = cfg.victory_multiplier; - gold = Math.floor((cfg.gold.base + this.monLv * cfg.gold.per_lv) * mult); - talentExp = Math.floor((cfg.talent_exp.base + this.monLv * cfg.talent_exp.per_lv) * mult); + // 通关:每回合基础 × 胜利回合数 × 全员存活加成 + gold = Math.floor((cfg.gold.base + this.monLv * cfg.gold.per_lv) * winWaves * aliveBonus); + talentExp = Math.floor((cfg.talent_exp.base + this.monLv * cfg.talent_exp.per_lv) * winWaves * aliveBonus); } else if (defeatQualified) { // 失败达标 = 坚持奖励(固定)+ 击杀比例奖励 gold = Math.floor(cfg.defeat.survive.gold + cfg.defeat.kill.gold * killRatio); @@ -625,25 +633,31 @@ export class VictoryComp extends CCComp { talentExp = 0; } - // 1.5 银币固定发放;钻石按输赢在 [min, max] 内随机(失败不达标时同样清零) - const silver = !isVictory && !defeatQualified ? 0 : cfg.silver; + // 1.5 银币 / 钻石:通关按胜利回合数累计;失败不达标清零 + const silverBase = !isVictory && !defeatQualified ? 0 : cfg.silver; + const silver = isVictory ? Math.floor(silverBase * winWaves) : silverBase; let diamond = 0; - if (isVictory || defeatQualified) { - const dRange = isVictory ? cfg.diamond.victory : cfg.diamond.defeat; + if (isVictory) { + const dRange = cfg.diamond.victory; + const perWave = dRange.min + Math.floor(Math.random() * (dRange.max - dRange.min + 1)); + diamond = perWave * winWaves; + } else if (defeatQualified) { + const dRange = cfg.diamond.defeat; diamond = dRange.min + Math.floor(Math.random() * (dRange.max - dRange.min + 1)); } - // 2. 英雄碎片:出场英雄各保底 + 1 个随机英雄(失败不达标时不发) + // 2. 英雄碎片:出场英雄各保底 + 1 个随机英雄(通关 × 胜利回合数累计;失败不达标时不发) const fragments: FragmentReward[] = []; if (isVictory || defeatQualified) { const playedUuids = this.collectPlayedHeroUuids(); const playedSet = new Set(playedUuids); + const fragMult = isVictory ? winWaves : 1; for (const uuid of playedUuids) { - fragments.push({ uuid, count: cfg.played_fragment, isPlayed: true }); + fragments.push({ uuid, count: cfg.played_fragment * fragMult, isPlayed: true }); } const randomUuid = this.pickRandomHeroUuid(playedSet); if (randomUuid > 0) { - fragments.push({ uuid: randomUuid, count: cfg.random_fragment, isPlayed: false }); + fragments.push({ uuid: randomUuid, count: cfg.random_fragment * fragMult, isPlayed: false }); } } @@ -667,7 +681,8 @@ export class VictoryComp extends CCComp { this._rewardResult = { gold, talentExp, silver, diamond, fragments }; mLogger.log(this.debugMode, 'VictoryComp', '[VictoryComp] 结算奖励发放', { - isVictory, monLv: this.monLv, gold, talentExp, silver, diamond, fragments, + isVictory, monLv: this.monLv, winWaves, aliveWaves, aliveBonus, + gold, talentExp, silver, diamond, fragments, defeatStats: this.defeatStats, defeatQualified, killRatio, }); }