Dualogue event API doc/samples

This commit is contained in:
2026-05-02 18:25:30 +03:00
parent 3e7b0169d5
commit c5da977857
5 changed files with 1087 additions and 0 deletions

View File

@@ -0,0 +1,109 @@
-- =============================================================================
-- Dialogue: Basic Show via Event System
-- =============================================================================
-- This example demonstrates how to show a simple dialogue box using the
-- EventBus "dialogue_show" event.
--
-- The DialogueSystem listens for "dialogue_show" events and displays the
-- text on any entity that has a DialogueComponent.
--
-- Event payload parameters:
-- "text" (string) - Narration text to display
-- "speaker" (string) - Optional speaker name (shown above text)
-- "choices" (string) - Comma-separated choice labels (optional)
-- "auto_progress" (int) - If 1, click anywhere progresses (no choices)
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 1. Create an entity with a DialogueComponent
-- ---------------------------------------------------------------------------
-- First we need an entity that has the Dialogue component so the system
-- knows where to render the dialogue box.
local dialogue_entity = ecs.create_entity()
ecs.set_entity_name(dialogue_entity, "DialogueBox")
-- Add the Dialogue component with default settings:
ecs.add_component(dialogue_entity, "Dialogue")
-- You can also configure the dialogue box appearance:
ecs.set_component(dialogue_entity, "Dialogue", {
fontName = "Jupiteroid-Regular.ttf",
fontSize = 24.0,
speakerFontSize = 20.0,
backgroundOpacity = 0.85,
boxHeightFraction = 0.25, -- 25% of screen height
boxPositionFraction = 0.75, -- bottom quarter of screen
enabled = true
})
print("Dialogue entity created with ID: " .. dialogue_entity)
-- ---------------------------------------------------------------------------
-- 2. Show a simple narration (no choices)
-- ---------------------------------------------------------------------------
-- Send a "dialogue_show" event with just text. The dialogue box will appear
-- and the player can click anywhere to dismiss it.
ecs.send_event("dialogue_show", {
stringValues = {
text = "Welcome to the world of World2!",
speaker = "Narrator"
}
})
print("Sent basic narration dialogue")
-- ---------------------------------------------------------------------------
-- 3. Show dialogue with player choices
-- ---------------------------------------------------------------------------
-- When "choices" is provided (comma-separated), the dialogue box shows
-- buttons instead of click-to-progress. The player must pick one.
ecs.send_event("dialogue_show", {
stringValues = {
text = "Where would you like to go?",
speaker = "Guide",
choices = "The Forest,The Village,The Mountains"
}
})
print("Sent dialogue with choices")
-- ---------------------------------------------------------------------------
-- 4. Show dialogue without a speaker name
-- ---------------------------------------------------------------------------
ecs.send_event("dialogue_show", {
stringValues = {
text = "A mysterious voice echoes through the chamber..."
}
})
print("Sent anonymous narration")
-- ---------------------------------------------------------------------------
-- 5. Multi-line dialogue (use \n for line breaks)
-- ---------------------------------------------------------------------------
ecs.send_event("dialogue_show", {
stringValues = {
text = "Greetings, traveler.\n\nI have been expecting you.\nThe prophecy spoke of your arrival.",
speaker = "Elder Marcus"
}
})
print("Sent multi-line dialogue")
-- =============================================================================
-- Summary
-- =============================================================================
-- To show dialogue from Lua:
-- 1. Ensure an entity with DialogueComponent exists (create one if needed)
-- 2. Call ecs.send_event("dialogue_show", { stringValues = { ... } })
-- 3. Required: text = "The narration text"
-- 4. Optional: speaker = "Speaker Name"
-- 5. Optional: choices = "Choice1,Choice2,Choice3" (comma-separated)
-- =============================================================================
print("Dialogue basic show examples completed!")

View File

