1
This commit is contained in:
@@ -48,6 +48,7 @@ import type {
|
||||
CustomWorldLibraryEntry,
|
||||
ProfilePlayedWorkSummary,
|
||||
ProfilePlayStatsResponse,
|
||||
ProfileSaveArchiveResumeResponse,
|
||||
ProfileSaveArchiveSummary,
|
||||
} from '../../../packages/shared/src/contracts/runtime';
|
||||
import { buildCustomWorldPlayableCharacters } from '../../data/characterPresets';
|
||||
@@ -125,13 +126,18 @@ import {
|
||||
extendLocalPuzzleTime,
|
||||
isLocalPuzzleRun,
|
||||
refreshLocalPuzzleTimer,
|
||||
resolvePuzzleRestartLevelId,
|
||||
restartLocalPuzzleLevel,
|
||||
setLocalPuzzlePaused,
|
||||
startLocalPuzzleRun,
|
||||
submitLocalPuzzleLeaderboard,
|
||||
swapLocalPuzzlePieces,
|
||||
} from '../../services/puzzle-runtime/puzzleLocalRuntime';
|
||||
import { deletePuzzleWork, listPuzzleWorks } from '../../services/puzzle-works';
|
||||
import {
|
||||
claimPuzzleWorkPointIncentive,
|
||||
deletePuzzleWork,
|
||||
listPuzzleWorks,
|
||||
} from '../../services/puzzle-works';
|
||||
import { deleteRpgCreationAgentSession } from '../../services/rpg-creation';
|
||||
import { rpgCreationPreviewAdapter } from '../../services/rpg-creation/rpgCreationPreviewAdapter';
|
||||
import {
|
||||
@@ -141,10 +147,8 @@ import {
|
||||
recordRpgEntryWorldGalleryPlay,
|
||||
remixRpgEntryWorldGallery,
|
||||
} from '../../services/rpg-entry/rpgEntryLibraryClient';
|
||||
import {
|
||||
getRpgProfilePlayStats,
|
||||
resumeRpgProfileSaveArchive,
|
||||
} from '../../services/rpg-entry/rpgProfileClient';
|
||||
import { getRpgProfilePlayStats } from '../../services/rpg-entry/rpgProfileClient';
|
||||
import { requestRpgRuntimeJson } from '../../services/rpg-runtime/rpgRuntimeRequest';
|
||||
import type { CustomWorldProfile } from '../../types';
|
||||
import { useAuthUi } from '../auth/AuthUiContext';
|
||||
import {
|
||||
@@ -201,6 +205,16 @@ type PuzzleSaveArchiveState = {
|
||||
currentLevelId?: unknown;
|
||||
};
|
||||
|
||||
async function resumePuzzleProfileSaveArchiveRaw(worldKey: string) {
|
||||
return requestRpgRuntimeJson<
|
||||
ProfileSaveArchiveResumeResponse<PuzzleSaveArchiveState>
|
||||
>(
|
||||
`/profile/save-archives/${encodeURIComponent(worldKey)}`,
|
||||
{ method: 'POST' },
|
||||
'恢复拼图存档失败',
|
||||
);
|
||||
}
|
||||
|
||||
type AgentResultBlockerView = {
|
||||
code?: string;
|
||||
message: string;
|
||||
@@ -297,6 +311,10 @@ function mapPublicWorkDetailToPuzzleWork(
|
||||
playCount: entry.playCount ?? 0,
|
||||
remixCount: entry.remixCount ?? 0,
|
||||
likeCount: entry.likeCount ?? 0,
|
||||
pointIncentiveTotalHalfPoints: 0,
|
||||
pointIncentiveClaimedPoints: 0,
|
||||
pointIncentiveTotalPoints: 0,
|
||||
pointIncentiveClaimablePoints: 0,
|
||||
publishReady: true,
|
||||
levels:
|
||||
entry.coverSlides?.map((slide, index) => ({
|
||||
@@ -729,11 +747,10 @@ function mergePuzzleServiceRuntimeState(
|
||||
}
|
||||
|
||||
const serviceLevel = serviceRun.currentLevel;
|
||||
const leaderboardEntries =
|
||||
serviceLevel.leaderboardEntries.length > 0
|
||||
? serviceLevel.leaderboardEntries
|
||||
: serviceRun.leaderboardEntries;
|
||||
|
||||
if (
|
||||
currentRun.currentLevel.status === 'cleared' &&
|
||||
serviceLevel.status !== 'cleared'
|
||||
) {
|
||||
return {
|
||||
...currentRun,
|
||||
recommendedNextProfileId: serviceRun.recommendedNextProfileId,
|
||||
@@ -741,8 +758,27 @@ function mergePuzzleServiceRuntimeState(
|
||||
nextLevelProfileId: serviceRun.nextLevelProfileId,
|
||||
nextLevelId: serviceRun.nextLevelId,
|
||||
recommendedNextWorks: serviceRun.recommendedNextWorks,
|
||||
leaderboardEntries,
|
||||
currentLevel: {
|
||||
leaderboardEntries:
|
||||
currentRun.currentLevel.leaderboardEntries.length > 0
|
||||
? currentRun.currentLevel.leaderboardEntries
|
||||
: currentRun.leaderboardEntries,
|
||||
};
|
||||
}
|
||||
|
||||
const leaderboardEntries =
|
||||
serviceLevel.leaderboardEntries.length > 0
|
||||
? serviceLevel.leaderboardEntries
|
||||
: serviceRun.leaderboardEntries;
|
||||
|
||||
return {
|
||||
...currentRun,
|
||||
recommendedNextProfileId: serviceRun.recommendedNextProfileId,
|
||||
nextLevelMode: serviceRun.nextLevelMode,
|
||||
nextLevelProfileId: serviceRun.nextLevelProfileId,
|
||||
nextLevelId: serviceRun.nextLevelId,
|
||||
recommendedNextWorks: serviceRun.recommendedNextWorks,
|
||||
leaderboardEntries,
|
||||
currentLevel: {
|
||||
...currentRun.currentLevel,
|
||||
status: serviceLevel.status,
|
||||
startedAtMs: serviceLevel.startedAtMs,
|
||||
@@ -836,6 +872,8 @@ export function PlatformEntryFlowShellImpl({
|
||||
const [deletingCreationWorkId, setDeletingCreationWorkId] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [claimingPuzzlePointIncentiveProfileId, setClaimingPuzzlePointIncentiveProfileId] =
|
||||
useState<string | null>(null);
|
||||
const isBigFishCreationVisible = isPlatformCreationTypeVisible('big-fish');
|
||||
const [profilePlayStats, setProfilePlayStats] =
|
||||
useState<ProfilePlayStatsResponse | null>(null);
|
||||
@@ -1569,6 +1607,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
setIsPuzzleNextLevelGenerating(false);
|
||||
setPuzzleError(null);
|
||||
setDeletingCreationWorkId(null);
|
||||
setClaimingPuzzlePointIncentiveProfileId(null);
|
||||
setProfilePlayStats(null);
|
||||
setProfilePlayStatsError(null);
|
||||
setIsProfilePlayStatsOpen(false);
|
||||
@@ -1812,6 +1851,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
setPuzzleRun(run);
|
||||
setPuzzleRuntimeReturnStage(returnStage);
|
||||
setSelectionStage('puzzle-runtime');
|
||||
void platformBootstrap.refreshSaveArchives();
|
||||
pushAppHistoryPath(
|
||||
buildPublicWorkStagePath(
|
||||
'puzzle-runtime',
|
||||
@@ -1830,6 +1870,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
},
|
||||
[
|
||||
isPuzzleBusy,
|
||||
platformBootstrap,
|
||||
resolvePuzzleErrorMessage,
|
||||
setIsPuzzleBusy,
|
||||
setPuzzleError,
|
||||
@@ -1863,6 +1904,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
playCount: 0,
|
||||
remixCount: 0,
|
||||
likeCount: 0,
|
||||
pointIncentiveTotalHalfPoints: 0,
|
||||
pointIncentiveClaimedPoints: 0,
|
||||
pointIncentiveTotalPoints: 0,
|
||||
pointIncentiveClaimablePoints: 0,
|
||||
publishReady: Boolean(puzzleSession?.resultPreview?.publishReady),
|
||||
levels: draft.levels,
|
||||
} satisfies PuzzleWorkSummary;
|
||||
@@ -1963,9 +2008,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
}
|
||||
|
||||
const timerId = window.setInterval(() => {
|
||||
if (!isLocalPuzzleRun(puzzleRun)) {
|
||||
return;
|
||||
}
|
||||
// 中文注释:正式 run 的棋盘交互也在前端即时裁决,倒计时展示同样走本地时钟;超时落库仍由 onTimeExpired 拉取后端快照完成。
|
||||
setPuzzleRun((currentRun) =>
|
||||
currentRun ? refreshLocalPuzzleTimer(currentRun) : currentRun,
|
||||
);
|
||||
@@ -2009,7 +2052,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
const syncPuzzleRuntimeTimeout = useCallback(async () => {
|
||||
if (
|
||||
!puzzleRun?.currentLevel ||
|
||||
puzzleRun.currentLevel.status !== 'playing'
|
||||
puzzleRun.currentLevel.status === 'cleared'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -2040,9 +2083,11 @@ export function PlatformEntryFlowShellImpl({
|
||||
if (!puzzleRun?.currentLevel) {
|
||||
return null;
|
||||
}
|
||||
const expectedStatus =
|
||||
propKind === 'extendTime' ? 'failed' : 'playing';
|
||||
if (puzzleRun.currentLevel.status !== expectedStatus) {
|
||||
const canUseProp =
|
||||
propKind === 'extendTime'
|
||||
? puzzleRun.currentLevel.status !== 'cleared'
|
||||
: puzzleRun.currentLevel.status === 'playing';
|
||||
if (!canUseProp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -2072,6 +2117,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
puzzleRunRef.current = nextRun;
|
||||
setPuzzleRun(nextRun);
|
||||
void platformBootstrap.refreshProfileDashboard();
|
||||
void platformBootstrap.refreshSaveArchives();
|
||||
return nextRun;
|
||||
},
|
||||
[platformBootstrap, puzzleRun],
|
||||
@@ -2084,6 +2130,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
}
|
||||
|
||||
setPuzzleError(null);
|
||||
const restartLevelId = resolvePuzzleRestartLevelId(
|
||||
puzzleRun,
|
||||
selectedPuzzleDetail,
|
||||
);
|
||||
if (isLocalPuzzleRun(puzzleRun)) {
|
||||
const nextRun = restartLocalPuzzleLevel(puzzleRunRef.current ?? puzzleRun);
|
||||
puzzleRunRef.current = nextRun;
|
||||
@@ -2098,7 +2148,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
? selectedPuzzleDetail
|
||||
: undefined,
|
||||
false,
|
||||
currentLevel.levelId ?? null,
|
||||
restartLevelId,
|
||||
);
|
||||
}, [
|
||||
isPuzzleBusy,
|
||||
@@ -2120,7 +2170,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
platformBootstrap.setSaveError(null);
|
||||
|
||||
try {
|
||||
const resumedArchive = await resumeRpgProfileSaveArchive(
|
||||
const resumedArchive = await resumePuzzleProfileSaveArchiveRaw(
|
||||
entry.worldKey,
|
||||
);
|
||||
platformBootstrap.setSaveEntries((currentEntries) =>
|
||||
@@ -2130,8 +2180,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
: currentEntry,
|
||||
),
|
||||
);
|
||||
const gameState = resumedArchive.snapshot
|
||||
.gameState as PuzzleSaveArchiveState;
|
||||
const gameState = resumedArchive.snapshot.gameState;
|
||||
const profileId =
|
||||
typeof gameState.currentProfileId === 'string' &&
|
||||
gameState.currentProfileId.trim()
|
||||
@@ -2145,7 +2194,13 @@ export function PlatformEntryFlowShellImpl({
|
||||
gameState.currentLevelId.trim()
|
||||
? gameState.currentLevelId
|
||||
: null;
|
||||
await startPuzzleRunFromProfile(profileId, 'platform', undefined, false, levelId);
|
||||
await startPuzzleRunFromProfile(
|
||||
profileId,
|
||||
'platform',
|
||||
undefined,
|
||||
false,
|
||||
levelId,
|
||||
);
|
||||
} catch (error) {
|
||||
platformBootstrap.setSaveError(
|
||||
resolvePuzzleErrorMessage(error, '恢复拼图存档失败。'),
|
||||
@@ -2191,7 +2246,27 @@ export function PlatformEntryFlowShellImpl({
|
||||
|
||||
if (isLocalPuzzleRun(puzzleRun)) {
|
||||
setPuzzleRun(submitLocalPuzzleLeaderboard(puzzleRun, payload.nickname));
|
||||
setIsPuzzleLeaderboardBusy(false);
|
||||
void advanceLocalPuzzleNextLevel({
|
||||
run: puzzleRun,
|
||||
sourceSessionId:
|
||||
selectedPuzzleDetail?.sourceSessionId ??
|
||||
puzzleSession?.sessionId ??
|
||||
null,
|
||||
})
|
||||
.then(({ run }) => {
|
||||
setPuzzleRun((currentRun) => {
|
||||
if (!currentRun) {
|
||||
return currentRun;
|
||||
}
|
||||
return mergePuzzleServiceRuntimeState(currentRun, run);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
// 中文注释:本地试玩缺少后端候选时保留本地排行榜和既有下一关入口,避免结算被探测请求打断。
|
||||
})
|
||||
.finally(() => {
|
||||
setIsPuzzleLeaderboardBusy(false);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2203,6 +2278,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
}
|
||||
return mergePuzzleServiceRuntimeState(currentRun, run);
|
||||
});
|
||||
void platformBootstrap.refreshSaveArchives();
|
||||
})
|
||||
.catch((error) => {
|
||||
submittedPuzzleLeaderboardKeysRef.current.delete(submitKey);
|
||||
@@ -2215,8 +2291,11 @@ export function PlatformEntryFlowShellImpl({
|
||||
});
|
||||
}, [
|
||||
authUi?.user?.displayName,
|
||||
platformBootstrap,
|
||||
puzzleRun,
|
||||
puzzleSession,
|
||||
resolvePuzzleErrorMessage,
|
||||
selectedPuzzleDetail,
|
||||
setPuzzleError,
|
||||
]);
|
||||
|
||||
@@ -2263,6 +2342,9 @@ export function PlatformEntryFlowShellImpl({
|
||||
})
|
||||
: await advancePuzzleNextLevel(puzzleRun.runId);
|
||||
setPuzzleRun(run);
|
||||
if (!isLocalPuzzleRun(puzzleRun)) {
|
||||
void platformBootstrap.refreshSaveArchives();
|
||||
}
|
||||
} catch (error) {
|
||||
setPuzzleError(resolvePuzzleErrorMessage(error, '准备下一关失败。'));
|
||||
} finally {
|
||||
@@ -2272,6 +2354,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
}, [
|
||||
isPuzzleBusy,
|
||||
isPuzzleLeaderboardBusy,
|
||||
platformBootstrap,
|
||||
puzzleRun,
|
||||
puzzleSession,
|
||||
resolvePuzzleErrorMessage,
|
||||
@@ -2565,6 +2648,55 @@ export function PlatformEntryFlowShellImpl({
|
||||
[],
|
||||
);
|
||||
|
||||
const handleClaimPuzzlePointIncentive = useCallback(
|
||||
(work: PuzzleWorkSummary) => {
|
||||
if (claimingPuzzlePointIncentiveProfileId) {
|
||||
return;
|
||||
}
|
||||
|
||||
runProtectedAction(() => {
|
||||
setClaimingPuzzlePointIncentiveProfileId(work.profileId);
|
||||
setPuzzleError(null);
|
||||
|
||||
void claimPuzzleWorkPointIncentive(work.profileId)
|
||||
.then((response) => {
|
||||
const updatedWork = response.item;
|
||||
setPuzzleWorks((current) =>
|
||||
current.map((item) =>
|
||||
mergePuzzleWorkSummary(item, updatedWork),
|
||||
),
|
||||
);
|
||||
setPuzzleGalleryEntries((current) =>
|
||||
current.map((item) =>
|
||||
mergePuzzleWorkSummary(item, updatedWork),
|
||||
),
|
||||
);
|
||||
setSelectedPuzzleDetail((current) =>
|
||||
current ? mergePuzzleWorkSummary(current, updatedWork) : current,
|
||||
);
|
||||
syncUpdatedPublicWorkDetail(
|
||||
mapPuzzleWorkToPublicWorkDetail(updatedWork),
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
setPuzzleError(
|
||||
resolvePuzzleErrorMessage(error, '领取拼图积分激励失败。'),
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
setClaimingPuzzlePointIncentiveProfileId(null);
|
||||
});
|
||||
});
|
||||
},
|
||||
[
|
||||
claimingPuzzlePointIncentiveProfileId,
|
||||
resolvePuzzleErrorMessage,
|
||||
runProtectedAction,
|
||||
setPuzzleError,
|
||||
syncUpdatedPublicWorkDetail,
|
||||
],
|
||||
);
|
||||
|
||||
const likePublicWork = useCallback(
|
||||
(entry: PlatformPublicGalleryCard) => {
|
||||
if (isPublicWorkDetailBusy) {
|
||||
@@ -3480,6 +3612,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
onDeletePuzzle={(item) => {
|
||||
handleDeletePuzzleWork(item);
|
||||
}}
|
||||
onClaimPuzzlePointIncentive={(item) => {
|
||||
handleClaimPuzzlePointIncentive(item);
|
||||
}}
|
||||
claimingPuzzleProfileId={claimingPuzzlePointIncentiveProfileId}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user