feat: 添加段位和闯关奖励领取系统
1. 新增REWARD_CLAIMED游戏事件用于通知奖励领取完成 2. 新建RewardSet配置文件,定义段位和闯关奖励数据结构与具体配置 3. 在SingletonModuleComp中新增奖励领取相关逻辑: - 新增奖励领取状态存档字段 - 实现可领取奖励查询、奖励领取、通关标记等方法 - 新增统一的奖励发放工具方法
This commit is contained in:
@@ -10,6 +10,8 @@ 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, getTalentUpgradeCost, isTalentTierUnlocked } from "./config/TalentSet";
|
||||||
|
import { ScoreRankList, ScoreTier, getRankByScore } from "./config/ScoreRankSet";
|
||||||
|
import { RANK_REWARDS, STAGE_REWARDS, RewardItem, pickRandomHeroUuid } from "./config/RewardSet";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用远程数据覆盖本地数据(统一方法)
|
* 用远程数据覆盖本地数据(统一方法)
|
||||||
@@ -52,6 +54,15 @@ export interface GameDate {
|
|||||||
stage: number,
|
stage: number,
|
||||||
section: number,
|
section: number,
|
||||||
},
|
},
|
||||||
|
/** 奖励领取状态:段位/闯关首达奖励的已领取标记(玩家点击领取) */
|
||||||
|
reward_claimed?: {
|
||||||
|
/** 已领取的段位(ScoreTier 值) */
|
||||||
|
rank: string[],
|
||||||
|
/** 已领取的大关号 */
|
||||||
|
stage: number[],
|
||||||
|
},
|
||||||
|
/** 已通关大关的显式标记(覆盖存档停留场景,如第 10 关通关后进度停留 1-10-10) */
|
||||||
|
passed_stages?: number[],
|
||||||
}
|
}
|
||||||
export interface CloudData {
|
export interface CloudData {
|
||||||
openid: string;
|
openid: string;
|
||||||
@@ -157,6 +168,13 @@ export class SingletonModuleComp extends ecs.Comp {
|
|||||||
stage: 1,
|
stage: 1,
|
||||||
section: 1,
|
section: 1,
|
||||||
},
|
},
|
||||||
|
/** 奖励领取状态(玩家点击领取,跨局保留) */
|
||||||
|
reward_claimed: {
|
||||||
|
rank: [],
|
||||||
|
stage: [],
|
||||||
|
},
|
||||||
|
/** 已通关大关的显式标记 */
|
||||||
|
passed_stages: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
vmdata: any = {
|
vmdata: any = {
|
||||||
@@ -370,6 +388,129 @@ export class SingletonModuleComp extends ecs.Comp {
|
|||||||
}
|
}
|
||||||
oops.message.dispatchEvent(GameEvent.STAGE_PROGRESS_UPDATE);
|
oops.message.dispatchEvent(GameEvent.STAGE_PROGRESS_UPDATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 奖励领取(段位 / 闯关) ====================
|
||||||
|
|
||||||
|
/** 查询当前已达、未领取的段位列表(升序;跨档时可连领多份) */
|
||||||
|
public getClaimableRankRewards(): ScoreTier[] {
|
||||||
|
const current = getRankByScore(this.data.score ?? 0);
|
||||||
|
const curIdx = ScoreRankList.findIndex(r => r.tier === current.tier);
|
||||||
|
const claimed = this.data.reward_claimed?.rank ?? [];
|
||||||
|
const result: ScoreTier[] = [];
|
||||||
|
// 段位只涨不跌:当前段位即历史最高,低位段位全部视为已达
|
||||||
|
for (let i = 0; i <= curIdx; i++) {
|
||||||
|
const tier = ScoreRankList[i].tier;
|
||||||
|
if (!claimed.includes(tier)) {
|
||||||
|
result.push(tier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询已通关、未领取的大关号列表(升序) */
|
||||||
|
public getClaimableStageRewards(): number[] {
|
||||||
|
const claimed = this.data.reward_claimed?.stage ?? [];
|
||||||
|
const result: number[] = [];
|
||||||
|
for (const stage of Object.keys(STAGE_REWARDS).map(Number)) {
|
||||||
|
if (!claimed.includes(stage) && this.isStagePassed(stage)) {
|
||||||
|
result.push(stage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.sort((a, b) => a - b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 是否存在待领取奖励(UI 红点总开关) */
|
||||||
|
public hasUnclaimedRewards(): boolean {
|
||||||
|
return this.getClaimableRankRewards().length > 0
|
||||||
|
|| this.getClaimableStageRewards().length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 领取段位奖励(玩家点击触发)
|
||||||
|
* @param tier 目标段位
|
||||||
|
* @returns 实际发放的奖励列表;段位未达/已领取/配置缺失时返回 null
|
||||||
|
*/
|
||||||
|
public claimRankReward(tier: ScoreTier): RewardItem[] | null {
|
||||||
|
if (!this.getClaimableRankRewards().includes(tier)) return null;
|
||||||
|
const rewards = RANK_REWARDS[tier];
|
||||||
|
if (!rewards || rewards.length === 0) return null;
|
||||||
|
|
||||||
|
this.grantRewardItems(rewards);
|
||||||
|
if (!this.data.reward_claimed) this.data.reward_claimed = { rank: [], stage: [] };
|
||||||
|
this.data.reward_claimed.rank.push(tier);
|
||||||
|
gameDataSync.markDataDirty();
|
||||||
|
oops.message.dispatchEvent(GameEvent.REWARD_CLAIMED);
|
||||||
|
return rewards;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 领取闯关奖励(玩家点击触发)
|
||||||
|
* @param stage 目标大关号(1~10)
|
||||||
|
* @returns 实际发放的奖励列表;未通关/已领取/配置缺失时返回 null
|
||||||
|
*/
|
||||||
|
public claimStageReward(stage: number): RewardItem[] | null {
|
||||||
|
if (!this.getClaimableStageRewards().includes(stage)) return null;
|
||||||
|
const rewards = STAGE_REWARDS[stage];
|
||||||
|
if (!rewards || rewards.length === 0) return null;
|
||||||
|
|
||||||
|
this.grantRewardItems(rewards);
|
||||||
|
if (!this.data.reward_claimed) this.data.reward_claimed = { rank: [], stage: [] };
|
||||||
|
this.data.reward_claimed.stage.push(stage);
|
||||||
|
gameDataSync.markDataDirty();
|
||||||
|
oops.message.dispatchEvent(GameEvent.REWARD_CLAIMED);
|
||||||
|
return rewards;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记大关已通关(通关点调用,覆盖存档停留场景)
|
||||||
|
* Why: 第 10 关通关后存档停留 1-10-10,无法从 stage_progress 推导已通状态,
|
||||||
|
* 需在通关时显式写入。
|
||||||
|
* @param stage 大关号
|
||||||
|
*/
|
||||||
|
public markStagePassed(stage: number): void {
|
||||||
|
if (!this.data.passed_stages) this.data.passed_stages = [];
|
||||||
|
if (!this.data.passed_stages.includes(stage)) {
|
||||||
|
this.data.passed_stages.push(stage);
|
||||||
|
gameDataSync.markDataDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 判定大关是否已通关:显式标记优先,存档推进兜底(已进入后续关即已通关) */
|
||||||
|
private isStagePassed(stage: number): boolean {
|
||||||
|
if ((this.data.passed_stages ?? []).includes(stage)) return true;
|
||||||
|
return (this.data.stage_progress?.stage ?? 1) > stage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一发放奖励条目(复用现有货币/成长入口,自动落 data 持久层并标脏)
|
||||||
|
* @param items 奖励条目列表
|
||||||
|
*/
|
||||||
|
private grantRewardItems(items: RewardItem[]): void {
|
||||||
|
for (const item of items) {
|
||||||
|
switch (item.type) {
|
||||||
|
case 'gold':
|
||||||
|
if (item.value > 0) this.updateGold(item.value);
|
||||||
|
break;
|
||||||
|
case 'silver':
|
||||||
|
if (item.value > 0) this.updateSilver(item.value);
|
||||||
|
break;
|
||||||
|
case 'diamond':
|
||||||
|
if (item.value > 0) this.updateDiamond(item.value);
|
||||||
|
break;
|
||||||
|
case 'talent_exp':
|
||||||
|
if (item.value > 0) this.addTalentExp(item.value);
|
||||||
|
break;
|
||||||
|
case 'fragment': {
|
||||||
|
const uuid = item.heroUuid ?? pickRandomHeroUuid();
|
||||||
|
if (item.value > 0 && uuid > 0) {
|
||||||
|
this.addHeroFragments(uuid, item.value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getGameDate() {
|
getGameDate() {
|
||||||
let data: GameDate = {
|
let data: GameDate = {
|
||||||
...this.data,
|
...this.data,
|
||||||
|
|||||||
@@ -102,6 +102,8 @@ export enum GameEvent {
|
|||||||
TALENT_UPDATE = "TALENT_UPDATE",
|
TALENT_UPDATE = "TALENT_UPDATE",
|
||||||
/** 关卡进度变更(payload 为空,UI 重新读取 smc.vmdata.stage_progress) */
|
/** 关卡进度变更(payload 为空,UI 重新读取 smc.vmdata.stage_progress) */
|
||||||
STAGE_PROGRESS_UPDATE = "STAGE_PROGRESS_UPDATE",
|
STAGE_PROGRESS_UPDATE = "STAGE_PROGRESS_UPDATE",
|
||||||
|
/** 奖励领取完成(payload 为空,UI 刷新待领取列表/红点) */
|
||||||
|
REWARD_CLAIMED = "REWARD_CLAIMED",
|
||||||
/** 小节开始(payload: { chapter, stage, section },MissionMonComp 据此生成怪物) */
|
/** 小节开始(payload: { chapter, stage, section },MissionMonComp 据此生成怪物) */
|
||||||
SectionStart = "SectionStart",
|
SectionStart = "SectionStart",
|
||||||
/** 小节结束(payload: { chapter, stage, section },玩家点击"下一节"后推进) */
|
/** 小节结束(payload: { chapter, stage, section },玩家点击"下一节"后推进) */
|
||||||
|
|||||||
76
assets/script/game/common/config/RewardSet.ts
Normal file
76
assets/script/game/common/config/RewardSet.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* @file RewardSet.ts
|
||||||
|
* @description 段位奖励 + 闯关奖励配置(纯数据,无逻辑)
|
||||||
|
*
|
||||||
|
* 定位差异化:
|
||||||
|
* - 段位奖励(RANK_REWARDS):稀有向——钻石 + 英雄碎片,荣誉回报
|
||||||
|
* - 闯关奖励(STAGE_REWARDS):养成向——金币 + 天赋经验,进程回报(精英关/Boss 关加成)
|
||||||
|
*
|
||||||
|
* 领取模式:玩家点击领取(SMC.claimRankReward / claimStageReward),
|
||||||
|
* 状态持久化于 smc.data.reward_claimed,不领不丢、跨局保留。
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { HeroInfo } from "./heroSet";
|
||||||
|
import { FacSet } from "./GameSet";
|
||||||
|
import { ScoreTier } from "./ScoreRankSet";
|
||||||
|
|
||||||
|
/** 奖励资源类型 */
|
||||||
|
export type RewardType = 'gold' | 'silver' | 'diamond' | 'talent_exp' | 'fragment';
|
||||||
|
|
||||||
|
/** 单条奖励 */
|
||||||
|
export interface RewardItem {
|
||||||
|
/** 资源类型 */
|
||||||
|
type: RewardType;
|
||||||
|
/** 数量 */
|
||||||
|
value: number;
|
||||||
|
/** 英雄碎片专用:指定英雄 uuid,缺省随机抽取 */
|
||||||
|
heroUuid?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 段位首达奖励(每档一份,跨档可补领)
|
||||||
|
* 阈值与段位定义见 ScoreRankSet.ScoreRankList
|
||||||
|
*/
|
||||||
|
export const RANK_REWARDS: Record<ScoreTier, RewardItem[]> = {
|
||||||
|
[ScoreTier.BRONZE]: [{ type: 'diamond', value: 10 }],
|
||||||
|
[ScoreTier.SILVER]: [{ type: 'diamond', value: 20 }, { type: 'fragment', value: 3 }],
|
||||||
|
[ScoreTier.GOLD]: [{ type: 'diamond', value: 30 }, { type: 'fragment', value: 5 }],
|
||||||
|
[ScoreTier.PLATINUM]: [{ type: 'diamond', value: 50 }, { type: 'fragment', value: 8 }],
|
||||||
|
[ScoreTier.DIAMOND]: [{ type: 'diamond', value: 80 }, { type: 'fragment', value: 10 }],
|
||||||
|
[ScoreTier.MASTER]: [{ type: 'diamond', value: 120 }, { type: 'fragment', value: 15 }],
|
||||||
|
[ScoreTier.LEGEND]: [{ type: 'diamond', value: 200 }, { type: 'fragment', value: 20 }],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 闯关奖励(每大关首通一份,共 10 份)
|
||||||
|
* 养成向:金币 + 天赋经验;第 5 关(精英)/第 10 关(Boss)额外附赠银币
|
||||||
|
*/
|
||||||
|
export const STAGE_REWARDS: Record<number, RewardItem[]> = {
|
||||||
|
1: [{ type: 'gold', value: 200 }, { type: 'talent_exp', value: 20 }],
|
||||||
|
2: [{ type: 'gold', value: 300 }, { type: 'talent_exp', value: 30 }],
|
||||||
|
3: [{ type: 'gold', value: 400 }, { type: 'talent_exp', value: 40 }],
|
||||||
|
4: [{ type: 'gold', value: 500 }, { type: 'talent_exp', value: 50 }],
|
||||||
|
5: [{ type: 'gold', value: 700 }, { type: 'talent_exp', value: 70 }, { type: 'silver', value: 10 }],
|
||||||
|
6: [{ type: 'gold', value: 600 }, { type: 'talent_exp', value: 60 }],
|
||||||
|
7: [{ type: 'gold', value: 700 }, { type: 'talent_exp', value: 70 }],
|
||||||
|
8: [{ type: 'gold', value: 800 }, { type: 'talent_exp', value: 80 }],
|
||||||
|
9: [{ type: 'gold', value: 900 }, { type: 'talent_exp', value: 90 }],
|
||||||
|
10: [{ type: 'gold', value: 1500 }, { type: 'talent_exp', value: 150 }, { type: 'silver', value: 20 }],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从全英雄池随机抽取 1 个英雄 uuid(排除指定集合,池空时兜底返回 0)
|
||||||
|
* @param exclude 需排除的 uuid 集合(可空)
|
||||||
|
* @returns 随机英雄 uuid;英雄池为空返回 0
|
||||||
|
*/
|
||||||
|
export function pickRandomHeroUuid(exclude?: Set<number>): number {
|
||||||
|
const excludeSet = exclude ?? new Set<number>();
|
||||||
|
// 仅英雄池参与抽取(HeroInfo 同时含怪物条目,碎片属英雄局外成长)
|
||||||
|
const all = Object.keys(HeroInfo)
|
||||||
|
.map(k => Number(k))
|
||||||
|
.filter(u => u > 0 && HeroInfo[u]?.fac === FacSet.HERO);
|
||||||
|
const candidates = all.filter(u => !excludeSet.has(u));
|
||||||
|
const pool = candidates.length > 0 ? candidates : all;
|
||||||
|
if (pool.length === 0) return 0;
|
||||||
|
return pool[Math.floor(Math.random() * pool.length)];
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user