@@ -0,0 +1,224 @@
-- =============================================================================
-- Dialogue: Direct Component API Control
-- =============================================================================
-- This example demonstrates how to control the DialogueComponent directly
-- via the ECS component API, without using the EventBus.
--
-- The DialogueComponent has methods that can be called from C++:
-- show(text, choices, speaker) - Display dialogue
-- progress() - Dismiss (no-choices mode)
-- selectChoice(index) - Select a choice (1-based)
-- isActive() - Check if dialogue is active
-- reset() - Reset to idle state
--
-- From Lua, you manipulate the component's fields directly using the
-- ecs.set_component / ecs.get_component API.
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 1. Create an entity with DialogueComponent
-- ---------------------------------------------------------------------------
local dlg = ecs.create_entity()
ecs.set_entity_name(dlg, "DialogueBox")
ecs.add_component(dlg, "Dialogue")
-- ---------------------------------------------------------------------------
-- 2. Set dialogue text directly via component fields
-- ---------------------------------------------------------------------------
-- Instead of sending an event, you can set the component fields directly.
-- The DialogueSystem will pick up the state change on the next frame.
ecs.set_component(dlg, "Dialogue", {
text = "This dialogue was set directly via the component API!",
speaker = "Lua Script",
enabled = true
})
-- Note: Setting the fields directly does NOT automatically change the state
-- to Showing. You need to also set the state, or use the event system.
-- The DialogueComponent's show() method handles state transitions.
-- ---------------------------------------------------------------------------
-- 3. Read dialogue state from the component
-- ---------------------------------------------------------------------------
local comp = ecs.get_component(dlg, "Dialogue")
if comp then
print("Dialogue text: " .. (comp.text or "(empty)"))
print("Dialogue speaker: " .. (comp.speaker or "(none)"))
print("Dialogue enabled: " .. tostring(comp.enabled))
print("Font: " .. (comp.fontName or "default"))
print("Font size: " .. (comp.fontSize or 24))
end
-- ---------------------------------------------------------------------------
-- 4. Modify individual dialogue fields
-- ---------------------------------------------------------------------------
-- Change just the text:
ecs.set_field(dlg, "Dialogue", "text", "Updated dialogue text!")
-- Change just the speaker:
ecs.set_field(dlg, "Dialogue", "speaker", "Mysterious Stranger")
-- Change appearance settings:
ecs.set_field(dlg, "Dialogue", "backgroundOpacity", 0.9)
ecs.set_field(dlg, "Dialogue", "boxHeightFraction", 0.3)
ecs.set_field(dlg, "Dialogue", "boxPositionFraction", 0.7)
-- Read back the changes:
local updated_text = ecs.get_field(dlg, "Dialogue", "text")
local updated_speaker = ecs.get_field(dlg, "Dialogue", "speaker")
print("Updated text: " .. updated_text)
print("Updated speaker: " .. updated_speaker)
-- ---------------------------------------------------------------------------
-- 5. Toggle dialogue visibility
-- ---------------------------------------------------------------------------
-- Disable the dialogue box:
ecs.set_field(dlg, "Dialogue", "enabled", false)
print("Dialogue disabled")
-- Re-enable it:
ecs.set_field(dlg, "Dialogue", "enabled", true)
print("Dialogue re-enabled")
-- ---------------------------------------------------------------------------
-- 6. Check if dialogue component exists
-- ---------------------------------------------------------------------------
if ecs.has_component(dlg, "Dialogue") then
print("Entity has a Dialogue component")
end
-- ---------------------------------------------------------------------------
-- 7. Remove the dialogue component entirely
-- ---------------------------------------------------------------------------
-- ecs.remove_component(dlg, "Dialogue")
-- print("Dialogue component removed")
-- ---------------------------------------------------------------------------
-- 8. Practical: Configure dialogue appearance per-NPC
-- ---------------------------------------------------------------------------
function create_npc_with_dialogue(name, mesh, greeting_text)
local npc = ecs.create_entity()
ecs.set_entity_name(npc, name)
-- Basic NPC setup
ecs.set_component(npc, "Transform", {
position = { 0, 0, 0 },
rotation = { 1, 0, 0, 0 },
scale = { 1, 1, 1 }
})
ecs.set_component(npc, "Renderable", {
meshName = mesh or "character.mesh",
visible = true
})
-- Dialogue component with NPC-specific appearance
ecs.set_component(npc, "Dialogue", {
text = greeting_text or "Hello!",
speaker = name,
fontName = "Jupiteroid-Regular.ttf",
fontSize = 24.0,
speakerFontSize = 20.0,
backgroundOpacity = 0.85,
boxHeightFraction = 0.25,
boxPositionFraction = 0.75,
enabled = true
})
print("Created NPC with dialogue: " .. name)
return npc
end
-- Create a few NPCs with different dialogue configurations
local merchant = create_npc_with_dialogue(
"Merchant",
"merchant.mesh",
"Welcome to my shop! Best wares in town."
)
local guard = create_npc_with_dialogue(
"Guard",
"guard.mesh",
"Halt! Who goes there?"
)
-- ---------------------------------------------------------------------------
-- 9. Practical: Update dialogue based on game events
-- ---------------------------------------------------------------------------
function update_npc_dialogue(npc_entity, new_text, new_speaker)
-- Update the dialogue text and speaker
ecs.set_field(npc_entity, "Dialogue", "text", new_text)
if new_speaker then
ecs.set_field(npc_entity, "Dialogue", "speaker", new_speaker)
end
-- Show the updated dialogue via event (this triggers the state change)
ecs.send_event("dialogue_show", {
stringValues = {
text = new_text,
speaker = new_speaker or ecs.get_field(npc_entity, "Dialogue", "speaker")
}
})
end
-- Update the merchant's dialogue after a transaction
update_npc_dialogue(merchant, "Thank you for your business! Come again.")
-- Update the guard's dialogue when player has high reputation
update_npc_dialogue(guard, "At ease, friend. The town is safe with you around.")
-- ---------------------------------------------------------------------------
-- 10. Practical: Dialogue with dynamic choices from component data
-- ---------------------------------------------------------------------------
function show_dialogue_with_dynamic_choices(npc_entity, base_text, choice_list)
-- choice_list is a table of strings
local choices_str = table.concat(choice_list, ",")
-- Update the component
ecs.set_field(npc_entity, "Dialogue", "text", base_text)
-- Show via event (which handles state transitions properly)
ecs.send_event("dialogue_show", {
stringValues = {
text = base_text,
speaker = ecs.get_field(npc_entity, "Dialogue", "speaker"),
choices = choices_str
}
})
end
-- Example: Shop inventory as dialogue choices
local shop_items = { "Buy Sword (50 gold)", "Buy Shield (30 gold)", "Buy Potion (10 gold)", "Leave" }
show_dialogue_with_dynamic_choices(merchant, "What would you like to buy?", shop_items)
-- =============================================================================
-- Summary
-- =============================================================================
-- Direct component API vs EventBus approach:
--
-- Component API (ecs.set_component / ecs.get_component):
-- - Read/write any DialogueComponent field
-- - Configure appearance (font, size, opacity, position)
-- - Toggle enabled/disabled
-- - Does NOT trigger state transitions (Showing/AwaitingChoice/Idle)
--
-- EventBus (ecs.send_event "dialogue_show"):
-- - Triggers proper state transitions
-- - Parses choices from comma-separated string
-- - Best for showing dialogue to the player
--
-- Best practice: Use the EventBus to SHOW dialogue, and the component API
-- to CONFIGURE the dialogue box appearance.
-- =============================================================================
print("Dialogue component API examples completed!")

