Implement scene-based chapter quest progression
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-08 11:58:47 +08:00
parent 9d2fc9e4b8
commit bd9fdcbe31
170 changed files with 18259 additions and 1049 deletions

View File

@@ -7,6 +7,7 @@ import {BottomTab} from '../hooks/useGameFlow';
import {
type BattleRewardUi,
type CharacterChatUi,
type GoalFlowUi,
type InventoryFlowUi,
type QuestFlowUi,
type StoryGenerationNpcUi,
@@ -49,6 +50,7 @@ interface GameShellStoryProps {
inventoryUi: InventoryFlowUi;
battleRewardUi: BattleRewardUi;
questUi: QuestFlowUi;
goalUi: GoalFlowUi;
}
interface GameShellEntryProps {
@@ -63,6 +65,7 @@ interface GameShellEntryProps {
interface GameShellCompanionProps {
companionRenderStates: CompanionRenderState[];
buildCompanionRenderStates: (state: GameState) => CompanionRenderState[];
onBenchCompanion: (npcId: string) => void;
onActivateRosterCompanion: (npcId: string, swapNpcId?: string | null) => void;
}
@@ -201,6 +204,7 @@ export function GameShell({session, story, entry, companions, audio}: GameShellP
inventoryUi,
battleRewardUi,
questUi,
goalUi,
} = story;
const {
hasSavedGame,
@@ -211,7 +215,12 @@ export function GameShell({session, story, entry, companions, audio}: GameShellP
handleBackToWorldSelect,
handleCharacterSelect,
} = entry;
const {companionRenderStates, onBenchCompanion, onActivateRosterCompanion} = companions;
const {
companionRenderStates,
buildCompanionRenderStates,
onBenchCompanion,
onActivateRosterCompanion,
} = companions;
const {musicVolume, onMusicVolumeChange} = audio;
const [clockNow, setClockNow] = useState(() => Date.now());
@@ -287,13 +296,18 @@ export function GameShell({session, story, entry, companions, audio}: GameShellP
[gameState.characterChats],
);
const visibleCompanionRenderStates = useMemo(
() => buildCompanionRenderStates(visibleGameState),
[buildCompanionRenderStates, visibleGameState],
);
const canvasCompanionRenderStates = useMemo(() => {
const activeEncounterNpcId = visibleGameState.currentEncounter?.kind === 'npc'
? visibleGameState.currentEncounter.id ?? null
: null;
if (!activeEncounterNpcId) return companionRenderStates;
return companionRenderStates.filter(companion => companion.npcId !== activeEncounterNpcId);
}, [companionRenderStates, visibleGameState.currentEncounter]);
if (!activeEncounterNpcId) return visibleCompanionRenderStates;
return visibleCompanionRenderStates.filter(companion => companion.npcId !== activeEncounterNpcId);
}, [visibleCompanionRenderStates, visibleGameState.currentEncounter]);
const livePlayTimeMs = useMemo(
() => getLiveGamePlayTimeMs(gameState.runtimeStats, clockNow),
@@ -530,6 +544,9 @@ export function GameShell({session, story, entry, companions, audio}: GameShellP
worldType={visibleGameState.worldType}
quests={visibleGameState.quests}
questUi={questUi}
goalStack={goalUi.goalStack}
goalPulse={goalUi.pulse}
onDismissGoalPulse={goalUi.dismissPulse}
battleRewardUi={battleRewardUi}
playerHp={visibleGameState.playerHp}
playerMaxHp={visibleGameState.playerMaxHp}
@@ -542,15 +559,6 @@ export function GameShell({session, story, entry, companions, audio}: GameShellP
journeyBeat={
visibleGameState.storyEngineMemory?.currentJourneyBeat ?? null
}
recentChronicleSummary={
visibleGameState.storyEngineMemory?.continueGameDigest ?? null
}
currentCampEvent={
visibleGameState.storyEngineMemory?.currentCampEvent ?? null
}
setpieceDirective={
visibleGameState.storyEngineMemory?.currentSetpieceDirective ?? null
}
statistics={adventureStatistics}
musicVolume={musicVolume}
onMusicVolumeChange={onMusicVolumeChange}