Implement Scene 7 evidence goal flow
This commit is contained in:
@@ -20,6 +20,13 @@ export type RuntimeNode = {
|
||||
utterances?: RuntimeUtterance[]; rootId?: string | null
|
||||
}
|
||||
export type PlaythroughState = { playthrough: PlaythroughSummary; node: RuntimeNode | null }
|
||||
export type PlaythroughAdvanceResult = {
|
||||
ok: boolean
|
||||
state?: PlaythroughState
|
||||
error?: string
|
||||
errorCode?: 'goals_incomplete'
|
||||
pendingGoals?: { key: string; title: string }[]
|
||||
}
|
||||
|
||||
export type MysteryAuthoring = {
|
||||
slug: string
|
||||
@@ -46,7 +53,8 @@ export interface NarrativeRepository {
|
||||
resolveDialogue(nodeId: string): Promise<{ utterances: RuntimeUtterance[]; rootId: string | null }>
|
||||
createPlaythrough(userId: string, mysterySlug?: string): Promise<PlaythroughState | null>
|
||||
getCurrentPlaythrough(userId: string): Promise<PlaythroughState | null>
|
||||
advancePlaythrough(userId: string, playthroughId: string, terminalKey?: string): Promise<{ ok: boolean; state?: PlaythroughState; error?: string }>
|
||||
ownsActiveLevel(userId: string, levelSlug: string): Promise<boolean>
|
||||
advancePlaythrough(userId: string, playthroughId: string, terminalKey?: string): Promise<PlaythroughAdvanceResult>
|
||||
listAchievements(playthroughId: string): Promise<string[] | null>
|
||||
awardAchievement(playthroughId: string, flagKey: string, nodeId?: string | null): Promise<{ ok: boolean; earned: boolean; error?: string }>
|
||||
gotoNode(userId: string, playthroughId: string, nodeId: string): Promise<{ ok: boolean; state?: PlaythroughState; error?: string }>
|
||||
@@ -234,6 +242,13 @@ export function createNarrativeRepository(pool: Pool, objectStorage: ObjectStora
|
||||
return row ? stateForPlaythrough(row.id) : null
|
||||
},
|
||||
|
||||
async ownsActiveLevel(userId, levelSlug) {
|
||||
const result = await pool.query(`SELECT 1 FROM osint.playthroughs playthrough
|
||||
JOIN osint.levels level ON level.id=playthrough.current_level_id
|
||||
WHERE playthrough.user_id=$1 AND playthrough.status='active' AND level.slug=$2`, [userId,levelSlug])
|
||||
return Boolean(result.rowCount)
|
||||
},
|
||||
|
||||
async listAchievements(playthroughId) {
|
||||
if (!(await pool.query('SELECT 1 FROM osint.playthroughs WHERE id=$1', [playthroughId])).rowCount) return null
|
||||
const rows = (await pool.query<{ flag_key: string }>('SELECT flag_key FROM osint.achievements WHERE playthrough_id=$1 ORDER BY flag_key', [playthroughId])).rows
|
||||
@@ -278,11 +293,38 @@ export function createNarrativeRepository(pool: Pool, objectStorage: ObjectStora
|
||||
const client = await pool.connect()
|
||||
try {
|
||||
await client.query('BEGIN')
|
||||
const playthrough = (await client.query<{ current_node_id: string | null; mystery_slug: string }>(
|
||||
`SELECT p.current_node_id,m.slug AS mystery_slug FROM osint.playthroughs p JOIN osint.mysteries m ON m.id=p.mystery_id
|
||||
const playthrough = (await client.query<{ current_node_id: string | null; current_level_id: string | null; mystery_slug: string }>(
|
||||
`SELECT p.current_node_id,p.current_level_id,m.slug AS mystery_slug FROM osint.playthroughs p JOIN osint.mysteries m ON m.id=p.mystery_id
|
||||
WHERE p.id=$1 AND p.user_id=$2 AND p.status='active' FOR UPDATE OF p`, [playthroughId, userId])).rows[0]
|
||||
if (!playthrough) { await client.query('ROLLBACK'); return { ok: false, error: 'Playthrough not found' } }
|
||||
if (!playthrough.current_node_id) { await client.query('ROLLBACK'); return { ok: false, error: 'Playthrough already finished' } }
|
||||
const currentNode = (await client.query<{ node_type:string }>('SELECT node_type FROM osint.story_nodes WHERE id=$1', [playthrough.current_node_id])).rows[0]
|
||||
if (currentNode?.node_type === 'level' && playthrough.current_level_id) {
|
||||
const pendingGoals = (await client.query<{ goal_key:string; title:string }>(`SELECT goal.goal_key,goal.title
|
||||
FROM osint.level_goals goal JOIN osint.levels level ON level.board_id=goal.board_id
|
||||
WHERE level.id=$1 AND goal.enabled AND (
|
||||
NOT EXISTS (SELECT 1 FROM osint.level_goal_flag_requirements requirement WHERE requirement.goal_id=goal.id)
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM osint.level_goal_flag_requirements requirement
|
||||
WHERE requirement.goal_id=goal.id AND NOT EXISTS (
|
||||
SELECT 1 FROM osint.level_flags flag
|
||||
WHERE flag.level_id=level.id AND flag.flag_key=requirement.flag_key
|
||||
)
|
||||
)
|
||||
) ORDER BY goal.created_at,goal.id`, [playthrough.current_level_id])).rows
|
||||
if (pendingGoals.length) {
|
||||
await client.query('ROLLBACK')
|
||||
return { ok:false,error:'Complete the level objective before continuing',errorCode:'goals_incomplete',
|
||||
pendingGoals:pendingGoals.map(goal => ({ key:goal.goal_key,title:goal.title })) }
|
||||
}
|
||||
await client.query(`INSERT INTO osint.achievements (playthrough_id,flag_key,awarded_by_node_id)
|
||||
SELECT $1,requirement.flag_key,$3 FROM osint.levels level
|
||||
JOIN osint.level_goals goal ON goal.board_id=level.board_id AND goal.enabled
|
||||
JOIN osint.level_goal_flag_requirements requirement ON requirement.goal_id=goal.id
|
||||
JOIN osint.level_flags flag ON flag.level_id=level.id AND flag.flag_key=requirement.flag_key
|
||||
WHERE level.id=$2 ON CONFLICT (playthrough_id,flag_key) DO NOTHING`,
|
||||
[playthroughId,playthrough.current_level_id,playthrough.current_node_id])
|
||||
}
|
||||
const terminals = (await client.query<{ terminal_key: string; to_node_id: string | null }>(
|
||||
'SELECT terminal_key,to_node_id FROM osint.story_node_terminals WHERE parent_node_id=$1 ORDER BY sort_order', [playthrough.current_node_id])).rows
|
||||
const wired = terminals.filter(t => t.to_node_id)
|
||||
|
||||
Reference in New Issue
Block a user