diff --git a/src/features/editScene/lua-examples/dialogue_basic_show.lua b/src/features/editScene/lua-examples/dialogue_basic_show.lua new file mode 100644 index 0000000..a92d9ca --- /dev/null +++ b/src/features/editScene/lua-examples/dialogue_basic_show.lua @@ -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!") diff --git a/src/features/editScene/lua-examples/dialogue_component_api.lua b/src/features/editScene/lua-examples/dialogue_component_api.lua new file mode 100644 index 0000000..ff4d7d6 --- /dev/null +++ b/src/features/editScene/lua-examples/dialogue_component_api.lua @@ -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!") diff --git a/src/features/editScene/lua-examples/dialogue_event_handler.lua b/src/features/editScene/lua-examples/dialogue_event_handler.lua new file mode 100644 index 0000000..02f83ff --- /dev/null +++ b/src/features/editScene/lua-examples/dialogue_event_handler.lua @@ -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!") diff --git a/src/features/editScene/lua-examples/dialogue_event_subscribe.lua b/src/features/editScene/lua-examples/dialogue_event_subscribe.lua new file mode 100644 index 0000000..5c2da7d --- /dev/null +++ b/src/features/editScene/lua-examples/dialogue_event_subscribe.lua @@ -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!") diff --git a/src/features/editScene/lua-examples/dialogue_sequence.lua b/src/features/editScene/lua-examples/dialogue_sequence.lua new file mode 100644 index 0000000..a7a2aec --- /dev/null +++ b/src/features/editScene/lua-examples/dialogue_sequence.lua @@ -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!")