diff --git a/assets/script/game/hero/HeroAtkSystem.ts b/assets/script/game/hero/HeroAtkSystem.ts index 76403586..16bcc3b5 100644 --- a/assets/script/game/hero/HeroAtkSystem.ts +++ b/assets/script/game/hero/HeroAtkSystem.ts @@ -15,6 +15,8 @@ import { FieldSkillType } from "../common/config/SkillSet"; import { mLogger } from "../common/Logger"; import { SkillTriggerHelper } from "./SkillTriggerHelper"; +import { getMonsterGoldDrop } from "../map/RogueConfig"; +import { MissionEconomy } from "../map/MissionEconomy"; /** 最终伤害数据接口 * 用于封装一次攻击计算的所有结果数据 @@ -39,8 +41,8 @@ interface FinalData { * - 属性来源规范:攻击判定用施法者,防御判定用被攻击者 */ @ecs.register('HeroAtkSystem') -export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate { - +export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate { + private debugMode: boolean = false; // 是否启用调试模式 /** @@ -55,22 +57,22 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd * 处理伤害队列中的所有伤害事件 */ update(e: ecs.Entity): void { - if(!smc.mission.play ) return - if(smc.mission.pause) return + if (!smc.mission.play) return + if (smc.mission.pause) return const TAttrsComp = e.get(HeroAttrsComp) const damageQueue = e.get(DamageQueueComp) - + if (!TAttrsComp || !damageQueue || damageQueue.isEmpty()) return; - + // 标记正在处理 damageQueue.isProcessing = true; - + // 处理队列中的所有伤害事件 let processedCount = 0; while (!damageQueue.isEmpty()) { const damageEvent = damageQueue.getNextDamageEvent(); if (!damageEvent) break; - + // 处理单个伤害事件 this.doAttack(e, damageEvent); processedCount++; @@ -82,11 +84,11 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd break; } } - + // 如果队列已空,移除伤害队列组件 if (damageQueue.isEmpty()) { e.remove(DamageQueueComp); - + if (processedCount > 0) { mLogger.log(this.debugMode, 'HeroAtkSystem', ` ${TAttrsComp.hero_name} 伤害队列处理完成,共处理 ${processedCount} 个伤害事件`); } @@ -109,9 +111,9 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd private doAttack(target: ecs.Entity, damageEvent: DamageEvent): FinalData { const TAttrsComp = target.get(HeroAttrsComp); const targetView = target.get(HeroViewComp); - let reDate:FinalData={ - damage:0, - isCrit:false, + let reDate: FinalData = { + damage: 0, + isCrit: false, } if (!TAttrsComp || TAttrsComp.is_dead || TAttrsComp.is_reviving) return reDate; @@ -120,30 +122,30 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd // 获取技能配置 const skillConf = SkillSet[damageEvent.s_uuid]; if (!skillConf) return reDate; - + // 触发被攻击事件 this.onAttacked(target); - + // 暴击判定 // 使用施法者的暴击率属性(damageEvent.Attrs 快照),- 被攻击者的暴击抗性属 const criticalChance = (damageEvent.Attrs[Attrs.critical] || 0) - (TAttrsComp.critical_res || 0); const isCrit = this.checkChance(criticalChance); - + // 计算基础伤害 - let damage = this.dmgCount(damageEvent,TAttrsComp); - - mLogger.log(this.debugMode, 'HeroAtkSystem', " dmgCount",damage) + let damage = this.dmgCount(damageEvent, TAttrsComp); + + mLogger.log(this.debugMode, 'HeroAtkSystem', " dmgCount", damage) if (isCrit) { const critDamageBonus = damageEvent.Attrs[Attrs.critical_damage] || 0; damage = Math.floor(damage * (1 + (FightSet.CRIT_DAMAGE + critDamageBonus) / 100)); - reDate.isCrit=true; + reDate.isCrit = true; if (damageEvent.Attrs.fac === FacSet.HERO) { // 【评分系统 - 输出分】统计暴击次数与暴击造成的总伤害 smc.vmdata.scores.crt_count++; smc.vmdata.scores.crit_dmg_total += damage; } } - mLogger.log(this.debugMode, 'HeroAtkSystem', " after crit",damage) + mLogger.log(this.debugMode, 'HeroAtkSystem', " after crit", damage) // 护盾吸收 const shieldResult = this.absorbShield(TAttrsComp, damage); damage = shieldResult.remainingDamage; @@ -151,16 +153,16 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd // 【评分系统 - 防御分】统计护盾成功抵挡伤害的次数 smc.vmdata.scores.shield_block_count += shieldResult.absorbedDamage; } - mLogger.log(this.debugMode, 'HeroAtkSystem', " after shield",damage) + mLogger.log(this.debugMode, 'HeroAtkSystem', " after shield", damage) // 显示护盾吸收飘字 if (shieldResult.absorbedDamage > 0 && targetView) { targetView.shield_tip(shieldResult.absorbedDamage); } if (damage <= 0) return reDate; - + // TAttrsComp.hp -= damage; // 应用伤害到数据层 TAttrsComp.add_hp(-damage); // 使用 add_hp 以触发 dirty_hp 和 UI 更新 - + if (damageEvent.Attrs.fac === FacSet.HERO) { // 【评分系统 - 输出分】统计团队造成的总伤害以及单次最高伤害记录 smc.vmdata.scores.total_dmg += damage; @@ -168,7 +170,7 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd smc.vmdata.scores.highest_dmg = damage; } } - + // 增加受击计数并触发 atked 技能 TAttrsComp.atked_count++; this.checkAndTriggerAtkedSkills(TAttrsComp, targetView); @@ -178,12 +180,12 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd // targetView.back(); // } - mLogger.log(this.debugMode, 'HeroAtkSystem', ` 英雄${TAttrsComp.hero_name} (uuid: ${TAttrsComp.hero_uuid}) 受到 eid:${casterEid} 的 伤害 ${damage},${isCrit?"暴击":"普通"}攻击,技能ID ${damageEvent.s_uuid}`); - + mLogger.log(this.debugMode, 'HeroAtkSystem', ` 英雄${TAttrsComp.hero_name} (uuid: ${TAttrsComp.hero_uuid}) 受到 eid:${casterEid} 的 伤害 ${damage},${isCrit ? "暴击" : "普通"}攻击,技能ID ${damageEvent.s_uuid}`); + // 冰冻判定 const freezeChance = (damageEvent.Attrs[Attrs.freeze_chance] || 0) - (TAttrsComp.freeze_res || 0); const isFrost = !TAttrsComp.isFrost() && this.checkChance(freezeChance); - + // 击晕判定 const stunChance = (damageEvent.Attrs[Attrs.stun_chance] || 0) - (TAttrsComp.stun_res || 0); const isStun = !TAttrsComp.isStun() && this.checkChance(stunChance); @@ -191,9 +193,9 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd // 击退判定 const knockbackChance = (damageEvent.Attrs[Attrs.knockback_chance] || 0) - (TAttrsComp.knockback_res || 0); const isKnockback = this.checkChance(knockbackChance); - + // ✅ 触发视图层表现(伤害数字、受击动画、冰冻、击晕、击退) - if (targetView) { + if (targetView) { targetView.do_atked(damage, isCrit, damageEvent.s_uuid, false); targetView.playEnd(skillConf.endAnm); if (isFrost) { @@ -214,8 +216,8 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd targetView.back(damageEvent.Attrs[Attrs.knockback_distance] || 0); } } - - + + // 检查死亡 if (TAttrsComp.hp <= 0) { // 先触发死亡技能(如亡语),不管后续是否复活都应触发 @@ -245,7 +247,7 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd // 根据技能配置恢复生命值 TAttrsComp.hp = Math.floor(TAttrsComp.hp_max * (reviveHpPercent / 100)); TAttrsComp.dirty_hp = true; - + // 触发复活动画 if (targetView && reviveSkillConf) { targetView.playReady(reviveSkillConf.readyAnm); @@ -269,14 +271,14 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd targetView.do_dead(); } } - + mLogger.log(this.debugMode, 'HeroAtkSystem', ` ${TAttrsComp.hero_name} 受到 ${damage} 点伤害 (暴击: ${isCrit})`); - - reDate.damage=damage; + + reDate.damage = damage; return reDate; } - + /** * 详细伤害计算核心方法 * @@ -295,23 +297,23 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd * @important 注意事项: */ - private dmgCount(damageEvent:DamageEvent,TAttrsComp:HeroAttrsComp){ + private dmgCount(damageEvent: DamageEvent, TAttrsComp: HeroAttrsComp) { // 1. 获取技能配置 - 如果技能不存在,直接返回0伤害 - const CAttrs=damageEvent.Attrs; - const TAttrs=TAttrsComp; + const CAttrs = damageEvent.Attrs; + const TAttrs = TAttrsComp; let sConf = SkillSet[damageEvent.s_uuid]; if (!sConf) return 0; - mLogger.log(this.debugMode, 'HeroAtkSystem', ` 伤害处理对象`,CAttrs,TAttrs); + mLogger.log(this.debugMode, 'HeroAtkSystem', ` 伤害处理对象`, CAttrs, TAttrs); // 2. 计算原始物理伤害和魔法伤害 // 物理伤害基础值 = 技能物理倍率 * (施法者物理攻击力 + 额外伤害) / 100 * 额外伤害比例 - let apBase = (sConf.ap||0)*(CAttrs[Attrs.ap]+damageEvent.ext_dmg)/100*damageEvent.dmg_ratio; + let apBase = (sConf.ap || 0) * (CAttrs[Attrs.ap] + damageEvent.ext_dmg) / 100 * damageEvent.dmg_ratio; mLogger.log(this.debugMode, 'HeroAtkSystem', ` 物理伤害基础值: ${apBase}, 技能ap=${sConf.ap},施法者物理攻击力: ${CAttrs[Attrs.ap]},} 额外伤害:${damageEvent.ext_dmg}, 额外伤害比例:${damageEvent.dmg_ratio}`); // 4. 确保伤害值非负 let total = Math.max(0, apBase); - - if (this.debugMode) mLogger.log(this.debugMode, 'HeroAtkSystem', ` 最终伤害: ${total} (Base: ${apBase}`); + + if (this.debugMode) mLogger.log(this.debugMode, 'HeroAtkSystem', ` 最终伤害: ${total} (Base: ${apBase}`); return total; } @@ -350,12 +352,12 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd private doDead(entity: ecs.Entity): void { const TAttrsComp = entity.get(HeroAttrsComp); if (!TAttrsComp || TAttrsComp.is_dead) return; - + TAttrsComp.is_dead = true; - + // 触发死亡事件 this.onDeath(entity); - + if (this.debugMode) { mLogger.log(this.debugMode, 'HeroAtkSystem', ` ${TAttrsComp.hero_name} 死亡`); } @@ -405,10 +407,10 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd * @param damage 原始伤害值 * @returns {remainingDamage, absorbedDamage} 剩余伤害值和吸收的护盾值 */ - private absorbShield(TAttrsComp: HeroAttrsComp, damage: number): {remainingDamage: number, absorbedDamage: number} { - if (TAttrsComp.shield <= 0) { + private absorbShield(TAttrsComp: HeroAttrsComp, damage: number): { remainingDamage: number, absorbedDamage: number } { + if (TAttrsComp.shield <= 0) { mLogger.log(this.debugMode, 'HeroAtkSystem', " 护盾值小于等于0,无法吸收伤害"); - return {remainingDamage: damage, absorbedDamage: 0}; + return { remainingDamage: damage, absorbedDamage: 0 }; } TAttrsComp.shield = Math.max(0, TAttrsComp.shield - 1); if (TAttrsComp.shield <= 0) { @@ -416,7 +418,7 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd } TAttrsComp.dirty_shield = true; mLogger.log(this.debugMode, 'HeroAtkSystem', ` 护盾抵挡1次伤害,剩余次数 ${TAttrsComp.shield}`); - return {remainingDamage: 0, absorbedDamage: 1}; + return { remainingDamage: 0, absorbedDamage: 1 }; } /** @@ -437,7 +439,7 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd TAttrsComp.atked_count++; // 这里可以添加被攻击时的特殊处理逻辑 if (TAttrsComp.fac === FacSet.MON) return; - + // 例如:触发某些天赋效果、反击逻辑等 } @@ -462,7 +464,7 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd private onDeath(entity: ecs.Entity): void { const TAttrsComp = entity.get(HeroAttrsComp); if (!TAttrsComp) return; - + if (TAttrsComp.fac === FacSet.MON) { // 怪物死亡处理 this.scheduleDrop(entity); @@ -473,24 +475,28 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd } /** - * 延迟执行掉落逻辑 - * - * 采用延迟执行的原因: - * 1. 避免在伤害计算过程中阻塞主线程 - * 2. 给死亡动画播放留出时间 - * 3. 可以批量处理多个掉落,优化性能 - * + * 怪物死亡掉落金币 + * + * 实现说明: + * - 通过 hero_uuid 查 HeroInfo 获取 monType + * - 按怪物类型查 MonsterGoldSet 获取固定金币掉落数(Boss 有独立的高额奖励) + * - 调用 MissionEconomy.addCoin 统一发放金币(会自动触发 UI 更新和评分统计) + * * @param entity 死亡的怪物实体 - * - * @todo 具体实现可以包括: - * - 根据怪物等级计算基础掉落 - * - 幸运值影响掉落品质 - * - 特殊事件(双倍掉落、稀有掉落等) - * - 掉落物在场景中的生成位置计算 */ private scheduleDrop(entity: ecs.Entity): void { - // 这里可以添加掉落逻辑 - // 例如:延迟一段时间后生成掉落物品 + const TAttrsComp = entity.get(HeroAttrsComp); + if (!TAttrsComp) return; + + // 通过 hero_uuid 查配置获取怪物类型 + const info = HeroInfo[TAttrsComp.hero_uuid]; + const monType = info?.monType ?? 0; + const gold = getMonsterGoldDrop(monType, !!TAttrsComp.is_boss); + if (gold <= 0) return; + + MissionEconomy.addCoin(gold); + mLogger.log(this.debugMode, 'HeroAtkSystem', + ` ${TAttrsComp.hero_name} 死亡掉落金币 ${gold}(monType=${monType}, isBoss=${TAttrsComp.is_boss})`); } /** @@ -520,7 +526,7 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd enableDebug() { this.debugMode = true; } - + /** * 禁用调试模式 */ diff --git a/assets/script/game/map/MissionCardComp.ts b/assets/script/game/map/MissionCardComp.ts index 08eb9317..3fbb2b1a 100644 --- a/assets/script/game/map/MissionCardComp.ts +++ b/assets/script/game/map/MissionCardComp.ts @@ -135,9 +135,6 @@ export class MissionCardComp extends CCComp { // ======================== 运行时状态 ======================== - /** 当前是否为战斗阶段 */ - private isBattlePhase: boolean = false; - /** 四个槽位对应的 CardComp 控制器缓存(有序数组) */ private cardComps: CardComp[] = []; /** 技能卡槽控制器缓存 */ @@ -203,7 +200,6 @@ export class MissionCardComp extends CCComp { * 6. 执行首次抽卡并分发到 4 个槽位。 */ onMissionStart() { - this.isBattlePhase = false; this.enterPreparePhase(); this.poolLv = CARD_POOL_INIT_LEVEL; const missionData = this.getMissionData(); @@ -326,11 +322,9 @@ export class MissionCardComp extends CCComp { } } - /** 战斗开始:不收起面板,不再强制清空卡牌 */ + /** 战斗开始:保留卡牌面板,允许玩家在战斗阶段继续抽卡和召唤英雄 */ private onFightStart() { - this.isBattlePhase = true; this.enterBattlePhase(); - this.clearAllCards(); // 第一次进入战斗阶段,关闭guide4 if (!smc.finish_guides.includes(4)) { @@ -392,10 +386,10 @@ export class MissionCardComp extends CCComp { if (tipNode) { tipNode.active = true; Tween.stopAllByTarget(tipNode); - + // 设置初始状态:缩放为 0 tipNode.setScale(new Vec3(0, 0, 1)); - + tween(tipNode) // 1. 弹出动画(微放大再回弹) .to(0.15, { scale: new Vec3(1.1, 1.1, 1) }, { easing: 'quadOut' }) @@ -418,7 +412,6 @@ export class MissionCardComp extends CCComp { /** 新一波:展开面板 → 刷新费用 UI → 重新抽卡分发 */ private onNewWave() { - this.isBattlePhase = false; this.enterPreparePhase(); this.updateCoinAndCostUI(); this.layoutCardSlots(); @@ -495,13 +488,13 @@ export class MissionCardComp extends CCComp { // 如果我们把它包在 `if (!smc.finish_guides.includes(2))` 里, // 当玩家点击 guide2 把它关掉时(finish_guides 存入了 2), // 再点技能卡触发这个方法,外层 if 就会进不去,guide3 就永远弹不出来了! - + // 修复:独立判断 guide2 的关闭 和 guide3 的开启 if (!smc.finish_guides.includes(2)) { smc.finish_guides.push(2); oops.gui.remove(UIID.Guide2); } - + if (!smc.finish_guides.includes(3)) { oops.gui.open(UIID.Guide3); } @@ -563,7 +556,7 @@ export class MissionCardComp extends CCComp { smc.finish_guides.push(3); oops.gui.remove(UIID.Guide3); } - + if (!smc.finish_guides.includes(4)) { oops.gui.open(UIID.Guide4); } @@ -589,12 +582,7 @@ export class MissionCardComp extends CCComp { const payload = args ?? event; if (!payload) return; - if (this.isBattlePhase) { - payload.cancel = true; - payload.reason = "battle_phase"; - oops.gui.toast("战斗阶段无法召唤英雄"); - return; - } + // 战斗阶段也允许召唤英雄(无需额外费用),仅校验英雄数量上限 const current = this.getAliveHeroCount(); this.syncMissionHeroData(current); @@ -781,10 +769,7 @@ export class MissionCardComp extends CCComp { * 3. 重新布局槽位 → 从卡池构建 4 张卡 → 分发到槽位。 */ private onClickDraw() { - if (this.isBattlePhase) { - oops.gui.toast("战斗阶段无法抽卡"); - return; - } + // 战斗阶段和倒计时阶段均允许刷新抽卡 const cost = MissionEconomy.getRefreshCost(this.refreshCost); const success = MissionEconomy.executeRefresh(this.refreshCost); if (!success) { @@ -868,22 +853,13 @@ export class MissionCardComp extends CCComp { private enterBattlePhase() { if (!this.cards_node || !this.cards_node.isValid) return; this.initCardsPanelPos(); + // 战斗阶段允许抽卡:nobg 按"金币是否足够"判断,而非强制置灰 if (this.cards_chou && this.cards_chou.isValid) { const nobg = this.cards_chou.getChildByName("nobg"); if (nobg) { - nobg.active = true; + nobg.active = !this.canDrawCards(); } } - // 战斗阶段不再隐藏抽卡面板 - // Tween.stopAllByTarget(this.cards_node); - // tween(this.cards_node) - // .to(this.cardsPanelMoveDuration, { scale: this.cardsHideScale }) - // .call(() => { - // if (this.cards_node && this.cards_node.isValid) { - // this.cards_node.active = false; - // } - // }) - // .start(); } /** 构建本次抽卡结果,保证最终可分发3条数据 */ @@ -924,7 +900,7 @@ export class MissionCardComp extends CCComp { unique: true }); if (fallback.length === 0) break; - + // 如果池子数量不足,只能被迫允许重复,但尽量拿没被抽到的 const fPick = fallback.find(c => !filled.some(fc => fc.uuid === c.uuid)); if (fPick) { @@ -1046,7 +1022,7 @@ export class MissionCardComp extends CCComp { label.string = `lv.${lv}`; } } - + const nextNode = this.pool_lv_node.getChildByName("next"); if (nextNode) { const nextLabel = nextNode.getComponent(Label); @@ -1098,10 +1074,11 @@ export class MissionCardComp extends CCComp { } private updateDrawCostUI() { + // 战斗阶段也允许抽卡,nobg 统一按"金币是否足够"判断 if (this.cards_chou) { const nobg = this.cards_chou.getChildByName("nobg"); if (nobg) { - nobg.active = this.isBattlePhase ? true : !this.canDrawCards(); + nobg.active = !this.canDrawCards(); } const coinNode = this.cards_chou.getChildByName("coin"); const numLabel = coinNode?.getChildByName("num")?.getComponent(Label); diff --git a/assets/script/game/map/MissionComp.ts b/assets/script/game/map/MissionComp.ts index 8713f2bd..57bbc5a5 100644 --- a/assets/script/game/map/MissionComp.ts +++ b/assets/script/game/map/MissionComp.ts @@ -49,7 +49,6 @@ import { Timer } from "db://oops-framework/core/common/timer/Timer"; import { FieldSkillType } from "../common/config/SkillSet"; import { FieldSkillHelper } from "../hero/FieldSkillHelper"; import { spawningEngine } from "./RogueConfig"; -import { MissionEconomy } from "./MissionEconomy"; const { ccclass, property } = _decorator; /** 任务(关卡)生命周期阶段 */ @@ -129,6 +128,10 @@ export class MissionComp extends CCComp { } /**秒计时 */ PhaseTime: Timer = new Timer(1) + /** 波次间倒计时(秒) */ + private waveCountdown: number = 0; + /** 波次间倒计时总时长(秒) */ + private readonly WAVE_COUNTDOWN_DURATION: number = 5; /** 上一次显示的时间字符串(避免重复设置) */ private lastTimeStr: string = ""; /** 上一次显示的秒数(避免重复计算) */ @@ -159,10 +162,10 @@ export class MissionComp extends CCComp { private currentWave: number = 0; /** 是否为Boss波次 */ private isBossWave: boolean = false; - /** 上一次发放金币奖励的波数(防止重复发放) */ - private lastPrepareCoinWave: number = 0; /** 当前任务阶段 */ public currentPhase: MissionPhase = MissionPhase.None; + /** 是否处于波次间倒计时状态 */ + private isWaveCountdown: boolean = false; // ======================== ECS 查询匹配器(预缓存) ======================== @@ -219,9 +222,18 @@ export class MissionComp extends CCComp { // 如果是暂停状态,且不在 BattleEnd 阶段(全灭时需要播放完 fend 技能动画并自动流转),才真正停止 update 逻辑 if (smc.mission.pause && this.currentPhase !== MissionPhase.BattleEnd) return + // 波次间倒计时:PrepareStart 复用为倒计时阶段,5 秒后自动进入下一波 + if (this.currentPhase === MissionPhase.PrepareStart) { + this.waveCountdown -= dt; + this.updateCountdownUI(); + if (this.waveCountdown <= 0) { + this.autoNextPhase(); + } + return; + } + // 处理过渡阶段的计时 - if (this.currentPhase === MissionPhase.PrepareStart || - this.currentPhase === MissionPhase.PrepareEnd || + if (this.currentPhase === MissionPhase.PrepareEnd || this.currentPhase === MissionPhase.BattleStart || this.currentPhase === MissionPhase.BattleEnd) { if (this.PhaseTime.update(dt)) { @@ -260,6 +272,39 @@ export class MissionComp extends CCComp { } } + // ======================== 波次倒计时 ======================== + + /** 进入波次间倒计时:重置倒计时并显示提示 */ + private startWaveCountdown() { + this.waveCountdown = this.WAVE_COUNTDOWN_DURATION; + this.isWaveCountdown = true; + this.updateCountdownUI(true); + } + + /** 更新倒计时 UI(显示剩余秒数) */ + private updateCountdownUI(force: boolean = false) { + if (!this.isWaveCountdown) return; + const remain = Math.max(0, Math.ceil(this.waveCountdown)); + if (!force && remain === this.lastTimeSecond) return; + this.lastTimeSecond = remain; + + if (this.time_node && this.time_node.isValid) { + const phaseNode = this.time_node.getChildByPath("Phase/Label"); + if (phaseNode) { + const label = phaseNode.getComponent(Label); + if (label) { + label.string = `下一波 ${remain}s`; + } + } + } + } + + /** 结束波次间倒计时 */ + private stopWaveCountdown() { + this.isWaveCountdown = false; + this.lastTimeSecond = -1; + } + // ======================== 奖励与广告 ======================== /** 奖励发放(预留) */ @@ -307,7 +352,7 @@ export class MissionComp extends CCComp { loading.active = false }, 0.5) } - + // 播放战斗背景音乐,并稍微降低音量 oops.audio.volumeMusic = 0.5; oops.audio.playerMusicLoop("music/BATTLE"); @@ -425,7 +470,13 @@ export class MissionComp extends CCComp { smc.mission.in_fight = false; smc.vmdata.mission_data.in_fight = false; smc.mission.stop_spawn_mon = true; - // 不隐藏开始按钮,点击事件在 onStartFightBtnClick 内部做了阶段拦截 + // 波次间倒计时:隐藏开始按钮(nobg 激活表示不可点击状态) + if (this.start_btn && this.start_btn.isValid) { + const nobg = this.start_btn.getChildByName("nobg"); + if (nobg) nobg.active = true; + } + // 启动 5 秒倒计时,由 update 驱动自动进入下一波 + this.startWaveCountdown(); oops.message.dispatchEvent("PhasePrepareStart"); break; @@ -526,7 +577,9 @@ export class MissionComp extends CCComp { private autoNextPhase() { switch (this.currentPhase) { case MissionPhase.PrepareStart: - this.changePhase(MissionPhase.Prepare); + // 波次间倒计时结束,停止倒计时状态,直接进入 PrepareEnd(不再等待玩家点击) + this.stopWaveCountdown(); + this.changePhase(MissionPhase.PrepareEnd); break; case MissionPhase.PrepareEnd: this.changePhase(MissionPhase.BattleStart); @@ -643,7 +696,7 @@ export class MissionComp extends CCComp { if (!smc.mission.play) return; if (smc.mission.pause) return; if (this.currentPhase !== MissionPhase.Prepare) return; - + oops.audio.playEffect("music/button"); this.to_fight(); } @@ -728,7 +781,6 @@ export class MissionComp extends CCComp { this.heapTrendTimer = 0; this.heapTrendBaseMB = -1; this.monsterCountSyncTimer = 0; - this.lastPrepareCoinWave = 0; spawningEngine.reset(); @@ -746,8 +798,9 @@ export class MissionComp extends CCComp { * 新一波事件回调: * 1. 进入准备阶段。 * 2. 更新当前波数。 - * 3. 发放本波金币奖励。 - * 4. 刷新时间显示。 + * 3. 刷新时间显示。 + * + * 注意:金币不再按波次固定发放,改为怪物死亡时掉落(见 HeroAtkSystem)。 * * @param event 事件名 * @param data { wave: number } @@ -772,7 +825,7 @@ export class MissionComp extends CCComp { this.currentWave = wave; smc.vmdata.mission_data.level = wave; - this.grantPrepareCoinByWave(wave); + // 金币改为怪物死亡掉落(见 HeroAtkSystem),不再每波固定发放 this.lastTimeSecond = -1; this.clearTime = 0; this.update_time(); @@ -794,26 +847,6 @@ export class MissionComp extends CCComp { } } - /** - * 按波数发放固定金币奖励(外加技能加成)。 - * 第1波不发放(使用初始金币),从第2波起发放。 - * 仅在波数首次到达时发放,防止重复。 - * - * @param wave 当前波数 - */ - private grantPrepareCoinByWave(wave: number) { - if (wave <= 1) return; - if (wave <= this.lastPrepareCoinWave) return; - - // 波次金币公式: baseReward + (wave-1) * waveGrow,且不超过 prepareMaxCoinReward - const calculatedReward = FightSet.WAVE_COIN_BASE + (wave - 1) * FightSet.WAVE_COIN_GROW; - const waveReward = Math.min(FightSet.WAVE_COIN_MAX, calculatedReward); - const reward = MissionEconomy.executeWaveGold(waveReward); - - this.lastPrepareCoinWave = wave; - mLogger.log(this.debugMode, "MissionComp", "grantPrepareCoinByWave", { wave, waveReward, reward, coin: smc.vmdata.mission_data.coin }); - } - // ======================== 怪物数量管理 ======================== /** diff --git a/assets/script/game/map/RogueConfig.ts b/assets/script/game/map/RogueConfig.ts index f92a8a25..0e4cbd2c 100644 --- a/assets/script/game/map/RogueConfig.ts +++ b/assets/script/game/map/RogueConfig.ts @@ -23,11 +23,11 @@ import { FacSet } from "../common/config/GameSet"; * 待 HeroAttrsComp 升级后可启用 cd_mul / regen 扩展点。 */ export enum MonsterElite { - Elite = 0, // 精英:全面强化 - Berserk = 1, // 狂暴:高攻低血 - Shield = 2, // 护盾:坦克 - Regen = 3, // 再生:高血偏弱攻 - Swift = 4, // 疾风:平衡偏输出 + Elite = 0, // 精英:全面强化 + Berserk = 1, // 狂暴:高攻低血 + Shield = 2, // 护盾:坦克 + Regen = 3, // 再生:高血偏弱攻 + Swift = 4, // 疾风:平衡偏输出 } /** 个体强化效果定义 */ @@ -36,11 +36,11 @@ export const MonsterEliteSet: Record = { - [MonsterElite.Elite]: { name: "精英", hp_mul: 1.5, ap_mul: 1.30 }, - [MonsterElite.Berserk]: { name: "狂暴", hp_mul: 0.9, ap_mul: 1.60 }, - [MonsterElite.Shield]: { name: "护盾", hp_mul: 1.8, ap_mul: 0.80 }, - [MonsterElite.Regen]: { name: "再生", hp_mul: 1.3, ap_mul: 0.95 }, - [MonsterElite.Swift]: { name: "疾风", hp_mul: 1.0, ap_mul: 1.15 }, + [MonsterElite.Elite]: { name: "精英", hp_mul: 1.5, ap_mul: 1.30 }, + [MonsterElite.Berserk]: { name: "狂暴", hp_mul: 0.9, ap_mul: 1.60 }, + [MonsterElite.Shield]: { name: "护盾", hp_mul: 1.8, ap_mul: 0.80 }, + [MonsterElite.Regen]: { name: "再生", hp_mul: 1.3, ap_mul: 0.95 }, + [MonsterElite.Swift]: { name: "疾风", hp_mul: 1.0, ap_mul: 1.15 }, }; /** @@ -49,16 +49,16 @@ export const MonsterEliteSet: Record = { + [MonType.Melee]: { base: 1, boss: 0 }, + [MonType.Heavy]: { base: 2, boss: 0 }, + [MonType.Long]: { base: 2, boss: 0 }, + [MonType.Support]: { base: 3, boss: 0 }, + [MonType.Summoner]: { base: 3, boss: 0 }, + [MonType.Assassin]: { base: 3, boss: 0 }, + [MonType.MeleeBoss]: { base: 0, boss: 15 }, + [MonType.LongBoss]: { base: 0, boss: 15 }, +}; + +/** + * 获取指定怪物类型的金币掉落数量 + * @param monType 怪物类型(MonType) + * @param isBoss 是否为 Boss + * @returns 掉落的金币数量(≥0) + */ +export function getMonsterGoldDrop(monType: number, isBoss: boolean): number { + const cfg = MonsterGoldSet[monType]; + if (!cfg) return 0; + return Math.max(0, Math.floor(isBoss ? cfg.boss : cfg.base)); +} + // ======================== 3. 波次级强化库 ======================== /** @@ -95,11 +129,11 @@ export interface WaveEnchant { /** 波次强化库(硬编码) */ export const WaveEnchantLibrary: Record = { - frenzy: { id: "frenzy", name: "狂热浪潮", desc: "全员攻击力+25%", ap_mul: 1.25, weight: 10 }, - ironhide: { id: "ironhide", name: "铁皮大军", desc: "全员生命+40%", hp_mul: 1.40, weight: 10 }, - swift: { id: "swift", name: "疾风突袭", desc: "全员攻速+20%", cd_mul: 0.80, weight: 8 }, - nightmare: { id: "nightmare", name: "梦魇来袭", desc: "个体强化率×2,HP+15%", elite_rate_mul: 2.0, hp_mul: 1.15, weight: 5 }, - fortress: { id: "fortress", name: "钢铁堡垒", desc: "全员 HP+60% AP-15%", hp_mul: 1.60, ap_mul: 0.85, weight: 6 }, + frenzy: { id: "frenzy", name: "狂热浪潮", desc: "全员攻击力+25%", ap_mul: 1.25, weight: 10 }, + ironhide: { id: "ironhide", name: "铁皮大军", desc: "全员生命+40%", hp_mul: 1.40, weight: 10 }, + swift: { id: "swift", name: "疾风突袭", desc: "全员攻速+20%", cd_mul: 0.80, weight: 8 }, + nightmare: { id: "nightmare", name: "梦魇来袭", desc: "个体强化率×2,HP+15%", elite_rate_mul: 2.0, hp_mul: 1.15, weight: 5 }, + fortress: { id: "fortress", name: "钢铁堡垒", desc: "全员 HP+60% AP-15%", hp_mul: 1.60, ap_mul: 0.85, weight: 6 }, }; // ======================== 4. 小队模板库 ======================== @@ -120,12 +154,12 @@ export interface SquadConfig { /** 小队模板库(硬编码) */ export const SquadLibrary: Record = { - melee_grunt: { id: "melee_grunt", name: "近战步兵组", weight: 10, slots: [{ type: MonType.Melee, count: 3 }] }, - assassin_squad: { id: "assassin_squad", name: "刺客突袭组", weight: 6, slots: [{ type: MonType.Assassin, count: 2 }, { type: MonType.Support, count: 1 }] }, - mixed_balanced: { id: "mixed_balanced", name: "平衡混合组", weight: 8, slots: [{ type: MonType.Melee, count: 1 }, { type: MonType.Long, count: 1 }, { type: MonType.Heavy, count: 1 }] }, - long_line: { id: "long_line", name: "远程线列组", weight: 7, slots: [{ type: MonType.Long, count: 2 }, { type: MonType.Support, count: 1 }] }, - heavy_shield: { id: "heavy_shield", name: "重盾堡垒组", weight: 5, slots: [{ type: MonType.Heavy, count: 1 }, { type: MonType.Melee, count: 2 }] }, - summoner_cult: { id: "summoner_cult", name: "召唤教派组", weight: 4, slots: [{ type: MonType.Summoner, count: 1 }, { type: MonType.Long, count: 2 }] }, + melee_grunt: { id: "melee_grunt", name: "近战步兵组", weight: 10, slots: [{ type: MonType.Melee, count: 3 }] }, + assassin_squad: { id: "assassin_squad", name: "刺客突袭组", weight: 6, slots: [{ type: MonType.Assassin, count: 2 }, { type: MonType.Support, count: 1 }] }, + mixed_balanced: { id: "mixed_balanced", name: "平衡混合组", weight: 8, slots: [{ type: MonType.Melee, count: 1 }, { type: MonType.Long, count: 1 }, { type: MonType.Heavy, count: 1 }] }, + long_line: { id: "long_line", name: "远程线列组", weight: 7, slots: [{ type: MonType.Long, count: 2 }, { type: MonType.Support, count: 1 }] }, + heavy_shield: { id: "heavy_shield", name: "重盾堡垒组", weight: 5, slots: [{ type: MonType.Heavy, count: 1 }, { type: MonType.Melee, count: 2 }] }, + summoner_cult: { id: "summoner_cult", name: "召唤教派组", weight: 4, slots: [{ type: MonType.Summoner, count: 1 }, { type: MonType.Long, count: 2 }] }, }; // ======================== 5. 波次配置表 ======================== @@ -152,43 +186,43 @@ export interface WaveConfig { */ export const WaveConfigs: Record = { // ===== 教学期 ===== - 1: { base_count: 3, squad_pool: ["melee_grunt"] }, - 2: { base_count: 4, squad_pool: ["melee_grunt", "mixed_balanced"] }, - 3: { base_count: 5, squad_pool: ["melee_grunt", "mixed_balanced", "long_line"] }, - 4: { base_count: 6, squad_pool: ["mixed_balanced", "long_line", "heavy_shield"] }, + 1: { base_count: 3, squad_pool: ["melee_grunt"] }, + 2: { base_count: 4, squad_pool: ["melee_grunt", "mixed_balanced"] }, + 3: { base_count: 5, squad_pool: ["melee_grunt", "mixed_balanced", "long_line"] }, + 4: { base_count: 6, squad_pool: ["mixed_balanced", "long_line", "heavy_shield"] }, // ===== 第一 Boss ===== - 5: { base_count: 7, squad_pool: ["melee_grunt", "mixed_balanced", "heavy_shield"], boss_wave: true }, + 5: { base_count: 7, squad_pool: ["melee_grunt", "mixed_balanced", "heavy_shield"], boss_wave: true }, // ===== 引入波次强化 ===== - 6: { base_count: 7, squad_pool: ["assassin_squad", "long_line", "mixed_balanced"], enchant_pool: ["frenzy"], elite_base_rate: 0.10 }, - 7: { base_count: 8, squad_pool: ["melee_grunt", "heavy_shield", "long_line"], enchant_pool: ["ironhide"], elite_base_rate: 0.12 }, - 8: { base_count: 8, squad_pool: ["assassin_squad", "mixed_balanced", "summoner_cult"], enchant_pool: ["frenzy", "swift"], elite_base_rate: 0.14 }, - 9: { base_count: 9, squad_pool: ["heavy_shield", "long_line", "mixed_balanced"], enchant_pool: ["ironhide", "frenzy"],elite_base_rate: 0.16 }, + 6: { base_count: 7, squad_pool: ["assassin_squad", "long_line", "mixed_balanced"], enchant_pool: ["frenzy"], elite_base_rate: 0.10 }, + 7: { base_count: 8, squad_pool: ["melee_grunt", "heavy_shield", "long_line"], enchant_pool: ["ironhide"], elite_base_rate: 0.12 }, + 8: { base_count: 8, squad_pool: ["assassin_squad", "mixed_balanced", "summoner_cult"], enchant_pool: ["frenzy", "swift"], elite_base_rate: 0.14 }, + 9: { base_count: 9, squad_pool: ["heavy_shield", "long_line", "mixed_balanced"], enchant_pool: ["ironhide", "frenzy"], elite_base_rate: 0.16 }, // ===== 第二 Boss ===== - 10: { base_count: 10, squad_pool: ["melee_grunt", "assassin_squad", "mixed_balanced"], enchant_pool: ["frenzy", "ironhide"],boss_wave: true, elite_base_rate: 0.18 }, + 10: { base_count: 10, squad_pool: ["melee_grunt", "assassin_squad", "mixed_balanced"], enchant_pool: ["frenzy", "ironhide"], boss_wave: true, elite_base_rate: 0.18 }, // ===== 组合多样化 ===== - 11: { base_count: 10, squad_pool: ["assassin_squad", "long_line", "summoner_cult", "mixed_balanced"],enchant_pool: ["swift", "frenzy"], elite_base_rate: 0.18 }, - 12: { base_count: 10, squad_pool: ["heavy_shield", "melee_grunt", "mixed_balanced", "long_line"], enchant_pool: ["ironhide"], elite_base_rate: 0.20 }, - 13: { base_count: 11, squad_pool: ["assassin_squad", "summoner_cult", "mixed_balanced"], enchant_pool: ["frenzy", "nightmare"], elite_base_rate: 0.22 }, - 14: { base_count: 11, squad_pool: ["heavy_shield", "long_line", "melee_grunt"], enchant_pool: ["ironhide", "swift"], elite_base_rate: 0.24 }, + 11: { base_count: 10, squad_pool: ["assassin_squad", "long_line", "summoner_cult", "mixed_balanced"], enchant_pool: ["swift", "frenzy"], elite_base_rate: 0.18 }, + 12: { base_count: 10, squad_pool: ["heavy_shield", "melee_grunt", "mixed_balanced", "long_line"], enchant_pool: ["ironhide"], elite_base_rate: 0.20 }, + 13: { base_count: 11, squad_pool: ["assassin_squad", "summoner_cult", "mixed_balanced"], enchant_pool: ["frenzy", "nightmare"], elite_base_rate: 0.22 }, + 14: { base_count: 11, squad_pool: ["heavy_shield", "long_line", "melee_grunt"], enchant_pool: ["ironhide", "swift"], elite_base_rate: 0.24 }, // ===== 第三 Boss(中期高潮) ===== - 15: { base_count: 11, squad_pool: ["assassin_squad", "mixed_balanced", "summoner_cult"], enchant_pool: ["nightmare", "fortress"], boss_wave: true, elite_base_rate: 0.25 }, + 15: { base_count: 11, squad_pool: ["assassin_squad", "mixed_balanced", "summoner_cult"], enchant_pool: ["nightmare", "fortress"], boss_wave: true, elite_base_rate: 0.25 }, // ===== 高压阶段 ===== - 16: { base_count: 11, squad_pool: ["assassin_squad", "heavy_shield", "long_line"], enchant_pool: ["frenzy", "swift"], elite_base_rate: 0.26 }, - 17: { base_count: 11, squad_pool: ["melee_grunt", "summoner_cult", "mixed_balanced"], enchant_pool: ["ironhide", "frenzy"], elite_base_rate: 0.27 }, - 18: { base_count: 12, squad_pool: ["assassin_squad", "long_line", "heavy_shield"], enchant_pool: ["nightmare", "swift"], elite_base_rate: 0.28 }, - 19: { base_count: 12, squad_pool: ["mixed_balanced", "summoner_cult", "melee_grunt"], enchant_pool: ["fortress"], elite_base_rate: 0.28 }, - 20: { base_count: 12, squad_pool: ["assassin_squad", "heavy_shield", "long_line"], enchant_pool: ["frenzy", "ironhide"], boss_wave: true, elite_base_rate: 0.30 }, - 21: { base_count: 12, squad_pool: ["melee_grunt", "assassin_squad", "summoner_cult"], enchant_pool: ["swift", "nightmare"], elite_base_rate: 0.30 }, - 22: { base_count: 12, squad_pool: ["heavy_shield", "long_line", "mixed_balanced"], enchant_pool: ["ironhide", "fortress"], elite_base_rate: 0.30 }, - 23: { base_count: 12, squad_pool: ["assassin_squad", "summoner_cult", "melee_grunt"], enchant_pool: ["frenzy", "nightmare"], elite_base_rate: 0.32 }, - 24: { base_count: 12, squad_pool: ["mixed_balanced", "heavy_shield", "long_line"], enchant_pool: ["fortress", "swift"], elite_base_rate: 0.32 }, + 16: { base_count: 11, squad_pool: ["assassin_squad", "heavy_shield", "long_line"], enchant_pool: ["frenzy", "swift"], elite_base_rate: 0.26 }, + 17: { base_count: 11, squad_pool: ["melee_grunt", "summoner_cult", "mixed_balanced"], enchant_pool: ["ironhide", "frenzy"], elite_base_rate: 0.27 }, + 18: { base_count: 12, squad_pool: ["assassin_squad", "long_line", "heavy_shield"], enchant_pool: ["nightmare", "swift"], elite_base_rate: 0.28 }, + 19: { base_count: 12, squad_pool: ["mixed_balanced", "summoner_cult", "melee_grunt"], enchant_pool: ["fortress"], elite_base_rate: 0.28 }, + 20: { base_count: 12, squad_pool: ["assassin_squad", "heavy_shield", "long_line"], enchant_pool: ["frenzy", "ironhide"], boss_wave: true, elite_base_rate: 0.30 }, + 21: { base_count: 12, squad_pool: ["melee_grunt", "assassin_squad", "summoner_cult"], enchant_pool: ["swift", "nightmare"], elite_base_rate: 0.30 }, + 22: { base_count: 12, squad_pool: ["heavy_shield", "long_line", "mixed_balanced"], enchant_pool: ["ironhide", "fortress"], elite_base_rate: 0.30 }, + 23: { base_count: 12, squad_pool: ["assassin_squad", "summoner_cult", "melee_grunt"], enchant_pool: ["frenzy", "nightmare"], elite_base_rate: 0.32 }, + 24: { base_count: 12, squad_pool: ["mixed_balanced", "heavy_shield", "long_line"], enchant_pool: ["fortress", "swift"], elite_base_rate: 0.32 }, // ===== 第四 Boss ===== - 25: { base_count: 12, squad_pool: ["assassin_squad", "summoner_cult", "heavy_shield"], enchant_pool: ["nightmare", "fortress"], boss_wave: true, elite_base_rate: 0.35 }, + 25: { base_count: 12, squad_pool: ["assassin_squad", "summoner_cult", "heavy_shield"], enchant_pool: ["nightmare", "fortress"], boss_wave: true, elite_base_rate: 0.35 }, // ===== 终极阶段 ===== - 26: { base_count: 12, squad_pool: ["melee_grunt", "assassin_squad", "long_line", "summoner_cult"], enchant_pool: ["frenzy", "nightmare"], elite_base_rate: 0.35 }, - 27: { base_count: 12, squad_pool: ["heavy_shield", "mixed_balanced", "summoner_cult"], enchant_pool: ["ironhide", "fortress"], elite_base_rate: 0.38 }, - 28: { base_count: 12, squad_pool: ["assassin_squad", "long_line", "melee_grunt"], enchant_pool: ["swift", "nightmare"], elite_base_rate: 0.40 }, - 29: { base_count: 12, squad_pool: ["mixed_balanced", "heavy_shield", "summoner_cult"], enchant_pool: ["frenzy", "ironhide", "fortress"], elite_base_rate: 0.42 }, + 26: { base_count: 12, squad_pool: ["melee_grunt", "assassin_squad", "long_line", "summoner_cult"], enchant_pool: ["frenzy", "nightmare"], elite_base_rate: 0.35 }, + 27: { base_count: 12, squad_pool: ["heavy_shield", "mixed_balanced", "summoner_cult"], enchant_pool: ["ironhide", "fortress"], elite_base_rate: 0.38 }, + 28: { base_count: 12, squad_pool: ["assassin_squad", "long_line", "melee_grunt"], enchant_pool: ["swift", "nightmare"], elite_base_rate: 0.40 }, + 29: { base_count: 12, squad_pool: ["mixed_balanced", "heavy_shield", "summoner_cult"], enchant_pool: ["frenzy", "ironhide", "fortress"], elite_base_rate: 0.42 }, // ===== 最终 Boss ===== 30: { base_count: 12, squad_pool: ["assassin_squad", "heavy_shield", "summoner_cult", "mixed_balanced"], enchant_pool: ["frenzy", "ironhide", "swift", "nightmare", "fortress"], boss_wave: true, elite_base_rate: 0.45 }, };