refactor: 将刷新石替换为银币作为卡牌刷新消耗

1. 重构全局配置,移除刷新石相关定义,新增银币刷新消耗常量
2. 新增局外持久货币银币的数据结构与管理逻辑
3. 重构卡牌刷新经济系统,统一使用银币进行刷新消耗
4. 更新UI显示与事件通知,适配银币系统变更
5. 调整清屏奖励逻辑,将刷新石奖励改为金币奖励
This commit is contained in:
panFD
2026-09-10 14:12:51 +08:00
parent a02bf54ede
commit 317a500439
9 changed files with 1763 additions and 343 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1628,7 +1628,7 @@
"watchPath": "data.data.gold", "watchPath": "data.data.gold",
"labelType": "cc.Label", "labelType": "cc.Label",
"watchPathArr": [ "watchPathArr": [
"data.gold" "data.silver"
], ],
"_id": "" "_id": ""
}, },

View File

@@ -21,6 +21,8 @@ export interface GameDate {
mission?: number, mission?: number,
diamond?: number, diamond?: number,
gold?: number, gold?: number,
/** 银币:局外持久货币,用于局内刷新英雄卡牌,仅通过日常任务等途径积攒 */
silver?: number,
task?: number, task?: number,
noStop?: boolean, noStop?: boolean,
showInfo?: boolean, showInfo?: boolean,
@@ -110,6 +112,8 @@ export class SingletonModuleComp extends ecs.Comp {
mission: 1, mission: 1,
diamond: 100, //商店购买 及 双倍奖励资源 diamond: 100, //商店购买 及 双倍奖励资源
gold: 1000, gold: 1000,
/** 银币:刷新英雄卡牌专用,每局不重置,仅日常积攒 */
silver: 0,
task: 0, task: 0,
noStop: false, noStop: false,
showInfo: true, showInfo: true,
@@ -157,7 +161,6 @@ export class SingletonModuleComp extends ecs.Comp {
level: 1,//关卡等级 level: 1,//关卡等级
max_mission: 4,//最大关卡 max_mission: 4,//最大关卡
coin: 0, coin: 0,
refresh_stone: 0,//刷新石(刷新卡池时优先消耗,不足时消耗金币)
time: 15 * 60,//游戏时间 time: 15 * 60,//游戏时间
}, },
scores: { scores: {
@@ -216,6 +219,7 @@ export class SingletonModuleComp extends ecs.Comp {
gold: 0, // 金币数据(MVVM绑定字段,镜像 data.gold) gold: 0, // 金币数据(MVVM绑定字段,镜像 data.gold)
diamond: 0, // 钻石数据(MVVM绑定字段,镜像 data.diamond) diamond: 0, // 钻石数据(MVVM绑定字段,镜像 data.diamond)
silver: 0, // 银币数据(MVVM绑定字段,镜像 data.silver,刷新卡牌消耗)
switch_effect: true, // 音效开关(MVVM绑定字段) switch_effect: true, // 音效开关(MVVM绑定字段)
switch_music: true, // 音乐开关(MVVM绑定字段) switch_music: true, // 音乐开关(MVVM绑定字段)
}; };
@@ -324,6 +328,7 @@ export class SingletonModuleComp extends ecs.Comp {
public syncCurrencyToView(): void { public syncCurrencyToView(): void {
this.vmdata.gold = this.data.gold ?? 0; this.vmdata.gold = this.data.gold ?? 0;
this.vmdata.diamond = this.data.diamond ?? 0; this.vmdata.diamond = this.data.diamond ?? 0;
this.vmdata.silver = this.data.silver ?? 0;
} }
getGameDate() { getGameDate() {
let data: GameDate = { let data: GameDate = {
@@ -363,6 +368,22 @@ export class SingletonModuleComp extends ecs.Comp {
return true return true
} }
/**
* 变更银币(正数获得 / 负数消耗),以 data 为权威源并镜像到 vmdata。
* Why: 银币为局外持久货币,仅用于刷新英雄卡牌,日常任务等途径积攒。
* @param silver 变动量
* @param is_sync 是否标记脏并触发云端/本地同步
*/
updateSilver(silver: number, is_sync: boolean = true) {
this.data.silver = Math.max(0, (this.data.silver ?? 0) + silver);
this.vmdata.silver = this.data.silver;
if (is_sync) {
gameDataSync.markDataDirty();
}
oops.message.dispatchEvent(GameEvent.SILVER_UPDATE)
return true
}
updateSwitchEffect(isOpen: boolean, is_sync: boolean = true) { updateSwitchEffect(isOpen: boolean, is_sync: boolean = true) {
this.vmdata.switch_effect = isOpen; this.vmdata.switch_effect = isOpen;
if (oops.audio) oops.audio.switchEffect = isOpen; if (oops.audio) oops.audio.switchEffect = isOpen;

View File

@@ -79,7 +79,7 @@ export enum GameEvent {
UpdateMissionGet = "UpdateMissionGet", UpdateMissionGet = "UpdateMissionGet",
GlobalAttrChange = "GlobalAttrChange", GlobalAttrChange = "GlobalAttrChange",
CoinAdd = "CoinAdd", CoinAdd = "CoinAdd",
RefreshStone = "RefreshStone", // 刷新石数量变更事件 SILVER_UPDATE = "SILVER_UPDATE", // 银币数量变更事件(刷新卡牌消耗)
ShowSmallTip = "ShowSmallTip", ShowSmallTip = "ShowSmallTip",
CardPoolUpgrade = "CardPoolUpgrade", CardPoolUpgrade = "CardPoolUpgrade",
TriggerSkill = "TriggerSkill", // 瞬间触发施法事件 TriggerSkill = "TriggerSkill", // 瞬间触发施法事件

View File

@@ -73,9 +73,8 @@ export enum FightSet {
REFRESH_COST = 2, REFRESH_COST = 2,
BASE_COST = 5, BASE_COST = 5,
INIT_COIN = 7, // 初始金币数 INIT_COIN = 7, // 初始金币数
INIT_REFRESH_STONE = 3, // 初始刷新石数量 /** 单次刷新卡牌消耗的银币数量 */
/** 单次刷新消耗的刷新石数量 */ SILVER_REFRESH_COST = 1,
REFRESH_STONE_COST = 1,
/** 全局默认闪避率(百分点),heroSet 未配置时生效 */ /** 全局默认闪避率(百分点),heroSet 未配置时生效 */
DEFAULT_DODGE = 15, DEFAULT_DODGE = 15,
/** 全局默认命中率(百分点),heroSet 未配置时生效 */ /** 全局默认命中率(百分点),heroSet 未配置时生效 */

View File

@@ -25,6 +25,8 @@ const { ccclass, property } = _decorator;
const FAST_CLEAR_COIN = 2; const FAST_CLEAR_COIN = 2;
/** 清屏奖励:极速清场(< 50% 时长) */ /** 清屏奖励:极速清场(< 50% 时长) */
const ULTRA_CLEAR_COIN = 5; const ULTRA_CLEAR_COIN = 5;
/** 清屏奖励:全员存活(allAlive)额外金币 */
const ALL_ALIVE_COIN = 3;
/** 连杀分级金币爆发(tier 0/1/2 对应 5/10/20 连杀) */ /** 连杀分级金币爆发(tier 0/1/2 对应 5/10/20 连杀) */
const COMBO_COIN = [2, 5, 10]; const COMBO_COIN = [2, 5, 10];
@@ -58,7 +60,7 @@ export class BattleBannerComp extends CCComp {
private onWaveClear(event: string, data: { wave: number; clearTime: number; allAlive: boolean; fastClear: number }) { private onWaveClear(event: string, data: { wave: number; clearTime: number; allAlive: boolean; fastClear: number }) {
if (data.allAlive) { if (data.allAlive) {
this.playBanner("Perfect!", new Color(255, 80, 80), 1.2); this.playBanner("Perfect!", new Color(255, 80, 80), 1.2);
MissionEconomy.addRefreshStone(1); MissionEconomy.addCoin(ALL_ALIVE_COIN);
oops.audio.playEffect("music/flash"); oops.audio.playEffect("music/flash");
} else if (data.fastClear === 2) { } else if (data.fastClear === 2) {
this.playBanner("极速清场!", new Color(255, 170, 0), 1.15); this.playBanner("极速清场!", new Color(255, 170, 0), 1.15);

View File

@@ -81,8 +81,6 @@ export class MissionCardComp extends CCComp {
private readonly buttonPressScale: number = 0.94; private readonly buttonPressScale: number = 0.94;
/** 按钮弹起缩放(峰值) */ /** 按钮弹起缩放(峰值) */
private readonly buttonClickScale: number = 1.06; private readonly buttonClickScale: number = 1.06;
/** 抽卡(刷新)费用 */
refreshCost: number = FightSet.REFRESH_COST;
/** 卡牌面板展开/收起动画时长(秒) */ /** 卡牌面板展开/收起动画时长(秒) */
cardsPanelMoveDuration: number = 0.2; cardsPanelMoveDuration: number = 0.2;
@@ -170,7 +168,7 @@ export class MissionCardComp extends CCComp {
/** 英雄数量显示节点(含 icon + num 子节点) */ /** 英雄数量显示节点(含 icon + num 子节点) */
@property(Node) @property(Node)
hero_num_node: Node = null! hero_num_node: Node = null!
/** 刷新石数量显示节点(含 icon + num 子节点) */ /** 银币数量显示节点(含 icon + num 子节点) */
@property(Node) @property(Node)
refresh_stone_num_node: Node = null! refresh_stone_num_node: Node = null!
@@ -330,6 +328,7 @@ export class MissionCardComp extends CCComp {
this.resetButtonScale(this.cards_refresh); this.resetButtonScale(this.cards_refresh);
this.resetPanelOpenCosts(); this.resetPanelOpenCosts();
this.updateCoinAndCostUI(); this.updateCoinAndCostUI();
this.updateSilverNumUI();
this.updateHeroNumUI(false, false); this.updateHeroNumUI(false, false);
if (this.node && this.node.isValid) { if (this.node && this.node.isValid) {
this.node.active = true; this.node.active = true;
@@ -416,7 +415,7 @@ export class MissionCardComp extends CCComp {
this.on("PhasePrepareStart", this.onPhasePrepareStart, this); this.on("PhasePrepareStart", this.onPhasePrepareStart, this);
oops.message.on(GameEvent.CoinAdd, this.onCoinAdd, this); oops.message.on(GameEvent.CoinAdd, this.onCoinAdd, this);
oops.message.on(GameEvent.RefreshStone, this.onRefreshStone, this); oops.message.on(GameEvent.SILVER_UPDATE, this.onSilverUpdate, this);
oops.message.on(GameEvent.MasterCalled, this.onMasterCalled, this); oops.message.on(GameEvent.MasterCalled, this.onMasterCalled, this);
oops.message.on(GameEvent.HeroDead, this.onHeroDead, this); oops.message.on(GameEvent.HeroDead, this.onHeroDead, this);
oops.message.on(GameEvent.HeroSell, this.onHeroSell, this); oops.message.on(GameEvent.HeroSell, this.onHeroSell, this);
@@ -477,13 +476,14 @@ export class MissionCardComp extends CCComp {
} }
} }
/** 刷新石数量变更:刷新刷新按钮状态/费用显示,并播放数量变化动画 */ /** 银币数量变更:刷新银币数值显示 + 刷新按钮状态/费用显示,并播放数量变化动画 */
private onRefreshStone(event: string, args: any) { private onSilverUpdate(event: string, args: any) {
const payload = args ?? event; const payload = args ?? event;
const v = typeof payload === 'number' ? payload : (payload?.delta ?? payload?.value ?? 0); const v = typeof payload === 'number' ? payload : (payload?.delta ?? payload?.value ?? 0);
this.updateSilverNumUI();
this.updateDrawCostUI(); this.updateDrawCostUI();
if (v !== 0) { if (v !== 0) {
this.playRefreshStoneChangeAnim(v > 0); this.playSilverChangeAnim(v > 0);
} }
} }
@@ -506,9 +506,10 @@ export class MissionCardComp extends CCComp {
* 挂在 showHeros / showEquips / showSkills / showShop 按钮节点(按钮下需预置 smalltip 子节点) * 挂在 showHeros / showEquips / showSkills / showShop 按钮节点(按钮下需预置 smalltip 子节点)
* @param text 可选:覆盖气泡文字(默认读 prefab 中配置的文本) * @param text 可选:覆盖气泡文字(默认读 prefab 中配置的文本)
*/ */
public showSmallTip(type: "refresh_coin" | "buy_coin" | "hero_full" | "panel_hero_full" | "panel_equip_full" | "panel_skill_full" | "panel_shop_full", text?: string) { public showSmallTip(type: "refresh_silver" | "refresh_coin" | "buy_coin" | "hero_full" | "panel_hero_full" | "panel_equip_full" | "panel_skill_full" | "panel_shop_full", text?: string) {
let targetNode: Node | null = null; let targetNode: Node | null = null;
switch (type) { switch (type) {
case "refresh_silver":
case "refresh_coin": case "refresh_coin":
targetNode = this.cards_refresh; targetNode = this.cards_refresh;
break; break;
@@ -673,7 +674,7 @@ export class MissionCardComp extends CCComp {
/** 解除按钮监听,避免节点销毁后回调泄漏 */ /** 解除按钮监听,避免节点销毁后回调泄漏 */
private unbindEvents() { private unbindEvents() {
oops.message.off(GameEvent.CoinAdd, this.onCoinAdd, this); oops.message.off(GameEvent.CoinAdd, this.onCoinAdd, this);
oops.message.off(GameEvent.RefreshStone, this.onRefreshStone, this); oops.message.off(GameEvent.SILVER_UPDATE, this.onSilverUpdate, this);
oops.message.off(GameEvent.MasterCalled, this.onMasterCalled, this); oops.message.off(GameEvent.MasterCalled, this.onMasterCalled, this);
oops.message.off(GameEvent.HeroDead, this.onHeroDead, this); oops.message.off(GameEvent.HeroDead, this.onHeroDead, this);
oops.message.off(GameEvent.HeroSell, this.onHeroSell, this); oops.message.off(GameEvent.HeroSell, this.onHeroSell, this);
@@ -1447,10 +1448,9 @@ export class MissionCardComp extends CCComp {
this.playButtonResetAnim(this.skill_refresh); this.playButtonResetAnim(this.skill_refresh);
} }
/** 装备刷新:扣费后重新抽取 3 张装备展示 */ /** 装备刷新:扣银币后重新抽取 3 张装备展示 */
private onClickEquipRefresh() { private onClickEquipRefresh() {
const cost = MissionEconomy.getRefreshCost(this.refreshCost); const success = MissionEconomy.executeRefresh();
const success = MissionEconomy.executeRefresh(this.refreshCost);
if (!success) { if (!success) {
this.showSmallTip("refresh_coin"); this.showSmallTip("refresh_coin");
return; return;
@@ -1458,10 +1458,9 @@ export class MissionCardComp extends CCComp {
this.populateEquipments(); this.populateEquipments();
} }
/** 药品刷新:扣费后重新抽取 3 张商品展示 */ /** 药品刷新:扣银币后重新抽取 3 张商品展示 */
private onClickShopRefresh() { private onClickShopRefresh() {
const cost = MissionEconomy.getRefreshCost(this.refreshCost); const success = MissionEconomy.executeRefresh();
const success = MissionEconomy.executeRefresh(this.refreshCost);
if (!success) { if (!success) {
this.showSmallTip("refresh_coin"); this.showSmallTip("refresh_coin");
return; return;
@@ -1469,9 +1468,9 @@ export class MissionCardComp extends CCComp {
this.populateShopItems(); this.populateShopItems();
} }
/** 技能刷新:扣费后重新抽取 3 张技能卡展示 */ /** 技能刷新:扣银币后重新抽取 3 张技能卡展示 */
private onClickSkillRefresh() { private onClickSkillRefresh() {
const success = MissionEconomy.executeRefresh(this.refreshCost); const success = MissionEconomy.executeRefresh();
if (!success) { if (!success) {
this.showSmallTip("refresh_coin"); this.showSmallTip("refresh_coin");
return; return;
@@ -1568,18 +1567,18 @@ export class MissionCardComp extends CCComp {
/** /**
* 刷新卡池按钮核心逻辑: * 刷新卡池按钮核心逻辑:
* 1. 检查金币/刷新石是否足够 → 不够则 toast 提示。 * 1. 检查银币是否足够 → 不够则 toast 提示。
* 2. 扣除费用、播放金币动画。 * 2. 扣除银币费用。
* 3. 重新布局槽位 → 从卡池构建 3 张卡 → 分发到槽位(三选一展示)。 * 3. 重新布局槽位 → 从卡池构建 3 张卡 → 分发到槽位(三选一展示)。
*/ */
private onClickDraw() { private onClickDraw() {
const cost = MissionEconomy.getRefreshCost(this.refreshCost); const cost = MissionEconomy.getSilverRefreshCost();
const success = MissionEconomy.executeRefresh(this.refreshCost); const success = MissionEconomy.executeRefresh();
if (!success) { if (!success) {
this.showSmallTip("refresh_coin"); this.showSmallTip("refresh_silver", "银币不足");
this.updateCoinAndCostUI(); this.updateCoinAndCostUI();
mLogger.log(this.debugMode, "MissionCardComp", "draw coin not enough", { mLogger.log(this.debugMode, "MissionCardComp", "draw silver not enough", {
currentCoin: MissionEconomy.getCoin(), currentSilver: MissionEconomy.getSilver(),
cost cost
}); });
return; return;
@@ -1587,7 +1586,7 @@ export class MissionCardComp extends CCComp {
mLogger.log(this.debugMode, "MissionCardComp", "click refresh", { mLogger.log(this.debugMode, "MissionCardComp", "click refresh", {
cost, cost,
leftCoin: MissionEconomy.getCoin() leftSilver: MissionEconomy.getSilver()
}); });
this.layoutCardSlots(); this.layoutCardSlots();
const cards = this.buildDrawCards(); const cards = this.buildDrawCards();
@@ -1626,8 +1625,10 @@ export class MissionCardComp extends CCComp {
} }
// 开局三选一期满后永久隐藏 showHeros(战前限定,不随阶段恢复) // 开局三选一期满后永久隐藏 showHeros(战前限定,不随阶段恢复)
this.updateShowHerosVisibility(); this.updateShowHerosVisibility();
// 刷新所有刷新按钮的费用显示(含刷新石/金币切换与置灰状态) // 刷新所有刷新按钮的费用显示(含银币置灰状态)
this.updateDrawCostUI(); this.updateDrawCostUI();
// 同步银币数量显示(阶段切换时确保数值最新)
this.updateSilverNumUI();
// 显示卡牌面板(默认进入准备阶段时展开,缩放放大到 1,点亮按钮 active 高亮) // 显示卡牌面板(默认进入准备阶段时展开,缩放放大到 1,点亮按钮 active 高亮)
if (this.cardPanNode && this.cardPanNode.isValid) { if (this.cardPanNode && this.cardPanNode.isValid) {
this.showCardsPanel(); this.showCardsPanel();
@@ -1851,17 +1852,17 @@ export class MissionCardComp extends CCComp {
} }
private canDrawCards() { private canDrawCards() {
// 有刷新石时可直接刷新,否则校验金币是否足够 // 银币足够时才可刷新
return MissionEconomy.canRefresh(this.refreshCost); return MissionEconomy.canRefresh();
} }
private updateDrawCostUI() { private updateDrawCostUI() {
const stones = MissionEconomy.getRefreshStone(); const silver = MissionEconomy.getSilver();
const stoneCost = MissionEconomy.getRefreshStoneCost(); const silverCost = MissionEconomy.getSilverRefreshCost();
this.updateOneRefreshBtnUI(this.cards_refresh, stones, stoneCost); this.updateOneRefreshBtnUI(this.cards_refresh, silver, silverCost);
this.updateOneRefreshBtnUI(this.equip_refresh, stones, stoneCost); this.updateOneRefreshBtnUI(this.equip_refresh, silver, silverCost);
this.updateOneRefreshBtnUI(this.shop_refresh, stones, stoneCost); this.updateOneRefreshBtnUI(this.shop_refresh, silver, silverCost);
this.updateOneRefreshBtnUI(this.skill_refresh, stones, stoneCost); this.updateOneRefreshBtnUI(this.skill_refresh, silver, silverCost);
} }
/** /**
@@ -1871,50 +1872,42 @@ export class MissionCardComp extends CCComp {
* - 背景 :按钮底图 * - 背景 :按钮底图
* - nobg :置灰遮罩,不可刷新时激活 * - nobg :置灰遮罩,不可刷新时激活
* - Node/ :费用容器,下含 Label + coin + stone * - Node/ :费用容器,下含 Label + coin + stone
* - coin/ :金币费用(含 num 子节点 Label),刷新石=0 时显示 * - coin/ :金币费用(含 num 子节点 Label),银币机制下隐藏
* - stone/ :刷新石费用(含 num 子节点 Label),刷新石>0 时显示 * - stone/ :银币数量(含 num 子节点 Label),显示当前持有银币数
* *
* @param btnNode 刷新按钮节点 * @param btnNode 刷新按钮节点
* @param stones 当前刷新石数量(外部统一读取,避免 4 次重复访问) * @param silver 当前银币数量(外部统一读取,避免 4 次重复访问)
* @param stoneCost 单次刷新消耗的刷新石数量 * @param silverCost 单次刷新消耗的银币数量(仅用于判断是否置灰)
*/ */
private updateOneRefreshBtnUI(btnNode: Node | null, stones: number, stoneCost: number) { private updateOneRefreshBtnUI(btnNode: Node | null, silver: number, silverCost: number) {
if (!btnNode || !btnNode.isValid) return; if (!btnNode || !btnNode.isValid) return;
// 刷新石足够一次消耗时走刷新石通道,否则走金币通道 // 银币足够一次消耗时走银币通道
const useStone = stones >= stoneCost; const useSilver = silver >= silverCost;
// 置灰遮罩:有刷新石必定可刷新,否则按金币判断 // 置灰遮罩:银币不足时置灰
const nobg = btnNode.getChildByName("nobg"); const nobg = btnNode.getChildByName("nobg");
if (nobg) { if (nobg) {
nobg.active = useStone ? false : !this.canDrawCards(); nobg.active = !useSilver;
} }
// 费用容器:按钮下的 Node 子节点(统一结构:背景 + Node[Label/coin/stone]) // 费用容器:按钮下的 Node 子节点(统一结构:背景 + Node[Label/coin/stone])
const container = btnNode.getChildByName("Node"); const container = btnNode.getChildByName("Node");
if (!container) return; if (!container) return;
// 金币费用节点 // 金币费用节点:银币机制下隐藏,不再显示金币费用
const coinNode = container.getChildByName("coin"); const coinNode = container.getChildByName("coin");
if (coinNode) { if (coinNode) {
coinNode.active = !useStone; coinNode.active = false;
if (!useStone) {
const numLabel = coinNode.getChildByName("num")?.getComponent(Label);
if (numLabel) {
numLabel.string = `${MissionEconomy.getRefreshCost(this.refreshCost)}`;
}
}
} }
// 刷新石费用节点(显示单次刷新消耗数量) // 银币费用节点(复用 stone 子节点结构,显示当前持有银币数量)
const stoneNode = container.getChildByName("stone"); const stoneNode = container.getChildByName("stone");
if (stoneNode) { if (stoneNode) {
stoneNode.active = useStone; stoneNode.active = true;
if (useStone) { const numLabel = stoneNode.getChildByName("num")?.getComponent(Label);
const numLabel = stoneNode.getChildByName("num")?.getComponent(Label); if (numLabel) {
if (numLabel) { numLabel.string = `${silver}`;
numLabel.string = `${stoneCost}`;
}
} }
} }
} }
@@ -1930,6 +1923,17 @@ export class MissionCardComp extends CCComp {
} }
} }
/** 刷新银币数量显示(refresh_stone_num_node 下的 num Label),未绑定或节点失效时静默跳过 */
private updateSilverNumUI() {
if (!this.refresh_stone_num_node || !this.refresh_stone_num_node.isValid) return;
const numNode = this.refresh_stone_num_node.getChildByName("num");
if (!numNode || !numNode.isValid) return;
const label = numNode.getComponent(Label);
if (label) {
label.string = `${MissionEconomy.getSilver()}`;
}
}
private playCoinChangeAnim(isIncrease: boolean) { private playCoinChangeAnim(isIncrease: boolean) {
if (!this.coins_node || !this.coins_node.isValid) return; if (!this.coins_node || !this.coins_node.isValid) return;
const icon = this.coins_node.getChildByName("icon"); const icon = this.coins_node.getChildByName("icon");
@@ -1941,8 +1945,8 @@ export class MissionCardComp extends CCComp {
this.playHeroNumNodePop(num, peak); this.playHeroNumNodePop(num, peak);
} }
/** 刷新石计数变化动画(与金币一致的 icon+num 弹跳) */ /** 银币计数变化动画(与金币一致的 icon+num 弹跳) */
private playRefreshStoneChangeAnim(isIncrease: boolean) { private playSilverChangeAnim(isIncrease: boolean) {
if (!this.refresh_stone_num_node || !this.refresh_stone_num_node.isValid) return; if (!this.refresh_stone_num_node || !this.refresh_stone_num_node.isValid) return;
const peak = isIncrease ? 1.2 : 1.2; const peak = isIncrease ? 1.2 : 1.2;
const icon = this.refresh_stone_num_node.getChildByName("icon"); const icon = this.refresh_stone_num_node.getChildByName("icon");

View File

@@ -830,9 +830,7 @@ export class MissionComp extends CCComp {
smc.resetScores(); smc.resetScores();
smc.vmdata.mission_data.coin = Math.max(0, Math.floor(FightSet.INIT_COIN)); smc.vmdata.mission_data.coin = Math.max(0, Math.floor(FightSet.INIT_COIN));
// 初始化刷新石数量,并派发事件通知 UI 刷新(MissionStart 早于 data_init,需手动通知) // 银币为局外持久货币,每局不重置,仅通过日常积攒,无需初始化
smc.vmdata.mission_data.refresh_stone = Math.max(0, Math.floor(FightSet.INIT_REFRESH_STONE));
oops.message.dispatchEvent(GameEvent.RefreshStone, { delta: FightSet.INIT_REFRESH_STONE });
// 【评分系统 - 效率分】记录初始获得的金币收入 // 【评分系统 - 效率分】记录初始获得的金币收入
smc.vmdata.scores.gold_earned += smc.vmdata.mission_data.coin; smc.vmdata.scores.gold_earned += smc.vmdata.mission_data.coin;
} }

View File

@@ -39,68 +39,45 @@ export class MissionEconomy {
return true; return true;
} }
/** /** 获取当前银币数量(局外持久货币,刷新卡牌消耗) */
* 计算卡牌刷新费用 static getSilver(): number {
* @param baseCost 基础费用,默认为1 return Math.max(0, Math.floor(smc.vmdata.silver ?? 0));
*/
static getRefreshCost(baseCost: number = 1): number {
return Math.floor(Math.max(0, baseCost));
}
/** 获取当前刷新石数量 */
static getRefreshStone(): number {
return Math.max(0, Math.floor(smc.vmdata.mission_data.refresh_stone ?? 0));
} }
/** /**
* 增加刷新石 * 增加银币
* @param amount 增加数量,<=0 时忽略 * @param amount 增加数量,<=0 时忽略
*/ */
static addRefreshStone(amount: number) { static addSilver(amount: number) {
if (amount <= 0) return; if (amount <= 0) return;
amount = Math.floor(amount); smc.updateSilver(Math.floor(amount));
smc.vmdata.mission_data.refresh_stone = this.getRefreshStone() + amount;
// 发送 UI 更新事件(复用 RefreshStone 事件通知刷新按钮状态刷新)
oops.message.dispatchEvent(GameEvent.RefreshStone, { delta: amount });
} }
/** 单次刷新消耗的刷新石数量 */ /** 单次刷新消耗的银币数量 */
static getRefreshStoneCost(): number { static getSilverRefreshCost(): number {
return Math.max(1, Math.floor(FightSet.REFRESH_STONE_COST)); return Math.max(1, Math.floor(FightSet.SILVER_REFRESH_COST));
} }
/** /**
* 判断当前是否可以执行一次刷新(刷新石足够或金币足够) * 判断当前是否可以执行一次刷新(银币足够)
*/ */
static canRefresh(baseCost: number = 1): boolean { static canRefresh(): boolean {
if (this.getRefreshStone() >= this.getRefreshStoneCost()) return true; return this.getSilver() >= this.getSilverRefreshCost();
return this.getCoin() >= this.getRefreshCost(baseCost);
} }
/** /**
* 执行刷新卡牌: * 执行刷新卡牌:
* 1. 刷新石足够时优先扣除 REFRESH_STONE_COST 颗刷新石(不消耗金币)。 * 银币足够时扣除 SILVER_REFRESH_COST 枚银币,否则刷新失败。
* 2. 刷新石不足时扣除金币(含刷新优惠折扣)。 * Why: 英雄卡牌只能银币刷新,不能金币刷新,银币为局外持久货币,靠日常积攒。
*/ */
static executeRefresh(baseCost: number = 1): boolean { static executeRefresh(): boolean {
// 优先消耗刷新石 const cost = this.getSilverRefreshCost();
const stoneCost = this.getRefreshStoneCost(); const silver = this.getSilver();
const stones = this.getRefreshStone(); if (silver < cost) return false;
if (stones >= stoneCost) {
smc.vmdata.mission_data.refresh_stone = stones - stoneCost;
smc.vmdata.scores.refresh_count++;
oops.message.dispatchEvent(GameEvent.RefreshStone, { delta: -stoneCost });
return true;
}
// 刷新石不足 → 扣除金币 smc.updateSilver(-cost);
const cost = this.getRefreshCost(baseCost); smc.vmdata.scores.refresh_count++;
const success = this.spendCoin(cost); return true;
if (success) {
smc.vmdata.scores.refresh_count++;
}
return success;
} }
/** /**