--- name: dialogic description: Dialogic 2 addon for Godot 4 — a dialog system with timelines, events, characters, subsystems, styles, layouts, variables, save/load, and text effects. Use when working with dialog trees, visual novel scenes, character portraits, branching conversations, or any Dialogic-related code in this project. --- # Dialogic 2 Skill Dialogic is a Godot 4 addon (`addons/dialogic/`) providing a full dialog and visual novel system. It is an autoload (`Dialogic`) with a modular subsystem/event architecture. > **Official docs:** https://docs.dialogic.pro/ ## Architecture Overview ``` DialogicGameHandler (autoload "Dialogic") ├── Subsystems (modular, extendable) │ ├── Animations — tweening/transition helpers │ ├── Audio — sound effects, music, channels │ ├── Backgrounds — background images/scenes with transitions │ ├── Choices — branching choice menus │ ├── Expressions — expression/variable evaluation │ ├── Glossary — hover-to-inspect word definitions │ ├── History — conversation log │ ├── Inputs — keyboard/mouse input handling │ ├── Jump — label navigation & jump/return logic │ ├── PortraitContainers — layout slots for portraits │ ├── Portraits — character portrait display & animation │ ├── Save — built-in save/load system │ ├── Settings — runtime settings │ ├── Styles — UI layout/style management │ ├── Text — text display with BBCode effects │ ├── TextInput — player text input prompts │ ├── VAR — variable storage & manipulation │ └── Voice — voice/audio per character line ``` ## Core Files | File | Purpose | |------|---------| | `addons/dialogic/Core/DialogicGameHandler.gd` | Autoload: manages state, timeline execution, subsystems | | `addons/dialogic/Core/DialogicUtil.gd` | Static helpers (filesystem, variables, conversions, suggestions) | | `addons/dialogic/Core/DialogicResourceUtil.gd` | Resource directory management, caches, lookups | | `addons/dialogic/Core/Dialogic_Subsystem.gd` | Base class for all subsystems | | `addons/dialogic/Resources/event.gd` | Base class for all events | | `addons/dialogic/Resources/timeline.gd` | Timeline resource | | `addons/dialogic/Resources/character.gd` | Character resource | | `addons/dialogic/Resources/dialogic_style.gd` | Style resource | | `addons/dialogic/Resources/dialogic_layout_base.gd` | Layout resource | | `addons/dialogic/Resources/dialogic_layout_layer.gd` | Layout layer resource | | `addons/dialogic/Resources/dialogic_style_layer.gd` | Style layer resource | ## DialogicGameHandler API (`Dialogic` autoload) Access via `Dialogic` (global autoload name) or `DialogicUtil.autoload()`. ### States (enum `States`) - `IDLE` — awaiting input to advance - `REVEALING_TEXT` — currently revealing text - `ANIMATING` — an animation is playing - `AWAITING_CHOICE` — awaiting choice selection - `WAITING` — awaiting something (e.g., timer) `current_state` property; `state_changed(new_state)` signal. ### Pausing ```gdscript Dialogic.paused = true # pauses all subsystems (not the scene tree) Dialogic.paused = false # resumes ``` Signals: `dialogic_paused`, `dialogic_resumed` ### Starting/Ending Timelines ```gdscript # Start a timeline AND load a layout scene (use for first dialog) var layout_node = Dialogic.start(timeline, label_or_idx) # timeline: DialogicTimeline resource OR string path OR identifier name # label_or_idx: optional label (string) or event index (int) to jump to # Start without loading layout (use when layout already exists) Dialogic.start_timeline(timeline, label_or_idx) # Preload (prepare without playing) var preloaded = Dialogic.preload_timeline(timeline_resource) # End the current timeline Dialogic.end_timeline(skip_ending:=false) # Check if timeline exists if Dialogic.timeline_exists(timeline): ... # Only start dialog if none is active if Dialogic.current_timeline == null: Dialogic.start("my_timeline") ``` Signals: `timeline_started`, `timeline_ended`, `event_handled(resource)` ### Timeline Properties ```gdscript Dialogic.current_timeline # DialogicTimeline resource Dialogic.current_timeline_events # Array of event resources Dialogic.current_event_idx # int Dialogic.current_state_info # Dictionary of state info ``` ### Clear States (enum `ClearFlags`) ```gdscript Dialogic.clear(Dialogic.ClearFlags.FULL_CLEAR) # clear everything Dialogic.clear(Dialogic.ClearFlags.KEEP_VARIABLES) # keep variables Dialogic.clear(Dialogic.ClearFlags.TIMELINE_INFO_ONLY) # only clear timeline/index ``` ### Save/Load Full State ```gdscript var state = Dialogic.get_full_state() # returns Dictionary Dialogic.load_full_state(state) # restores full dialog state ``` ### Event Handling ```gdscript Dialogic.handle_next_event() # advance to next event Dialogic.handle_event(event_index) # jump to specific event index ``` ### Subsystem Access All subsystems are accessible as properties: ```gdscript Dialogic.Animations Dialogic.Audio Dialogic.Backgrounds Dialogic.Choices Dialogic.Expressions Dialogic.History Dialogic.Inputs Dialogic.Jump Dialogic.PortraitContainers Dialogic.Portraits Dialogic.Save Dialogic.Settings Dialogic.Styles Dialogic.Text Dialogic.TextInput Dialogic.VAR Dialogic.Voice Dialogic.Glossary ``` Check existence: `Dialogic.has_subsystem("SubsystemName")` ## Timeline Events Events are scripts in `addons/dialogic/Modules//event_.gd`. They extend `DialogicEvent` (base class in `Resources/event.gd`). ### Event Types | Module | Event | Purpose | |--------|-------|---------| | Text | `event_text` | Display dialog text with character portrait | | Character | `event_character` | Join/leave/update character portraits | | Choice | `event_choice` | Present branching choices | | Condition | `event_condition` | Branch based on variable conditions | | Audio | `event_audio` | Play sound effects / music on channels | | Background | `event_background` | Change background with transition | | Jump | `event_jump` | Jump to a label | | Jump | `event_label` | Define a label target | | Jump | `event_return` | Return from a jump | | Signal | `event_signal` | Emit custom signals | | Variable | `event_variable` | Set/modify variables | | Wait | `event_wait` | Wait for a duration | | WaitInput | `event_wait_input` | Wait for player input | | Clear | `event_clear` | Clear subsystems | | Comment | `event_comment` | Editor-only comments | | End | `event_end` | End the timeline | | Save | `event_save` | Trigger save | | Settings | `event_setting` | Change runtime settings | | Style | `event_style` | Switch style/layout | | Glossary | `event_glossary` | Manage glossary | | History | `event_history` | Manage history | | TextInput | `event_text_input` | Prompt player for text | | Voice | `event_voice` | Play voice clip | | Call | `event_call` | Call a method on an autoload | | Core | `event_end_branch` | Internal branch-ending marker | ### Timeline Text Format Timelines are saved in text format (`.dtl` files). They use TAB indentation to know what events belong to a choice or condition block. Only changes in indentation matter — be consistent within one block. #### Custom-syntax events (bare text, no brackets): ``` # Comments (lines starting with #) # Comment Event — editor-only, ignored at runtime # Text Event CharacterName: Dialog text here. CharacterName (portrait): Dialog text with portrait change. NoName: Text said by no one in particular. Long text ending with \ will include the next line too.\ This line is part of the same text event. # Character Event join CharacterName (portrait) position [animation="Bounce In"] leave CharacterName [animation="Bounce Out" length="0.3"] update CharacterName (portrait) position [animation="Tada" wait="true"] # Condition Event (indentation-based blocks) if {coins} == 0: # indented events run when true elif {health} <= 10: # another branch else: # fallback # Choice Event (each option starts with -) - Yes, let's go! # indented events fire when chosen - No, not now. | [if {relationship} > 23] # conditional choice - Maybe | [if {charisma} > 10] [else="disable" alt_text="Maybe [too insecure]"] # Set Variable Event set {MyVariable} += 10 set {flag} = true # Label Event label LabelIdentifier label LabelIdentifier (Display Name) # Jump Event jump LabelIdentifier jump TimelineName/LabelIdentifier # jump to label in another timeline jump TimelineName/ # jump to beginning of another timeline # Return Event return # Do/Call Event do AutoloadName.method("argument") ``` #### Shortcode events (wrapped in `[...]`): ``` [background path="res://icon.png" fade="1.0"] [audio path="res://music.ogg" channel="music" fade="1.0"] [signal arg="activate_something"] [end_timeline] [wait time=1.5] [clear time=0.5] [style name="MyStyle"] [save slot="auto"] ``` Shortcode parameter values must be in double quotes. Order doesn't matter. ## Characters Characters are `.dch` resources managed via `DialogicResourceUtil`: ```gdscript var char = DialogicResourceUtil.get_character_resource("CharacterName") var all_chars = DialogicResourceUtil.get_character_directory() ``` ### Character Properties - **Display Name**: Shown on name labels. Can contain `{variables}`. - **Nicknames**: Used for autocolor names feature. - **Default Portrait**: Used when no portrait is specified (Character join, `[portrait]` text effect). Does NOT affect Text events without a portrait. - **Portraits**: Dictionary of portrait name → scene settings. A portrait is a scene (`.tscn`) instanced by Dialogic. The default portrait scene uses an image. Custom portraits extend `DialogicPortrait`. - **Style**: Optional custom style used when this character speaks. - **Sound Moods**: Random typing sounds per portrait. - **Prefix/Suffix**: Text segments wrapped around each text section (e.g., quotation marks). - **Scale, Offset, Mirror**: Per-character and per-portrait transform settings. ### Character lookup Characters are found by identifier string in the `dch_directory`. The directory maps identifiers to file paths. By default, the identifier is derived from the filename (e.g., `Miko.dch` → `"Miko"`). The lookup is **case-sensitive** — ensure timeline text uses the exact same case as the `.dch` filename. If a character is not found at runtime, Dialogic creates a temporary character with no portraits. Portraits won't display because `change_speaker()` skips characters with empty portraits. ## Variables Variables are stored in `ProjectSettings` under `dialogic/variables` as a nested Dictionary. Access/manage via `Dialogic.VAR` subsystem. **Important:** Variables must be pre-declared in the Dialogic Variables editor (or `project.godot`) before they can be set by timeline `set` commands. The Variable event reads the current value first and fails if the variable doesn't exist. ```gdscript DialogicUtil.get_default_variables() # returns the full variable dict DialogicUtil.list_variables(dict, path) # flatten to array of paths DialogicUtil.get_variable_type(path, dict) # get VarTypes enum value # VarTypes enum: ANY, STRING, FLOAT, INT, BOOL ``` ### Variable references in expressions Dialogic variables are referenced with **curly braces** in expressions (conditions, set values, choice conditions): ``` if {coins} == 0: set {coins} += 10 - Option | [if {player_has_key}] ``` Bare names like `coins` are NOT resolved as variables — they're treated as autoload properties by Godot's `Expression` class. ## Text Effects (inline BBCode) Text effects are tags within TextEvent text that trigger actions when reached during text reveal: | Effect | Syntax | Purpose | |--------|--------|---------| | Speed | `[speed=x]` / `[speed]` | Set speed multiplier (1 = normal, 0 = instant). Resets per event. | | Letter Speed | `[lspeed=x]` / `[lspeed=x!]` | Set letter reveal time in seconds. `!` makes it absolute (ignores multiplier). | | Pause | `[pause=x]` / `[pause=x!]` | Pause reveal for x seconds. `!` makes it absolute. | | Portrait | `[portrait=name]` | Switch speaker's portrait mid-text. | | Signal | `[signal=arg]` | Emit `Dialogic.text_signal` with argument. | | Mood | `[mood=name]` | Change typing sound mood. | | Extra Data | `[extra_data=value]` | Set extra data on speaker's portrait. | | New Event | `[n]` / `[n+]` | Split text into manual-advance sections. `+` appends to previous without clearing. | | Input | `[input]` | Await any input (can be skipped). | | Auto-Advance | `[aa]` / `[aa=x]` / `[aa=x?]` | Enable auto-advance. `x` sets time. `?` sets time without enabling. | | No Skip | `[ns]` / `[ns=x]` | Disable text skipping for this event. `x` sets auto-advance time. | **Important:** Text effects are NOT paired — there is no `[/speed]` closing tag. Effects change behavior from that point until the next text event resets them. Writing `[/speed]` will appear as visible text. ## Text Modifiers (preprocessors) Text modifiers run on text before display: | Modifier | Syntax | Purpose | |----------|--------|---------| | Line Break | `[br]` | Insert line break (useful in choices or with "New Lines as New events") | | Random Selection | `` | Random variation for repeated text | | Conditional Text | `[if {cond} Text if true./Text if false.]` | Inline conditional text | | Autopauses | (automatic) | Insert `[pause]` after configured punctuation characters | ## Styles & Layouts Styles define the visual presentation. Layouts define the scene structure. - **Dialogic Nodes**: UI nodes (DialogText, NameLabel, PortraitContainer, ChoiceButton, etc.) that display things. They register themselves in groups. - **Layout Scenes**: Scenes containing Dialogic Nodes and custom scripts. Base layout extends `DialogicLayoutBase`, layers extend `DialogicLayoutLayer`. - **Styles**: Resources combining one base layout + list of layer layouts with overridable settings. Can inherit from other styles. ```gdscript Dialogic.Styles.load_style("StyleName") # load/switch to a style Dialogic.Styles.has_active_layout_node() # check if layout is present Dialogic.Styles.get_layout_node() # get the layout scene node Dialogic.Styles.change_style("StyleName") # change style, preserving state ``` ## Signals & Signal Events ### Signal Event Emits `Dialogic.signal_event` with an argument: ```gdscript Dialogic.signal_event.connect(_on_dialogic_signal) func _on_dialogic_signal(argument: String): if argument == "activate_something": print("Activated!") ``` ### Text Signal (via `[signal=arg]` text effect) Emits `Dialogic.text_signal`: ```gdscript Dialogic.text_signal.connect(_on_dialogic_text_signal) ``` ### Timeline Start/End ```gdscript Dialogic.timeline_started.connect(_on_timeline_started) Dialogic.timeline_ended.connect(_on_timeline_ended) ``` ### Key Subsystem Signals ```gdscript Dialogic.Text.about_to_show_text(info: Dictionary) Dialogic.Text.text_finished(info: Dictionary) Dialogic.Text.speaker_updated(character: DialogicCharacter) Dialogic.Text.textbox_visibility_changed(visible: bool) Dialogic.Portraits.character_joined(info: Dictionary) Dialogic.Portraits.character_left(info: Dictionary) Dialogic.Portraits.character_portrait_changed(info: Dictionary) Dialogic.VAR.variable_changed(info: Dictionary) Dialogic.VAR.variable_was_set(info: Dictionary) # only from set variable events ``` ## Saving & Loading ```gdscript # Simple save/load (takes screenshots too) Dialogic.Save.save("slot_name") Dialogic.Save.load("slot_name") # Save without screenshot Dialogic.Save.save("slot_name", false, Dialogic.Save.ThumbnailMode.NONE) # Save with extra info (date, custom data) var extra = {"text": Dialogic.current_state_info["text"], "date": Time.get_datetime_string_from_system(false, true)} Dialogic.Save.save("slot_name", false, Dialogic.Save.ThumbnailMode.STORE_ONLY, extra) # Check if slot exists if Dialogic.Save.has_slot("slot_name"): var info = Dialogic.Save.get_slot_info("slot_name") ``` Savegames store Dialogic's timeline state and variables. Extra info is stored per-slot but not loaded into Dialogic state. ## Audio Channels Audio plays on named channels. Default channels defined in project settings: ```gdscript DialogicUtil.get_audio_channel_defaults() # returns Dictionary DialogicUtil.get_audio_channel_suggestions("search") DialogicUtil.validate_audio_channel_name("name") ``` Channel names: `""` (default), `"music"` (looping by default), plus custom. ## Resource Management (DialogicResourceUtil) ```gdscript # Directories (map identifier -> path) DialogicResourceUtil.get_directory('dtl') # timelines DialogicResourceUtil.get_directory('dch') # characters DialogicResourceUtil.update_directory(extension) # rescan # Lookups DialogicResourceUtil.get_timeline_resource("name") DialogicResourceUtil.get_character_resource("name") DialogicResourceUtil.get_unique_identifier_by_path("res://...") DialogicResourceUtil.get_resource_path_from_identifier("name", "dtl") # Label cache (editor only) DialogicResourceUtil.get_label_cache() DialogicResourceUtil.update_label_cache() # Event cache DialogicResourceUtil.get_event_cache() # all event types DialogicResourceUtil.update_event_cache() ``` ## Utility Helpers (DialogicUtil) ```gdscript # Variables DialogicUtil.VarTypes # ANY, STRING, FLOAT, INT, BOOL DialogicUtil.get_default_variables() DialogicUtil.list_variables(dict, path, type) DialogicUtil.get_variable_value_type(value) DialogicUtil.get_variable_type(path, dict) DialogicUtil.logical_convert(value) # auto-convert string to int/float/bool # Suggestions (for editor fields) DialogicUtil.get_character_suggestions(search, current, allow_none, allow_all) DialogicUtil.get_portrait_suggestions(search, character, allow_empty) DialogicUtil.get_autoload_suggestions(filter) DialogicUtil.get_autoload_method_suggestions(filter, autoload_name) DialogicUtil.get_autoload_property_suggestions(filter, autoload_name) DialogicUtil.get_audio_bus_suggestions(filter) DialogicUtil.get_audio_channel_suggestions(search) # Timer DialogicUtil.is_physics_timer() DialogicUtil.update_timer_process_callback(timer) ``` ## Project Settings Key settings (under `dialogic/`): - `dialogic/variables` — default variable dictionary - `dialogic/layout/default_style` — default style name - `dialogic/layout/end_behaviour` — 0=free, 1=hide, 2=keep layout on end - `dialogic/text/letter_speed` — default letter reveal time - `dialogic/text/autocolor_names` — auto-color character names in text - `dialogic/text/split_at_new_lines` — treat newlines as `[n]` - `dialogic/text/autopauses` — auto-insert pauses after punctuation - `dialogic/extensions_folder` — custom modules path (default: `res://addons/dialogic_additions/`) - `dialogic/directories/*_directory` — resource directories (dch, dtl, style) - `dialogic/audio/channel_defaults` — default audio channels ## Working with Dialogic in This Project ### Common Patterns **Starting dialog (guard against double-start):** ```gdscript func _input(event): if player_is_in_area and Input.is_action_pressed("start_interaction"): if Dialogic.current_timeline == null: Dialogic.start("my_timeline") ``` **Reacting to dialog signals:** ```gdscript Dialogic.signal_event.connect(_on_dialogic_signal) Dialogic.timeline_ended.connect(_on_dialog_finished) func _on_dialog_finished(): Dialogic.timeline_ended.disconnect(_on_dialog_finished) # game logic here ``` **Setting variables from code:** ```gdscript Dialogic.VAR.set_variable("player_name", "Miko") Dialogic.VAR.set_variable("coins", 42) ``` **Creating timelines in code:** ```gdscript # From strings (follow text syntax) var events: Array = """ Jowan (Surprised): Wow this is interesting! - Yes set {MyAutoload.excitement} += 20 - No set {MyAutoload.excitement} -= 10 """.split('\n') var timeline = DialogicTimeline.new() timeline.events = events Dialogic.start(timeline) ``` **Hiding/showing textbox:** ```gdscript if Dialogic.Text.is_textbox_visible(): Dialogic.Text.hide_textbox() else: Dialogic.Text.show_textbox() ``` **Getting current speaker:** ```gdscript var speaker: DialogicCharacter = Dialogic.Text.get_current_speaker() var portrait_info := Dialogic.Portraits.get_character_info(speaker) ``` ### Troubleshooting - **Portraits not showing**: Characters must join the scene with a Character Event (Join mode) before their portraits appear. Ensure `.dch` filename case matches timeline text exactly. - **Wrong text displayed**: If translation is enabled, CSV rows take priority. Disable translation during development or update CSVs. - **Lag on dialog start**: Preload styles with `style.prepare()` or preload an empty timeline during your loading screen. - **Input conflicts**: If your interaction key matches the dialogic input action, guard with `if Dialogic.current_timeline == null:` before starting new dialog.