master
humingxing 6 hours ago
parent d8c3eb53b3
commit 24c424810a
  1. 6
      assets/bundles/first/config/Const.json
  2. 2
      assets/res/font/str.txt
  3. 51
      assets/script/battle/bag/BattleBagSession.ts
  4. 23
      assets/script/battle/view/CocosBagBoardView.ts
  5. 4
      assets/script/battle/view/TextListLandHost.ts
  6. 2
      assets/script/game/GameDataEnums.ts
  7. 48
      assets/script/ui/BattleFail/UIBattleFail.ts
  8. 27
      assets/script/ui/BattleGame/BattleGameKillPeachPresenter.ts
  9. 101
      assets/script/ui/BattleGame/BattlePeachCoinFlyPresenter.ts
  10. 137
      assets/script/ui/BattleGame/UIBattleGame.ts
  11. 10
      assets/script/ui/BattleSuccess/BattleSuccessCoinFlyPresenter.ts
  12. 3
      assets/script/ui/BattleSuccess/BattleSuccessOpenAnim.ts
  13. 37
      assets/script/ui/BattleSuccess/UIBattleSuccess.ts

@ -226,5 +226,11 @@
"desc": "关卡技能宝箱个数",
"para": 6,
"para1": ""
},
{
"id": 39,
"desc": "回收金币",
"para": 5,
"para1": ""
}
]

File diff suppressed because one or more lines are too long

