Download universe/DialogueSystem.ts from Snapkitty/grisp-shadow-fleet: direct link, hf CLI and curl.
- Browser
- Download file 4.29 kB
-
https://huggingface.co/Snapkitty/grisp-shadow-fleet/resolve/main/universe/DialogueSystem.ts
- Command line
-
hf download hf://Snapkitty/grisp-shadow-fleet/universe/DialogueSystem.ts
-
curl -L -o DialogueSystem.ts https://huggingface.co/Snapkitty/grisp-shadow-fleet/resolve/main/universe/DialogueSystem.ts
4.29 kB
| import { clamp } from './deterministic.js'; | |
| import { emitWorldEvent, findRequired, trimHistory } from './state-utils.js'; | |
| import type { CommandResult, DialogueSessionState, EntityId, UniverseState } from './types.js'; | |
| export class DialogueSystem { | |
| constructor(private state: UniverseState) {} | |
| replaceState(state: UniverseState): void { | |
| this.state = state; | |
| } | |
| start(npcId: EntityId): CommandResult { | |
| const npc = findRequired(this.state.npcs, npcId, 'NPC'); | |
| if (!npc.alive) return this.failure(`${npc.name} cannot converse.`); | |
| if (npc.currentLocationId !== this.state.player.locationId) return this.failure(`${npc.name} is not in the player's location.`); | |
| const firstNode = this.state.dialogue.nodes.find((node) => node.id === npc.dialogueStateId); | |
| if (!firstNode) return this.failure(`${npc.name} has no reachable dialogue state.`); | |
| const session: DialogueSessionState = { | |
| id: `dialogue-session-${npc.id}-${this.state.tick}-${this.state.dialogue.sessions.length + 1}`, | |
| npcId: npc.id, | |
| currentNodeId: firstNode.id, | |
| visitedNodeIds: [firstNode.id], | |
| active: true, | |
| }; | |
| this.state.dialogue.sessions.push(session); | |
| trimHistory(this.state.dialogue.sessions, 24); | |
| const event = emitWorldEvent(this.state, 'dialogue-started', this.state.player.id, npc.id, `Conversation started with ${npc.name}.`, { persistent: false, data: { sessionId: session.id } }); | |
| return { ok: true, message: firstNode.text, eventIds: [event.id] }; | |
| } | |
| getSession(sessionId: EntityId): DialogueSessionState { | |
| return findRequired(this.state.dialogue.sessions, sessionId, 'Dialogue session'); | |
| } | |
| availableChoices(sessionId: EntityId) { | |
| const session = this.getSession(sessionId); | |
| const npc = findRequired(this.state.npcs, session.npcId, 'NPC'); | |
| const node = findRequired(this.state.dialogue.nodes, session.currentNodeId, 'Dialogue node'); | |
| const trust = npc.trustValues[this.state.player.id] ?? 0; | |
| return node.choices.filter((choice) => trust >= choice.requiredTrust); | |
| } | |
| choose(sessionId: EntityId, choiceId: EntityId): CommandResult { | |
| const session = this.getSession(sessionId); | |
| if (!session.active) return this.failure('Dialogue session is closed.'); | |
| const npc = findRequired(this.state.npcs, session.npcId, 'NPC'); | |
| const node = findRequired(this.state.dialogue.nodes, session.currentNodeId, 'Dialogue node'); | |
| const choice = node.choices.find((candidate) => candidate.id === choiceId); | |
| if (!choice) return this.failure(`Choice '${choiceId}' is unavailable at this dialogue node.`); | |
| const trust = npc.trustValues[this.state.player.id] ?? 0; | |
| if (trust < choice.requiredTrust) return this.failure(`${npc.name} does not trust the player enough for that response.`); | |
| const nextTrust = clamp(trust + choice.trustDelta, -100, 100); | |
| npc.trustValues[this.state.player.id] = nextTrust; | |
| npc.playerReaction = nextTrust <= -60 ? 'hostile' : nextTrust < -20 ? 'wary' : nextTrust >= 70 ? 'loyal' : nextTrust >= 25 ? 'friendly' : 'neutral'; | |
| npc.memories.push({ | |
| id: `memory-${npc.id}-dialogue-${this.state.tick}-${npc.memories.length + 1}`, | |
| tick: this.state.tick, | |
| subjectId: this.state.player.id, | |
| summary: `Player said: ${choice.text}`, | |
| emotionalWeight: clamp(choice.trustDelta / 10, -1, 1), | |
| confidence: 1, | |
| tags: ['dialogue', choice.eventType ?? 'conversation'], | |
| }); | |
| trimHistory(npc.memories, 64); | |
| if (choice.nextNodeId) { | |
| const next = findRequired(this.state.dialogue.nodes, choice.nextNodeId, 'Next dialogue node'); | |
| session.currentNodeId = next.id; | |
| session.visitedNodeIds.push(next.id); | |
| } else { | |
| session.active = false; | |
| } | |
| const event = emitWorldEvent(this.state, choice.eventType ?? 'dialogue-choice', this.state.player.id, npc.id, `${npc.name} responded to '${choice.text}'.`, { data: { choiceId, trustDelta: choice.trustDelta, sessionId } }); | |
| const responseText = choice.nextNodeId ? findRequired(this.state.dialogue.nodes, choice.nextNodeId, 'Dialogue node').text : 'Conversation ended.'; | |
| return { ok: true, message: responseText, eventIds: [event.id] }; | |
| } | |
| private failure(message: string): CommandResult { | |
| return { ok: false, message, eventIds: [] }; | |
| } | |
| } | |