View File

@@ -0,0 +1,275 @@
-- =============================================================================
-- Dialogue: EventHandler Component Integration
-- =============================================================================
-- This example demonstrates how to use the EventHandlerComponent to trigger
-- dialogue automatically when an event is received.
--
-- The EventHandlerComponent links an event name to a GoapAction. When the
-- event fires, the action's behavior tree is executed. This allows you to
-- wire up dialogue triggers declaratively without writing Lua code.
--
-- Combined with the EventBus, you can create complex event-driven dialogue
-- sequences where one event triggers dialogue, and the player's choice
-- triggers another event.
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 1. Create the dialogue entity
-- ---------------------------------------------------------------------------
local dlg = ecs.create_entity()
ecs.set_entity_name(dlg, "DialogueBox")
ecs.add_component(dlg, "Dialogue")
-- ---------------------------------------------------------------------------
-- 2. Create an NPC with EventHandler for dialogue triggers
-- ---------------------------------------------------------------------------
local npc = ecs.create_entity()
ecs.set_entity_name(npc, "QuestGiver")
ecs.set_component(npc, "Transform", {
position = { 5, 0, 5 },
rotation = { 1, 0, 0, 0 },
scale = { 1, 1, 1 }
})
ecs.set_component(npc, "Renderable", {
meshName = "character.mesh",
visible = true
})
-- Add an EventHandler that triggers dialogue when the player approaches
ecs.set_component(npc, "EventHandler", {
eventName = "player_approached",
actionName = "npc_greeting",
enabled = true
})
print("Created NPC with EventHandler for player_approached event")
-- ---------------------------------------------------------------------------
-- 3. Create an EventHandler that triggers on quest completion
-- ---------------------------------------------------------------------------
local quest_npc = ecs.create_entity()
ecs.set_entity_name(quest_npc, "QuestRewarder")
ecs.set_component(quest_npc, "EventHandler", {
eventName = "quest_completed",
actionName = "quest_reward_dialogue",
enabled = true
})
print("Created NPC with EventHandler for quest_completed event")
-- ---------------------------------------------------------------------------
-- 4. Trigger dialogue via events from other game systems
-- ---------------------------------------------------------------------------
-- Simulate a proximity trigger: when the player gets close to an NPC,
-- send an event that triggers the dialogue.
function on_player_near_npc(npc_name, distance)
print("Player is " .. distance .. "m from " .. npc_name)
if distance < 5.0 then
-- Send the event that the EventHandler is listening for
ecs.send_event("player_approached", {
stringValues = {
npc_name = npc_name,
location = "town_square"
},
floatValues = {
distance = distance
}
})
-- Also show dialogue directly
ecs.send_event("dialogue_show", {
stringValues = {
text = "Hello there! I have a quest for a brave adventurer.",
speaker = npc_name,
choices = "I'll help!,What's the reward?,Not interested"
}
})
end
end
-- Simulate the player approaching
on_player_near_npc("QuestGiver", 3.0)
-- ---------------------------------------------------------------------------
-- 5. Chain events: choice -> event -> next dialogue
-- ---------------------------------------------------------------------------
-- When the player makes a choice, we can send a new event that triggers
-- another EventHandler, creating a chain reaction.
-- Subscribe to dialogue choices
local choice_sub = ecs.subscribe_event("dialogue_choice", function(event, params)
local choice_index = params.values and params.values.choice_index or 0
local choice_text = params.stringValues and params.stringValues.choice_text or ""
if choice_text == "I'll help!" then
-- Player accepted the quest - trigger quest acceptance event
ecs.send_event("quest_accepted", {
stringValues = {
quest_name = "The Lost Artifact",
giver = "QuestGiver"
},
values = {
reward_gold = 100,
reward_xp = 500
}
})
-- Show follow-up dialogue
ecs.send_event("dialogue_show", {
stringValues = {
text = "Excellent! The ancient artifact was stolen from the temple.\nBring it back and you'll be richly rewarded!",
speaker = "QuestGiver",
choices = "Where is the temple?,I'm on it!,Tell me more"
}
})
elseif choice_text == "What's the reward?" then
ecs.send_event("dialogue_show", {
stringValues = {
text = "100 gold pieces and a magical amulet! What do you say?",
speaker = "QuestGiver",
choices = "I'll help!,Sounds good,Maybe later"
}
})
elseif choice_text == "Not interested" then
ecs.send_event("dialogue_show", {
stringValues = {
text = "Very well. The offer stands if you change your mind.",
speaker = "QuestGiver"
}
})
end
end)
print("Subscribed to dialogue_choice for event chaining")
-- ---------------------------------------------------------------------------
-- 6. Subscribe to custom events for game logic
-- ---------------------------------------------------------------------------
-- Listen for quest acceptance
local quest_sub = ecs.subscribe_event("quest_accepted", function(event, params)
local quest_name = params.stringValues and params.stringValues.quest_name or "unknown"
local reward = params.values and params.values.reward_gold or 0
local xp = params.values and params.values.reward_xp or 0
print("Quest accepted: " .. quest_name)
print(" Reward: " .. reward .. " gold, " .. xp .. " XP")
-- This could trigger other EventHandlers on other entities
ecs.send_event("quest_log_updated", {
stringValues = { quest_name = quest_name },
values = { active_quests = 1 }
})
end)
print("Subscribed to quest_accepted events")
-- ---------------------------------------------------------------------------
-- 7. Practical: Zone entry dialogue
-- ---------------------------------------------------------------------------
-- When the player enters a new area, show contextual dialogue.
function on_zone_entered(zone_name)
local zone_dialogues = {
forest = {
text = "You enter the Dark Forest. The trees loom overhead,\nblocking out the sunlight.",
speaker = "Narrator"
},
village = {
text = "Welcome to Greenhaven Village. Smoke rises from\nchimneys and children play in the streets.",
speaker = "Narrator"
},
dungeon = {
text = "The air grows cold and damp as you descend into\nthe ancient dungeon. Somewhere, water drips.",
speaker = "Narrator"
},
beach = {
text = "The sea stretches to the horizon. Waves crash\nagainst the shore. A ship is docked nearby.",
speaker = "Narrator"
}
}
local dialogue = zone_dialogues[zone_name]
if dialogue then
ecs.send_event("dialogue_show", {
stringValues = {
text = dialogue.text,
speaker = dialogue.speaker
}
})
-- Also send a zone-specific event for other systems
ecs.send_event("zone_entered", {
stringValues = { zone = zone_name }
})
end
end
-- Simulate zone transitions
on_zone_entered("forest")
on_zone_entered("village")
on_zone_entered("dungeon")
-- ---------------------------------------------------------------------------
-- 8. Practical: Item pickup dialogue
-- ---------------------------------------------------------------------------
function on_item_picked_up(item_name, item_count)
local pickup_messages = {
health_potion = "You pick up a Health Potion. It glows with a warm light.",
ancient_key = "An ancient key, covered in rust. It must open something important.",
gold_coins = "You find " .. item_count .. " gold coins. They clink satisfyingly.",
mysterious_map = "A faded map with markings you can't decipher. Someone might know what it means.",
sword = "A fine steel sword. It feels balanced in your hand."
}
local message = pickup_messages[item_name]
if message then
ecs.send_event("dialogue_show", {
stringValues = {
text = message,
speaker = "Narrator"
}
})
end
end
-- Simulate item pickups
on_item_picked_up("health_potion", 1)
on_item_picked_up("ancient_key", 1)
on_item_picked_up("gold_coins", 50)
-- =============================================================================
-- Summary
-- =============================================================================
-- EventHandler + Dialogue integration patterns:
--
-- 1. Proximity triggers:
-- Player near NPC -> send event -> EventHandler triggers action -> dialogue
--
-- 2. Choice chaining:
-- Player picks choice -> send event -> EventHandler triggers -> next dialogue
--
-- 3. Zone entry:
-- Player enters area -> send event -> dialogue shows description
--
-- 4. Item pickup:
-- Player picks up item -> send event -> contextual dialogue
--
-- 5. Quest flow:
-- Accept quest -> event -> update quest log -> next dialogue
-- Complete quest -> event -> reward dialogue -> next dialogue
-- =============================================================================
print("Dialogue EventHandler integration examples completed!")