@ -44,6 +44,7 @@ import type { BagRollEntry } from './BagWeaponPool';
import { buildGmWeaponPresetQueue } from '../debug/GmBattlePreset';
import type { ProjectBattleHandle } from '../runtime/BattleRuntime';
import { TableNames } from '../../game/ConfigTableData';
import { ConstNumType } from '../../game/GameDataEnums';
import { RankDataManager } from '../../manager/RankDataManager';
import type { DailyShopParsed } from '../../manager/DailyShopTypes';
import {
@ -100,6 +101,14 @@ export class BattleBagSession {
* @param ownerPlayerId 归属玩家
*/
onUnlockCoinFly: ((gridIndex: number, coin: number, ownerPlayerId: number) => void) | null = null;
/**
* 数字拖垃圾桶回收发金币后(金币已入账;飞币等字飞到垃圾桶后再播)。
* @param gold 本次金币
* @param ownerPlayerId 归属玩家
*/
onGlyphRecycleGold: ((gold: number, ownerPlayerId: number) => void) | null = null;
/** 待播飞币的回收金币(入账后写入,飞到 ui_delete 播完清零) */
pendingRecycleGoldFx = 0;
/**
* 三选一回调:铲子铲开宝箱格后触发(dugChestCount 为本局已铲宝箱数)。
* @param ownerPlayerId 归属玩家
@ -942,13 +951,16 @@ export class BattleBagSession {
}
/**
* 地图字拖到 ui_delete 时删掉,不退回字池。
* 地图字拖到 ui_delete 时删掉,不退回字池;数字回收发 Const39 金币。
* @param gridIndex 来源地图格
*/
discardMapGlyph(gridIndex: number): boolean {
const tile = this.merge.map.getTile(gridIndex);
const weapon2Id = tile?.weapon2Id ?? 0;
const before = this.merge.map.getCellKind(gridIndex);
const ok = this.merge.discardDraggedMapGlyph(gridIndex);
if (!ok) return false;
this._tryGrantGlyphRecycleGold(weapon2Id);
const next = this.merge.map.getCellKind(gridIndex);
if (this._walkabilityChanged(before, next)) {
const becameBlocked = before === MapCellKind.Walkable && next !== MapCellKind.Walkable;
@ -961,7 +973,7 @@ export class BattleBagSession {
}
/**
* 背包字拖到 ui_delete 时删掉,不退回字池。
* 背包字拖到 ui_delete 时删掉,不退回字池;数字回收发 Const39 金币。
* @param slotIndex 来源槽
*/
discardBagSlot(slotIndex: number): boolean {
@ -970,11 +982,46 @@ export class BattleBagSession {
console.log('[BattleBagSession] 空槽无法删除', slotIndex);
return false;
}
const weapon2Id = rec.weapon2Id;
this.merge.bag.clearSlot(slotIndex);
this._tryGrantGlyphRecycleGold(weapon2Id);
GEvent.Ins.emit(GEvent.UpdateBestPlace);
return true;
}
/**
* 五行数字回收:读 Const39 加金币并通知飞币。
* @param weapon2Id Text.id
*/
private _tryGrantGlyphRecycleGold(weapon2Id: number): void {
const wid = Math.floor(Number(weapon2Id) || 0);
if (wid <= 0) return;
const w2Type = WeaponComposeConfigProvider.instance.getWeapon2Type(wid);
if (!isElementGlyphType(w2Type)) return;
if (typeof gg === 'undefined' || !gg.data?.table?.getConst) {
console.warn('[BattleBagSession] 回收金币:gg.data.table 未就绪');
return;
}
const gold = Math.floor(Number(gg.data.table.getConst(ConstNumType.GlyphRecycleGold)) || 0);
if (gold <= 0) {
console.warn('[BattleBagSession] Const39 回收金币无效', gold);
return;
}
const battle = gg.game?.CurentBattle as ProjectBattleHandle | null | undefined;
const slot = battle?.session?.players?.[this._ownerPlayerId]
?? (this._ownerPlayerId === 0 ? battle?.localSlot : undefined);
if (!slot) {
console.warn('[BattleBagSession] 回收金币:player slot 缺失', this._ownerPlayerId);
return;
}
slot.addGold(gold);
if (this._ownerPlayerId === (battle?.localPlayerId ?? 0)) {
battle.CurentCoinNum = slot.gold;
}
this.pendingRecycleGoldFx = gold;
this.onGlyphRecycleGold?.(gold, this._ownerPlayerId);
}
drop(source: DragSource, target: DropTarget): PlaceRejectReason | null {
const toolHit = this._tryApplyBagTool(source, target);
if (toolHit !== 'skip') {

@ -137,6 +137,8 @@ export class CocosBagBoardView {
private _bagList: Node | null = null;
/** 拖字删除落点(BattleGame ui_delete) */
private _deleteNode: Node | null = null;
/** 字飞到垃圾桶后播回收金币飞币 */
private _onRecycleGoldFx: ((gold: number) => void) | null = null;
/** ui_textList 开启 / 待解锁 / 未解锁 */
private readonly _land = new TextListLandHost();
/** 开格展示字块补显重试帧计数(textGrid 预制体异步加载未就绪时留空格) */
@ -1316,6 +1318,14 @@ export class CocosBagBoardView {
this._deleteNode = node?.isValid ? node : null;
}
/**
* 字飞到垃圾桶后播回收金币飞币。
* @param handler 入账金币数量;传 null 清除
*/
setRecycleGoldFxHandler(handler: ((gold: number) => void) | null): void {
this._onRecycleGoldFx = handler;
}
bind(
battle: ProjectBattleHandle,
map: MapManager,
@ -1497,6 +1507,7 @@ export class CocosBagBoardView {
this.setInkstoneWorldPosProvider(null);
this.setOtherBagAnchor(null);
this.setDeleteNode(null);
this.setRecycleGoldFxHandler(null);
this.setBagEntranceHandler(null, null);
this.setSkillPeachFlyHandler(null);
this.setAfterShovelDig(null);
@ -4299,7 +4310,7 @@ export class CocosBagBoardView {
}
/**
* 飞到 ui_delete 后回收字块,并补上推迟的盘面刷新。
* 飞到 ui_delete 后回收字块,并补上推迟的盘面刷新;有回收金币则播飞币。
* @param grid 飞行中的字
*/
private _finishDiscardFlight(grid: TextGrid): void {
@ -4309,6 +4320,16 @@ export class CocosBagBoardView {
this._flushAllPendingRebuilds();
this._refreshDragEnabled();
this._refreshCanUpgradeHints();
this._playPendingRecycleGoldFx();
}
/** 字到达垃圾桶后,消费 pendingRecycleGoldFx 播飞币。 */
private _playPendingRecycleGoldFx(): void {
const session = this._bagSession;
const gold = Math.floor(Number(session?.pendingRecycleGoldFx) || 0);
if (!session || gold <= 0) return;
session.pendingRecycleGoldFx = 0;
this._onRecycleGoldFx?.(gold);
}
/**

@ -35,7 +35,7 @@ export interface TextListPromoteResult {
* 按关卡 normalland / tolockland 管理 ui_textList 格子状态与拖入解锁。
* normalland 为开启且开局不放字;tolockland 为待开启(有字、不能拖、靠同类型同等级合成打开)。
* 待开启被合成后自身变为开启,上下左右的未解锁格变为待开启。其余为未解锁。
* 每局记录解锁次数,bind 时清零。第 1 次读 Const 39 的 index 0,依次类推;超出则用最后一项。
* 每局记录解锁次数,bind 时清零。第 1 次读 Const26(UnlockLandLevelAdd)的 index 0,依次类推;超出则用最后一项。
* 该加值累加到本次上下左右新待解锁格的数字等级上。
* grid1-24 都是普通地块(含 grid17、grid23、grid24)。名为 temp 的节点跳过。
* 格子上的字块是背包槽位数据(BattleBagSession),由视图按槽位记录生成标准 TextGrid;
@ -52,7 +52,7 @@ export class TextListLandHost {
private readonly _open: number[] = [];
/** 待解锁下标(开启但不能拖) */
private readonly _gate: number[] = [];
/** 本局已解锁次数;bind / clear 清零。下次解锁用这个下标读 Const 39 */
/** 本局已解锁次数;bind / clear 清零。下次解锁用这个下标读 Const26 */
private _unlockCount = 0;
/** 最近一次解锁的邻格等级加值,供本条解锁日志使用 */
private _lastLevelAdd = 0;

@ -566,6 +566,8 @@ export enum ConstNumType {
BattleBuffAdRefreshCount = 37,
/** 关卡技能宝箱个数 para */
BattleDigChestCount = 38,
/** 数字拖垃圾桶回收获得金币 para */
GlyphRecycleGold = 39,
}
export enum TaskType {

@ -33,6 +33,10 @@ export class UIBattleFail extends Auto_BattleFail {
private _coinFly: BattleSuccessCoinFlyPresenter | null = null;
/** 领取流程中 */
private _claiming = false;
/** 打开结算时缓存的波次(广告回调时 CurentBattle 可能已空) */
private _settleWaveNum = 1;
/** 打开结算时缓存的关卡 id */
private _settleLevelId = 0;
/** 降星动画目标 index(star1~star5) */
private _starDropIndex = 0;
/** 降星动画结束后星条点亮数 */
@ -71,22 +75,24 @@ export class UIBattleFail extends Auto_BattleFail {
onInit(): void {
this.addNum = 1;
this._claiming = false;
this.btnGet.active = true;
this.btnADGet.active = true;
const battle = gg.game.CurentBattle;
this._settleWaveNum = Math.max(1, Math.floor(Number(battle?.CurentWaveNum) || 1));
this._settleLevelId = Math.floor(Number(battle?.LevelConfigId) || gg.game.CurentSelectLevelId || 0);
if (this.btnGet?.isValid) this.btnGet.active = true;
if (this.btnADGet?.isValid) this.btnADGet.active = true;
BattleAudio.playFail();
const starsBefore = RankDataManager.getPlayerRankStars();
MatchWinStats.record(false, gg.game.CurentBattle?.opponentRankId ?? 0);
MatchWinStats.record(false, battle?.opponentRankId ?? 0);
gg.sdk.reportDY('inLevel', `${gg.game.getReportDYRankLabel()}-失败`);
void this._refreshRankUi(starsBefore);
if (gg.game.CurentBattle.LevelConfigId == gg.game.CurentSelectLevelId) {
if (this._settleLevelId === gg.game.CurentSelectLevelId) {
gg.data.setStatistics(StatisticsType.SuccessContinuousCount, 0);
}
const curWaveNum = gg.game.CurentBattle.CurentWaveNum;
gg.sdk.reportDY("inLevel", `${gg.game.getReportDYBattleCountLabel(curWaveNum)}-过关失败`);
gg.sdk.reportDY("inLevel", `${gg.game.getReportDYBattleCountLabel(this._settleWaveNum)}-过关失败`);
const levelId = gg.sdk.get30rLevelId();
gg.sdk.report30r('LEVEL_LOSE', {
level_id: levelId,
@ -99,7 +105,7 @@ export class UIBattleFail extends Auto_BattleFail {
this._refreshMoneyDisplay();
this._refreshCoinRewardDisplay();
if (!gg.game.CurentBattle.IsWatchAd && gg.game.CurentSelectLevelId >= 3) {
if (!battle?.IsWatchAd && gg.game.CurentSelectLevelId >= 3) {
gg.sdk.showInterstitialAd(() => { }, () => { }, true);
}
}
@ -198,7 +204,15 @@ export class UIBattleFail extends Auto_BattleFail {
/** 仅发放表配 loserreward;局内击杀掉落失败不获得 */
showReward(): void {
const levelId = gg.game.CurentBattle.LevelConfigId;
const levelId = this._settleLevelId > 0
? this._settleLevelId
: (gg.game.CurentBattle?.LevelConfigId ?? gg.game.CurentSelectLevelId);
if (!(levelId > 0)) {
console.warn('[BattleFail] 结算奖励缺少关卡 id');
this._pendingCoin = 0;
this._dataList = [];
return;
}
const rewardStr = gg.data.project.getLevelRewardStr(levelId, false);
if (!rewardStr) {
console.log('[BattleFail] Level 无 loserreward', levelId);
@ -272,8 +286,7 @@ export class UIBattleFail extends Auto_BattleFail {
click_btnGet(): void {
if (this._claiming) return;
const curWaveNum = gg.game.CurentBattle.CurentWaveNum;
gg.sdk.reportDY("inLevel", `${gg.game.getReportDYBattleCountLabel(curWaveNum)}-失败领取奖励`);
gg.sdk.reportDY("inLevel", `${gg.game.getReportDYBattleCountLabel(this._settleWaveNum)}-失败领取奖励`);
this._claimWithCoinFly();
}
@ -281,8 +294,8 @@ export class UIBattleFail extends Auto_BattleFail {
private _claimWithCoinFly(): void {
if (this._claiming) return;
this._claiming = true;
this.btnGet.active = false;
this.btnADGet.active = false;
if (this.btnGet?.isValid) this.btnGet.active = false;
if (this.btnADGet?.isValid) this.btnADGet.active = false;
const coinGain = Math.floor(this._pendingCoin * this.addNum);
const icon = this._coinIconNode();
@ -321,19 +334,22 @@ export class UIBattleFail extends Auto_BattleFail {
if (!this.isValid) return;
this._claiming = false;
gg.game.returnedFromBattleFail = true;
gg.game.returnedFromBattleFailChapterId = gg.game.CurentBattle.LevelConfigId;
const levelId = this._settleLevelId > 0
? this._settleLevelId
: (gg.game.CurentBattle?.LevelConfigId ?? gg.game.CurentSelectLevelId);
gg.game.returnedFromBattleFailChapterId = levelId;
gg.game.enterMain(UIID.Home);
}
click_btnADGet(): void {
if (this._claiming) return;
gg.sdk.showVideoAd((res) => {
if (!this.isValid) return;
if (res) {
const curWaveNum = gg.game.CurentBattle.CurentWaveNum;
gg.sdk.reportDY("inLevel", `${gg.game.getReportDYBattleCountLabel(curWaveNum)}-AD-失败双倍领取奖励`);
gg.sdk.reportDY("inLevel", `${gg.game.getReportDYBattleCountLabel(this._settleWaveNum)}-AD-失败双倍领取奖励`);
this.addNum = 2;
this._refreshCoinRewardDisplay();
gg.game.CurentBattle.IsWatchAd = true;
if (gg.game.CurentBattle) gg.game.CurentBattle.IsWatchAd = true;
this._claimWithCoinFly();
} else {
gg.ui.showToast('观看视频失败');

@ -1,44 +1,38 @@
import type { EntityId } from 'db://assets/mx/module/battle';
import type { Vec3 } from 'cc';
import { BattleBuffCarryStore } from '../../manager/battleBuff/BattleBuffCarryStore';
import { rollCarriedKillCoinBonus } from '../../manager/battleBuff/BattleBuffPassiveSkillUtil';
import type { ProjectBattleHandle } from '../../battle/runtime/BattleRuntime';
import type { CocosBagBoardView } from '../../battle/view/CocosBagBoardView';
import type { BattlePeachCoinFlyPresenter } from './BattlePeachCoinFlyPresenter';
/**
* 赏金令:击杀小怪概率额外金币飞币表现。
*/
export class BattleGameKillPeachPresenter {
private _battle: ProjectBattleHandle | null = null;
private _bagView: CocosBagBoardView | null = null;
private _coinFly: BattlePeachCoinFlyPresenter | null = null;
private _resolveDeathWorldPos: ((victim: EntityId) => import('cc').Vec3 | null) | null = null;
private _playLocalCoinFly: ((fromWorld: Readonly<Vec3>, amount: number) => void) | null = null;
private _resolveDeathWorldPos: ((victim: EntityId) => Vec3 | null) | null = null;
/**
* 绑定击杀事件上下文。
* @param battle 战斗
* @param bagView 棋盘
* @param coinFly 飞币
* @param playLocalCoinFly 本机飞币(挂起 UI,到达后刷新)
* @param resolveDeathWorldPos 死亡世界坐标
*/
bind(
battle: ProjectBattleHandle,
bagView: CocosBagBoardView | null,
coinFly: BattlePeachCoinFlyPresenter | null,
resolveDeathWorldPos: (victim: EntityId) => import('cc').Vec3 | null,
playLocalCoinFly: (fromWorld: Readonly<Vec3>, amount: number) => void,
resolveDeathWorldPos: (victim: EntityId) => Vec3 | null,
): void {
this.unbind();
this._battle = battle;
this._bagView = bagView;
this._coinFly = coinFly;
this._playLocalCoinFly = playLocalCoinFly;
this._resolveDeathWorldPos = resolveDeathWorldPos;
}
/** 解除绑定 */
unbind(): void {
this._battle = null;
this._bagView = null;
this._coinFly = null;
this._playLocalCoinFly = null;
this._resolveDeathWorldPos = null;
}
@ -55,13 +49,12 @@ export class BattleGameKillPeachPresenter {
if (ownerId !== battle.localPlayerId) return;
const bonus = rollCarriedKillCoinBonus(BattleBuffCarryStore.getPassives(ownerId));
if (bonus <= 0) return;
battle.addPlayerGold(ownerId, bonus);
const from = this._resolveDeathWorldPos?.(victim);
if (!from) {
console.log('[KillCoin] 击杀额外金币无死亡坐标', victim);
battle.addPlayerGold(ownerId, bonus);
return;
}
this._coinFly?.playKillDrop(from, bonus);
battle.addPlayerGold(ownerId, bonus);
this._playLocalCoinFly?.(from, bonus);
}
}

@ -30,8 +30,6 @@ const PLUS_CHILD = 'dropPlusOne';
export class BattlePeachCoinFlyPresenter {
private _moneyNode: Node | null = null;
private _flyLayer: Node | null = null;
private _flyPending = 0;
private _flyComplete: (() => void) | null = null;
/** 掉落 +1 飘字 */
private readonly _plusQueue: Node[] = [];
/** 波次金币还待生成的枚数 */
@ -52,6 +50,8 @@ export class BattlePeachCoinFlyPresenter {
private _waveSpawnFinished = false;
/** 正在冒出并等待一起飞 */
private _waveAwaitFly = false;
/** 波次金币全部飞到 btnCoin 后回调 */
private _waveOnAllArrived: (() => void) | null = null;
/** 绑定飞向目标(btnCoin/金币)与飞行层(ui_fly) */
bind(moneyNode: Node | null | undefined, flyLayer: Node | null | undefined): void {
@ -65,8 +65,7 @@ export class BattlePeachCoinFlyPresenter {
this._recycleAllPlus();
this._moneyNode = null;
this._flyLayer = null;
this._flyPending = 0;
this._flyComplete = null;
this._waveOnAllArrived = null;
}
/**
@ -109,19 +108,23 @@ export class BattlePeachCoinFlyPresenter {
* @param startWorld 起点世界坐标
* @param showGold 单枚金币上的显示数量
* @param count 生成枚数
* @param onAllArrived 全部到达 btnCoin 后回调
*/
playWaveCoin(flyLayer: Node, startWorld: Readonly<Vec3>, showGold: number, count: number): void {
playWaveCoin(flyLayer: Node, startWorld: Readonly<Vec3>, showGold: number, count: number, onAllArrived: (() => void) | null = null): void {
if (!flyLayer?.isValid) {
console.log('[BattlePeachCoinFly] 波次金币缺少 ui_fly');
onAllArrived?.();
return;
}
const amount = Math.floor(showGold);
const flyCount = Math.floor(count);
if (!(amount > 0) || !(flyCount > 0)) {
console.log('[BattlePeachCoinFly] 波次金币数量非法', showGold, count);
onAllArrived?.();
return;
}
this._stopWaveSpawn();
this._waveOnAllArrived = onAllArrived;
this._waveLeft = flyCount;
this._waveShowGold = amount;
this._waveLayer = flyLayer;
@ -197,11 +200,22 @@ export class BattlePeachCoinFlyPresenter {
const end = money?.isValid ? money.worldPosition.clone() : null;
const coins = this._takeWaveCoins();
this._clearWaveSpawn();
const done = this._waveOnAllArrived;
this._waveOnAllArrived = null;
if (!end) {
console.log('[BattlePeachCoinFly] 波次金币缺少飞向目标');
for (let i = 0; i < coins.length; i++) this._recycleWaveCoin(coins[i]);
done?.();
return;
}
const batch = { left: 0 };
const onOneDone = (peach?: Node): void => {
if (peach?.isValid) this._putPlusChild(peach);
batch.left--;
if (batch.left > 0) return;
done?.();
GEvent.Ins.emit(GEvent.UpdateGameCoinNum);
};
for (let i = 0; i < coins.length; i++) {
const node = coins[i];
if (!node?.isValid) continue;
@ -210,8 +224,10 @@ export class BattlePeachCoinFlyPresenter {
this._recycleWaveCoin(node);
continue;
}
comp.flyTo(end, this._onPeachDone);
batch.left++;
comp.flyTo(end, onOneDone);
}
if (batch.left <= 0) done?.();
}
/**
@ -260,6 +276,9 @@ export class BattlePeachCoinFlyPresenter {
const coins = this._takeWaveCoins();
for (let i = 0; i < coins.length; i++) this._recycleWaveCoin(coins[i]);
this._clearWaveSpawn();
const done = this._waveOnAllArrived;
this._waveOnAllArrived = null;
done?.();
}
/** 清空波次金币生成状态,不回收已经开始飞行的金币 */
@ -273,21 +292,26 @@ export class BattlePeachCoinFlyPresenter {
}
/**
* 击杀掉落飞桃:先弹跳再飞向 ui_money,不占用回收完成回调。
* 击杀掉落飞桃:先弹跳再飞向 btnCoin。
* @param startWorld 死亡世界坐标
* @param peachCount Monster.peach
* @param onAllArrived 全部到达后回调(刷新金币 UI)
*/
playKillDrop(startWorld: Readonly<Vec3>, peachCount: number): void {
if (peachCount <= 0) return;
playKillDrop(startWorld: Readonly<Vec3>, peachCount: number, onAllArrived: (() => void) | null = null): void {
if (peachCount <= 0) {
onAllArrived?.();
return;
}
BattleAudio.playCoinFly();
const n = this._spawnCoinFlies(startWorld.clone(), peachCount, null, true);
const n = this._spawnCoinFlies(startWorld.clone(), peachCount, onAllArrived, true);
if (n <= 0) {
console.log('[BattlePeachCoinFly] 击杀掉桃未生成');
onAllArrived?.();
}
}
/**
* AI 击杀掉落:原地弹跳后回池,不飞向本机 ui_money。
* AI 击杀掉落:原地弹跳后回池,不飞向本机 btnCoin。
* @param startWorld 死亡世界坐标
* @param peachCount Monster.peach
*/
@ -300,38 +324,33 @@ export class BattlePeachCoinFlyPresenter {
}
/**
* 乾坤袋回收:与怪物死亡产桃相同(coinFly 弹跳 + battleText「+1」飞向 ui_money)。
* 乾坤袋回收:与怪物死亡产桃相同(coinFly 弹跳 + battleText「+1」飞向 btnCoin)。
* @param startWorld 起点世界坐标
* @param peachCount 回收蟠桃数量
* @param onAllArrived 全部到达 ui_money 后回调
* @param onAllArrived 全部到达 btnCoin 后回调
*/
playRecycleFromWorld(startWorld: Readonly<Vec3>, peachCount: number, onAllArrived: () => void): void {
this.playFromWorld(startWorld, peachCount, onAllArrived);
}
/** 从世界坐标飘仙桃到 ui_money(coinFly,多枚) */
/** 从世界坐标飘仙桃到 btnCoin(coinFly,多枚) */
private _playFromWorldStart(startWorld: Vec3, peachCount: number, onAllArrived: () => void): void {
this._flyPending = 0;
this._flyComplete = onAllArrived;
const spawned = this._spawnCoinFlies(startWorld, peachCount, this._onOneCoinFlyArrived.bind(this), true);
if (spawned <= 0) {
this._flyComplete = null;
onAllArrived();
}
const spawned = this._spawnCoinFlies(startWorld, peachCount, onAllArrived, true);
if (spawned <= 0) onAllArrived();
}
/**
* 生成 coinFly:先原地弹跳;toMoney 时再飞向 ui_money。
* 生成 coinFly:先原地弹跳;toMoney 时再飞向 btnCoin。每批独立计数,可并发。
* @param startWorld 起点
* @param peachCount 桃子数量
* @param onArrive 单枚到达;击杀掉落可空
* @param toMoney 是否飞向本机 ui_money
* @param onAllArrived 本批全部到达;可空
* @param toMoney 是否飞向本机 btnCoin
* @param flySec 飞向终点时长;≤0 为击杀掉落的短飞行
* @param layer 指定父节点;缺省用已绑定的飞行层
* @param plusText 金币上的数字;缺省为击杀掉落的 +1
* @param maxCount 本次最多生成枚数;波次结算按配置次数,击杀掉落沿用上限
*/
private _spawnCoinFlies(startWorld: Vec3, peachCount: number, onArrive: (() => void) | null, toMoney: boolean, flySec = 0, layer: Node | null = null, plusText = '+1', maxCount = MAX_FLY_COUNT): number {
private _spawnCoinFlies(startWorld: Vec3, peachCount: number, onAllArrived: (() => void) | null, toMoney: boolean, flySec = 0, layer: Node | null = null, plusText = '+1', maxCount = MAX_FLY_COUNT): number {
const flyLayer = layer?.isValid ? layer : this._flyLayer;
const moneyNode = this._moneyNode;
if (!flyLayer?.isValid) {
@ -346,7 +365,14 @@ export class BattlePeachCoinFlyPresenter {
const endPos = toMoney && moneyNode?.isValid ? moneyNode.worldPosition.clone() : null;
const flyCount = Math.min(Math.max(1, Math.floor(peachCount)), Math.floor(maxCount));
let spawned = 0;
const batch = { left: 0 };
const onOneDone = (peach?: Node): void => {
if (peach?.isValid) this._putPlusChild(peach);
batch.left--;
if (batch.left > 0) return;
onAllArrived?.();
if (toMoney) GEvent.Ins.emit(GEvent.UpdateGameCoinNum);
};
for (let i = 0; i < flyCount; i++) {
const node = gg.res.getNode('prefab/', 'coinFly', GBundle.BattleGame);
@ -367,12 +393,11 @@ export class BattlePeachCoinFlyPresenter {
gg.res.putNode(node);
continue;
}
spawned++;
if (onArrive) this._flyPending++;
batch.left++;
this._attachPlus(node, plusText);
comp.flyAni(endPos, this._onPeachDone, i * FLY_STAGGER_SEC, flySec);
comp.flyAni(endPos, onOneDone, i * FLY_STAGGER_SEC, flySec);
}
return spawned;
return batch.left;
}
/**
@ -412,12 +437,6 @@ export class BattlePeachCoinFlyPresenter {
this._plusQueue.push(node);
}
/** 桃子动画结束:拆 +1,再走到达计数 */
private readonly _onPeachDone = (peach?: Node): void => {
if (peach?.isValid) this._putPlusChild(peach);
if (this._flyPending > 0) this._onOneCoinFlyArrived();
};
/**
* 从桃子上拆下 +1 并回池。
* @param peach 桃子节点
@ -464,14 +483,4 @@ export class BattlePeachCoinFlyPresenter {
node.removeFromParent();
gg.res.putNode(node);
}
/** 单枚 coinFly 到达 ui_money */
private _onOneCoinFlyArrived(): void {
this._flyPending = Math.max(0, this._flyPending - 1);
if (this._flyPending > 0) return;
const done = this._flyComplete;
this._flyComplete = null;
done?.();
GEvent.Ins.emit(GEvent.UpdateGameCoinNum);
}
}

@ -150,6 +150,8 @@ export class UIBattleGame extends Auto_BattleGame {
private _illustratedHintShownThisBattle = false;
/** 桃 UI 缓存,避免每帧写 Label */
private _peachUiGold = -1;
/** 已入账、飞币未到 btnCoin 的金币(显示暂不计入) */
private _peachUiPendingFly = 0;
/** 召唤消耗缓存 */
private _peachUiCost = -1;
/** 金币广告已用次数缓存 */
@ -297,6 +299,7 @@ export class UIBattleGame extends Auto_BattleGame {
this._coinAdVideoIcon = this.btnCoin?.getChildByName('视频标') ?? null;
this._waveUiIdx = -999;
this._peachUiGold = -1;
this._peachUiPendingFly = 0;
this._peachUiCost = -1;
this._peachUiCoinAdUsed = -1;
this._refreshPeachUi();
@ -427,6 +430,7 @@ export class UIBattleGame extends Auto_BattleGame {
console.error('[UIBattleGame] AiBattleBridge.start 异常', String(e));
}
this._bindUnlockCoinFly(battle);
this._bindGlyphRecycleGold(battle);
this._bindBattleBuffSelect(battle);
this._bindPeerMapToolApplied(battle);
this._bindDigChest(map);
@ -529,7 +533,7 @@ export class UIBattleGame extends Auto_BattleGame {
const gold = this._waveSettleGold();
if (!(gold > 0)) return;
battle.addPlayerGold(battle.localPlayerId, gold);
this._refreshPeachUi();
this._holdPeachUiForFly(gold);
this._playWaveSettleCoin(gold);
this._grantWaveGoldToOthers(gold);
}
@ -607,11 +611,13 @@ export class UIBattleGame extends Auto_BattleGame {
}
this._ensurePeachCoinFly();
if (!layer.active) layer.active = true;
this._peachCoinFly?.playWaveCoin(layer, layer.worldPosition, show, count);
this._peachCoinFly?.playWaveCoin(layer, layer.worldPosition, show, count, () => {
this._releasePeachUiAfterFly(gold);
});
}
/**
* 击杀掉桃表现:本机飞向 ui_money;AI 归属原地弹跳(金已入 AI 槽)。
* 击杀掉桃表现:本机飞向 btnCoin;AI 归属原地弹跳(金已入 AI 槽)。
* @param killer 击杀者
* @param victim 死亡实体
*/
@ -628,12 +634,50 @@ export class UIBattleGame extends Auto_BattleGame {
}
const ownerId = battle.resolveKillPeachOwnerPlayerId(killer, victim);
if (ownerId === battle.localPlayerId) {
fly.playKillDrop(from, peach);
this._playLocalCoinFly(from, peach);
return;
}
fly.playKillDropLocal(from, peach);
}
/**
* 本机飞币:入账已发生,挂起 UI 数字,到达 btnCoin 后再显示。
* @param fromWorld 起点
* @param amount 本次金币
*/
private _playLocalCoinFly(fromWorld: Readonly<Vec3>, amount: number): void {
const gold = Math.floor(Number(amount) || 0);
if (!(gold > 0)) return;
this._ensurePeachCoinFly();
this._holdPeachUiForFly(gold);
const fly = this._peachCoinFly;
if (!fly) {
this._releasePeachUiAfterFly(gold);
return;
}
fly.playKillDrop(fromWorld, gold, () => this._releasePeachUiAfterFly(gold));
}
/**
* 飞币未到前暂不把新增金币画到 lb_coin。
* @param amount 本次挂起数量
*/
private _holdPeachUiForFly(amount: number): void {
const n = Math.floor(Number(amount) || 0);
if (!(n > 0)) return;
this._peachUiPendingFly += n;
}
/**
* 飞币到达后解除挂起并刷新 lb_coin。
* @param amount 本次解除数量
*/
private _releasePeachUiAfterFly(amount: number): void {
const n = Math.floor(Number(amount) || 0);
if (n > 0) this._peachUiPendingFly = Math.max(0, this._peachUiPendingFly - n);
this._refreshPeachUi();
}
/**
* 击杀时怪物世界坐标(节点优先,否则 presenter 缓存)。
* @param victim 死亡实体
@ -714,6 +758,7 @@ export class UIBattleGame extends Auto_BattleGame {
console.log('[UIBattleGame] ui_delete 缺失,无法拖字删除');
}
this._bagView.setDeleteNode(this.ui_delete);
this._bagView.setRecycleGoldFxHandler(this._onRecycleGoldFx);
// bind() 开头会 unbind,入场回调必须在 bind 之后再挂
this._bagView.setOtherBagAnchor(this.ui_otherBag);
this._bagView.setInkstoneWorldPosProvider(this._inkstoneWorldPos);
@ -833,9 +878,7 @@ export class UIBattleGame extends Auto_BattleGame {
* @param fromWorld 金矿图标世界坐标
*/
private readonly _onGoldMineCoinFly = (fromWorld: Readonly<Vec3>): void => {
this._ensurePeachCoinFly();
this._peachCoinFly?.playKillDrop(fromWorld, 1);
this._refreshPeachUi();
this._playLocalCoinFly(fromWorld, 1);
};
/** AI 金矿飞币:敌方被动金矿图标原地弹跳 */
@ -872,6 +915,50 @@ export class UIBattleGame extends Auto_BattleGame {
}
}
/**
* 数字拖垃圾桶回收:金币入账后刷新 UI;飞币等字飞到垃圾桶后再播。
* @param battle 战斗
*/
private _bindGlyphRecycleGold(battle: ProjectBattleHandle): void {
this._ensurePeachCoinFly();
if (battle.bagSession) {
battle.bagSession.onGlyphRecycleGold = this._onGlyphRecycleGold;
}
}
/**
* 回收金币已入账:先挂起 UI,飞币到垃圾桶后再播(见 `_onRecycleGoldFx`)。
* @param gold Const39 数量
* @param ownerPlayerId 归属
*/
private readonly _onGlyphRecycleGold = (gold: number, ownerPlayerId: number): void => {
if (!(gold > 0)) return;
const battle = this._battle;
if (ownerPlayerId !== (battle?.localPlayerId ?? 0)) return;
this._holdPeachUiForFly(gold);
};
/**
* 字飞到垃圾桶后:从 ui_delete 弹跳飞向 btnCoin,到达后刷新数字。
* @param gold Const39 数量
*/
private readonly _onRecycleGoldFx = (gold: number): void => {
if (!(gold > 0)) return;
this._ensurePeachCoinFly();
const from = this.ui_delete?.isValid ? this.ui_delete.worldPosition.clone() : null;
if (!from) {
console.warn('[UIBattleGame] 回收金币缺少 ui_delete 坐标');
this._releasePeachUiAfterFly(gold);
return;
}
const fly = this._peachCoinFly;
if (!fly) {
this._releasePeachUiAfterFly(gold);
return;
}
fly.playKillDrop(from, gold, () => this._releasePeachUiAfterFly(gold));
};
/**
* 地图解锁达 Const27 次数:本机弹三选一,AI 自动选。
* @param battle 战斗
@ -1012,12 +1099,12 @@ export class UIBattleGame extends Auto_BattleGame {
const fly = this._peachCoinFly;
const from = this._bagView?.getMapGridWorldPosition(gridIndex);
if (ownerPlayerId === battle?.localPlayerId) {
this._refreshPeachUi();
if (!from) {
console.log('[UIBattleGame] 寻金尺无飞币坐标', gridIndex);
this._refreshPeachUi();
return;
}
fly?.playKillDrop(from, coin);
this._playLocalCoinFly(from, coin);
return;
}
if (!from) {
@ -1028,7 +1115,7 @@ export class UIBattleGame extends Auto_BattleGame {
};
/**
* 技能产桃:本机 coinFly 飞向 ui_money;敌方格心 coinFly 原地弹跳后回池。
* 技能产桃:本机飞到 btnCoin 后再入账刷新;敌方格心原地弹跳。
* @param gridIndex 施法格
* @param peachCount 蟠桃数量
*/
@ -1041,10 +1128,19 @@ export class UIBattleGame extends Auto_BattleGame {
if (!from) {
console.log('[UIBattleGame] 产桃坐标缺失', gridIndex, ownerPlayerId);
bagView?.grantBattlePeach(peachCount);
this._refreshPeachUi();
return;
}
this._ensurePeachCoinFly();
if (!fly) {
bagView?.grantBattlePeach(peachCount);
this._refreshPeachUi();
return;
}
fly?.playKillDrop(from, peachCount);
bagView?.grantBattlePeach(peachCount);
fly.playKillDrop(from, peachCount, () => {
bagView?.grantBattlePeach(peachCount);
this._refreshPeachUi();
});
return;
}
if (battle && ownerPlayerId >= 0) {
@ -1064,8 +1160,7 @@ export class UIBattleGame extends Auto_BattleGame {
this._killPeach = new BattleGameKillPeachPresenter();
this._killPeach.bind(
battle,
this._bagView,
this._peachCoinFly,
(from, amount) => this._playLocalCoinFly(from, amount),
(victim) => this._monsterDeathWorldPos(victim),
);
}
@ -1144,23 +1239,24 @@ export class UIBattleGame extends Auto_BattleGame {
const battle = this._battle;
if (!battle) return;
const gold = battle.localGold;
const realGold = battle.localGold;
const showGold = Math.max(0, realGold - this._peachUiPendingFly);
const cost = battle.bagSession
? battle.bagSession.normalRefreshCost
: getDiscountedBagNormalRefreshCost(0, resolveCarriedSummonCostReduceWan().reduceWan);
const coinAdUsed = battle.coinAdUsed;
if (gold === this._peachUiGold && cost === this._peachUiCost && coinAdUsed === this._peachUiCoinAdUsed) return;
this._peachUiGold = gold;
if (showGold === this._peachUiGold && cost === this._peachUiCost && coinAdUsed === this._peachUiCoinAdUsed) return;
this._peachUiGold = showGold;
this._peachUiCost = cost;
this._peachUiCoinAdUsed = coinAdUsed;
if (this.lb_coin?.isValid) {
this.lb_coin.string = `${gold}`;
this.lb_coin.string = `${showGold}`;
}
this._syncCoinAdUi(battle);
if (this.lb_RefreshNeed?.isValid) {
this.lb_RefreshNeed.string = `${cost}`;
}
const enough = cost <= 0 || gold >= cost;
const enough = cost <= 0 || realGold >= cost;
this._showRefreshBg1(enough);
this._syncRefreshBounce(enough);
}
@ -1387,12 +1483,14 @@ export class UIBattleGame extends Auto_BattleGame {
AiBattleBridge.inst.setGoldMineCoinFlyHandler(null);
if (this._battle?.bagSession) {
this._battle.bagSession.onUnlockCoinFly = null;
this._battle.bagSession.onGlyphRecycleGold = null;
this._battle.bagSession.onBattleBuffSelect = null;
this._battle.bagSession.onPeerMapToolApplied = null;
}
const aiBagTeardown = AiBattleBridge.inst.aiBagSession;
if (aiBagTeardown) {
aiBagTeardown.onUnlockCoinFly = null;
aiBagTeardown.onGlyphRecycleGold = null;
aiBagTeardown.onBattleBuffSelect = null;
aiBagTeardown.onPeerMapToolApplied = null;
}
@ -1444,6 +1542,7 @@ export class UIBattleGame extends Auto_BattleGame {
this._refreshBounceHinted = false;
this._illustratedHintShownThisBattle = false;
this._peachUiGold = -1;
this._peachUiPendingFly = 0;
this._peachUiCost = -1;
this._peachUiCoinAdUsed = -1;
this._coinAdVideoIcon = null;

@ -1,4 +1,5 @@
import { instantiate, Node, Tween, tween, UIOpacity, UITransform, v3, Vec3 } from 'cc';
import { GAudioPath } from '../../game/ConfigRes';
/** 单次飞行时长(秒) */
const COIN_FLY_SEC = 0.45;
@ -50,9 +51,11 @@ export class BattleSuccessCoinFlyPresenter {
}
this._stopShadows();
gg.audio.playEffect({ name: '结算金币效果' });
gg.audio.playEffect({ name: '结算金币效果', path: GAudioPath.SfxUi });
const endPos = this._resolveWorldCenter(targetCoin);
const srcPos = sourceIcon.worldPosition.clone();
const srcScale = sourceIcon.worldScale;
const shadowScale = new Vec3(srcScale.x, srcScale.y, srcScale.z);
let arrived = 0;
for (let i = 0; i < count; i++) {
@ -62,8 +65,9 @@ export class BattleSuccessCoinFlyPresenter {
shadow.setSiblingIndex(flyLayer.children.length - 1);
const spread = (i - (count - 1) * 0.5) * 14;
shadow.setWorldPosition(srcPos.x + spread, srcPos.y + (Math.random() - 0.5) * 10, srcPos.z);
shadow.setWorldScale(sourceIcon.worldScale.clone());
shadow.getOrAddComponent(UIOpacity).opacity = SHADOW_OPACITY;
shadow.setWorldScale(shadowScale);
const fade = shadow.getOrAddComponent(UIOpacity);
if (fade) fade.opacity = SHADOW_OPACITY;
this._shadowNodes.push(shadow);
Tween.stopAllByTarget(shadow);

@ -421,7 +421,8 @@ function playButtonsEnter(buttons: Node[], highlightBtn: Node | null, delay: num
});
}
export function stopHighlightButtonAnim(node: Node): void {
export function stopHighlightButtonAnim(node: Node | null | undefined): void {
if (!node?.isValid) return;
stopNode(node);
const pose = _poseMap.get(node);
if (pose) setFinalPose(node, pose);

@ -62,6 +62,8 @@ export class UIBattleSuccess extends Auto_BattleSuccess {
private _coinFly: BattleSuccessCoinFlyPresenter | null = null;
/** 领取流程中 */
private _claiming = false;
/** 打开结算时缓存的波次(广告回调时 CurentBattle 可能已空) */
private _settleWaveNum = 1;
onLoad(): void {
super.onLoad();
@ -154,8 +156,10 @@ export class UIBattleSuccess extends Auto_BattleSuccess {
onInit(): void {
this.addNum = 1;
this.btnADGet.active = false;
this.btnShareGet.active = false;
this._claiming = false;
this._settleWaveNum = Math.max(1, Math.floor(Number(gg.game.CurentBattle?.CurentWaveNum) || 1));
if (this.btnADGet?.isValid) this.btnADGet.active = false;
if (this.btnShareGet?.isValid) this.btnShareGet.active = false;
const levelId = gg.sdk.get30rLevelId();
gg.sdk.report30r('LEVEL_PASS', {
@ -185,10 +189,10 @@ export class UIBattleSuccess extends Auto_BattleSuccess {
this._refreshMoneyDisplay();
this._refreshCoinRewardDisplay();
this.btnADGet.active = true
this.btnShareGet.active = false
if (this.btnADGet?.isValid) this.btnADGet.active = true;
if (this.btnShareGet?.isValid) this.btnShareGet.active = false;
if (!gg.game.CurentBattle.IsWatchAd && gg.game.CurentSelectLevelId >= 3) {
if (!gg.game.CurentBattle?.IsWatchAd && gg.game.CurentSelectLevelId >= 3) {
gg.sdk.showInterstitialAd(() => {
}, () => {
@ -445,7 +449,13 @@ export class UIBattleSuccess extends Auto_BattleSuccess {
this._recycleWeaponLoot();
if (this.ui_content?.isValid) this.ui_content.removeAllChildren();
const levelId = gg.game.CurentBattle.LevelConfigId;
const levelId = gg.game.CurentBattle?.LevelConfigId ?? gg.game.CurentSelectLevelId;
if (!(levelId > 0)) {
console.warn('[UIBattleSuccess] 结算奖励缺少关卡 id');
this._pendingCoin = 0;
this._dataList = [];
return;
}
const rewardStr = gg.data.project.getLevelRewardStr(levelId, true);
const pairs = gg.data.project.parseItemPipeNumList(rewardStr);
const itemsArray: ItemData[] = [];
@ -572,8 +582,7 @@ export class UIBattleSuccess extends Auto_BattleSuccess {
click_btnGet(): void {
if (this._claiming) return;
let curWaveNum = gg.game.CurentBattle.CurentWaveNum
gg.sdk.reportDY("inLevel", `${gg.game.getReportDYBattleCountLabel(curWaveNum)}-成功领取奖励`)
gg.sdk.reportDY("inLevel", `${gg.game.getReportDYBattleCountLabel(this._settleWaveNum)}-成功领取奖励`)
this._claimWithCoinFly();
}
@ -581,9 +590,9 @@ export class UIBattleSuccess extends Auto_BattleSuccess {
private _claimWithCoinFly(): void {
if (this._claiming) return;
this._claiming = true;
this.btnGet.active = false;
this.btnADGet.active = false;
this.btnShareGet.active = false;
if (this.btnGet?.isValid) this.btnGet.active = false;
if (this.btnADGet?.isValid) this.btnADGet.active = false;
if (this.btnShareGet?.isValid) this.btnShareGet.active = false;
const coinGain = Math.floor(this._pendingCoin * this.addNum);
const icon = this._coinIconNode();
@ -627,13 +636,13 @@ export class UIBattleSuccess extends Auto_BattleSuccess {
click_btnADGet(): void {
if (this._claiming) return;
gg.sdk.showVideoAd((res) => {
if (!this.isValid) return;
if (res) {
let curWaveNum = gg.game.CurentBattle.CurentWaveNum
gg.sdk.reportDY("inLevel", `${gg.game.getReportDYBattleCountLabel(curWaveNum)}-AD-广告成功双倍领取`)
gg.sdk.reportDY("inLevel", `${gg.game.getReportDYBattleCountLabel(this._settleWaveNum)}-AD-广告成功双倍领取`)
this.addNum = 2
stopHighlightButtonAnim(this.btnADGet);
this._refreshCoinRewardDisplay();
gg.game.CurentBattle.IsWatchAd = true
if (gg.game.CurentBattle) gg.game.CurentBattle.IsWatchAd = true;
this._claimWithCoinFly();
} else {
gg.ui.showToast('观看视频失败');

Loading…
Cancel
Save