Merge branch 'test' into feat-prac
This commit is contained in:
10
src/App.vue
10
src/App.vue
@@ -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>
|
||||||
|
|||||||
@@ -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: "当前有未支付订单",
|
||||||
|
|||||||
@@ -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
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 对抗模式)----- -->
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
<!-- 设备离线提示弹窗 -->
|
<!-- 设备离线提示弹窗 -->
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 |
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user