Implement scene-based chapter quest progression
Some checks failed
CI / verify (push) Has been cancelled
Some checks failed
CI / verify (push) Has been cancelled
This commit is contained in:
@@ -4,6 +4,7 @@ import type { BottomTab } from '../../hooks/useGameFlow';
|
||||
import type {
|
||||
BattleRewardUi,
|
||||
CharacterChatUi,
|
||||
GoalFlowUi,
|
||||
InventoryFlowUi,
|
||||
QuestFlowUi,
|
||||
} from '../../hooks/useStoryGeneration';
|
||||
@@ -62,6 +63,7 @@ export function GameShellMainContent({
|
||||
inventoryUi,
|
||||
battleRewardUi,
|
||||
questUi,
|
||||
goalUi,
|
||||
companionRenderStates,
|
||||
characterChatSummaries,
|
||||
openOverlayPanel,
|
||||
@@ -98,6 +100,7 @@ export function GameShellMainContent({
|
||||
inventoryUi: InventoryFlowUi;
|
||||
battleRewardUi: BattleRewardUi;
|
||||
questUi: QuestFlowUi;
|
||||
goalUi: GoalFlowUi;
|
||||
companionRenderStates: CompanionRenderState[];
|
||||
characterChatSummaries: Record<string, string>;
|
||||
openOverlayPanel: (panel: 'character' | 'inventory') => void;
|
||||
@@ -171,6 +174,7 @@ export function GameShellMainContent({
|
||||
inventoryUi={inventoryUi}
|
||||
battleRewardUi={battleRewardUi}
|
||||
questUi={questUi}
|
||||
goalUi={goalUi}
|
||||
companionRenderStates={companionRenderStates}
|
||||
characterChatSummaries={characterChatSummaries}
|
||||
openOverlayPanel={openOverlayPanel}
|
||||
|
||||
@@ -33,6 +33,7 @@ export function GameShellRuntime({session, story, entry, companions, audio}: Gam
|
||||
inventoryUi,
|
||||
battleRewardUi,
|
||||
questUi,
|
||||
goalUi,
|
||||
} = story;
|
||||
const {
|
||||
hasSavedGame,
|
||||
@@ -43,7 +44,12 @@ export function GameShellRuntime({session, story, entry, companions, audio}: Gam
|
||||
handleBackToWorldSelect,
|
||||
handleCharacterSelect,
|
||||
} = entry;
|
||||
const {companionRenderStates, onBenchCompanion, onActivateRosterCompanion} = companions;
|
||||
const {
|
||||
companionRenderStates,
|
||||
buildCompanionRenderStates,
|
||||
onBenchCompanion,
|
||||
onActivateRosterCompanion,
|
||||
} = companions;
|
||||
const {musicVolume, onMusicVolumeChange} = audio;
|
||||
|
||||
const [clockNow, setClockNow] = useState(() => Date.now());
|
||||
@@ -119,13 +125,18 @@ export function GameShellRuntime({session, story, entry, companions, audio}: Gam
|
||||
[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),
|
||||
@@ -229,6 +240,7 @@ export function GameShellRuntime({session, story, entry, companions, audio}: Gam
|
||||
inventoryUi={inventoryUi}
|
||||
battleRewardUi={battleRewardUi}
|
||||
questUi={questUi}
|
||||
goalUi={goalUi}
|
||||
companionRenderStates={companionRenderStates}
|
||||
characterChatSummaries={characterChatSummaries}
|
||||
openOverlayPanel={openOverlayPanel}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { BottomTab } from '../../hooks/useGameFlow';
|
||||
import type {
|
||||
BattleRewardUi,
|
||||
CharacterChatUi,
|
||||
GoalFlowUi,
|
||||
InventoryFlowUi,
|
||||
QuestFlowUi,
|
||||
} from '../../hooks/useStoryGeneration';
|
||||
@@ -69,6 +70,7 @@ export function GameShellStoryPanels({
|
||||
inventoryUi,
|
||||
battleRewardUi,
|
||||
questUi,
|
||||
goalUi,
|
||||
companionRenderStates,
|
||||
characterChatSummaries,
|
||||
openOverlayPanel,
|
||||
@@ -94,6 +96,7 @@ export function GameShellStoryPanels({
|
||||
inventoryUi: InventoryFlowUi;
|
||||
battleRewardUi: BattleRewardUi;
|
||||
questUi: QuestFlowUi;
|
||||
goalUi: GoalFlowUi;
|
||||
companionRenderStates: CompanionRenderState[];
|
||||
characterChatSummaries: Record<string, string>;
|
||||
openOverlayPanel: (panel: 'character' | 'inventory') => void;
|
||||
@@ -199,6 +202,9 @@ export function GameShellStoryPanels({
|
||||
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}
|
||||
@@ -211,15 +217,6 @@ export function GameShellStoryPanels({
|
||||
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}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
readSavedCustomWorldProfiles,
|
||||
upsertSavedCustomWorldProfile,
|
||||
} from '../../data/customWorldLibrary';
|
||||
import { resolveCustomWorldCampSceneImage } from '../../data/customWorldVisuals';
|
||||
import { getScenePreset } from '../../data/scenePresets';
|
||||
import {
|
||||
type CustomWorldGenerationProgress,
|
||||
@@ -18,6 +19,7 @@ import {
|
||||
buildCustomWorldCreatorIntentGenerationText,
|
||||
createEmptyCustomWorldCreatorIntent,
|
||||
} from '../../services/customWorldCreatorIntent';
|
||||
import { detectCustomWorldThemeMode } from '../../services/customWorldTheme';
|
||||
import {
|
||||
type CustomWorldCreatorIntent,
|
||||
type CustomWorldGenerationMode,
|
||||
@@ -217,30 +219,24 @@ export function PreGameSelectionFlow({
|
||||
|
||||
const savedCustomWorldCards = useMemo(
|
||||
() =>
|
||||
savedCustomWorldProfiles.map((profile, index) => {
|
||||
const anchorWorldType = profile.templateWorldType;
|
||||
savedCustomWorldProfiles.map((profile) => {
|
||||
const themeMode = detectCustomWorldThemeMode(profile);
|
||||
const leadCharacter =
|
||||
buildCustomWorldPlayableCharacters(profile)[0] ?? null;
|
||||
|
||||
return {
|
||||
id: profile.id,
|
||||
profile,
|
||||
texture:
|
||||
anchorWorldType === WorldType.WUXIA
|
||||
? UI_CHROME.worldButtonWuxia
|
||||
: UI_CHROME.worldButtonXianxia,
|
||||
sceneImage:
|
||||
profile.landmarks[0]?.imageSrc ??
|
||||
getScenePreset(anchorWorldType, (index % 3) + 1)?.imageSrc ??
|
||||
getScenePreset(anchorWorldType, 0)?.imageSrc ??
|
||||
'',
|
||||
texture: UI_CHROME.panel,
|
||||
sceneImage: resolveCustomWorldCampSceneImage(profile) ?? '',
|
||||
featurePortrait: leadCharacter?.portrait ?? '',
|
||||
featureIcon:
|
||||
anchorWorldType === WorldType.WUXIA
|
||||
themeMode === 'martial'
|
||||
? WORLD_SELECT_ICONS.wuxia
|
||||
: WORLD_SELECT_ICONS.xianxia,
|
||||
accentLabel:
|
||||
anchorWorldType === WorldType.WUXIA ? '武侠基础' : '仙侠基础',
|
||||
: themeMode === 'arcane'
|
||||
? WORLD_SELECT_ICONS.xianxia
|
||||
: CHROME_ICONS.refreshOptions,
|
||||
accentLabel: '自定义世界',
|
||||
};
|
||||
}),
|
||||
[savedCustomWorldProfiles],
|
||||
@@ -900,10 +896,8 @@ export function PreGameSelectionFlow({
|
||||
<div className="rounded-full border border-sky-300/20 bg-sky-500/10 px-3 py-1 text-[10px] tracking-[0.2em] text-sky-100">
|
||||
已保存
|
||||
</div>
|
||||
<div className="rounded-full border border-white/10 bg-black/24 px-2.5 py-1 text-[10px] text-zinc-100">
|
||||
{world.accentLabel === '武侠基础'
|
||||
? '武侠'
|
||||
: '仙侠'}
|
||||
<div className="rounded-full border border-white/10 bg-black/24 px-2.5 py-1 text-[10px] text-zinc-100">
|
||||
{world.accentLabel}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-auto">
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { BottomTab } from '../../hooks/useGameFlow';
|
||||
import type {
|
||||
BattleRewardUi,
|
||||
CharacterChatUi,
|
||||
GoalFlowUi,
|
||||
InventoryFlowUi,
|
||||
QuestFlowUi,
|
||||
StoryGenerationNpcUi,
|
||||
@@ -37,6 +38,7 @@ export interface GameShellStoryProps {
|
||||
inventoryUi: InventoryFlowUi;
|
||||
battleRewardUi: BattleRewardUi;
|
||||
questUi: QuestFlowUi;
|
||||
goalUi: GoalFlowUi;
|
||||
}
|
||||
|
||||
export interface GameShellEntryProps {
|
||||
@@ -51,6 +53,7 @@ export interface GameShellEntryProps {
|
||||
|
||||
export interface GameShellCompanionProps {
|
||||
companionRenderStates: CompanionRenderState[];
|
||||
buildCompanionRenderStates: (state: GameState) => CompanionRenderState[];
|
||||
onBenchCompanion: (npcId: string) => void;
|
||||
onActivateRosterCompanion: (npcId: string, swapNpcId?: string | null) => void;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user