feat(engine): add story fields to GameState
This commit is contained in:
@@ -76,6 +76,16 @@ describe('createGameState()', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('createGameState() story fields', () => {
|
||||
it('initializes empty story state', () => {
|
||||
const content = testContent();
|
||||
const state = createGameState(content);
|
||||
expect(state.storyFlags).toEqual({});
|
||||
expect(state.currentStoryNodeId).toBe('');
|
||||
expect(state.seenStoryNodeIds).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('startAction()', () => {
|
||||
it('activates the action and resets its progress', () => {
|
||||
const content = testContent();
|
||||
@@ -202,7 +212,8 @@ describe('unlock conditions', () => {
|
||||
});
|
||||
const state = createGameState(content);
|
||||
expect(() => enqueueAction(state, content, 'secret')).toThrow(/cannot enqueue/i);
|
||||
expect(canUnlockAction(state, content, 'secret', { path_scouted: true })).toBe(true);
|
||||
state.storyFlags.path_scouted = true;
|
||||
expect(canUnlockAction(state, content, 'secret')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+19
-16
@@ -16,6 +16,12 @@ export interface GameState {
|
||||
actionElapsedMs: number;
|
||||
/** Action ids waiting to run after the active action finishes. */
|
||||
actionQueue: string[];
|
||||
/** Story progression flags set by the narrative graph. */
|
||||
storyFlags: Record<string, boolean>;
|
||||
/** Id of the story node currently displayed, or empty when none. */
|
||||
currentStoryNodeId: string;
|
||||
/** Story node ids the player has already seen. */
|
||||
seenStoryNodeIds: string[];
|
||||
}
|
||||
|
||||
export function createGameState(content: Content): GameState {
|
||||
@@ -23,7 +29,15 @@ export function createGameState(content: Content): GameState {
|
||||
for (const resource of content.resources) {
|
||||
resources[resource.id] = resource.startAmount;
|
||||
}
|
||||
return { resources, activeActionId: null, actionElapsedMs: 0, actionQueue: [] };
|
||||
return {
|
||||
resources,
|
||||
activeActionId: null,
|
||||
actionElapsedMs: 0,
|
||||
actionQueue: [],
|
||||
storyFlags: {},
|
||||
currentStoryNodeId: '',
|
||||
seenStoryNodeIds: [],
|
||||
};
|
||||
}
|
||||
|
||||
function assertKnownAction(content: Content, actionId: string): void {
|
||||
@@ -38,13 +52,7 @@ export function canAffordAction(state: GameState, content: Content, actionId: st
|
||||
return action.costs.every((cost) => (state.resources[cost.resourceId] ?? 0) >= cost.amount);
|
||||
}
|
||||
|
||||
/** Story flags land in PR2; placeholder field keeps unlock schema honest. */
|
||||
export function canUnlockAction(
|
||||
state: GameState,
|
||||
content: Content,
|
||||
actionId: string,
|
||||
storyFlags: Record<string, boolean> = {},
|
||||
): boolean {
|
||||
export function canUnlockAction(state: GameState, content: Content, actionId: string): boolean {
|
||||
const action = content.actionsById[actionId];
|
||||
if (!action) return false;
|
||||
const unlock = action.unlock;
|
||||
@@ -56,21 +64,16 @@ export function canUnlockAction(
|
||||
}
|
||||
if (unlock.requireStoryFlags) {
|
||||
for (const flag of unlock.requireStoryFlags) {
|
||||
if (!storyFlags[flag]) return false;
|
||||
if (!state.storyFlags[flag]) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isActionAvailable(
|
||||
state: GameState,
|
||||
content: Content,
|
||||
actionId: string,
|
||||
storyFlags: Record<string, boolean> = {},
|
||||
): boolean {
|
||||
export function isActionAvailable(state: GameState, content: Content, actionId: string): boolean {
|
||||
return (
|
||||
canAffordAction(state, content, actionId) &&
|
||||
canUnlockAction(state, content, actionId, storyFlags)
|
||||
canUnlockAction(state, content, actionId)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -79,6 +79,9 @@ describe('loadGame()', () => {
|
||||
activeActionId: 'ghost-action',
|
||||
actionElapsedMs: 0,
|
||||
actionQueue: [],
|
||||
storyFlags: {},
|
||||
currentStoryNodeId: '',
|
||||
seenStoryNodeIds: [],
|
||||
},
|
||||
1000,
|
||||
),
|
||||
|
||||
@@ -103,6 +103,9 @@ export async function loadGame(
|
||||
activeActionId,
|
||||
actionElapsedMs: save.state.actionElapsedMs,
|
||||
actionQueue: [...(save.state.actionQueue ?? [])],
|
||||
storyFlags: { ...base.storyFlags },
|
||||
currentStoryNodeId: base.currentStoryNodeId,
|
||||
seenStoryNodeIds: [...base.seenStoryNodeIds],
|
||||
};
|
||||
savedAt = save.savedAt;
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user