View File

@@ -0,0 +1,141 @@
-- =============================================================================
-- Dialogue: Event Subscription & Choice Handling
-- =============================================================================
-- This example demonstrates how to subscribe to dialogue-related events
-- and handle player choices from Lua.
--
-- The DialogueSystem fires events when the player interacts with the
-- dialogue box. You can subscribe to these events to drive game logic.
--
-- Dialogue-related events you can subscribe to:
-- "dialogue_show" - Fired when dialogue should be displayed
-- "dialogue_choice" - Fired when player selects a choice
-- "dialogue_dismiss" - Fired when dialogue is dismissed (no choices)
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 1. Create the dialogue entity
-- ---------------------------------------------------------------------------
local dialogue_entity = ecs.create_entity()
ecs.set_entity_name(dialogue_entity, "DialogueBox")
ecs.add_component(dialogue_entity, "Dialogue")
-- ---------------------------------------------------------------------------
-- 2. Subscribe to dialogue choice events
-- ---------------------------------------------------------------------------
-- When the player selects a choice in the dialogue box, we can react to it.
-- The DialogueComponent's onChoiceSelected callback fires with the 1-based
-- choice index. We bridge this via the EventBus.
local choice_sub = ecs.subscribe_event("dialogue_choice", function(event, params)
local choice_index = params.values and params.values.choice_index or 0
local choice_text = params.stringValues and params.stringValues.choice_text or "unknown"
print("Player selected choice #" .. choice_index .. ": " .. choice_text)
-- React based on which choice was selected
if choice_index == 1 then
print(" -> Player chose the first option!")
elseif choice_index == 2 then
print(" -> Player chose the second option!")
elseif choice_index == 3 then
print(" -> Player chose the third option!")
end
end)
print("Subscribed to dialogue_choice events (ID: " .. choice_sub .. ")")
-- ---------------------------------------------------------------------------
-- 3. Subscribe to dialogue dismiss events
-- ---------------------------------------------------------------------------
-- When dialogue is dismissed (clicked through with no choices), we can
-- trigger follow-up actions.
local dismiss_sub = ecs.subscribe_event("dialogue_dismiss", function(event, params)
print("Dialogue was dismissed by the player")
-- You could trigger follow-up dialogue or game logic here
local next_text = params.stringValues and params.stringValues.next_text or ""
if next_text ~= "" then
print(" -> Next dialogue queued: " .. next_text)
end
end)
print("Subscribed to dialogue_dismiss events (ID: " .. dismiss_sub .. ")")
-- ---------------------------------------------------------------------------
-- 4. Subscribe to dialogue show events (for logging/tracking)
-- ---------------------------------------------------------------------------
local show_sub = ecs.subscribe_event("dialogue_show", function(event, params)
local text = params.stringValues and params.stringValues.text or ""
local speaker = params.stringValues and params.stringValues.speaker or "Unknown"
local choices = params.stringValues and params.stringValues.choices or ""
print("[Dialogue Log] " .. speaker .. ": \"" .. text .. "\"")
if choices ~= "" then
print("[Dialogue Log] Choices: " .. choices)
end
end)
print("Subscribed to dialogue_show events for logging (ID: " .. show_sub .. ")")
-- ---------------------------------------------------------------------------
-- 5. Example: Branching dialogue with choice handling
-- ---------------------------------------------------------------------------
-- This shows a complete flow: show dialogue -> handle choice -> react
function show_branching_dialogue()
-- Step 1: Show the dialogue with choices
ecs.send_event("dialogue_show", {
stringValues = {
text = "You see a dark cave entrance. What do you do?",
speaker = "Narrator",
choices = "Enter the cave,Look around first,Leave"
}
})
-- Step 2: The choice will be handled by our subscriber above.
-- In a real scenario, you'd use a state machine or coroutine to
-- manage the flow. See dialogue_sequence.lua for a more advanced example.
end
show_branching_dialogue()
-- ---------------------------------------------------------------------------
-- 6. Example: NPC greeting with follow-up
-- ---------------------------------------------------------------------------
function npc_greeting(npc_name, greeting_text)
-- Show initial greeting
ecs.send_event("dialogue_show", {
stringValues = {
text = greeting_text,
speaker = npc_name,
choices = "Who are you?,Tell me about this place,Goodbye"
}
})
-- The choice subscriber will handle the response.
-- You could extend this with a lookup table for NPC responses.
end
npc_greeting("Elder Marcus", "Ah, a new face in our village! Welcome, traveler.")
-- =============================================================================
-- Summary
-- =============================================================================
-- To handle dialogue choices from Lua:
-- 1. Subscribe to "dialogue_choice" events
-- 2. Check params.values.choice_index (1-based) to see which was picked
-- 3. Check params.stringValues.choice_text for the label text
-- 4. React accordingly in your game logic
--
-- To handle dialogue dismissal:
-- 1. Subscribe to "dialogue_dismiss" events
-- 2. Trigger follow-up actions as needed
-- =============================================================================
print("Dialogue event subscription examples completed!")

