Merge branch 'test' into feat-prac

This commit is contained in:
2026-06-15 09:32:27 +08:00
14 changed files with 208 additions and 64 deletions

View File

@@ -22,7 +22,8 @@
const { const {
updateUser, updateUser,
updateOnline, updateOnline,
clearSessionState clearSessionState,
clearDevice
} = store; } = store;
watch( watch(
@@ -63,6 +64,11 @@
updateOnline(data.online); updateOnline(data.online);
} }
function onDeviceBindInvalid() {
clearDevice();
uni.setStorageSync("calibration", false);
}
function onDeviceShoot() { function onDeviceShoot() {
// audioManager.play("射箭声音") // audioManager.play("射箭声音")
} }
@@ -78,6 +84,7 @@
uni.$on("update-user", emitUpdateUser); uni.$on("update-user", emitUpdateUser);
uni.$on("update-online", emitUpdateOnline); uni.$on("update-online", emitUpdateOnline);
uni.$on("session-kicked-out", onSessionKickedOut); uni.$on("session-kicked-out", onSessionKickedOut);
uni.$on("device-bind-invalid", onDeviceBindInvalid);
const token = uni.getStorageSync( const token = uni.getStorageSync(
`${uni.getAccountInfoSync().miniProgram.envVersion}_token` `${uni.getAccountInfoSync().miniProgram.envVersion}_token`
); );
@@ -91,6 +98,7 @@
uni.$off("update-user", emitUpdateUser); uni.$off("update-user", emitUpdateUser);
uni.$off("update-online", emitUpdateOnline); uni.$off("update-online", emitUpdateOnline);
uni.$off("session-kicked-out", onSessionKickedOut); uni.$off("session-kicked-out", onSessionKickedOut);
uni.$off("device-bind-invalid", onDeviceBindInvalid);
websocket.closeWebSocket(); websocket.closeWebSocket();
}); });
</script> </script>

View File

