Working with Flags
Track and modify state during conversations. This guide covers setting flags, using both scopes, and integrating with your game's state management.
Prerequisites
Before starting, you should:
Overview
We'll build a quest dialogue that tracks progress, rewards the player, and remembers conversation state.
- Set flags via actions
- Use game flags for persistent state
- Use conversation flags for dialogue-specific state
- Integrate an external flag store
Step 1: Set Flags via Actions
Actions in nodes or choices can set, increment, decrement, or clear flags.
import { createDialogueRunner, DialogueDefinition } from '@motioneffector/dialogue'
const dialogue: DialogueDefinition = {
id: 'quest-giver',
startNode: 'start',
nodes: {
start: {
text: 'Will you retrieve the ancient artifact?',
choices: [
{
text: 'I accept this quest',
next: 'accepted',
actions: [
{ type: 'set', flag: 'quest_artifact', value: 'active' },
{ type: 'increment', flag: 'activeQuests' }
]
},
{
text: 'Not right now',
next: 'declined'
}
]
},
accepted: {
text: 'Excellent! Find it in the northern caves.',
isEnd: true
},
declined: {
text: 'Return when you\'re ready.',
isEnd: true
}
}
}
Step 2: Use Game Flags for Persistent State
Game flags persist across dialogues. Perfect for inventory, quest progress, and player stats.
import { createFlagStore } from '@motioneffector/flags'
const gameFlags = createFlagStore()
gameFlags.set('gold', 100)
gameFlags.set('playerLevel', 5)
const runner = createDialogueRunner({ gameFlags })
await runner.start(dialogue)
// After accepting the quest:
console.log(gameFlags.get('quest_artifact')) // 'active'
console.log(gameFlags.get('activeQuests')) // 1
// Start a different dialogue - game flags persist
await runner.start(anotherDialogue)
console.log(gameFlags.get('quest_artifact')) // Still 'active'
Step 3: Use Conversation Flags for Dialogue State
Conversation flags reset when a new dialogue starts. Use them for "have we discussed this topic?" tracking.
const dialogue: DialogueDefinition = {
id: 'informant',
startNode: 'start',
nodes: {
start: {
text: 'What do you want to know?',
choices: [
{
text: 'Tell me about the thieves guild',
next: 'thieves',
conditions: { not: { check: ['conv:asked_thieves', '==', true] } }
},
{
text: 'Any news about the thieves guild?',
next: 'thieves-update',
conditions: { check: ['conv:asked_thieves', '==', true] }
},
{ text: 'Nevermind', next: 'end' }
]
},
thieves: {
text: 'They operate from the old warehouse...',
actions: [{ type: 'set', flag: 'conv:asked_thieves', value: true }],
next: 'start'
},
'thieves-update': {
text: 'Nothing new since we last spoke.',
next: 'start'
},
end: { text: 'Be careful out there.', isEnd: true }
}
}
The first time you ask about thieves, you get the full explanation. Ask again in the same conversation, you get the shorter response. Leave and return - the conversation flags reset.
Step 4: Integrate External Flag Store
For real games, you likely have your own state management. Pass a compatible FlagStore:
import { createFlagStore } from '@motioneffector/flags'
// Your game's central state
const gameState = createFlagStore()
// Initialize from save data, database, etc.
gameState.set('gold', savedGame.gold)
gameState.set('playerName', savedGame.name)
// Pass to dialogue runner
const runner = createDialogueRunner({ gameFlags: gameState })
// After dialogues, save your state
function saveGame() {
const data = gameState.all()
localStorage.setItem('save', JSON.stringify(data))
}
Complete Example
import { createDialogueRunner, DialogueDefinition } from '@motioneffector/dialogue'
import { createFlagStore } from '@motioneffector/flags'
const dialogue: DialogueDefinition = {
id: 'blacksmith',
startNode: 'greeting',
nodes: {
greeting: {
text: 'Welcome to my forge! You have {{gold}} gold.',
choices: [
{
text: 'Buy Iron Sword (50g)',
next: 'bought-sword',
conditions: { check: ['gold', '>=', 50] },
actions: [
{ type: 'decrement', flag: 'gold', value: 50 },
{ type: 'set', flag: 'hasIronSword', value: true },
{ type: 'set', flag: 'conv:madePurchase', value: true }
]
},
{
text: 'Upgrade my sword',
next: 'upgrade',
conditions: {
and: [
{ check: ['hasIronSword', '==', true] },
{ check: ['gold', '>=', 100] }
]
}
},
{
text: 'Just browsing',
next: 'browse'
}
]
},
'bought-sword': {
text: 'A fine choice! This blade will serve you well.',
next: 'greeting'
},
upgrade: {
text: 'I\'ll make this blade even stronger!',
actions: [
{ type: 'decrement', flag: 'gold', value: 100 },
{ type: 'set', flag: 'hasSteelSword', value: true },
{ type: 'clear', flag: 'hasIronSword' }
],
next: 'greeting'
},
browse: {
text: 'Take your time. Let me know if you need anything.',
choices: [
{
text: 'Actually, I\'ll buy something',
next: 'greeting',
conditions: { not: { check: ['conv:madePurchase', '==', true] } }
},
{
text: 'Thanks, goodbye',
next: 'farewell'
}
]
},
farewell: {
text: 'Come back when you need quality steel!',
isEnd: true
}
}
}
async function main() {
const gameFlags = createFlagStore()
gameFlags.set('gold', 200)
gameFlags.set('playerName', 'Hero')
const runner = createDialogueRunner({ gameFlags })
await runner.start(dialogue)
// Simulate gameplay
console.log('Starting gold:', gameFlags.get('gold'))
// After some choices...
console.log('Has sword:', gameFlags.get('hasIronSword'))
console.log('Remaining gold:', gameFlags.get('gold'))
}
Variations
Counter Flags
Track counts with increment/decrement:
actions: [
{ type: 'increment', flag: 'timesVisited' },
{ type: 'increment', flag: 'reputation', value: 5 }
]
// Use in conditions
conditions: { check: ['timesVisited', '>=', 3] }
Clearing Flags
Remove a flag entirely:
actions: [
{ type: 'clear', flag: 'temporaryBuff' }
]
Reading Flags at Runtime
// Get all conversation flags
const convFlags = runner.getConversationFlags()
// Clear conversation flags manually
runner.clearConversationFlags()
// Game flags through your store
console.log(gameFlags.all())
Troubleshooting
Flag Value Not Updating
Symptom: Actions run but flag values seem unchanged.
Cause: Checking the wrong scope or reading before the action executes.
Solution: Remember node actions run on entry. The new value is available immediately after.
Conversation Flags Persisting
Symptom: Conversation flags from a previous dialogue appear in a new one.
Cause: You haven't called start() on a new dialogue.
Solution: start() clears conversation flags. If you need them cleared manually, call runner.clearConversationFlags().
See Also
- Flags - Understanding the two scopes
- Actions - All action types
- Dynamic Text - Displaying flag values in text