update:新增稳定训练
This commit is contained in:
@@ -17,7 +17,10 @@ import {
|
||||
import {
|
||||
getPrecisionShotAudioKeys,
|
||||
getRhythmShotAudioKeys,
|
||||
getStabilityShotAudioKeys,
|
||||
getTrainingStartAudioKey,
|
||||
STABILITY_ENERGY_50_AUDIO_KEY,
|
||||
STABILITY_ENERGY_70_AUDIO_KEY,
|
||||
} from "@/audioManager";
|
||||
import {
|
||||
normalizeId,
|
||||
@@ -220,6 +223,13 @@ function buildPracticeSyncMessage(message) {
|
||||
if (currentContext && practiceInfo.trainingType) {
|
||||
currentContext.trainingType = String(practiceInfo.trainingType).trim();
|
||||
}
|
||||
updateStabilityEnergyContext(practiceInfo, {
|
||||
fullSnapshot: true,
|
||||
detectCrossing: false,
|
||||
});
|
||||
updateStabilityShotContext(practiceInfo, {
|
||||
detectShot: false,
|
||||
});
|
||||
const matchId = normalizeId(
|
||||
pickField(message, "matchId", "match_id") ||
|
||||
practiceInfo.id ||
|
||||
@@ -378,12 +388,171 @@ function getShootResultAudioKeys(shootData) {
|
||||
return keys;
|
||||
}
|
||||
|
||||
function normalizeScoreSlot(value) {
|
||||
const scoreSlot = Number(value);
|
||||
return Number.isFinite(scoreSlot) && scoreSlot > 0 ? scoreSlot : 0;
|
||||
}
|
||||
|
||||
function clampEnergy(value, scoreSlot = 0) {
|
||||
const energy = Number(value);
|
||||
if (!Number.isFinite(energy)) return 0;
|
||||
const maxEnergy = normalizeScoreSlot(scoreSlot);
|
||||
return maxEnergy > 0
|
||||
? Math.min(maxEnergy, Math.max(0, energy))
|
||||
: Math.max(0, energy);
|
||||
}
|
||||
|
||||
function getEnergyPercent(energy, scoreSlot) {
|
||||
const maxEnergy = normalizeScoreSlot(scoreSlot);
|
||||
if (!maxEnergy) return 0;
|
||||
return Math.min(100, (clampEnergy(energy, maxEnergy) / maxEnergy) * 100);
|
||||
}
|
||||
|
||||
function getStabilityArrowCount(source = {}) {
|
||||
const candidates = [
|
||||
Array.isArray(source.details) ? source.details.length : NaN,
|
||||
Number(source.currentArrows),
|
||||
Number(source.totalArrows),
|
||||
].filter((value) => Number.isFinite(value) && value >= 0);
|
||||
return candidates.length > 0 ? Math.max(...candidates) : null;
|
||||
}
|
||||
|
||||
function updateStabilityShotContext(
|
||||
source = {},
|
||||
{ detectShot = false } = {}
|
||||
) {
|
||||
const trainingType = String(
|
||||
source.trainingType || currentContext?.trainingType || ""
|
||||
).trim();
|
||||
if (trainingType !== "stability" || !currentContext) return false;
|
||||
|
||||
const nextArrowCount = getStabilityArrowCount(source);
|
||||
if (nextArrowCount === null) return false;
|
||||
|
||||
const storedArrowCount = Number(currentContext.stabilityArrowCount);
|
||||
const previousArrowCount = Number.isFinite(storedArrowCount)
|
||||
? storedArrowCount
|
||||
: 0;
|
||||
const hasNewShot =
|
||||
detectShot && nextArrowCount > previousArrowCount;
|
||||
|
||||
if (nextArrowCount >= previousArrowCount) {
|
||||
currentContext.stabilityArrowCount = nextArrowCount;
|
||||
}
|
||||
return hasNewShot;
|
||||
}
|
||||
|
||||
// 稳定训练的阈值语音必须和 ACK 使用同一份判定结果,避免页面播放了
|
||||
// 里程碑语音但比赛服提前收到 ACK。能量跌破阈值后再次向上跨越可重复触发。
|
||||
function updateStabilityEnergyContext(
|
||||
source = {},
|
||||
{ fullSnapshot = false, detectCrossing = false } = {}
|
||||
) {
|
||||
const trainingType = String(
|
||||
source.trainingType || currentContext?.trainingType || ""
|
||||
).trim();
|
||||
if (trainingType !== "stability" || !currentContext) return "";
|
||||
|
||||
const hasCurrentEnergy = Object.prototype.hasOwnProperty.call(
|
||||
source,
|
||||
"currentEnergy"
|
||||
);
|
||||
if (!hasCurrentEnergy && !fullSnapshot) return "";
|
||||
|
||||
const previousScoreSlot = normalizeScoreSlot(
|
||||
currentContext.stabilityScoreSlot
|
||||
);
|
||||
const nextScoreSlot =
|
||||
normalizeScoreSlot(source.scoreSlot) || previousScoreSlot;
|
||||
const previousEnergy = clampEnergy(
|
||||
currentContext.stabilityEnergy,
|
||||
previousScoreSlot
|
||||
);
|
||||
const nextEnergy = clampEnergy(
|
||||
hasCurrentEnergy ? source.currentEnergy : 0,
|
||||
nextScoreSlot
|
||||
);
|
||||
const previousPercent = getEnergyPercent(
|
||||
previousEnergy,
|
||||
previousScoreSlot || nextScoreSlot
|
||||
);
|
||||
const nextPercent = getEnergyPercent(nextEnergy, nextScoreSlot);
|
||||
|
||||
currentContext.stabilityEnergy = nextEnergy;
|
||||
currentContext.stabilityScoreSlot = nextScoreSlot;
|
||||
|
||||
if (!detectCrossing) {
|
||||
currentContext.stabilityEnergy50Armed = nextPercent < 50;
|
||||
currentContext.stabilityEnergy70Armed = nextPercent < 70;
|
||||
return "";
|
||||
}
|
||||
|
||||
let energy50Armed =
|
||||
typeof currentContext.stabilityEnergy50Armed === "boolean"
|
||||
? currentContext.stabilityEnergy50Armed
|
||||
: previousPercent < 50;
|
||||
let energy70Armed =
|
||||
typeof currentContext.stabilityEnergy70Armed === "boolean"
|
||||
? currentContext.stabilityEnergy70Armed
|
||||
: previousPercent < 70;
|
||||
|
||||
if (nextPercent < 50) energy50Armed = true;
|
||||
if (nextPercent < 70) energy70Armed = true;
|
||||
|
||||
let milestoneAudioKey = "";
|
||||
if (energy70Armed && nextPercent >= 70) {
|
||||
milestoneAudioKey = STABILITY_ENERGY_70_AUDIO_KEY;
|
||||
energy70Armed = false;
|
||||
// 同一次直接跨过两个阈值时只播70%,不能在后续补播50%。
|
||||
energy50Armed = false;
|
||||
} else if (energy50Armed && nextPercent >= 50) {
|
||||
milestoneAudioKey = STABILITY_ENERGY_50_AUDIO_KEY;
|
||||
energy50Armed = false;
|
||||
}
|
||||
|
||||
currentContext.stabilityEnergy50Armed = energy50Armed;
|
||||
currentContext.stabilityEnergy70Armed = energy70Armed;
|
||||
return milestoneAudioKey;
|
||||
}
|
||||
|
||||
function getTestDistanceAudioKeys(shootData) {
|
||||
if (!shootData) return [];
|
||||
return [getDistanceCheckAudioKey(shootData)];
|
||||
}
|
||||
|
||||
function attachStabilityAudioContext(message, businessMessage) {
|
||||
const fullStabilitySnapshot = [
|
||||
ServerMessageType.SERVER_MSG_MATCH_START,
|
||||
ServerMessageType.SERVER_MSG_SHOT,
|
||||
ServerMessageType.SERVER_MSG_MATCH_END,
|
||||
ServerMessageType.SERVER_MSG_TIMEOUT,
|
||||
ServerMessageType.SERVER_MSG_PRACTICE_END,
|
||||
].includes(message.type);
|
||||
const stabilityMilestoneAudioKey = updateStabilityEnergyContext(
|
||||
businessMessage,
|
||||
{
|
||||
fullSnapshot: fullStabilitySnapshot,
|
||||
detectCrossing: message.type === ServerMessageType.SERVER_MSG_SHOT,
|
||||
}
|
||||
);
|
||||
const stabilityHasNewShot = updateStabilityShotContext(businessMessage, {
|
||||
detectShot: message.type === ServerMessageType.SERVER_MSG_SHOT,
|
||||
});
|
||||
if (businessMessage && stabilityMilestoneAudioKey) {
|
||||
businessMessage.stabilityMilestoneAudioKey = stabilityMilestoneAudioKey;
|
||||
}
|
||||
if (businessMessage) {
|
||||
businessMessage.stabilityHasNewShot = stabilityHasNewShot;
|
||||
}
|
||||
}
|
||||
|
||||
function getAckAudioKeys(message, businessMessage) {
|
||||
const stabilityMilestoneAudioKey = String(
|
||||
businessMessage?.stabilityMilestoneAudioKey || ""
|
||||
).trim();
|
||||
const stabilityHasNewShot =
|
||||
businessMessage?.stabilityHasNewShot === true;
|
||||
|
||||
switch (message.type) {
|
||||
case ServerMessageType.SERVER_MSG_MATCH_START:
|
||||
if (businessMessage?.isSecondHalfStart) return ["下半场开始"];
|
||||
@@ -419,6 +588,14 @@ function getAckAudioKeys(message, businessMessage) {
|
||||
if (trainingType === "rhythm") {
|
||||
return getRhythmShotAudioKeys(businessMessage?.shootData);
|
||||
}
|
||||
if (trainingType === "stability") {
|
||||
return [
|
||||
...(stabilityHasNewShot
|
||||
? getStabilityShotAudioKeys(businessMessage?.shootData)
|
||||
: []),
|
||||
stabilityMilestoneAudioKey,
|
||||
].filter(Boolean);
|
||||
}
|
||||
return getShootResultAudioKeys(businessMessage?.shootData);
|
||||
case ServerMessageType.SERVER_MSG_MATCH_END:
|
||||
return ["比赛结束"];
|
||||
@@ -880,6 +1057,8 @@ function handleMessage(data) {
|
||||
if (businessMessage?.matchId && currentContext) {
|
||||
currentContext.matchId = businessMessage.matchId;
|
||||
}
|
||||
// 阈值事件必须先于 ACK 判断生成,确保无 sequence 的状态推送也能播放。
|
||||
attachStabilityAudioContext(message, businessMessage);
|
||||
queueAckAfterAudio(message, businessMessage);
|
||||
emitBusinessMessage(businessMessage);
|
||||
}
|
||||
@@ -991,6 +1170,21 @@ export function connectMatchWebSocket(options = {}) {
|
||||
trainingType:
|
||||
normalizedTrainingType ||
|
||||
(isSameContext ? currentContext?.trainingType || "" : ""),
|
||||
stabilityEnergy: isSameContext
|
||||
? currentContext?.stabilityEnergy
|
||||
: undefined,
|
||||
stabilityScoreSlot: isSameContext
|
||||
? currentContext?.stabilityScoreSlot
|
||||
: undefined,
|
||||
stabilityEnergy50Armed: isSameContext
|
||||
? currentContext?.stabilityEnergy50Armed
|
||||
: undefined,
|
||||
stabilityEnergy70Armed: isSameContext
|
||||
? currentContext?.stabilityEnergy70Armed
|
||||
: undefined,
|
||||
stabilityArrowCount: isSameContext
|
||||
? currentContext?.stabilityArrowCount
|
||||
: undefined,
|
||||
meleeHalfRest: isSameContext
|
||||
? currentContext?.meleeHalfRest === true
|
||||
: false,
|
||||
|
||||
Reference in New Issue
Block a user