Rework story engine flow and reorganize project docs
Some checks failed
CI / verify (push) Has been cancelled
Some checks failed
CI / verify (push) Has been cancelled
This commit is contained in:
@@ -17,7 +17,16 @@ import {type InventoryUseEffect, isInventoryItemUsable} from '../../data/invento
|
||||
import {getRarityLabel} from '../../data/npcInteractions';
|
||||
import {isQuestReadyToClaim} from '../../data/questFlow';
|
||||
import type {BattleRewardUi, QuestFlowUi} from '../../hooks/useStoryGeneration';
|
||||
import type {EquipmentSlotId, InventoryItem, QuestLogEntry, WorldType} from '../../types';
|
||||
import type {
|
||||
CampEvent,
|
||||
ChapterState,
|
||||
EquipmentSlotId,
|
||||
InventoryItem,
|
||||
JourneyBeat,
|
||||
QuestLogEntry,
|
||||
SetpieceDirective,
|
||||
WorldType,
|
||||
} from '../../types';
|
||||
import {CHROME_ICONS, getInventoryCategoryIcon, getNineSliceStyle, UI_CHROME} from '../../uiAssets';
|
||||
import {HostileNpcAnimator} from '../HostileNpcAnimator';
|
||||
import {PixelIcon} from '../PixelIcon';
|
||||
@@ -57,12 +66,19 @@ interface AdventurePanelOverlaysProps {
|
||||
onMusicVolumeChange: (value: number) => void;
|
||||
onSaveAndExit: () => void;
|
||||
saveAndExitDisabled: boolean;
|
||||
isChapterPanelOpen: boolean;
|
||||
setIsChapterPanelOpen: (open: boolean) => void;
|
||||
isQuestPanelOpen: boolean;
|
||||
setIsQuestPanelOpen: (open: boolean) => void;
|
||||
isSettingsPanelOpen: boolean;
|
||||
setIsSettingsPanelOpen: (open: boolean) => void;
|
||||
isStatsPanelOpen: boolean;
|
||||
setIsStatsPanelOpen: (open: boolean) => void;
|
||||
chapterState: ChapterState | null;
|
||||
journeyBeat: JourneyBeat | null;
|
||||
recentChronicleSummary: string | null;
|
||||
currentCampEvent: CampEvent | null;
|
||||
setpieceDirective: SetpieceDirective | null;
|
||||
selectedQuest: QuestLogEntry | null;
|
||||
setSelectedQuestId: (questId: string | null) => void;
|
||||
completionNoticeQuest: QuestLogEntry | null;
|
||||
@@ -82,6 +98,78 @@ interface AdventurePanelOverlaysProps {
|
||||
getQuestStatusLabel: (status: QuestLogEntry['status']) => string;
|
||||
}
|
||||
|
||||
function getChapterStageLabel(stage: ChapterState['stage'] | null | undefined) {
|
||||
switch (stage) {
|
||||
case 'opening':
|
||||
return '开篇';
|
||||
case 'expansion':
|
||||
return '展开';
|
||||
case 'turning_point':
|
||||
return '转折';
|
||||
case 'climax':
|
||||
return '高潮';
|
||||
case 'aftermath':
|
||||
return '余波';
|
||||
default:
|
||||
return '进行中';
|
||||
}
|
||||
}
|
||||
|
||||
function getJourneyBeatLabel(beatType: JourneyBeat['beatType'] | null | undefined) {
|
||||
switch (beatType) {
|
||||
case 'approach':
|
||||
return '接近';
|
||||
case 'investigation':
|
||||
return '调查';
|
||||
case 'camp':
|
||||
return '休整';
|
||||
case 'conflict':
|
||||
return '冲突';
|
||||
case 'boss_prelude':
|
||||
return '决战前奏';
|
||||
case 'climax':
|
||||
return '高潮';
|
||||
case 'recovery':
|
||||
return '恢复';
|
||||
default:
|
||||
return '旅程';
|
||||
}
|
||||
}
|
||||
|
||||
function getCampEventLabel(eventType: CampEvent['eventType'] | null | undefined) {
|
||||
switch (eventType) {
|
||||
case 'private_talk':
|
||||
return '私话';
|
||||
case 'party_banter':
|
||||
return '同行插话';
|
||||
case 'conflict':
|
||||
return '争执';
|
||||
case 'comfort':
|
||||
return '安抚';
|
||||
case 'reveal':
|
||||
return '透露';
|
||||
case 'decision':
|
||||
return '抉择';
|
||||
default:
|
||||
return '营地事件';
|
||||
}
|
||||
}
|
||||
|
||||
function getSetpieceLabel(setpieceType: SetpieceDirective['setpieceType'] | null | undefined) {
|
||||
switch (setpieceType) {
|
||||
case 'boss_prelude':
|
||||
return '决战前奏';
|
||||
case 'showdown':
|
||||
return '对峙';
|
||||
case 'climax':
|
||||
return '高潮';
|
||||
case 'aftermath':
|
||||
return '余波';
|
||||
default:
|
||||
return '剧情节点';
|
||||
}
|
||||
}
|
||||
|
||||
function getQuestRewardItemIcon(item: InventoryItem) {
|
||||
if (item.iconSrc) return item.iconSrc;
|
||||
if (item.tags.includes('weapon')) return '/UI/Icon_Eq_Weapon.png';
|
||||
@@ -393,12 +481,19 @@ export function AdventurePanelOverlays({
|
||||
onMusicVolumeChange,
|
||||
onSaveAndExit,
|
||||
saveAndExitDisabled,
|
||||
isChapterPanelOpen,
|
||||
setIsChapterPanelOpen,
|
||||
isQuestPanelOpen,
|
||||
setIsQuestPanelOpen,
|
||||
isSettingsPanelOpen,
|
||||
setIsSettingsPanelOpen,
|
||||
isStatsPanelOpen,
|
||||
setIsStatsPanelOpen,
|
||||
chapterState,
|
||||
journeyBeat,
|
||||
recentChronicleSummary,
|
||||
currentCampEvent,
|
||||
setpieceDirective,
|
||||
selectedQuest,
|
||||
setSelectedQuestId,
|
||||
completionNoticeQuest,
|
||||
@@ -421,6 +516,120 @@ export function AdventurePanelOverlays({
|
||||
|
||||
return (
|
||||
<>
|
||||
<AnimatePresence>
|
||||
{isChapterPanelOpen && (
|
||||
<motion.div
|
||||
initial={{opacity: 0}}
|
||||
animate={{opacity: 1}}
|
||||
exit={{opacity: 0}}
|
||||
className="fixed inset-0 z-[76] flex items-center justify-center bg-black/72 p-3 backdrop-blur-sm sm:p-4"
|
||||
onClick={() => setIsChapterPanelOpen(false)}
|
||||
>
|
||||
<motion.div
|
||||
initial={{opacity: 0, scale: 0.96, y: 8}}
|
||||
animate={{opacity: 1, scale: 1, y: 0}}
|
||||
exit={{opacity: 0, scale: 0.96, y: 8}}
|
||||
transition={{duration: 0.18, ease: 'easeOut'}}
|
||||
className="pixel-nine-slice pixel-modal-shell flex max-h-[min(86vh,40rem)] w-full max-w-lg flex-col overflow-hidden shadow-[0_24px_80px_rgba(0,0,0,0.55)]"
|
||||
style={getNineSliceStyle(UI_CHROME.modalPanel)}
|
||||
onClick={event => event.stopPropagation()}
|
||||
>
|
||||
<div className="relative border-b border-white/10 px-4 py-3 sm:px-5 sm:py-4">
|
||||
<div className="min-w-0 pr-10">
|
||||
<div className="text-sm font-semibold text-white">
|
||||
{chapterState?.title ?? '当前章节'}
|
||||
</div>
|
||||
<div className="mt-1 text-[11px] text-zinc-500">
|
||||
只展示当前旅程里的剧情进展与回顾
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsChapterPanelOpen(false)}
|
||||
className="absolute right-4 top-3 p-1 text-zinc-400 transition-colors hover:text-white sm:right-5 sm:top-4"
|
||||
>
|
||||
<PixelIcon src={CHROME_ICONS.close} className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 flex-1 space-y-4 overflow-y-auto p-4 scrollbar-hide">
|
||||
<div className="rounded-2xl border border-white/8 bg-black/20 px-4 py-3.5">
|
||||
<div className="text-[10px] tracking-[0.24em] text-zinc-500">
|
||||
章节标题
|
||||
</div>
|
||||
<div className="mt-2 text-lg font-semibold text-white">
|
||||
{chapterState?.title ?? '旅程推进中'}
|
||||
</div>
|
||||
{chapterState && (
|
||||
<div className="mt-2 inline-flex rounded-full border border-white/10 bg-white/5 px-2.5 py-1 text-[10px] text-zinc-300">
|
||||
{getChapterStageLabel(chapterState.stage)}
|
||||
</div>
|
||||
)}
|
||||
{chapterState?.chapterSummary && (
|
||||
<div className="mt-3 text-sm leading-relaxed text-zinc-300">
|
||||
{chapterState.chapterSummary}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{journeyBeat && (
|
||||
<div className="rounded-2xl border border-white/8 bg-black/20 px-4 py-3.5">
|
||||
<div className="text-[10px] tracking-[0.24em] text-zinc-500">
|
||||
当前段落
|
||||
</div>
|
||||
<div className="mt-2 text-sm font-semibold text-white">
|
||||
{getJourneyBeatLabel(journeyBeat.beatType)} · {journeyBeat.title}
|
||||
</div>
|
||||
<div className="mt-2 text-sm leading-relaxed text-zinc-300">
|
||||
{journeyBeat.emotionalGoal}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{currentCampEvent && (
|
||||
<div className="rounded-2xl border border-white/8 bg-black/20 px-4 py-3.5">
|
||||
<div className="text-[10px] tracking-[0.24em] text-zinc-500">
|
||||
营地风向
|
||||
</div>
|
||||
<div className="mt-2 text-sm font-semibold text-white">
|
||||
{getCampEventLabel(currentCampEvent.eventType)} · {currentCampEvent.title}
|
||||
</div>
|
||||
<div className="mt-2 text-sm leading-relaxed text-zinc-300">
|
||||
{currentCampEvent.triggerReason}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{setpieceDirective && (
|
||||
<div className="rounded-2xl border border-white/8 bg-black/20 px-4 py-3.5">
|
||||
<div className="text-[10px] tracking-[0.24em] text-zinc-500">
|
||||
剧情高光
|
||||
</div>
|
||||
<div className="mt-2 text-sm font-semibold text-white">
|
||||
{getSetpieceLabel(setpieceDirective.setpieceType)} · {setpieceDirective.title}
|
||||
</div>
|
||||
<div className="mt-2 text-sm leading-relaxed text-zinc-300">
|
||||
{setpieceDirective.dramaticQuestion}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{recentChronicleSummary && (
|
||||
<div className="rounded-2xl border border-white/8 bg-black/20 px-4 py-3.5">
|
||||
<div className="text-[10px] tracking-[0.24em] text-zinc-500">
|
||||
近期回顾
|
||||
</div>
|
||||
<div className="mt-2 whitespace-pre-wrap text-sm leading-relaxed text-zinc-300">
|
||||
{recentChronicleSummary}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<AnimatePresence>
|
||||
{isSettingsPanelOpen && (
|
||||
<motion.div
|
||||
|
||||
Reference in New Issue
Block a user