From 9996cdc08bc0f41b185e52e9db3d635f5dd28f3a Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Thu, 18 Sep 2025 14:35:28 -0400 Subject: [PATCH] Added ability to have character dialogs. --- speech.py | 161 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 158 insertions(+), 3 deletions(-) diff --git a/speech.py b/speech.py index 4da3f28..2b8a0ea 100644 --- a/speech.py +++ b/speech.py @@ -130,15 +130,34 @@ def speak(text, interrupt=True): _speechInstance = Speech.get_instance() _speechInstance.speak(text, interrupt) -def messagebox(text): - """Display a simple message box with text. +def messagebox(text, sounds=None): + """Display a message box with text and optional dialog support. Shows a message that can be repeated until the user chooses to continue. + Supports both simple text messages and dialog sequences with character speech and sounds. Args: - text (str): Message to display + text (str or dict): Simple string message or dialog configuration dict + sounds (Sound object, optional): Sound system for playing dialog audio """ speech = Speech.get_instance() + + # Handle simple string (backward compatibility) + if isinstance(text, str): + _show_simple_message(speech, text) + return + + # Handle dialog format + if isinstance(text, dict) and "entries" in text: + _show_dialog_sequence(speech, text, sounds) + return + + # Fallback to simple message if format not recognized + _show_simple_message(speech, str(text)) + + +def _show_simple_message(speech, text): + """Show a simple text message (original messagebox behavior).""" speech.speak(text + "\nPress any key to repeat or enter to continue.") while True: event = pygame.event.wait() @@ -147,3 +166,139 @@ def messagebox(text): speech.speak(" ") return speech.speak(text + "\nPress any key to repeat or enter to continue.") + + +def _show_dialog_sequence(speech, dialog_config, sounds): + """Show a dialog sequence with character speech and optional sounds. + + Args: + speech: Speech instance for text-to-speech + dialog_config (dict): Dialog configuration with entries list and optional settings + sounds: Sound system for playing audio files + """ + entries = dialog_config.get("entries", []) + allow_skip = dialog_config.get("allow_skip", False) + dialog_sound = dialog_config.get("sound", None) + + if not entries: + return + + entry_index = 0 + while entry_index < len(entries): + entry = entries[entry_index] + + # Play sound before showing dialog + _play_dialog_sound(entry, dialog_config, sounds) + + # Format and show the dialog text + formatted_text = _format_dialog_entry(entry) + if not formatted_text: + entry_index += 1 + continue + + # Show dialog with appropriate controls (only on first entry) + if entry_index == 0: + if allow_skip: + control_text = "\nPress any key to repeat, enter for next, or escape to skip all." + else: + control_text = "\nPress any key to repeat or enter for next." + else: + control_text = "" # No instructions after first entry + + speech.speak(formatted_text + control_text) + + # Handle user input + while True: + event = pygame.event.wait() + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + if allow_skip: + speech.speak(" ") + return # Skip entire dialog sequence + else: + # Escape acts like enter if skip not allowed + speech.speak(" ") + entry_index += 1 + break + elif event.key == pygame.K_RETURN: + speech.speak(" ") + entry_index += 1 + break + else: + # Repeat current entry (include instructions only on first entry) + repeat_text = formatted_text + if entry_index == 0: + repeat_text += control_text + speech.speak(repeat_text) + + +def _format_dialog_entry(entry): + """Format a dialog entry for display. + + Args: + entry (dict): Dialog entry with text, optional speaker, and optional narrative flag + + Returns: + str: Formatted text for speech + """ + text = entry.get("text", "") + speaker = entry.get("speaker", None) + is_narrative = entry.get("narrative", False) + + if not text: + return "" + + if is_narrative: + # Narrative text - no speaker name + return text + elif speaker: + # Character dialog - include speaker name + return f"{speaker}: \"{text}\"" + else: + # Plain text - no special formatting + return text + + +def _play_dialog_sound(entry, dialog_config, sounds): + """Play appropriate sound for a dialog entry and wait for it to complete. + + Args: + entry (dict): Dialog entry that may have a sound + dialog_config (dict): Dialog configuration that may have a default sound + sounds: Sound system for playing audio files + """ + if not sounds: + return + + sound_to_play = None + + # Determine which sound to play (priority order) + if entry.get("sound"): + # Entry-specific sound (highest priority) + sound_to_play = entry["sound"] + elif dialog_config.get("sound"): + # Dialog-specific sound (medium priority) + sound_to_play = dialog_config["sound"] + else: + # Default dialogue.ogg (lowest priority) + sound_to_play = "dialogue" # Will look for dialogue.ogg + + if sound_to_play: + try: + # Check if sound exists in the sound system + if hasattr(sounds, 'sounds') and sound_to_play in sounds.sounds: + sound_obj = sounds.sounds[sound_to_play] + # Play the sound + channel = sound_obj.play() + # Wait for the sound to finish + sound_duration = sound_obj.get_length() + if sound_duration > 0: + pygame.time.wait(int(sound_duration * 1000)) # Convert to milliseconds + elif hasattr(sounds, 'play'): + # Try using a play method if available + sounds.play(sound_to_play) + # If we can't get duration, add a small delay + pygame.time.wait(500) # 0.5 second default delay + except Exception: + # Sound missing or error - continue silently without crashing + pass