@@ -39,6 +39,8 @@ export function GameShellCanvasStage({
|
||||
encounter={visibleGameState.currentEncounter}
|
||||
currentScenePreset={visibleGameState.currentScenePreset}
|
||||
worldType={visibleGameState.worldType}
|
||||
customWorldProfile={visibleGameState.customWorldProfile}
|
||||
storyEngineMemory={visibleGameState.storyEngineMemory}
|
||||
sceneHostileNpcs={visibleGameState.sceneHostileNpcs}
|
||||
playerX={visibleGameState.playerX}
|
||||
playerOffsetY={visibleGameState.playerOffsetY}
|
||||
|
||||
@@ -703,7 +703,6 @@ export function PlatformHomeView({
|
||||
saves: Archive,
|
||||
profile: UserRound,
|
||||
} as const;
|
||||
const latestSaveEntry = saveEntries[0] ?? null;
|
||||
const openUserSurface = () => {
|
||||
if (authUi?.user) {
|
||||
authUi.openAccountModal();
|
||||
@@ -876,41 +875,6 @@ export function PlatformHomeView({
|
||||
<div className={MOBILE_PAGE_STAGE_CLASS}>
|
||||
{authUi?.user ? (
|
||||
<>
|
||||
<section
|
||||
className={`${HERO_SURFACE_CLASS} relative overflow-hidden px-[18px] py-4 text-left`}
|
||||
>
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(255,255,255,0.18),transparent_30%),radial-gradient(circle_at_right,rgba(255,205,178,0.18),transparent_28%),linear-gradient(135deg,rgba(255,92,120,0.92),rgba(255,139,98,0.9))]" />
|
||||
<div className="relative z-10 flex min-h-[10.5rem] flex-col gap-4">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<span className="platform-pill platform-pill--cool">
|
||||
SAVE ARCHIVE
|
||||
</span>
|
||||
<div className="platform-pill platform-pill--neutral px-3 text-[11px] tracking-[0.08em]">
|
||||
{saveEntries.length > 0 ? `${saveEntries.length} 个存档` : '暂无存档'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex min-w-0 items-stretch gap-3 sm:gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="line-clamp-2 break-words text-[1.95rem] font-black leading-[1.02] text-white sm:text-3xl">
|
||||
{latestSaveEntry ? latestSaveEntry.worldName : '存档'}
|
||||
</div>
|
||||
<div className="mt-2 text-sm leading-6 text-zinc-200/88">
|
||||
{latestSaveEntry
|
||||
? `最近更新于 ${formatSnapshotTime(latestSaveEntry.lastPlayedAt)},点开后可直接继续游玩。`
|
||||
: '你在平台里留下的最近可恢复存档会显示在这里。'}
|
||||
</div>
|
||||
</div>
|
||||
{latestSaveEntry ? (
|
||||
<SaveArchivePreview
|
||||
entry={latestSaveEntry}
|
||||
label="最近更新"
|
||||
className="h-[8.8rem] w-[6.1rem] sm:h-[9.4rem] sm:w-[7rem]"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{saveError ? (
|
||||
<div className="rounded-2xl border border-rose-400/20 bg-rose-500/10 px-4 py-3 text-sm leading-6 text-rose-100">
|
||||
{saveError}
|
||||
|
||||
@@ -641,6 +641,7 @@ test('authenticated users with save archives default into the saves tab', async
|
||||
expect((await screen.findAllByText('全部存档')).length).toBeGreaterThan(0);
|
||||
expect((await screen.findAllByText('潮雾列岛')).length).toBeGreaterThan(0);
|
||||
expect(screen.getAllByText('最近更新时间排序').length).toBeGreaterThan(0);
|
||||
expect(screen.queryByText('SAVE ARCHIVE')).toBeNull();
|
||||
});
|
||||
|
||||
test('save tab can resume a selected archive directly into the game', async () => {
|
||||
|
||||
@@ -85,6 +85,10 @@ export interface GameShellAdventureStatistics {
|
||||
scenesTraveled: number;
|
||||
currentSceneName: string;
|
||||
playerCurrency: number;
|
||||
playerLevel?: number;
|
||||
playerCurrentLevelXp?: number;
|
||||
playerXpToNextLevel?: number;
|
||||
playerTotalXp?: number;
|
||||
inventoryItemCount: number;
|
||||
inventoryStackCount: number;
|
||||
activeCompanionCount: number;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { normalizePlayerProgressionState } from '../../data/playerProgression';
|
||||
import { getLiveGamePlayTimeMs } from '../../data/runtimeStats';
|
||||
import { getWorldCampScenePreset } from '../../data/scenePresets';
|
||||
import type {
|
||||
@@ -41,7 +42,8 @@ export function buildGameShellDialogueIndicator(params: {
|
||||
return {
|
||||
showPlayer: true,
|
||||
showEncounter: true,
|
||||
activeSpeaker: lastSpeaker === 'player' ? 'player' : lastSpeaker ? 'npc' : null,
|
||||
activeSpeaker:
|
||||
lastSpeaker === 'player' ? 'player' : lastSpeaker ? 'npc' : null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -62,7 +64,7 @@ export function buildCanvasCompanionRenderStates(params: {
|
||||
}) {
|
||||
const activeEncounterNpcId =
|
||||
params.visibleGameState.currentEncounter?.kind === 'npc'
|
||||
? params.visibleGameState.currentEncounter.id ?? null
|
||||
? (params.visibleGameState.currentEncounter.id ?? null)
|
||||
: null;
|
||||
if (!activeEncounterNpcId) {
|
||||
return params.visibleCompanionRenderStates;
|
||||
@@ -79,6 +81,9 @@ export function buildAdventureStatistics(params: {
|
||||
livePlayTimeMs: number;
|
||||
}): GameShellAdventureStatistics {
|
||||
const { gameState, visibleGameState, livePlayTimeMs } = params;
|
||||
const playerProgression = normalizePlayerProgressionState(
|
||||
visibleGameState.playerProgression ?? null,
|
||||
);
|
||||
|
||||
return {
|
||||
playTimeMs: livePlayTimeMs,
|
||||
@@ -94,6 +99,10 @@ export function buildAdventureStatistics(params: {
|
||||
scenesTraveled: gameState.runtimeStats.scenesTraveled,
|
||||
currentSceneName: visibleGameState.currentScenePreset?.name ?? '当前区域',
|
||||
playerCurrency: visibleGameState.playerCurrency,
|
||||
playerLevel: playerProgression.level,
|
||||
playerCurrentLevelXp: playerProgression.currentLevelXp,
|
||||
playerXpToNextLevel: playerProgression.xpToNextLevel,
|
||||
playerTotalXp: playerProgression.totalXp,
|
||||
inventoryItemCount: visibleGameState.playerInventory.reduce(
|
||||
(sum, item) => sum + item.quantity,
|
||||
0,
|
||||
@@ -104,17 +113,11 @@ export function buildAdventureStatistics(params: {
|
||||
};
|
||||
}
|
||||
|
||||
export function useGameShellRuntimeViewModel(params: Pick<
|
||||
GameShellProps,
|
||||
'session' | 'story' | 'companions'
|
||||
>) {
|
||||
export function useGameShellRuntimeViewModel(
|
||||
params: Pick<GameShellProps, 'session' | 'story' | 'companions'>,
|
||||
) {
|
||||
const { session, story, companions } = params;
|
||||
const {
|
||||
gameState,
|
||||
currentStory,
|
||||
isLoading,
|
||||
isMapOpen,
|
||||
} = session;
|
||||
const { gameState, currentStory, isLoading, isMapOpen } = session;
|
||||
const { npcUi, characterChatUi, handleChoice } = story;
|
||||
const { buildCompanionRenderStates } = companions;
|
||||
|
||||
@@ -122,7 +125,7 @@ export function useGameShellRuntimeViewModel(params: Pick<
|
||||
const openingCampSceneId = useMemo(
|
||||
() =>
|
||||
gameState.worldType
|
||||
? getWorldCampScenePreset(gameState.worldType)?.id ?? null
|
||||
? (getWorldCampScenePreset(gameState.worldType)?.id ?? null)
|
||||
: null,
|
||||
[gameState.worldType],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user