@@ -70,6 +70,15 @@ function request(method, url, data = {}) {
resolve({binded: true}); resolve({binded: true});
return; return;
} }
if (message === "BIND_FAILD") {
uni.$emit("device-bind-invalid");
uni.showToast({
title: "设备绑定状态已失效,请重新绑定",
icon: "none",
});
reject({type: "DEVICE_BIND_INVALID", message});
return;
}
if (message === "ERROR_ORDER_UNPAY") { if (message === "ERROR_ORDER_UNPAY") {
uni.showToast({ uni.showToast({
title: "当前有未支付订单", title: "当前有未支付订单",

View File

@@ -15,6 +15,12 @@ const props = defineProps({
}); });
const rowCount = new Array(6).fill(0); const rowCount = new Array(6).fill(0);
const getRingText = (arrow) => {
if (!arrow) return "-";
if (arrow.ringX && arrow.ring) return "X环";
return arrow.ring ? `${arrow.ring}` : "-";
};
</script> </script>
<template> <template>
@@ -35,23 +41,19 @@ const rowCount = new Array(6).fill(0);
<view> <view>
<view> <view>
<view v-for="(_, index) in rowCount" :key="index"> <view v-for="(_, index) in rowCount" :key="index">
<text>{{ <text>{{ getRingText(scores[0]?.[index]) }}</text>
scores[0] && scores[0][index] ? `${scores[0][index].ring}` : "-"
}}</text>
</view> </view>
</view> </view>
<view> <view>
<view v-for="(_, index) in rowCount" :key="index"> <view v-for="(_, index) in rowCount" :key="index">
<text>{{ <text>{{ getRingText(scores[1]?.[index]) }}</text>
scores[1] && scores[1][index] ? `${scores[1][index].ring}` : "-"
}}</text>
</view> </view>
</view> </view>
</view> </view>
<text <text
>{{ >{{
scores scores
.map((s) => s.reduce((last, next) => last + next.ring, 0)) .map((s) => (s || []).reduce((last, next) => last + next.ring, 0))
.reduce((last, next) => last + next, 0) .reduce((last, next) => last + next, 0)
}}</text }}</text
> >

View File

@@ -16,7 +16,7 @@ import {
import useStore from "@/store"; import useStore from "@/store";
const store = useStore(); const store = useStore();
const { updateUser, updateDevice, updateOnline } = store; const { updateUser, updateDevice, updateOnline, clearDevice } = store;
const props = defineProps({ const props = defineProps({
show: { show: {
@@ -107,6 +107,8 @@ async function doLogin() {
); );
const data = await getDeviceBatteryAPI(); const data = await getDeviceBatteryAPI();
updateOnline(data.online); updateOnline(data.online);
} else {
clearDevice();
} }
props.onClose(); props.onClose();
} catch (error) { } catch (error) {

View File

@@ -305,7 +305,7 @@ function goBack() {
<Container <Container
:bgType="data.mode > 3 ? -1 : 0" :bgType="data.mode > 3 ? -1 : 0"
bgColor="#000000" bgColor="#000000"
:onBack="goBack" :onBack="exit"
> >
<!-- ----- Banner game 胜负展示图 NvN 对抗模式----- --> <!-- ----- Banner game 胜负展示图 NvN 对抗模式----- -->

View File

@@ -26,6 +26,7 @@ const {
updateConfig, updateConfig,
updateUser, updateUser,
updateDevice, updateDevice,
clearDevice,
getLvlName, getLvlName,
getLvlNameByScore, getLvlNameByScore,
updateOnline, updateOnline,
@@ -127,6 +128,8 @@ onShow(async () => {
); );
const data = await getDeviceBatteryAPI(); const data = await getDeviceBatteryAPI();
updateOnline(data.online); updateOnline(data.online);
} else {
clearDevice();
} }
} }
} }

View File

@@ -30,11 +30,65 @@ const playersSorted = ref([]);
const playersScores = ref([]); const playersScores = ref([]);
const halfTimeTip = ref(false); const halfTimeTip = ref(false);
const halfRest = ref(false); const halfRest = ref(false);
const HALF_REST_SECONDS = 20;
const halfRestRemain = ref(HALF_REST_SECONDS);
let halfRestTimer = null;
/** 控制设备离线提示弹窗的显示状态 */ /** 控制设备离线提示弹窗的显示状态 */
const showOfflineModal = ref(false); const showOfflineModal = ref(false);
/** 记录每位玩家当前半场连续 X 环key 为 playerId用于触发 tententen 音效 */ /** 记录每位玩家当前半场连续 10 环及以上次key 为 playerId用于触发 tententen 音效 */
const xRingStreaks = ref({}); const xRingStreaks = ref({});
function clearHalfRestCountdown() {
if (halfRestTimer) {
clearInterval(halfRestTimer);
halfRestTimer = null;
}
}
function getHalfRestSeconds(battleInfo) {
const remainCandidates = [
battleInfo?.halfRestRemain,
battleInfo?.halfRestRemainSeconds,
battleInfo?.restRemain,
battleInfo?.restRemainSeconds,
];
for (const item of remainCandidates) {
const remain = Number(item);
if (Number.isFinite(remain) && remain > 0 && remain <= HALF_REST_SECONDS) {
return Math.ceil(remain);
}
}
const endTime = Number(battleInfo?.halfRestEndTime ?? battleInfo?.restEndTime);
if (!Number.isFinite(endTime) || endTime <= 0) return HALF_REST_SECONDS;
const timestamp = endTime < 1e12 ? endTime * 1000 : endTime;
const diffSeconds = (timestamp - Date.now()) / 1000;
if (diffSeconds > 0 && diffSeconds <= HALF_REST_SECONDS) {
return Math.ceil(diffSeconds);
}
return HALF_REST_SECONDS;
}
function startHalfRestCountdown(seconds = HALF_REST_SECONDS) {
clearHalfRestCountdown();
halfRestRemain.value = Math.max(0, Math.ceil(Number(seconds) || HALF_REST_SECONDS));
if (halfRestRemain.value <= 0) return;
halfRestTimer = setInterval(() => {
if (halfRestRemain.value <= 1) {
halfRestRemain.value = 0;
clearHalfRestCountdown();
return;
}
halfRestRemain.value -= 1;
}, 1000);
}
/** /**
* 监听设备在线状态,大乱斗比赛进行中设备离线时弹窗提示用户 * 监听设备在线状态,大乱斗比赛进行中设备离线时弹窗提示用户
*/ */
@@ -91,8 +145,7 @@ function recoverData(battleInfo, { force = false } = {}) {
halfTimeTip.value = true; halfTimeTip.value = true;
halfRest.value = true; halfRest.value = true;
tips.value = "准备下半场"; tips.value = "准备下半场";
// 剩余休息时间 startHalfRestCountdown(getHalfRestSeconds(battleInfo));
// const remain = (Date.now() - battleInfo.timeoutTime) / 1000;
setTimeout(() => { setTimeout(() => {
uni.$emit("update-remain", 0); uni.$emit("update-remain", 0);
}, 200); }, 200);
@@ -123,23 +176,27 @@ onLoad(async (options) => {
}); });
/** /**
* 检测指定玩家连续 X 环是否达到 3 箭,达到则在环数播报入队后追加 tententen 音效 * 检测指定玩家连续 10 环及以上是否达到 3 箭,达到则在环数播报入队后追加 tententen 音效
* @param {number|string} playerId - 本次射手的 ID大乱斗中 ShootResult 保留 playerId * @param {number|string} playerId - 本次射手的 ID大乱斗中 ShootResult 保留 playerId
* @param {boolean} isXRing - 本次射击是否为 X 环 * @param {boolean} isTenPlusRingShot - 本次射击是否为 10 环及以上
*/ */
function checkAndPlayTententen(playerId, isXRing) { function isTenPlusRing(shot) {
return !!(shot?.ringX || Number(shot?.ring) >= 10);
}
function checkAndPlayTententen(playerId, isTenPlusRingShot) {
if (!playerId) return; if (!playerId) return;
const id = parseInt(playerId); const id = parseInt(playerId);
if (isXRing) { if (isTenPlusRingShot) {
xRingStreaks.value[id] = (xRingStreaks.value[id] || 0) + 1; xRingStreaks.value[id] = (xRingStreaks.value[id] || 0) + 1;
// 同一玩家连续 3 箭均为 X 环,追加到环数音效队列尾部播放 // 同一玩家连续 3 箭均为 10 环及以上,追加到环数音效队列尾部播放
if (xRingStreaks.value[id] >= 3) { if (xRingStreaks.value[id] >= 3) {
xRingStreaks.value[id] = 0; xRingStreaks.value[id] = 0;
// nextTick 确保 HeaderProgress 的环数播报已入队后再追加 tententen避免播放顺序颠倒 // nextTick 确保 HeaderProgress 的环数播报已入队后再追加 tententen避免播放顺序颠倒
nextTick(() => audioManager.play("tententen", false)); nextTick(() => audioManager.play("tententen", false));
} }
} else { } else {
// 非 X 环则重置该玩家的连续计数 // 低于 10 环或未上靶则重置该玩家的连续计数
xRingStreaks.value[id] = 0; xRingStreaks.value[id] = 0;
} }
} }
@@ -147,6 +204,7 @@ function checkAndPlayTententen(playerId, isXRing) {
async function onReceiveMessage(msg) { async function onReceiveMessage(msg) {
if (Array.isArray(msg)) return; if (Array.isArray(msg)) return;
if (msg.type === MESSAGETYPESV2.BattleStart) { if (msg.type === MESSAGETYPESV2.BattleStart) {
clearHalfRestCountdown();
halfTimeTip.value = false; halfTimeTip.value = false;
halfRest.value = false; halfRest.value = false;
recoverData(msg); recoverData(msg);
@@ -161,22 +219,23 @@ async function onReceiveMessage(msg) {
// 对比更新后数据找出箭数增加的玩家(即本次射手),并读取其最新箭的 ring 数据 // 对比更新后数据找出箭数增加的玩家(即本次射手),并读取其最新箭的 ring 数据
const newRound = playersScores.value[playersScores.value.length - 1] || {}; const newRound = playersScores.value[playersScores.value.length - 1] || {};
let shooterId = null; let shooterId = null;
let isXRing = false; let isTenPlusRingShot = false;
for (const pid of Object.keys(newRound)) { for (const pid of Object.keys(newRound)) {
const newLen = (newRound[pid] || []).length; const newLen = (newRound[pid] || []).length;
if (newLen > (prevCounts[pid] || 0)) { if (newLen > (prevCounts[pid] || 0)) {
shooterId = parseInt(pid); shooterId = parseInt(pid);
const shot = newRound[pid][newLen - 1]; const shot = newRound[pid][newLen - 1];
isXRing = !!(shot?.ringX && shot?.ring); isTenPlusRingShot = isTenPlusRing(shot);
break; break;
} }
} }
// 检测同一玩家三箭全 X 环,触发 tententen 音效 // 检测同一玩家连续三箭 10 环及以上,触发 tententen 音效
checkAndPlayTententen(shooterId, isXRing); checkAndPlayTententen(shooterId, isTenPlusRingShot);
} else if (msg.type === MESSAGETYPESV2.HalfRest) { } else if (msg.type === MESSAGETYPESV2.HalfRest) {
halfTimeTip.value = true; halfTimeTip.value = true;
halfRest.value = true; halfRest.value = true;
tips.value = "准备下半场"; tips.value = "准备下半场";
startHalfRestCountdown();
} else if (msg.type === MESSAGETYPESV2.BattleEnd) { } else if (msg.type === MESSAGETYPESV2.BattleEnd) {
setTimeout(() => { setTimeout(() => {
// 全部跳转到新结算页 // 全部跳转到新结算页
@@ -197,6 +256,7 @@ onBeforeUnmount(() => {
uni.setKeepScreenOn({ uni.setKeepScreenOn({
keepScreenOn: false, keepScreenOn: false,
}); });
clearHalfRestCountdown();
uni.$off("socket-inbox", onReceiveMessage); uni.$off("socket-inbox", onReceiveMessage);
audioManager.stopAll(); audioManager.stopAll();
}); });
@@ -261,7 +321,7 @@ onShow(async () => {
> >
<view class="half-time-tip"> <view class="half-time-tip">
<text>上半场结束休息一下吧:</text> <text>上半场结束休息一下吧:</text>
<text>20秒后开始下半场</text> <text>{{ halfRestRemain }}秒后开始下半场</text>
</view> </view>
</ScreenHint> </ScreenHint>
<!-- 设备离线提示弹窗 --> <!-- 设备离线提示弹窗 -->

View File

@@ -16,7 +16,7 @@ const showTip = ref(false);
const confirmBindTip = ref(false); const confirmBindTip = ref(false);
const addDevice = ref(); const addDevice = ref();
const store = useStore(); const store = useStore();
const { updateDevice } = store; const { updateDevice, clearDevice } = store;
const { user, device } = storeToRefs(store); const { user, device } = storeToRefs(store);
const justBind = ref(false); const justBind = ref(false);
const calibration = ref(false); const calibration = ref(false);
@@ -84,13 +84,21 @@ const toFristTryPage = () => {
}; };
const unbindDevice = async () => { const unbindDevice = async () => {
try {
await unbindDeviceAPI(device.value.deviceId); await unbindDeviceAPI(device.value.deviceId);
} catch (error) {
if (error?.type === "DEVICE_BIND_INVALID") {
uni.setStorageSync("calibration", false);
clearDevice();
}
return;
}
uni.setStorageSync("calibration", false); uni.setStorageSync("calibration", false);
uni.showToast({ uni.showToast({
title: "解绑成功", title: "解绑成功",
icon: "success", icon: "success",
}); });
device.value = {}; clearDevice();
}; };
const toDeviceIntroPage = () => { const toDeviceIntroPage = () => {
@@ -122,8 +130,23 @@ const goCalibration = async () => {
}); });
}; };
onShow(() => { const syncDeviceBinding = async () => {
if (!user.value.id) return;
try {
const devices = await getMyDevicesAPI();
if (devices.bindings && devices.bindings.length) {
updateDevice(devices.bindings[0].deviceId, devices.bindings[0].deviceName);
} else {
clearDevice();
}
} catch (error) {
console.log("sync device binding error", error);
}
};
onShow(async () => {
calibration.value = uni.getStorageSync("calibration"); calibration.value = uni.getStorageSync("calibration");
await syncDeviceBinding();
}); });
</script> </script>

View File

@@ -31,7 +31,7 @@ const { user } = storeToRefs(store);
const start = ref(false); const start = ref(false);
const scores = ref([]); const scores = ref([]);
const total = 12; const total = 12;
/** 当前练习中连续 X 环计数,用于触发 tententen 音效 */ /** 当前练习中连续 10 环及以上计数,用于触发 tententen 音效 */
const xRingStreak = ref(0); const xRingStreak = ref(0);
const practiseResult = ref({}); const practiseResult = ref({});
const practiseId = ref(""); const practiseId = ref("");
@@ -48,7 +48,7 @@ onLoad((options) => {
const onReady = async () => { const onReady = async () => {
await startPractiseAPI(); await startPractiseAPI();
scores.value = []; scores.value = [];
xRingStreak.value = 0; // 新一局开始,重置 X 环连续计数 xRingStreak.value = 0; // 新一局开始,重置 10 环及以上连续计数
start.value = true; start.value = true;
audioManager.play("练习开始"); audioManager.play("练习开始");
}; };
@@ -59,19 +59,23 @@ const onOver = async () => {
}; };
/** /**
* 检测连续 X 环是否达到 3 箭,达到则播放 tententen 音效 * 检测连续 10 环及以上是否达到 3 箭,达到则播放 tententen 音效
* @param {boolean} isXRing - 本次射击是否为 X 环 * @param {boolean} isTenPlusRingShot - 本次射击是否为 10 环及以上
*/ */
function checkAndPlayTententen(isXRing) { function isTenPlusRing(shot) {
if (isXRing) { return !!(shot?.ringX || Number(shot?.ring) >= 10);
}
function checkAndPlayTententen(isTenPlusRingShot) {
if (isTenPlusRingShot) {
xRingStreak.value += 1; xRingStreak.value += 1;
// 连续 3 箭均为 X 环,在环数播报入队后追加 tententen避免播放顺序颠倒 // 连续 3 箭均为 10 环及以上,在环数播报入队后追加 tententen避免播放顺序颠倒
if (xRingStreak.value >= 3) { if (xRingStreak.value >= 3) {
xRingStreak.value = 0; xRingStreak.value = 0;
nextTick(() => audioManager.play("tententen", false)); nextTick(() => audioManager.play("tententen", false));
} }
} else { } else {
// 非 X 环则重置连续计数 // 低于 10 环或未上靶则重置连续计数
xRingStreak.value = 0; xRingStreak.value = 0;
} }
} }
@@ -80,10 +84,10 @@ async function onReceiveMessage(msg) {
if (msg.type === MESSAGETYPESV2.ShootResult) { if (msg.type === MESSAGETYPESV2.ShootResult) {
const prevLen = scores.value.length; const prevLen = scores.value.length;
scores.value = msg.details; scores.value = msg.details;
// 有新箭时取最后一箭判断是否 X 环并检测连续计数 // 有新箭时取最后一箭判断是否 10 环及以上并检测连续计数
if (scores.value.length > prevLen) { if (scores.value.length > prevLen) {
const latestArrow = scores.value[scores.value.length - 1]; const latestArrow = scores.value[scores.value.length - 1];
checkAndPlayTententen(!!(latestArrow?.ringX && latestArrow?.ring)); checkAndPlayTententen(isTenPlusRing(latestArrow));
} }
} else if (msg.type === MESSAGETYPESV2.BattleEnd) { } else if (msg.type === MESSAGETYPESV2.BattleEnd) {
// setTimeout(onOver, 1500); // setTimeout(onOver, 1500);
@@ -101,7 +105,7 @@ async function onComplete() {
practiseResult.value = {}; practiseResult.value = {};
start.value = false; start.value = false;
scores.value = []; scores.value = [];
xRingStreak.value = 0; // 重新开始练习,重置 X 环连续计数 xRingStreak.value = 0; // 重新开始练习,重置 10 环及以上连续计数
const result = await createPractiseAPI(total, 120); const result = await createPractiseAPI(total, 120);
if (result) practiseId.value = result.id; if (result) practiseId.value = result.id;
} }

View File

@@ -31,7 +31,7 @@ const { user } = storeToRefs(store);
const start = ref(false); const start = ref(false);
const scores = ref([]); const scores = ref([]);
const total = 36; const total = 36;
/** 当前练习中连续 X 环计数,用于触发 tententen 音效 */ /** 当前练习中连续 10 环及以上计数,用于触发 tententen 音效 */
const xRingStreak = ref(0); const xRingStreak = ref(0);
const practiseResult = ref({}); const practiseResult = ref({});
const practiseId = ref(""); const practiseId = ref("");
@@ -47,7 +47,7 @@ onLoad((options) => {
const onReady = async () => { const onReady = async () => {
await startPractiseAPI(); await startPractiseAPI();
scores.value = []; scores.value = [];
xRingStreak.value = 0; // 新一局开始,重置 X 环连续计数 xRingStreak.value = 0; // 新一局开始,重置 10 环及以上连续计数
start.value = true; start.value = true;
audioManager.play("练习开始"); audioManager.play("练习开始");
}; };
@@ -58,19 +58,23 @@ const onOver = async () => {
}; };
/** /**
* 检测连续 X 环是否达到 3 箭,达到则播放 tententen 音效 * 检测连续 10 环及以上是否达到 3 箭,达到则播放 tententen 音效
* @param {boolean} isXRing - 本次射击是否为 X 环 * @param {boolean} isTenPlusRingShot - 本次射击是否为 10 环及以上
*/ */
function checkAndPlayTententen(isXRing) { function isTenPlusRing(shot) {
if (isXRing) { return !!(shot?.ringX || Number(shot?.ring) >= 10);
}
function checkAndPlayTententen(isTenPlusRingShot) {
if (isTenPlusRingShot) {
xRingStreak.value += 1; xRingStreak.value += 1;
// 连续 3 箭均为 X 环,在环数播报入队后追加 tententen避免播放顺序颠倒 // 连续 3 箭均为 10 环及以上,在环数播报入队后追加 tententen避免播放顺序颠倒
if (xRingStreak.value >= 3) { if (xRingStreak.value >= 3) {
xRingStreak.value = 0; xRingStreak.value = 0;
nextTick(() => audioManager.play("tententen", false)); nextTick(() => audioManager.play("tententen", false));
} }
} else { } else {
// 非 X 环则重置连续计数 // 低于 10 环或未上靶则重置连续计数
xRingStreak.value = 0; xRingStreak.value = 0;
} }
} }
@@ -79,10 +83,10 @@ async function onReceiveMessage(msg) {
if (msg.type === MESSAGETYPESV2.ShootResult) { if (msg.type === MESSAGETYPESV2.ShootResult) {
const prevLen = scores.value.length; const prevLen = scores.value.length;
scores.value = msg.details; scores.value = msg.details;
// 有新箭时取最后一箭判断是否 X 环并检测连续计数 // 有新箭时取最后一箭判断是否 10 环及以上并检测连续计数
if (scores.value.length > prevLen) { if (scores.value.length > prevLen) {
const latestArrow = scores.value[scores.value.length - 1]; const latestArrow = scores.value[scores.value.length - 1];
checkAndPlayTententen(!!(latestArrow?.ringX && latestArrow?.ring)); checkAndPlayTententen(isTenPlusRing(latestArrow));
} }
} else if (msg.type === MESSAGETYPESV2.BattleEnd) { } else if (msg.type === MESSAGETYPESV2.BattleEnd) {
setTimeout(onOver, 1500); setTimeout(onOver, 1500);
@@ -116,7 +120,7 @@ async function onComplete() {
practiseResult.value = {}; practiseResult.value = {};
start.value = false; start.value = false;
scores.value = []; scores.value = [];
xRingStreak.value = 0; // 重新开始练习,重置 X 环连续计数 xRingStreak.value = 0; // 重新开始练习,重置 10 环及以上连续计数
const result = await createPractiseAPI(total, 3600); const result = await createPractiseAPI(total, 3600);
if (result) practiseId.value = result.id; if (result) practiseId.value = result.id;
} }

View File

@@ -49,7 +49,7 @@ const battleWay = ref(0);
const lastToSomeoneShootKey = ref(""); const lastToSomeoneShootKey = ref("");
/** 控制设备离线提示弹窗的显示状态 */ /** 控制设备离线提示弹窗的显示状态 */
const showOfflineModal = ref(false); const showOfflineModal = ref(false);
/** 记录每位玩家当前轮连续 X 环key 为 playerId用于触发 tententen 音效 */ /** 记录每位玩家当前轮连续 10 环及以上次key 为 playerId用于触发 tententen 音效 */
const xRingStreaks = ref({}); const xRingStreaks = ref({});
/** /**
@@ -234,22 +234,26 @@ function onNewRound(msg, prevRound) {
} }
/** /**
* 检测指定射手连续 X 环是否达到 3 箭,达到则在环数播报入队后追加 tententen 音效 * 检测指定射手连续 10 环及以上是否达到 3 箭,达到则在环数播报入队后追加 tententen 音效
* @param {number} shooterId - 本次射手的 ID取自 currentShooterId.value * @param {number} shooterId - 本次射手的 ID取自 currentShooterId.value
* @param {boolean} isXRing - 本次射击是否为 X 环 * @param {boolean} isTenPlusRingShot - 本次射击是否为 10 环及以上
*/ */
function checkAndPlayTententen(shooterId, isXRing) { function isTenPlusRing(shot) {
return !!(shot?.ringX || Number(shot?.ring) >= 10);
}
function checkAndPlayTententen(shooterId, isTenPlusRingShot) {
if (!shooterId) return; if (!shooterId) return;
if (isXRing) { if (isTenPlusRingShot) {
xRingStreaks.value[shooterId] = (xRingStreaks.value[shooterId] || 0) + 1; xRingStreaks.value[shooterId] = (xRingStreaks.value[shooterId] || 0) + 1;
// 同一玩家连续 3 箭均为 X 环,追加到环数音效队列尾部播放 // 同一玩家连续 3 箭均为 10 环及以上,追加到环数音效队列尾部播放
if (xRingStreaks.value[shooterId] >= 3) { if (xRingStreaks.value[shooterId] >= 3) {
xRingStreaks.value[shooterId] = 0; xRingStreaks.value[shooterId] = 0;
// nextTick 确保 HeaderProgress 的环数播报已入队后再追加 tententen避免播放顺序颠倒 // nextTick 确保 HeaderProgress 的环数播报已入队后再追加 tententen避免播放顺序颠倒
nextTick(() => audioManager.play("tententen", false)); nextTick(() => audioManager.play("tententen", false));
} }
} else { } else {
// 非 X 环则重置该玩家的连续计数 // 低于 10 环或未上靶则重置该玩家的连续计数
xRingStreaks.value[shooterId] = 0; xRingStreaks.value[shooterId] = 0;
} }
} }
@@ -268,9 +272,9 @@ async function onReceiveMessage(msg) {
} else if (msg.type === MESSAGETYPESV2.ShootResult) { } else if (msg.type === MESSAGETYPESV2.ShootResult) {
showRoundTip.value = false; showRoundTip.value = false;
recoverData(msg, {arrowOnly: true}); recoverData(msg, {arrowOnly: true});
// 检测同一玩家三箭全 X 环,触发 tententen 音效 // 检测同一玩家连续三箭 10 环及以上,触发 tententen 音效
// currentShooterId 在 ToSomeoneShoot 时写入ShootResult 不会覆盖,可靠识别本次射手 // currentShooterId 在 ToSomeoneShoot 时写入ShootResult 不会覆盖,可靠识别本次射手
checkAndPlayTententen(currentShooterId.value, !!(msg.shootData?.ringX && msg.shootData?.ring)); checkAndPlayTententen(currentShooterId.value, isTenPlusRing(msg.shootData));
} else if (msg.type === MESSAGETYPESV2.NewRound) { } else if (msg.type === MESSAGETYPESV2.NewRound) {
// 在进入延迟前先捕获当前轮次,供 onNewRound 使用,防止 800ms 内 ToSomeoneShoot 提前更新 currentRound 造成 Tip 展示错轮 // 在进入延迟前先捕获当前轮次,供 onNewRound 使用,防止 800ms 内 ToSomeoneShoot 提前更新 currentRound 造成 Tip 展示错轮
const prevRound = currentRound.value; const prevRound = currentRound.value;

View File

@@ -318,6 +318,22 @@ function enqueueBattleMessage(message) {
if (battleEnded && message.type !== MESSAGETYPESV2.BattleEnd) return; if (battleEnded && message.type !== MESSAGETYPESV2.BattleEnd) return;
if (message.type === MESSAGETYPESV2.BattleEnd) battleEnded = true; if (message.type === MESSAGETYPESV2.BattleEnd) battleEnded = true;
if (message.type === MESSAGETYPESV2.InvalidShot) {
const receivedAt = Date.now();
const order = ++queueOrder;
battleQueue.value.push({
message,
type: message.type,
key: `${message.type}:invalid:${receivedAt}:${order}`,
serverTime: 0,
receivedAt,
order,
});
sortBattleQueue();
runBattleQueue();
return;
}
// 入队阶段只做排序、去重和时间边界判断,不直接改 UI。 // 入队阶段只做排序、去重和时间边界判断,不直接改 UI。
const serverTime = getServerTime(message); const serverTime = getServerTime(message);
const key = getMessageKey(message); const key = getMessageKey(message);
@@ -463,7 +479,8 @@ function updateGoldenRound(battleInfo) {
} }
const rounds = Array.isArray(battleInfo.rounds) ? battleInfo.rounds : []; const rounds = Array.isArray(battleInfo.rounds) ? battleInfo.rounds : [];
const finishedGoldCount = rounds.filter((round) => !!round?.ifGold).length; const finishedGoldCount = rounds.filter((round) => !!round?.ifGold).length;
goldenRound.value = Math.max(1, finishedGoldCount + (battleInfo.current?.playerId ? 1 : 0)); // goldenRound.value = Math.max(1, finishedGoldCount + (battleInfo.current?.playerId ? 1 : 0));
goldenRound.value = Math.max(1, finishedGoldCount);
} }
// Restore an info snapshot whose eventType points at the NewRound phase. // Restore an info snapshot whose eventType points at the NewRound phase.
@@ -841,10 +858,14 @@ async function runToSomeoneShootTask(task, runId) {
}); });
} }
function updateXRingStreak(shooterId, isXRing) { function isTenPlusRing(shot) {
return !!(shot?.ringX || Number(shot?.ring) >= 10);
}
function updateXRingStreak(shooterId, isTenPlusRingShot) {
if (!shooterId) return false; if (!shooterId) return false;
const id = String(shooterId); const id = String(shooterId);
if (!isXRing) { if (!isTenPlusRingShot) {
xRingStreaks.value[id] = 0; xRingStreaks.value[id] = 0;
saveXRingStreaks(); saveXRingStreaks();
return false; return false;
@@ -889,7 +910,7 @@ async function runShootResultTask(task) {
const isTententen = updateXRingStreak( const isTententen = updateXRingStreak(
currentShooterId.value, currentShooterId.value,
!!(battleInfo.shootData?.ringX && battleInfo.shootData?.ring) isTenPlusRing(battleInfo.shootData)
); );
const audioKeys = buildShootResultAudioKeys(battleInfo.shootData); const audioKeys = buildShootResultAudioKeys(battleInfo.shootData);
if (isTententen) audioKeys.push("tententen"); if (isTententen) audioKeys.push("tententen");
@@ -1233,7 +1254,7 @@ onShow(() => {
<view class="offline-modal"> <view class="offline-modal">
<text class="offline-title">设备已离线</text> <text class="offline-title">设备已离线</text>
<text class="offline-desc">检测到设备已断开连接请检查设备后继续比赛</text> <text class="offline-desc">检测到设备已断开连接请检查设备后继续比赛</text>
<SButton @click="showOfflineModal = false">我知道了</SButton> <SButton :onClick="() => (showOfflineModal = false)">我知道了</SButton>
</view> </view>
</SModal> </SModal>
</view> </view>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -137,6 +137,10 @@ export default defineStore("store", {
this.device.deviceId = deviceId; this.device.deviceId = deviceId;
this.device.deviceName = deviceName; this.device.deviceName = deviceName;
}, },
clearDevice() {
this.device = getDefaultDevice();
this.online = false;
},
async updateConfig(config) { async updateConfig(config) {
this.config = config; this.config = config;
if (this.user.scores !== undefined) { if (this.user.scores !== undefined) {