1
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-05-02 20:43:41 +08:00
parent 543ccf2509
commit 5831703156
36 changed files with 799 additions and 254 deletions

View File

@@ -132,6 +132,8 @@ import {
remixPuzzleGalleryWork,
} from '../../services/puzzle-gallery';
import {
advancePuzzleNextLevel,
startPuzzleRun,
submitPuzzleLeaderboard,
} from '../../services/puzzle-runtime';
import {
@@ -141,6 +143,7 @@ import {
extendLocalPuzzleTime,
isLocalPuzzleRun,
refreshLocalPuzzleTimer,
resolvePuzzleRestartLevelId,
restartLocalPuzzleLevel,
setLocalPuzzlePaused,
startLocalPuzzleRun,
@@ -876,31 +879,20 @@ function mergePuzzleServiceRuntimeState(
}
const serviceLevel = serviceRun.currentLevel;
if (
currentRun.currentLevel.status === 'cleared' &&
serviceLevel.status !== 'cleared'
) {
return {
...currentRun,
recommendedNextProfileId: serviceRun.recommendedNextProfileId,
nextLevelMode: serviceRun.nextLevelMode,
nextLevelProfileId: serviceRun.nextLevelProfileId,
nextLevelId: serviceRun.nextLevelId,
recommendedNextWorks: serviceRun.recommendedNextWorks,
leaderboardEntries:
currentRun.currentLevel.leaderboardEntries.length > 0
? currentRun.currentLevel.leaderboardEntries
: currentRun.leaderboardEntries,
};
}
const leaderboardEntries =
serviceLevel.leaderboardEntries.length > 0
? serviceLevel.leaderboardEntries
: serviceRun.leaderboardEntries;
// 中文注释:拼块布局和通关状态由前端即时裁决;后端快照只合并榜单与下一关 handoff。
return {
...currentRun,
runId: serviceRun.runId,
entryProfileId: serviceRun.entryProfileId,
clearedLevelCount: Math.max(
currentRun.clearedLevelCount,
serviceRun.clearedLevelCount,
),
recommendedNextProfileId: serviceRun.recommendedNextProfileId,
nextLevelMode: serviceRun.nextLevelMode,
nextLevelProfileId: serviceRun.nextLevelProfileId,
@@ -909,18 +901,10 @@ function mergePuzzleServiceRuntimeState(
leaderboardEntries,
currentLevel: {
...currentRun.currentLevel,
status: serviceLevel.status,
startedAtMs: serviceLevel.startedAtMs,
clearedAtMs: serviceLevel.clearedAtMs,
elapsedMs: serviceLevel.elapsedMs,
timeLimitMs: serviceLevel.timeLimitMs,
remainingMs: serviceLevel.remainingMs,
pausedAccumulatedMs: serviceLevel.pausedAccumulatedMs,
pauseStartedAtMs: serviceLevel.pauseStartedAtMs,
freezeAccumulatedMs: serviceLevel.freezeAccumulatedMs,
freezeStartedAtMs: serviceLevel.freezeStartedAtMs,
freezeUntilMs: serviceLevel.freezeUntilMs,
leaderboardEntries,
leaderboardEntries:
leaderboardEntries.length > 0
? leaderboardEntries
: currentRun.currentLevel.leaderboardEntries,
},
};
}
@@ -2181,8 +2165,12 @@ export function PlatformEntryFlowShellImpl({
setPuzzleError(null);
try {
const item = detailItem ?? (await getPuzzleGalleryDetail(profileId)).item;
const run = startLocalPuzzleRun(item, levelId ?? null);
const item =
detailItem ?? (await getPuzzleGalleryDetail(profileId)).item;
const { run } = await startPuzzleRun({
profileId: item.profileId,
levelId: levelId ?? null,
});
setSelectedPuzzleDetail(item);
setPuzzleRun(run);
setPuzzleRuntimeReturnStage(returnStage);
@@ -2411,14 +2399,20 @@ export function PlatformEntryFlowShellImpl({
setIsPuzzleBusy(true);
setPuzzleError(null);
try {
setPuzzleRun(swapLocalPuzzlePieces(puzzleRun, payload));
setPuzzleRun(
swapLocalPuzzlePieces(
puzzleRun,
payload,
isLocalPuzzleRun(puzzleRun) ? selectedPuzzleDetail : null,
),
);
} catch (error) {
setPuzzleError(resolvePuzzleErrorMessage(error, '交换拼图块失败。'));
} finally {
setIsPuzzleBusy(false);
}
},
[isPuzzleBusy, puzzleRun, resolvePuzzleErrorMessage],
[isPuzzleBusy, puzzleRun, resolvePuzzleErrorMessage, selectedPuzzleDetail],
);
const dragPuzzlePiece = useCallback(
@@ -2430,14 +2424,20 @@ export function PlatformEntryFlowShellImpl({
setIsPuzzleBusy(true);
setPuzzleError(null);
try {
setPuzzleRun(dragLocalPuzzlePiece(puzzleRun, payload));
setPuzzleRun(
dragLocalPuzzlePiece(
puzzleRun,
payload,
isLocalPuzzleRun(puzzleRun) ? selectedPuzzleDetail : null,
),
);
} catch (error) {
setPuzzleError(resolvePuzzleErrorMessage(error, '拖动拼图块失败。'));
} finally {
setIsPuzzleBusy(false);
}
},
[isPuzzleBusy, puzzleRun, resolvePuzzleErrorMessage],
[isPuzzleBusy, puzzleRun, resolvePuzzleErrorMessage, selectedPuzzleDetail],
);
useEffect(() => {
@@ -2515,18 +2515,46 @@ export function PlatformEntryFlowShellImpl({
);
const restartPuzzleCurrentLevel = useCallback(async () => {
const currentLevel = puzzleRun?.currentLevel ?? null;
if (!puzzleRun || !currentLevel || isPuzzleBusy) {
const currentRun = puzzleRunRef.current ?? puzzleRun;
const currentLevel = currentRun?.currentLevel ?? null;
if (!currentRun || !currentLevel || isPuzzleBusy) {
return;
}
setPuzzleError(null);
const nextRun = restartLocalPuzzleLevel(puzzleRunRef.current ?? puzzleRun);
puzzleRunRef.current = nextRun;
setPuzzleRun(nextRun);
setIsPuzzleBusy(true);
try {
if (isLocalPuzzleRun(currentRun)) {
const nextRun = restartLocalPuzzleLevel(currentRun);
puzzleRunRef.current = nextRun;
setPuzzleRun(nextRun);
return;
}
const detailItem =
selectedPuzzleDetail?.profileId === currentLevel.profileId
? selectedPuzzleDetail
: await getPuzzleGalleryDetail(currentLevel.profileId).then(
(response) => response.item,
);
const { run } = await startPuzzleRun({
profileId: currentLevel.profileId,
levelId: resolvePuzzleRestartLevelId(currentRun, detailItem),
});
setSelectedPuzzleDetail(detailItem);
puzzleRunRef.current = run;
setPuzzleRun(run);
} catch (error) {
setPuzzleError(resolvePuzzleErrorMessage(error, '重新开始拼图关卡失败。'));
} finally {
setIsPuzzleBusy(false);
}
}, [
isPuzzleBusy,
puzzleRun,
resolvePuzzleErrorMessage,
selectedPuzzleDetail,
setIsPuzzleBusy,
setPuzzleError,
]);
@@ -2565,14 +2593,19 @@ export function PlatformEntryFlowShellImpl({
gameState.currentLevelId.trim()
? gameState.currentLevelId
: null;
const item = selectedPuzzleDetail?.profileId === profileId
? selectedPuzzleDetail
: await getPuzzleGalleryDetail(profileId).then((response) => response.item);
const nextRun = startLocalPuzzleRun(item, levelId);
setSelectedPuzzleDetail(item);
setPuzzleRun(nextRun);
setPuzzleRuntimeReturnStage('platform');
setSelectionStage('puzzle-runtime');
const item =
selectedPuzzleDetail?.profileId === profileId
? selectedPuzzleDetail
: await getPuzzleGalleryDetail(profileId).then(
(response) => response.item,
);
await startPuzzleRunFromProfile(
item.profileId,
'platform',
item,
false,
levelId,
);
} catch (error) {
platformBootstrap.setSaveError(
resolvePuzzleErrorMessage(error, '恢复拼图存档失败。'),
@@ -2587,7 +2620,7 @@ export function PlatformEntryFlowShellImpl({
selectedPuzzleDetail,
resolvePuzzleErrorMessage,
setPuzzleError,
setSelectionStage,
startPuzzleRunFromProfile,
],
);
@@ -2651,7 +2684,7 @@ export function PlatformEntryFlowShellImpl({
]);
const advancePuzzleLevel = useCallback(
async (target?: { profileId?: string; levelId?: string | null }) => {
async (_target?: { profileId?: string; levelId?: string | null }) => {
if (!puzzleRun || isPuzzleBusy || isPuzzleLeaderboardBusy) {
return;
}
@@ -2665,12 +2698,43 @@ export function PlatformEntryFlowShellImpl({
setPuzzleError(null);
try {
const nextRun = advanceLocalPuzzleLevel(
puzzleRun,
selectedPuzzleDetail,
target,
);
setPuzzleRun(nextRun);
if (isLocalPuzzleRun(puzzleRun)) {
const nextRun = advanceLocalPuzzleLevel(
puzzleRun,
selectedPuzzleDetail,
_target,
);
setPuzzleRun(nextRun);
return;
}
const targetProfileId = _target?.profileId?.trim() ?? '';
if (puzzleRun.nextLevelMode === 'similarWorks' && targetProfileId) {
const itemPromise =
selectedPuzzleDetail?.profileId === targetProfileId
? Promise.resolve(selectedPuzzleDetail)
: getPuzzleGalleryDetail(targetProfileId).then(
(response) => response.item,
);
const [{ run }, item] = await Promise.all([
advancePuzzleNextLevel(puzzleRun.runId, {
targetProfileId,
}),
itemPromise,
]);
setSelectedPuzzleDetail(item);
setPuzzleRun(run);
pushAppHistoryPath(
buildPublicWorkStagePath(
'puzzle-runtime',
buildPuzzlePublicWorkCode(item.profileId),
),
);
return;
}
const { run } = await advancePuzzleNextLevel(puzzleRun.runId);
setPuzzleRun(run);
} catch (error) {
setPuzzleError(resolvePuzzleErrorMessage(error, '准备下一关失败。'));
} finally {
@@ -3563,7 +3627,10 @@ export function PlatformEntryFlowShellImpl({
}
if (isPuzzleGalleryEntry(selectedPublicWorkDetail)) {
const work = mapPublicWorkDetailToPuzzleWork(selectedPublicWorkDetail);
const work =
selectedPuzzleDetail?.profileId === selectedPublicWorkDetail.profileId
? selectedPuzzleDetail
: mapPublicWorkDetailToPuzzleWork(selectedPublicWorkDetail);
if (!work) {
setPublicWorkDetailError(
'当前拼图作品信息不完整,暂时无法进入玩法。',
@@ -3628,10 +3695,11 @@ export function PlatformEntryFlowShellImpl({
isPublicWorkDetailBusy,
runProtectedAction,
selectedDetailEntry,
selectedPuzzleDetail,
selectedPublicWorkDetail,
startBigFishRunFromWork,
startMatch3DRunFromProfile,
startPuzzleRunFromProfile,
startMatch3DRunFromProfile,
]);
const remixPublicWork = useCallback(