feat(victory): add defeat reward gate by clear time and kill ratio
Defeat rewards now require surviving 15s and killing at least one monster. Qualified defeats grant a fixed survive reward plus a kill-ratio-scaled bonus; unqualified defeats yield nothing. Victory rewards remain unchanged.
This commit is contained in:
@@ -240,4 +240,17 @@ export const VICTORY_REWARD_SET = {
|
||||
},
|
||||
/** 胜利加成系数(失败 1.0) */
|
||||
victory_multiplier: 1.2,
|
||||
/**
|
||||
* 失败奖励限制(不影响胜利):
|
||||
* 坚持 < defeat_time_threshold 秒或本节一只怪未杀 → 无任何奖励;
|
||||
* 达标后 = 坚持奖励(固定)+ 击杀比例奖励(基数 × 本节击杀比例)
|
||||
*/
|
||||
defeat: {
|
||||
/** 坚持时间门槛(秒) */
|
||||
time_threshold: 15,
|
||||
/** 超门槛固定奖励("超 15 秒奖励") */
|
||||
survive: { gold: 50, talent_exp: 10 },
|
||||
/** 击杀比例奖励基数(本节怪物全灭时全额发放) */
|
||||
kill: { gold: 150, talent_exp: 30 },
|
||||
},
|
||||
};
|
||||
|
||||
@@ -46,6 +46,7 @@ import { Timer } from "db://oops-framework/core/common/timer/Timer";
|
||||
import { FieldSkillType } from "../common/config/SkillSet";
|
||||
import { FieldSkillHelper } from "../hero/FieldSkillHelper";
|
||||
import { spawningEngine, WAVE_DURATION, BATTLE_TIMEOUT } from "./RogueConfig";
|
||||
import { MissionMonCompComp } from "./MissionMonComp";
|
||||
import {
|
||||
getProgressText,
|
||||
} from "../common/config/StageSet";
|
||||
@@ -637,6 +638,11 @@ export class MissionComp extends CCComp {
|
||||
// 结算类型:小节胜利显示"下一节",末关通关显示"确认退出" + 通关标题
|
||||
settle_type: this._settleType,
|
||||
settle_title: this._settleTitle,
|
||||
// 失败奖励判定数据:本节坚持时间 + 击杀统计(kills=已刷出-存活)
|
||||
defeat_stats: {
|
||||
clear_time: this.clearTime,
|
||||
...this.querySectionKillStats(),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// 如果 play 已经是 false,说明是通过 fight_end 进来的
|
||||
@@ -1080,6 +1086,17 @@ export class MissionComp extends CCComp {
|
||||
return hasHero && smc.mission.dead_heroes.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询本节击杀统计(转交结算弹窗做失败奖励判定)
|
||||
* @returns { kills, total },查询不到 MissionMonComp 时返回 0/0
|
||||
*/
|
||||
private querySectionKillStats(): { kills: number; total: number } {
|
||||
const entities = ecs.query(ecs.allOf(MissionMonCompComp));
|
||||
const comp = entities[0]?.get(MissionMonCompComp);
|
||||
if (!comp) return { kills: 0, total: 0 };
|
||||
return comp.getSectionKillStats();
|
||||
}
|
||||
|
||||
/**
|
||||
* 英雄全灭检测:若场上无存活英雄且处于战斗中,预约延迟结算(等待死亡动画播完再弹窗)。
|
||||
* @param heroCount 当前存活英雄数量
|
||||
|
||||
@@ -22,7 +22,7 @@ import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/O
|
||||
import { Monster } from "../hero/Mon";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
import { BoxSet, FightSet } from "../common/config/GameSet";
|
||||
import { BoxSet, FacSet, FightSet } from "../common/config/GameSet";
|
||||
import { MonType } from "../common/config/heroSet";
|
||||
import { spawningEngine, GeneratedMonster, TestModeConfig } from "./RogueConfig";
|
||||
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||
@@ -254,6 +254,25 @@ 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() { }
|
||||
}
|
||||
|
||||
@@ -177,6 +177,9 @@ export class VictoryComp extends CCComp {
|
||||
/** 是否胜利场(驱动主按钮文案与行为) */
|
||||
private isVictory: boolean = false;
|
||||
|
||||
/** 失败奖励判定数据(MissionComp 传入:本节坚持时间 + 击杀统计) */
|
||||
private defeatStats: { clear_time: number; kills: number; total: number } | null = null;
|
||||
|
||||
/** 加载时隐藏 loading 遮罩并绑定按钮事件 */
|
||||
protected onLoad(): void {
|
||||
this.node.getChildByName("loading").active = false
|
||||
@@ -221,6 +224,7 @@ export class VictoryComp extends CCComp {
|
||||
// 结算类型与胜负状态:缺省按失败处理
|
||||
this.settleType = args.settle_type ?? "defeat";
|
||||
this.isVictory = !!args.victory;
|
||||
this.defeatStats = args.defeat_stats ?? null;
|
||||
|
||||
// 根据是否可复活决定按钮显示
|
||||
this.node.getChildByName("btns").getChildByName("next").active = !args.can_revive
|
||||
@@ -604,34 +608,66 @@ export class VictoryComp extends CCComp {
|
||||
|
||||
/**
|
||||
* 计算并发放结算奖励,结果存入 _rewardResult。
|
||||
* @param isVictory 是否胜利(胜利整体 ×1.2)
|
||||
* 胜利:原公式不变(base + wave × per_wave,整体 ×1.2)。
|
||||
* 失败:受"坚持时间 + 击杀比例"限制——
|
||||
* - 坚持 < 15s 或本节一只怪未杀 → 无任何奖励;
|
||||
* - 达标 → 坚持奖励(固定)+ 击杀比例奖励(基数 × 本节击杀比例),
|
||||
* 银币/钻石/碎片同样受此门槛限制(不达标不发,达标正常发放)。
|
||||
* @param isVictory 是否胜利
|
||||
*/
|
||||
private grantRewards(isVictory: boolean) {
|
||||
const s = smc.vmdata.scores;
|
||||
const cfg = VICTORY_REWARD_SET;
|
||||
const mult = isVictory ? cfg.victory_multiplier : 1.0;
|
||||
const wave = Math.max(0, s.wave_win_count || 0);
|
||||
|
||||
// 1. 金币 / 天赋经验(按波次计算,胜利加成)
|
||||
const gold = Math.floor((cfg.gold.base + wave * cfg.gold.per_wave) * mult);
|
||||
const talentExp = Math.floor((cfg.talent_exp.base + wave * cfg.talent_exp.per_wave) * mult);
|
||||
// 失败奖励资格判定:坚持时间与击杀数不达标则整场无奖励
|
||||
let defeatQualified = false;
|
||||
let killRatio = 0;
|
||||
if (!isVictory) {
|
||||
const stats = this.defeatStats;
|
||||
const clearTime = stats?.clear_time ?? 0;
|
||||
const kills = stats?.kills ?? 0;
|
||||
const total = stats?.total ?? 0;
|
||||
defeatQualified = clearTime >= cfg.defeat.time_threshold && kills > 0;
|
||||
killRatio = total > 0 ? Math.min(1, kills / total) : 0;
|
||||
}
|
||||
|
||||
// 1.5 银币固定发放;钻石按输赢在 [min, max] 内随机(不吃胜利加成,避免与固定区间重复放大)
|
||||
const silver = cfg.silver;
|
||||
const dRange = isVictory ? cfg.diamond.victory : cfg.diamond.defeat;
|
||||
const diamond = dRange.min + Math.floor(Math.random() * (dRange.max - dRange.min + 1));
|
||||
// 1. 金币 / 天赋经验
|
||||
let gold: number;
|
||||
let talentExp: number;
|
||||
if (isVictory) {
|
||||
const mult = cfg.victory_multiplier;
|
||||
gold = Math.floor((cfg.gold.base + wave * cfg.gold.per_wave) * mult);
|
||||
talentExp = Math.floor((cfg.talent_exp.base + wave * cfg.talent_exp.per_wave) * mult);
|
||||
} else if (defeatQualified) {
|
||||
// 失败达标 = 坚持奖励(固定)+ 击杀比例奖励
|
||||
gold = Math.floor(cfg.defeat.survive.gold + cfg.defeat.kill.gold * killRatio);
|
||||
talentExp = Math.floor(cfg.defeat.survive.talent_exp + cfg.defeat.kill.talent_exp * killRatio);
|
||||
} else {
|
||||
gold = 0;
|
||||
talentExp = 0;
|
||||
}
|
||||
|
||||
// 2. 英雄碎片:出场英雄各保底 + 1 个随机英雄
|
||||
const playedUuids = this.collectPlayedHeroUuids();
|
||||
const playedSet = new Set(playedUuids);
|
||||
const fragments: FragmentReward[] = playedUuids.map(uuid => ({
|
||||
uuid,
|
||||
count: cfg.played_fragment,
|
||||
isPlayed: true,
|
||||
}));
|
||||
const randomUuid = this.pickRandomHeroUuid(playedSet);
|
||||
if (randomUuid > 0) {
|
||||
fragments.push({ uuid: randomUuid, count: cfg.random_fragment, isPlayed: false });
|
||||
// 1.5 银币固定发放;钻石按输赢在 [min, max] 内随机(失败不达标时同样清零)
|
||||
const silver = !isVictory && !defeatQualified ? 0 : cfg.silver;
|
||||
let diamond = 0;
|
||||
if (isVictory || defeatQualified) {
|
||||
const dRange = isVictory ? cfg.diamond.victory : cfg.diamond.defeat;
|
||||
diamond = dRange.min + Math.floor(Math.random() * (dRange.max - dRange.min + 1));
|
||||
}
|
||||
|
||||
// 2. 英雄碎片:出场英雄各保底 + 1 个随机英雄(失败不达标时不发)
|
||||
const fragments: FragmentReward[] = [];
|
||||
if (isVictory || defeatQualified) {
|
||||
const playedUuids = this.collectPlayedHeroUuids();
|
||||
const playedSet = new Set(playedUuids);
|
||||
for (const uuid of playedUuids) {
|
||||
fragments.push({ uuid, count: cfg.played_fragment, isPlayed: true });
|
||||
}
|
||||
const randomUuid = this.pickRandomHeroUuid(playedSet);
|
||||
if (randomUuid > 0) {
|
||||
fragments.push({ uuid: randomUuid, count: cfg.random_fragment, isPlayed: false });
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 落地发放
|
||||
@@ -655,6 +691,7 @@ export class VictoryComp extends CCComp {
|
||||
|
||||
mLogger.log(this.debugMode, 'VictoryComp', '[VictoryComp] 结算奖励发放', {
|
||||
isVictory, wave, gold, talentExp, silver, diamond, fragments,
|
||||
defeatStats: this.defeatStats, defeatQualified, killRatio,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user