View File

@@ -0,0 +1,338 @@
-- =============================================================================
-- Dialogue: Sequential Dialogue with Coroutines
-- =============================================================================
-- This example demonstrates how to create sequential, branching dialogue
-- using Lua coroutines. This is the most practical approach for story-driven
-- dialogue where you need to wait for player input between lines.
--
-- The pattern:
-- 1. Show dialogue with choices
-- 2. Wait for player to select a choice (via event subscription)
-- 3. React and show next dialogue based on the choice
-- 4. Repeat until the conversation ends
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 1. Create the dialogue entity
-- ---------------------------------------------------------------------------
local dialogue_entity = ecs.create_entity()
ecs.set_entity_name(dialogue_entity, "DialogueBox")
ecs.add_component(dialogue_entity, "Dialogue")
-- ---------------------------------------------------------------------------
-- 2. Dialogue Queue System
-- ---------------------------------------------------------------------------
-- A simple queue that lets you chain dialogue lines and wait for player
-- input between each one.
local DialogueQueue = {}
local dialogue_queue_active = false
local dialogue_queue_pending = false
local dialogue_queue_choice = 0
local dialogue_queue_choice_text = ""
-- Subscribe to choice events to unblock the queue
local queue_choice_sub = ecs.subscribe_event("dialogue_choice", function(event, params)
if dialogue_queue_pending then
dialogue_queue_choice = params.values and params.values.choice_index or 0
dialogue_queue_choice_text = params.stringValues and params.stringValues.choice_text or ""
dialogue_queue_pending = false
end
end)
-- Subscribe to dismiss events to unblock the queue
local queue_dismiss_sub = ecs.subscribe_event("dialogue_dismiss", function(event, params)
if dialogue_queue_pending then
dialogue_queue_choice = -1 -- signal dismissed
dialogue_queue_pending = false
end
end)
-- ---------------------------------------------------------------------------
-- 3. Helper: Show dialogue and wait for player response
-- ---------------------------------------------------------------------------
--- Show a line of dialogue and wait for the player to respond.
--- @param text string The narration text
--- @param speaker string|nil Optional speaker name
--- @param choices string|nil Comma-separated choices (nil = click to dismiss)
--- @return number choice_index (0 if dismissed, 1+ for choices)
function show_and_wait(text, speaker, choices)
-- Send the dialogue event
ecs.send_event("dialogue_show", {
stringValues = {
text = text,
speaker = speaker or "",
choices = choices or ""
}
})
-- Wait for player response
dialogue_queue_pending = true
dialogue_queue_choice = 0
-- Busy-wait loop (in a real coroutine-based system, you'd yield here)
-- This is a simplified version; see the coroutine example below.
local timeout = 1000
while dialogue_queue_pending and timeout > 0 do
-- In a real game loop, this would be a coroutine yield.
-- For this example, we simulate with a counter.
timeout = timeout - 1
if timeout <= 0 then
dialogue_queue_pending = false
print("WARNING: Dialogue wait timed out!")
end
end
return dialogue_queue_choice
end
-- ---------------------------------------------------------------------------
-- 4. Example: Simple linear conversation
-- ---------------------------------------------------------------------------
function simple_conversation()
print("=== Simple Conversation ===")
-- Line 1: Narration with no choices (click to continue)
ecs.send_event("dialogue_show", {
stringValues = {
text = "The old man sits by the fire, staring into the flames.",
speaker = "Narrator"
}
})
-- In a real game, you'd wait for the dismiss event here.
-- For this example, we just show the pattern.
-- Line 2: NPC speaks with choices
ecs.send_event("dialogue_show", {
stringValues = {
text = "I've been expecting you. The darkness grows stronger each day.",
speaker = "Old Man",
choices = "Tell me more,How can I help?,I must go"
}
})
print(" (Player would now see choices and pick one)")
end
-- ---------------------------------------------------------------------------
-- 5. Example: Branching conversation tree
-- ---------------------------------------------------------------------------
-- Define a conversation tree as a table of nodes
local conversations = {
village_elder = {
greeting = {
text = "Welcome to our village, stranger. What brings you here?",
speaker = "Elder Marcus",
choices = {
{ text = "I seek adventure", next = "adventure" },
{ text = "Just passing through", next = "passing" },
{ text = "I need a place to stay", next = "lodging" }
}
},
adventure = {
text = "Adventure, you say? The old ruins to the east have been stirring.",
speaker = "Elder Marcus",
choices = {
{ text = "Tell me about the ruins", next = "ruins" },
{ text = "I'll check them out", next = "goodbye_adventure" },
{ text = "Maybe another time", next = "goodbye" }
}
},
passing = {
text = "Safe travels! The road north is clear, but beware the forest at night.",
speaker = "Elder Marcus",
choices = {
{ text = "Thank you for the warning", next = "goodbye" },
{ text = "What's in the forest?", next = "forest" }
}
},
lodging = {
text = "The inn is just down the road. Tell them Marcus sent you.",
speaker = "Elder Marcus",
choices = {
{ text = "Thank you, elder", next = "goodbye" },
{ text = "Is there work in town?", next = "work" }
}
},
ruins = {
text = "Ancient ruins, full of traps and treasure. Several have entered, few returned.",
speaker = "Elder Marcus",
choices = {
{ text = "I'm not afraid", next = "goodbye_adventure" },
{ text = "Sounds too dangerous", next = "goodbye" }
}
},
forest = {
text = "Strange creatures have been seen. Wolves the size of horses!",
speaker = "Elder Marcus",
choices = {
{ text = "I'll be careful", next = "goodbye" },
{ text = "I can handle it", next = "goodbye_adventure" }
}
},
work = {
text = "The blacksmith needs an apprentice. Ask for Henrik.",
speaker = "Elder Marcus",
choices = {
{ text = "I'll visit the blacksmith", next = "goodbye" },
{ text = "Thanks, but I'll move on", next = "goodbye" }
}
},
goodbye_adventure = {
text = "Good luck, brave one. You'll need it.",
speaker = "Elder Marcus",
choices = {
{ text = "Farewell!", next = nil }
}
},
goodbye = {
text = "May the winds guide you safely.",
speaker = "Elder Marcus",
choices = {
{ text = "Farewell!", next = nil }
}
}
}
}
--- Walk through a conversation tree.
--- @param tree table The conversation tree definition
--- @param start_node string The starting node name
function run_conversation(tree, start_node)
print("=== Starting Conversation ===")
local current_node_name = start_node
local max_steps = 20
local step = 0
while current_node_name and step < max_steps do
step = step + 1
local node = tree[current_node_name]
if not node then
print("ERROR: Unknown conversation node: " .. current_node_name)
break
end
-- Build choices string from the node's choices table
local choices_str = ""
local choice_map = {}
if node.choices then
local parts = {}
for i, choice in ipairs(node.choices) do
table.insert(parts, choice.text)
choice_map[i] = choice
end
choices_str = table.concat(parts, ",")
end
-- Show the dialogue
ecs.send_event("dialogue_show", {
stringValues = {
text = node.text,
speaker = node.speaker or "",
choices = choices_str
}
})
-- In a real game, you'd wait for the player's choice here.
-- For this example, we simulate by picking the first choice.
print(" [Node: " .. current_node_name .. "] " .. node.speaker .. ": \"" .. node.text .. "\"")
-- Simulate picking a choice (in real game, wait for player input)
if node.choices and #node.choices > 0 then
local chosen = node.choices[1] -- Simulate picking first choice
print(" [Player chose: " .. chosen.text .. "]")
current_node_name = chosen.next
else
current_node_name = nil
end
end
print("=== Conversation Ended ===")
end
-- Run the conversation tree
run_conversation(conversations.village_elder, "greeting")
-- ---------------------------------------------------------------------------
-- 6. Example: NPC dialogue with state tracking
-- ---------------------------------------------------------------------------
-- Track NPC dialogue state
local npc_state = {
marcus_met = false,
marcus_friendship = 0,
quest_active = false,
quest_completed = false
}
function talk_to_elder_marcus()
if not npc_state.marcus_met then
-- First meeting
npc_state.marcus_met = true
npc_state.marcus_friendship = 10
ecs.send_event("dialogue_show", {
stringValues = {
text = "Ah, a new face! I am Elder Marcus, keeper of this village.\nIt's been so long since we had visitors.",
speaker = "Elder Marcus",
choices = "Pleasure to meet you,I've heard stories about you,Hello"
}
})
elseif npc_state.quest_active and npc_state.quest_completed then
-- Quest completed
npc_state.marcus_friendship = npc_state.marcus_friendship + 50
ecs.send_event("dialogue_show", {
stringValues = {
text = "You did it! The village is safe thanks to you.\nPlease, take this reward.",
speaker = "Elder Marcus",
choices = "Thank you, elder,I was happy to help"
}
})
elseif npc_state.quest_active then
-- Quest in progress
ecs.send_event("dialogue_show", {
stringValues = {
text = "Have you dealt with those bandits yet?\nThe villagers are growing anxious.",
speaker = "Elder Marcus",
choices = "I'm working on it,I need more information,Not yet"
}
})
else
-- Regular greeting
ecs.send_event("dialogue_show", {
stringValues = {
text = "Welcome back, friend. The village is peaceful today.",
speaker = "Elder Marcus",
choices = "Any news?,I need supplies,Goodbye"
}
})
end
end
-- Simulate talking to Marcus a few times
print("=== NPC State Tracking ===")
talk_to_elder_marcus() -- First meeting
npc_state.quest_active = true
talk_to_elder_marcus() -- Quest active
npc_state.quest_completed = true
talk_to_elder_marcus() -- Quest completed
-- =============================================================================
-- Summary
-- =============================================================================
-- For sequential dialogue:
-- 1. Use a queue/coroutine pattern to chain dialogue lines
-- 2. Subscribe to "dialogue_choice" and "dialogue_dismiss" events
-- 3. Wait for player input between each line
-- 4. Use conversation trees for branching narratives
-- 5. Track NPC state to change dialogue based on game progress
-- =============================================================================
print("Dialogue sequence examples completed!")