diff --git a/src/pages/team-battle/index.vue b/src/pages/team-battle/index.vue index 166b773..b269f64 100644 --- a/src/pages/team-battle/index.vue +++ b/src/pages/team-battle/index.vue @@ -41,6 +41,7 @@ const AUDIO_TIMEOUT_PER_KEY = 2600; const AUDIO_TIMEOUT_MAX = 12000; const BATTLE_CANCEL_RETURN_DELAY = 2000; const ROUND_AUDIO_NAMES = ["一", "二", "三", "四", "五"]; +const X_RING_STREAKS_KEY = "team-battle-x-ring-streaks"; const PROGRESS_ZERO_EVENT = "team-battle-progress-zero"; const COUNTDOWN_READY_EVENT = "team-battle-countdown-ready"; @@ -107,6 +108,21 @@ watch(online, (newVal, oldVal) => { }); // 统一把秒级或毫秒级时间戳转成毫秒,方便和本机时间比较。 +function loadXRingStreaks() { + const cached = uni.getStorageSync(X_RING_STREAKS_KEY); + xRingStreaks.value = + cached && typeof cached === "object" && !Array.isArray(cached) ? cached : {}; +} + +function saveXRingStreaks() { + uni.setStorageSync(X_RING_STREAKS_KEY, xRingStreaks.value); +} + +function clearXRingStreaks() { + xRingStreaks.value = {}; + uni.removeStorageSync(X_RING_STREAKS_KEY); +} + function normalizeTimestamp(value) { const numberValue = Number(value || 0); if (!numberValue) return 0; @@ -213,11 +229,19 @@ function waitForRoundTipClosed(isFinal) { }); } -function handleRoundTipAutoClose() { +function closeRoundTip() { showRoundTip.value = false; clearRoundTipWaiters(); } +function cancelRoundTipDisplay() { + closeRoundTip(); +} + +function handleRoundTipAutoClose() { + closeRoundTip(); +} + function markProgressDeadline(countdown, delayMs = 0) { if (!countdown?.value || !countdown?.durationMs) { progressDeadlineAt = 0; @@ -283,7 +307,7 @@ function invalidateBattleQueue({ stopAudio = false, stopProgress = false } = {}) clearAudioWaiters(); progressDeadlineAt = 0; clearProgressZeroWaiters(); - clearRoundTipWaiters(); + cancelRoundTipDisplay(); if (stopAudio) audioManager.stopAll(); if (stopProgress) uni.$emit("update-remain", { stop: true }); } @@ -442,6 +466,44 @@ function updateGoldenRound(battleInfo) { goldenRound.value = Math.max(1, finishedGoldCount + (battleInfo.current?.playerId ? 1 : 0)); } +// Restore an info snapshot whose eventType points at the NewRound phase. +function getRestorePrevRound(battleInfo) { + const currentRoundValue = Number(battleInfo?.current?.round || 0); + if (currentRoundValue > 1) return currentRoundValue - 1; + const rounds = Array.isArray(battleInfo?.rounds) ? battleInfo.rounds : []; + return Math.max(1, rounds.length || 1); +} + +function applyRestoreNewRoundSnapshot(battleInfo) { + const prevRound = getRestorePrevRound(battleInfo); + start.value = true; + showRoundTip.value = false; + scores.value = []; + blueScores.value = []; + currentRound.value = prevRound; + + if (battleInfo.current?.goldRound) { + store.updateShotInfo(0, 0); + currentBluePoint.value = battleInfo.teams?.[1]?.score ?? 0; + currentRedPoint.value = battleInfo.teams?.[2]?.score ?? 0; + } else { + const latestRound = battleInfo.rounds?.[prevRound - 1]; + if (battleInfo?.shootNumber) { + store.updateShotInfo(0, battleInfo.shootNumber); + } + + if (latestRound) { + currentBluePoint.value = latestRound.scores?.[1]?.score ?? 0; + currentRedPoint.value = latestRound.scores?.[2]?.score ?? 0; + } else { + currentBluePoint.value = 0; + currentRedPoint.value = 0; + } + } + + return prevRound; +} + // 回填比赛基础信息:队伍、比分、轮次、金箭状态等公共字段都在这里统一处理。 function applyBattleBase(battleInfo) { if (!battleInfo) return; @@ -650,7 +712,7 @@ function applyReadyState(battleInfo) { updateTips(""); progressDeadlineAt = 0; clearProgressZeroWaiters(); - clearRoundTipWaiters(); + cancelRoundTipDisplay(); const createTime = normalizeTimestamp(battleInfo?.createTime || Date.now()); const readyElapsed = (Date.now() - createTime) / 1000; @@ -662,7 +724,7 @@ function applyReadyState(battleInfo) { } // 快照恢复入口:只把页面拉到服务端最新状态,不重放已经发生过的语音。 -function applyBattleSnapshot(battleInfo, { restore = false } = {}) { +function applyBattleSnapshot(battleInfo, { restore = false, restoreEventType = 0 } = {}) { // 快照恢复只负责“把页面拉回最新状态”,不重放历史语音。 applyBattleBase(battleInfo); if (battleInfo.status === 0) { @@ -672,6 +734,12 @@ function applyBattleSnapshot(battleInfo, { restore = false } = {}) { start.value = true; showRoundTip.value = false; + + if (restore && restoreEventType === MESSAGETYPESV2.NewRound) { + applyRestoreNewRoundSnapshot(battleInfo); + return; + } + updateShotInfo(battleInfo); updateCurrentRoundScores(battleInfo); @@ -699,6 +767,7 @@ function applyBattleSnapshot(battleInfo, { restore = false } = {}) { // 开局任务:切换到正式比赛态,并播报“比赛开始”。 async function runBattleStartTask(task, runId) { // 开赛任务只负责切换正式态并播“比赛开始”,后续进入队列顺序。 + clearXRingStreaks(); applyBattleBase(task.message); start.value = true; pendingRoundAudio = true; @@ -711,12 +780,20 @@ async function runBattleStartTask(task, runId) { async function runToSomeoneShootTask(task, runId) { // 新射手任务:先等上一轮倒计时归零,再更新展示、播轮次/射手语音、启动倒计时。 const battleInfo = task.message; - await waitForProgressZero(); + const shouldEnterImmediately = showRoundTip.value; + if (shouldEnterImmediately) { + cancelRoundTipDisplay(); + progressDeadlineAt = 0; + clearProgressZeroWaiters(); + } else { + await waitForProgressZero(); + } if (!isQueueAlive(runId)) return; applyBattleBase(battleInfo); start.value = true; - showRoundTip.value = false; + hideRestoreLoading(); + cancelRoundTipDisplay(); updateShotInfo(battleInfo); const current = battleInfo.current || {}; @@ -769,12 +846,17 @@ function updateXRingStreak(shooterId, isXRing) { const id = String(shooterId); if (!isXRing) { xRingStreaks.value[id] = 0; + saveXRingStreaks(); return false; } xRingStreaks.value[id] = (xRingStreaks.value[id] || 0) + 1; - if (xRingStreaks.value[id] < 3) return false; + if (xRingStreaks.value[id] < 3) { + saveXRingStreaks(); + return false; + } xRingStreaks.value[id] = 0; + saveXRingStreaks(); return true; } @@ -818,7 +900,7 @@ async function runShootResultTask(task) { async function runNewRoundTask(task, runId) { // 新回合提示要故意延后一点,避免和上一箭结果展示抢先后顺序。 const battleInfo = task.message; - const prevRound = currentRound.value; + const prevRound = task.restorePrevRound || currentRound.value; await new Promise((resolve) => setTimeout(resolve, ROUND_TIP_DELAY)); if (!isQueueAlive(runId)) return; @@ -854,9 +936,6 @@ async function runNewRoundTask(task, runId) { currentRedPoint.value = 0; } pendingRoundAudio = true; - await waitForRoundTipClosed(!!battleInfo.current?.goldRound); - if (!isQueueAlive(runId)) return; - showRoundTip.value = false; } // 终局任务:播放结束语音后,根据状态跳结果页或返回上一页。 @@ -864,6 +943,7 @@ async function runBattleEndTask(task, runId) { const battleInfo = task.message; applyBattleBase(battleInfo); battleEnded = true; + clearXRingStreaks(); matchStatus.value = battleInfo.status; if (battleInfo.status === 4) { showRoundTip.value = true; @@ -924,15 +1004,44 @@ async function restoreLatestBattle() { latestAppliedServerTime.value = Math.max(latestAppliedServerTime.value, snapshotTime); } + const restoreEventType = Number(result?.eventType || 0); + if (result.status === 2) { + clearXRingStreaks(); hideRestoreLoading(); uni.redirectTo({ url: `/pages/friend-battle-result?battleId=${result.matchId}`, }); return; } + if (result.status === 4) { + clearXRingStreaks(); + } - applyBattleSnapshot(result, { restore: true }); + if (restoreEventType === MESSAGETYPESV2.NewRound) { + const prevRound = getRestorePrevRound(result); + const restoreMessage = { ...result, type: MESSAGETYPESV2.NewRound }; + const restoreKey = getMessageKey(restoreMessage); + applyBattleSnapshot(result, { restore: true, restoreEventType }); + + if (!handledMessageKeys.has(restoreKey) && !queuedMessageKeys.has(restoreKey)) { + queuedMessageKeys.add(restoreKey); + battleQueue.value.unshift({ + message: result, + type: MESSAGETYPESV2.NewRound, + key: restoreKey, + serverTime: snapshotTime, + receivedAt: Date.now(), + order: ++queueOrder, + restorePrevRound: prevRound, + }); + } + + runBattleQueue(); + return; + } + + applyBattleSnapshot(result, { restore: true, restoreEventType }); runBattleQueue(); } @@ -984,7 +1093,7 @@ onLoad((options) => { shootTimeTotal.value = DEFAULT_SHOOT_TIME; showOfflineModal.value = false; hideRestoreLoading(); - xRingStreaks.value = {}; + loadXRingStreaks(); queueGeneration += 1; battleQueue.value = []; queueRunning.value = false; @@ -998,7 +1107,7 @@ onLoad((options) => { queuedMessageKeys.clear(); progressDeadlineAt = 0; clearProgressZeroWaiters(); - clearRoundTipWaiters(); + cancelRoundTipDisplay(); store.updateShotInfo(0, 0); store.updateTips(""); latestShotFlash.value = null;