feat(state): story trigger orchestration and auto-open rules
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { content } from '../../content';
|
||||
import { createGameState } from '../../engine/game';
|
||||
import { initStory } from '../../engine/story';
|
||||
import { getPrefs } from '../prefs';
|
||||
import { processStoryTriggers } from '../storyOrchestration';
|
||||
|
||||
describe('processStoryTriggers()', () => {
|
||||
it('returns entered nodes on boot', () => {
|
||||
const state = createGameState(content);
|
||||
initStory(state, content);
|
||||
const result = processStoryTriggers(state, content, { reason: 'boot' }, getPrefs());
|
||||
expect(result.enteredNodeIds.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import type { GameContent } from '../content/index';
|
||||
import type { GameState } from '../engine/game';
|
||||
import { evaluateTriggers, type StoryEvent, type TriggerContext } from '../engine/story';
|
||||
import type { GamePrefs } from './prefs';
|
||||
import type { StoryLogEntry } from './store';
|
||||
|
||||
export interface StoryUiEffect {
|
||||
enteredNodeIds: string[];
|
||||
logEntries: StoryLogEntry[];
|
||||
shouldOpenPanel: boolean;
|
||||
eventLogLines: string[];
|
||||
}
|
||||
|
||||
export function storyEventsToLogEntries(events: StoryEvent[]): StoryLogEntry[] {
|
||||
return events
|
||||
.filter((e) => e.kind === 'enter')
|
||||
.map((e) => ({ nodeId: e.nodeId, prose: e.prose, choiceLabel: e.choiceLabel }));
|
||||
}
|
||||
|
||||
export function shouldAutoOpenPanel(
|
||||
prefs: GamePrefs,
|
||||
nodeId: string,
|
||||
content: GameContent,
|
||||
): boolean {
|
||||
const node = content.storyNodesById[nodeId];
|
||||
if (!node) return false;
|
||||
if (prefs.storyOpenMode === 'manual') return false;
|
||||
if (prefs.storyOpenMode === 'auto') return true;
|
||||
return (node.choices?.length ?? 0) > 0;
|
||||
}
|
||||
|
||||
export function processStoryTriggers(
|
||||
state: GameState,
|
||||
content: GameContent,
|
||||
ctx: TriggerContext,
|
||||
prefs: GamePrefs,
|
||||
): StoryUiEffect {
|
||||
const { enteredNodeIds, events } = evaluateTriggers(state, content, ctx);
|
||||
const logEntries = storyEventsToLogEntries(events);
|
||||
const shouldOpenPanel =
|
||||
enteredNodeIds.length > 0 &&
|
||||
enteredNodeIds.some((id) => shouldAutoOpenPanel(prefs, id, content));
|
||||
const eventLogLines = logEntries.map((e) =>
|
||||
e.choiceLabel
|
||||
? `Story: ${e.choiceLabel}`
|
||||
: `Story: ${content.storyNodesById[e.nodeId]?.prose.slice(0, 40)}…`,
|
||||
);
|
||||
return { enteredNodeIds, logEntries, shouldOpenPanel, eventLogLines };
|
||||
}
|
||||
Reference in New Issue
Block a user