feat: chapter boundary gateway nodes in flow, prevent cross-chapter BFS expansion
This commit is contained in:
@@ -23,7 +23,11 @@ const emit = defineEmits<{
|
||||
const showChapterPicker = ref(false)
|
||||
const showDetails = ref(false)
|
||||
|
||||
function collectReachable(startId: string): Set<string> {
|
||||
function isOtherChapterStart(sceneId: string, ownChapterId: string): boolean {
|
||||
return props.chapters.some(c => c.id !== ownChapterId && c.startScene === sceneId)
|
||||
}
|
||||
|
||||
function collectReachable(startId: string, chapterId: string): Set<string> {
|
||||
const visited = new Set<string>()
|
||||
const queue = [startId]
|
||||
while (queue.length > 0) {
|
||||
@@ -34,17 +38,22 @@ function collectReachable(startId: string): Set<string> {
|
||||
visited.add(id)
|
||||
if (scene.choices) {
|
||||
for (const c of scene.choices) {
|
||||
if (c.targetScene && !visited.has(c.targetScene)) queue.push(c.targetScene)
|
||||
if (c.targetScene && !visited.has(c.targetScene) && !isOtherChapterStart(c.targetScene, chapterId))
|
||||
queue.push(c.targetScene)
|
||||
}
|
||||
}
|
||||
if (scene.nextScene && !visited.has(scene.nextScene)) queue.push(scene.nextScene)
|
||||
if (scene.nextScene && !visited.has(scene.nextScene) && !isOtherChapterStart(scene.nextScene, chapterId))
|
||||
queue.push(scene.nextScene)
|
||||
if (scene.qte) {
|
||||
if (scene.qte.successScene && !visited.has(scene.qte.successScene)) queue.push(scene.qte.successScene)
|
||||
if (scene.qte.failScene && !visited.has(scene.qte.failScene)) queue.push(scene.qte.failScene)
|
||||
if (scene.qte.successScene && !visited.has(scene.qte.successScene) && !isOtherChapterStart(scene.qte.successScene, chapterId))
|
||||
queue.push(scene.qte.successScene)
|
||||
if (scene.qte.failScene && !visited.has(scene.qte.failScene) && !isOtherChapterStart(scene.qte.failScene, chapterId))
|
||||
queue.push(scene.qte.failScene)
|
||||
}
|
||||
if (scene.hotspots) {
|
||||
for (const h of scene.hotspots) {
|
||||
if (h.targetScene && !visited.has(h.targetScene)) queue.push(h.targetScene)
|
||||
if (h.targetScene && !visited.has(h.targetScene) && !isOtherChapterStart(h.targetScene, chapterId))
|
||||
queue.push(h.targetScene)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,7 +63,7 @@ function collectReachable(startId: string): Set<string> {
|
||||
const chapterReachable = computed(() => {
|
||||
const result: Record<string, Set<string>> = {}
|
||||
for (const ch of props.chapters) {
|
||||
result[ch.id] = collectReachable(ch.startScene)
|
||||
result[ch.id] = collectReachable(ch.startScene, ch.id)
|
||||
}
|
||||
return result
|
||||
})
|
||||
@@ -129,7 +138,7 @@ function selectChapter(chapterId: string) {
|
||||
showChapterPicker.value = false
|
||||
}
|
||||
|
||||
function buildPlayerTree(sceneId: string, depth: number, pathSet: Set<string>): PlayerTreeNode | null {
|
||||
function buildPlayerTree(sceneId: string, chapterId: string, depth: number, pathSet: Set<string>): PlayerTreeNode | null {
|
||||
if (depth > 10) return null
|
||||
if (pathSet.has(sceneId)) return null
|
||||
const scene = props.scenes[sceneId]
|
||||
@@ -140,35 +149,34 @@ function buildPlayerTree(sceneId: string, depth: number, pathSet: Set<string>):
|
||||
pathSet.add(sceneId)
|
||||
const children: PlayerTreeNode[] = []
|
||||
if (scene) {
|
||||
if (scene.choices) {
|
||||
for (const c of scene.choices) {
|
||||
if (c.targetScene) {
|
||||
const child = buildPlayerTree(c.targetScene, depth + 1, pathSet)
|
||||
if (child) children.push(child)
|
||||
}
|
||||
function pushChild(target: string | undefined) {
|
||||
if (!target) return
|
||||
if (isOtherChapterStart(target, chapterId)) {
|
||||
const gatewayCh = props.chapters.find(c => c.startScene === target)
|
||||
children.push({
|
||||
sceneId: '',
|
||||
label: gatewayCh ? (t(gatewayCh.labelKey || gatewayCh.label)) : target,
|
||||
visited: false,
|
||||
locked: !props.unlockedChapterIds.has(gatewayCh?.id ?? ''),
|
||||
children: [],
|
||||
isGateway: true,
|
||||
gatewayChapterId: gatewayCh?.id,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
if (scene.nextScene) {
|
||||
const child = buildPlayerTree(scene.nextScene, depth + 1, pathSet)
|
||||
const child = buildPlayerTree(target, chapterId, depth + 1, pathSet)
|
||||
if (child) children.push(child)
|
||||
}
|
||||
if (scene.choices) {
|
||||
for (const c of scene.choices) pushChild(c.targetScene)
|
||||
}
|
||||
pushChild(scene.nextScene)
|
||||
if (scene.qte) {
|
||||
if (scene.qte.successScene) {
|
||||
const child = buildPlayerTree(scene.qte.successScene, depth + 1, pathSet)
|
||||
if (child) children.push(child)
|
||||
}
|
||||
if (scene.qte.failScene) {
|
||||
const child = buildPlayerTree(scene.qte.failScene, depth + 1, pathSet)
|
||||
if (child) children.push(child)
|
||||
}
|
||||
pushChild(scene.qte.successScene)
|
||||
pushChild(scene.qte.failScene)
|
||||
}
|
||||
if (scene.hotspots) {
|
||||
for (const h of scene.hotspots) {
|
||||
if (h.targetScene) {
|
||||
const child = buildPlayerTree(h.targetScene, depth + 1, pathSet)
|
||||
if (child) children.push(child)
|
||||
}
|
||||
}
|
||||
for (const h of scene.hotspots) pushChild(h.targetScene)
|
||||
}
|
||||
}
|
||||
pathSet.delete(sceneId)
|
||||
@@ -178,7 +186,7 @@ function buildPlayerTree(sceneId: string, depth: number, pathSet: Set<string>):
|
||||
function buildTreeForChapter(chapterId: string): PlayerTreeNode | null {
|
||||
const ch = props.chapters.find(c => c.id === chapterId)
|
||||
if (!ch) return null
|
||||
return buildPlayerTree(ch.startScene, 0, new Set())
|
||||
return buildPlayerTree(ch.startScene, chapterId, 0, new Set())
|
||||
}
|
||||
|
||||
function onSelectScene(sceneId: string) {
|
||||
@@ -208,6 +216,7 @@ const tree = computed(() => buildTreeForChapter(currentChapterId.value))
|
||||
:scenes="scenes"
|
||||
:key="currentChapterId"
|
||||
@select-scene="onSelectScene"
|
||||
@select-gateway="selectChapter"
|
||||
/>
|
||||
<div v-else class="story-empty">暂无故事数据</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user