--- 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/ > **Class Reference:** https://docs.dialogic.pro/class_index.html ## 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 use the `.dtl` file extension and are saved in a human-readable text format. You can edit them in any text editor — Dialogic's built-in text editor provides autocompletion and syntax highlighting. > To find all parameters available on each shortcode event, search for the event class name (e.g. `DialogicBackgroundEvent`) in the Godot Help or the [Dialogic documentation](https://docs.dialogic.pro/). #### Indentation Rules Timelines use **TAB indentation** to determine which events belong to a choice or condition block. Only changes in indentation matter — be consistent within one block, but the exact amount doesn't matter. #### Custom-Syntax Events (bare text, no brackets) Some events have a special syntax for easier writing: **Text Event:** ``` # Plain text with no speaker — said by no one in particular A simple line of narration or internal monologue. # Speaker with optional portrait CharacterName: Dialog text here. CharacterName (portrait): Dialog text with a portrait change. # Multi-line text — end a line with \ to include the next line Emilio: This is a text event with \ multiple lines. Isn't that great? It is! ``` The `\` continuation joins the next line into the same text event. Trailing whitespace after `\` is ignored. **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" repeat="3" move_time="0.3"] ``` Positions: `left`, `center`, `right`. The animation shortcode at the end contains optional parameters. **Condition Event** (indentation-based blocks): ``` if {Player.Wisdom} > 3: # indented events run when condition is true elif {Player.Health} <= 10: # another branch else: # fallback — runs if no condition matched ``` **Choice Event** (each option starts with `-`): ``` - Yes, let's go! # indented events fire when this option is chosen - No, not now. | [if {John.Relationship} > 23] # conditional choice — only shown if condition is true - Maybe | [if {Stats.Charisma} > 10] [else="disable" alt_text="Maybe [too insecure]"] ``` The `|` separator attaches logic to the choice option. `[if]` controls visibility; `[else]` controls behavior when hidden (`disable` or `hide`). **Set Variable Event:** ``` set {coins} = 20 set {Player.Health} += 10 set {Player.Shield} -= 5 set {Player.Damage} *= 1.5 set {Player.Speed} /= 2 set {player_name} = "Miko" set Autoload.property = "Hello World" ``` Supported operators: `=`, `+=`, `-=`, `*=`, `/=` **Comment Event** (editor-only, ignored at runtime): ``` # This is a comment — useful for notes and organization ``` **Label Event:** ``` label LabelIdentifier label LabelIdentifier (Display Name) ``` **Jump Event:** ``` jump LabelIdentifier # jump to a label in the current timeline jump TimelineName/LabelIdentifier # jump to a label in another timeline jump TimelineName/ # jump to the beginning of another timeline ``` **Return Event:** ``` return ``` Returns from a previous `jump` to where the jump originated. **Do/Call Event:** ``` do AutoloadName.method("argument") ``` Calls a method on any autoload singleton. #### Shortcode Events (wrapped in `[...]`) Most other events use a shortcode style. Parameters are separated by at least one space (order doesn't matter). All parameter values must be wrapped in double quotes, regardless of type: ``` [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"] [text_input text="What's your name?" var="player_name" placeholder="Enter..." default="Hero" allow_empty="false"] ``` #### Full Example Timeline ``` [background path="res://assets/backgrounds/dialogic_factory.png"] join Jowan left Jowan (excited): Hello and welcome to[portrait=confused]...[pause=0.5] Wait? What is this? join Emilio (happy) right Emilio: Well, this is the example timeline. Jowan: I thought this was a cool new feature? Emilio: Nah, sorry. Jowan (sad): It's okay. # Choices loop back to this label label WhatAbout Jowan (default): So what should this example be about? - How to bake cookies Emilio (confused): Wait that hasn't to do with dialogic?! jump WhatAbout - How to reach the moon | [if {Player.Name} == "NASA"] Jowan (angry): NASA! It's you again. This is for making dialogs!\ Please ask someone else about the moon! jump WhatAbout - How to write timelines in text format Jowan: Oh, well it's pretty intuitive.[pause=0.2][portrait=questioning] I hope. Emilio: Let's hope it's easy as well. [end_timeline] ``` ## 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. ## Character Event The Character event controls how characters with portraits appear, move, and leave the scene. It has three modes: **Join**, **Leave**, and **Update**. ### Modes | Mode | Keyword | Purpose | |------|---------|--------| | **Join** | `join` | Add a character to the scene. Requires a position/transform. Uses `In`-animations. | | **Leave** | `leave` | Remove a character. Uses `Out`-animations. `--All--` removes all joined characters. | | **Update** | `update` | Modify an existing character's portrait, position, mirroring, z-index, extra data, or play an animation. Only changes what is explicitly set — unspecified properties stay unchanged. | ### Text Syntax ``` # Basic join — character at a preset position join MyCharacter center # Join with portrait and animation join MyCharacter (SomePortrait) center [animation="Bounce In"] # Join a character whose name contains spaces (quoted) with a grouped portrait join "My Character" (Special/Crazy) center # Join with manual transform (position, size, rotation) join MyCharacter pos=x0.5y1 size=y400px rot=20 # Basic leave leave MyCharacter # Leave with animation parameters leave MyCharacter [animation="Fade Down"] # Leave all characters at once leave --All-- # Update: animate the character update MyCharacter [animation="Tada" length="2" wait="true"] # Update: move character with tween update MyCharacter left [move_time="1" move_trans="Elastic" move_ease="In"] # Update: change portrait with crossfade update MyCharacter (OtherPortrait) [fade_time="0.5"] ``` ### Main Settings #### Character The character identifier (from the `.dch` filename). If the name contains spaces, wrap it in double quotes: `"My Character"`. For `leave`, use `--All--` to remove all joined characters. #### Portrait (optional) The portrait to display, specified in parentheses after the character: `(portrait_name)`. Portraits in groups use the full path: `(Group/SubGroup/Portrait)`. The portrait can be an expression inside `{curly braces}`: `({PlayerPortrait})`. - **Join**: Uses the character's default portrait if omitted. - **Update**: Does NOT change the portrait unless one is given (gated by `set_portrait`). - **Leave**: Ignored. #### Position / Transform Defines where the character appears. Two approaches: **Preset positions** — reference a `PortraitContainer` defined in your style layer: ``` join MyCharacter left join MyCharacter center ``` Default presets: `leftmost`, `left`, `center`, `right`, `rightmost`. Add more by creating a custom portrait layer. **Manual transform commands** — precise control with `pos=`, `size=`, `rot=`: ``` join MyCharacter pos=x0.5y1 size=y400px rot=20 ``` - `pos=x0.5 y1` — position (default: relative to viewport; x=1 is full width, y=1 is full height) - `pos=x100px y1%` — can also use `px` or `%` units - `size=x0.3y0.3` — size relative to viewport - `rot=20` — rotation in degrees around the portrait origin (usually bottom center) The position defines the ORIGIN of the portrait container. When joining, position/size/rotation defaults are copied from the first portrait preset found — so `pos=x0.3` alone is usually sufficient to vary along the x-axis. ### Animation Settings (shortcode) All animation parameters go in the `[...]` shortcode at the end: | Parameter | Shortcode | Description | |-----------|-----------|-------------| | Animation name | `animation="Bounce"` | Animation to play. Forgiving match: `Fade Up`, `fade up in`, `fade In Up` all resolve correctly. Join/Leave fall back to Settings defaults if omitted; Update plays nothing if omitted. | | Length | `length="1"` | Animation duration in seconds. Default: `0.5`. | | Wait | `wait="true"` | If true, pauses the timeline until the animation finishes. Default: `false`. | | Repeat | `repeat="3"` | **Update only.** Number of times to repeat the animation. Default: `1`. | ### Fade Settings (shortcode, Update mode only) Used when changing a portrait during an Update event: | Parameter | Shortcode | Description | |-----------|-----------|-------------| | Fade animation | `fade="Fade"` | Crossfade animation for portrait transition. Falls back to the default crossfade in Settings if omitted. | | Fade length | `fade_length="0.5"` | Crossfade duration in seconds. Default: `0.5`. | ### Move Settings (shortcode, Update mode only) Used when changing position during an Update event to tween the movement: | Parameter | Shortcode | Description | |-----------|-----------|-------------| | Move time | `move_time="0.8"` | Tween duration in seconds. If `0` (default), the change is instant. | | Move transition | `move_trans="Elastic"` | Tween transition type. | | Move easing | `move_ease="In Out"` | Tween easing direction. | **Transition types:** `Linear`, `Sine`, `Quint`, `Quart`, `Quad`, `Expo`, `Elastic`, `Cubic`, `Circ`, `Bounce`, `Back`, `Spring` **Easing types:** `In`, `Out`, `In Out`, `Out In` Reference: [Godot Tween Cheat Sheet](https://raw.githubusercontent.com/godotengine/godot-docs/master/img/tween_cheatsheet.webp) ### Other Settings (shortcode) | Parameter | Shortcode | Description | |-----------|-----------|-------------| | Z-Index | `z_index="2"` | Controls visual stacking order. Higher values appear in front. NOT Godot's native z_index — Dialogic sorts manually. | | Mirrored | `mirrored="true"` | Flips the portrait horizontally. Applied on top of the portrait's and container's mirror settings. | | Extra Data | `extra_data="something"` | Arbitrary string passed to the portrait scene. Handled by `_set_extra_data(data)` on custom portraits. | ### Portrait Expressions The portrait field supports `{curly brace}` expressions evaluated at runtime: ``` join MyCharacter ({PlayerPortrait}) center update MyCharacter ({CurrentMood}) ``` Useful for dynamic portrait switching based on variables. ### Character with No Portraits If a character has no portraits (empty `portraits` dictionary), the Character event editor hides portrait/position options. Timeline text using that character will still parse but no portrait will display. ## Jump, Label & Return Events These three events work together to control timeline flow: **Label** defines anchor points, **Jump** changes the current position (within or across timelines), and **Return** goes back to the last jump origin. Jumps use a **stack** to track where you came from. ### Text Syntax ``` # Jump to the beginning of the current timeline (no target) jump # Jump to a label in the current timeline jump LabelIdentifier # Jump to a label in another timeline jump TimelineName/LabelIdentifier # Jump to the beginning of another timeline jump TimelineName/ # Define a label (anchor point) label LabelIdentifier # Label with a display name (for debugging/history) label LabelIdentifier (Display Name) # Return to the last jump point return ``` **Dynamic labels:** Labels can include `{variables}` for runtime resolution: ``` jump {day_phase} ``` ### How the Jump Stack Works 1. When a **Jump** event executes, the current timeline + event index is **pushed** onto a stack. 2. Execution moves to the target (label, timeline start, etc.). 3. When a **Return** event executes, the stack is **popped** and execution resumes at the saved timeline + index. 4. If the stack is empty when a Return event is hit, the timeline ends (`dialogic.end_timeline()`). This allows building reusable timeline "subroutines" — jump to a timeline, run it, and return to exactly where you left off. ### Subsystem API (`Dialogic.Jump`) ```gdscript # Jump to a specific label in the current timeline (bypasses the stack) Dialogic.Jump.jump_to_label("LabelIdentifier") # Resume from the last jump (pops the stack) Dialogic.Jump.resume_from_last_jump() # Check if the jump stack is empty if Dialogic.Jump.is_jump_stack_empty(): print("No jump to return to") # Start a timeline at a specific label Dialogic.start(timeline, "LabelIdentifier") # Get info about the last passed label var label_id = Dialogic.Jump.get_last_label_identifier() var label_name = Dialogic.Jump.get_last_label_name() ``` ### Jump Signals ```gdscript # Emitted when a Jump event switches to a different timeline Dialogic.Jump.switched_timeline.connect(func(info): print("Switched from ", info.previous_timeline, " to ", info.timeline, " at ", info.label)) # info = {"previous_timeline": DialogicTimeline, "timeline": DialogicTimeline, "label": ""} # Emitted when a Jump event jumps to a label (same or different timeline) Dialogic.Jump.jumped_to_label.connect(func(info): print("Jumped to label ", info.label, " in ", info.timeline)) # info = {"timeline": DialogicTimeline, "label": "label_name"} # Emitted when a Return event pops the stack Dialogic.Jump.returned_from_jump.connect(func(info): print("Returned from ", info.sub_timeline, " to label ", info.label)) # info = {"sub_timeline": DialogicTimeline, "label": "label_name"} # Emitted when a Label event is passed during execution Dialogic.Jump.passed_label.connect(func(info): print("Passed label ", info.identifier, " (", info.display_name, ")")) # info = {"identifier": "name", "display_name": "Name", "display_name_orig": "Name", "timeline": "timeline_id"} ``` ## Variables Dialogic has a built-in variable system with folders, types, and expression evaluation. Variables can also reference external autoload singletons. ### Storage Architecture - **Default values** are stored in `ProjectSettings` under `dialogic/variables` as a nested Dictionary. This is where the Variables editor persists them. - **Runtime values** are stored in `Dialogic.current_state_info['variables']` — a copy of the defaults merged with current state. - **Folder grouping** is represented by nested Dictionaries. A dotted path like `Player.Health` maps to `{"Player": {"Health": 100}}`. - On `Dialogic.VAR.reset()` or `Dialogic.clear(..., false)`, runtime values are reset to the defaults from ProjectSettings. ### Variable Editor Set up variables in the Dialogic "Variables" tab. Each variable has a **name** and **default value**. Variables can be organized into **folders** (nested Dictionaries). Inside a folder, no two items (variable or subfolder) can share the same name. **Variable types** (from `DialogicUtil.VarTypes` enum): - `ANY` — auto-detected from default value - `STRING` — text - `FLOAT` — floating point number - `INT` — integer - `BOOL` — true/false ### Referencing Variables **Dialogic variables** use dotted paths for folders; wrap in `{curly braces}`: ``` {coins} # root-level variable {Player.Health} # variable inside a folder {FolderA.FolderB.variable_name} # nested folders ``` **Autoload variables** are referenced by `AutoloadName.property`: ``` {MySingleton.player_name} {GameState.difficulty} {MyAutoload.get_random_name()} # method calls return their result ``` **Complex expressions** are supported: ``` {{Player.Health}/10.0+2} ``` **Important:** Dialogic variables (native, no autoload prefix) **must be pre-declared** in the Variables editor before they can be set by timeline `set` commands or referenced in text. Referencing an undeclared variable evaluates to `null` (empty string in text). Autoload variables do NOT need pre-declaration — they're accessed directly via the `Expression` class. **Availability:** Variables are only accessible after the `Dialogic` autoload is `_ready()`. Do not read/write variables in `_ready()` of nodes that load before Dialogic. ### Using Variables in the Timeline #### In Text Events Insert variable values with curly braces: ``` You have {coins} coins remaining. Hello, {PlayerName}! Next level at {MyAutoload.xp_required} XP. ``` Variables can also be used in: - **Character Display Names** — type `{MyVariableName}` in the character editor's display name field. - **Glossary entries** — titles, texts, and extras all support the same `{curly braces}` syntax. #### In Conditions ``` if {coins} >= 10: # indented events run when true elif {Player.Health} <= 5: # another branch else: # fallback ``` Conditions can also reference autoloads without curly braces (bare names are treated as autoload names by Godot's `Expression` class): ``` if GameState.difficulty == "hard": if MyAutoload.check_info("Argument", false): ``` Use typical boolean operators: `==`, `!=`, `<`, `>`, `<=`, `>=`, `not`, `and`, `or`, and parentheses for grouping. #### Set Variable Event **Text syntax:** ``` # Dialogic variables (pre-declared) set {coins} = 20 set {Player.Health} += 2 set {Player.Shield} -= 5 set {Player.Damage} *= 1.5 set {Player.Speed} /= 2 # String assignment set {player_name} = "Miko" # Autoload variables (no pre-declaration needed) set Autoload.property = "Hello World" set Autoload.health = {Player.Health} + 2 # Complex expressions set {Player.Health} += range(10, 20).pick_random() ``` **Operations:** `=` (SET), `+=` (ADD), `-=` (SUBTRACT), `*=` (MULTIPLY), `/=` (DIVIDE) String concatenation is supported with `+=` when both the variable and value are strings. **Value types** (selectable in the visual editor): | Type | Example | Description | |------|---------|-------------| | String | `"Hello"` | Quoted string literal | | Number | `42` or `3.14` | Float or integer | | Variable | `{OtherVar}` | Value of another variable | | Bool | `true` / `false` | Boolean literal | | Expression | `range(10,20).pick_random()` | Complex GDScript expression | | Random Number | `range(0, 100).pick_random()` | Random integer in range | #### Text Input Event The Text Input event prompts the player to type text and stores the result in a dialogic variable: ``` [text_input text="What's your name?" var="player_name" placeholder="Enter name..." default="Hero" allow_empty="false"] ``` This is a specialized form of set variable that captures player keyboard input. ### Accessing Variables from Code #### Using `Dialogic.VAR` Subsystem ```gdscript # Direct property access (simple and preferred) Dialogic.VAR.coins = 42 print(Dialogic.VAR.coins) Dialogic.VAR.Player.Health = 100 # folder access print(Dialogic.VAR.Player.Health) # 100 # String-based access (useful for dynamic variable names) Dialogic.VAR.set_variable("coins", 42) print(Dialogic.VAR.get_variable("coins")) Dialogic.VAR.set_variable("Player.Health", 100) # Check existence if Dialogic.VAR.has("coins"): print("coins exists") # Reset to defaults Dialogic.VAR.reset() # reset all variables Dialogic.VAR.reset("Player.Health") # reset one variable to its default # Parse curly-brace expressions in strings var result = Dialogic.VAR.parse_variables("You have {coins} coins.") ``` #### Using `DialogicUtil` (static helpers) ```gdscript DialogicUtil.get_default_variables() # returns the full default variable dict DialogicUtil.list_variables(dict, path) # flatten to array of dotted paths DialogicUtil.get_variable_type(path, dict) # get VarTypes enum value DialogicUtil.get_variable_value_type(value) # get type of a value DialogicUtil.logical_convert(value) # auto-convert string to int/float/bool ``` #### Variable Folders When accessing via direct property access (`Dialogic.VAR.MyFolder`), folders return a `VariableFolder` object that supports: - `folders()` — list all subfolders (as `VariableFolder` objects) - `variables(absolute=false)` — list variable names in this folder - `has(name)` — check if a variable or subfolder exists - Direct property access to child variables: `Dialogic.VAR.Towns.TownA.Church` Example: ```gdscript # Pick a random location from nested folder structure var location = Dialogic.VAR.Towns.folders().pick_random().variables().pick_random() ``` ### Variable Signals ```gdscript # Emitted whenever any Dialogic variable changes Dialogic.VAR.variable_changed.connect(func(info): print(info.variable, " changed to ", info.new_value)) # info = {"variable": "variable_name", "new_value": new_value} # Emitted specifically from Set Variable events (not direct code sets) Dialogic.VAR.variable_was_set.connect(func(info): print(info.variable, ": ", info.orig_value, " -> ", info.new_value)) # info = {"variable": name, "orig_value": old, "new_value": result, "value": change_value, "value_str": raw_string} ``` ## Writing Text When writing text in Text events or Choice events, you can use four complementary systems: **BBCode**, **Variables/Expressions**, **Text Modifiers**, and **Text Effects**. ### BBCode (Styling) Standard Godot BBCode tags work in Text events for visual styling. Full reference: [BBCode in RichTextLabel](https://docs.godotengine.org/en/stable/tutorials/ui/bbcode_in_richtextlabel.html). Common tags: ``` [b]Bold[/b] [i]Italic[/i] [u]Underline[/u] [s]Strikethrough[/s] [color=#ff0000]Colored text[/color] [color=red]Named color[/color] [font=res://my_font.ttf]Custom font[/font] [font_size=24]Bigger text[/font_size] [img=32x32]res://icon.png[/img] # inline image [url=https://example.com]Link[/url] # clickable link [wave amp=20 freq=2]Wave effect[/wave] # animated wave [tornado radius=10 freq=5]Tornado effect[/tornado] # animated shake [shake rate=10 level=15]Shake effect[/shake] # animated shake [fade start=4 length=20]Fade effect[/fade] # fading characters [rainbow freq=0.2 sat=1 val=1]Rainbow text[/rainbow] # color cycling ``` **Important:** BBCode tags must be properly paired with closing tags (`[/tag]`). This differs from Text Effects which have no closing tag. **Choice buttons:** Standard choice buttons use `Label` (not `RichTextLabel`), so BBCode won't work there. To get BBCode on choices, use a custom choice button with `RichTextLabel`. ### Variables / Expressions in Text Anything in `{curly braces}` inside text is treated as an expression and evaluated at display time. The result is inserted as text: ``` You have {coins} coins remaining. Hello, {PlayerName}! Current HP: {Player.Health} # folder path Next level at {MyAutoload.xp_required} XP. # autoload property Your lucky number is {MySingleton.get_random_name()} # autoload method Stat bonus: {{Player.Health}/10.0+2} # complex expression ``` - **Dialogic variables** — resolved by dotted path: `{coins}`, `{Player.Health}`, `{FolderA.FolderB.var}` - **Autoload properties** — resolved via Godot's `Expression` class: `{MySingleton.some_property}` - **Autoload methods** — called and result inserted: `{MyAutoload.get_random_name()}` - **Complex expressions** — nested braces for calculations: `{{Player.Health}/10.0+2}` **Important:** Dialogic-native variables must be pre-declared in the Variables editor. Referencing an undeclared variable evaluates to `null` and displays as an empty string. Autoload variables don't need pre-declaration. ### Text Modifiers (Preprocessors) Text modifiers run **before** the text is displayed, transforming the text string. They work in both Text events and Choice events (most of them). See the [Text Modifiers](#text-modifiers-preprocessors) section below for the full list. ### Text Effects (Reveal-Time Actions) Text effects are tags that trigger an action when the text reveal reaches that position. They are NOT paired — there is no closing tag. They work only in Text events. See the [Text Effects](#text-effects-inline-bbcode) section below for the full list. ### Text Speed System Dialogic has three interacting speed controls: | Control | Scope | Description | |---------|-------|-------------| | **Letter speed** | Per-event (via `[lspeed]`) | Time in seconds for each letter to reveal. Default set in Text Settings. | | **Speed multiplier** | Per-event (via `[speed]`) | Multiplies letter speed and pause length. 1 = normal, 0 = instant, >1 = slower. Resets each Text event. | | **Text Speed setting** | Global (via `Dialogic.Settings`) | Global multiplier; stored automatically. Use for in-game speed options: `Dialogic.Settings.text_speed = 0.5` | All three multiply together: `actual_delay = letter_speed × speed_multiplier × text_speed_setting` Adding `!` to `[lspeed=x!]` or `[pause=x!]` makes the value absolute (ignores both multipliers). ### Custom Text Modifiers Custom modules can register text modifiers by implementing `_get_text_modifiers()`: ```gdscript func _get_text_modifiers() -> Array[Dictionary]: return [ { "subsystem": "MySubsystem", # or "node_path": "/root/MyAutoload", "method": "my_modifier_method", "mode": -1 # -1 = Text & Choice, 0 = Texts only, 1 = Choices only } ] ``` The modifier method receives a `String` and must return a `String`: ```gdscript func my_modifier_method(text: String) -> String: # transform text here (regex, custom syntax, etc.) return text ``` ## 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]` / `[lspeed=x!]` | Set letter reveal time in seconds. Bare resets to default. `!` makes it absolute (ignores multipliers). | | 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 | ## Auto-Advance Auto-Advance automatically progresses the timeline without user input — ideal for hands-free reading or visual novel "auto-forward" modes. It is managed through `Dialogic.Inputs.auto_advance`, a `DialogicAutoAdvance` object. ### Enable Flags (Stackable) Auto-Advance uses three independent boolean flags — if **any** is `true`, Auto-Advance is active. This stacking allows multiple systems to enable it without conflicting: | Flag | Set via | Stays enabled until | |------|---------|---------------------| | `enabled_until_user_input` | Player toggles Auto-Advance on | Player presses any Dialogic input action | | `enabled_until_next_event` | `[aa]` text effect or code | The next text event begins | | `enabled_forced` | Code: `auto_advance.enabled_forced = true` | Code: `auto_advance.enabled_forced = false` | **Example:** If the player enables Auto-Advance AND a text event uses `[aa]`, disabling one flag won't stop it until all flags are cleared. ### Configuring from Code ```gdscript # Enable Auto-Advance (player toggle) Dialogic.Inputs.auto_advance.enabled_until_user_input = true # Enable until next event (e.g., for a cutscene) Dialogic.Inputs.auto_advance.enabled_until_next_event = true # Force enable (script-controlled) Dialogic.Inputs.auto_advance.enabled_forced = true # Check if currently enabled if Dialogic.Inputs.auto_advance.is_enabled(): print("Auto-advancing") # Check if currently waiting (timer active) if Dialogic.Inputs.auto_advance.is_advancing(): print("Timer running: ", Dialogic.Inputs.auto_advance.get_time_left()) ``` ### Delay Calculation When Auto-Advance starts, the delay is calculated from the current text: ``` delay = (per_word_delay × word_count + per_character_delay × char_count) × delay_modifier + fixed_delay ``` | Property | Default | Description | |----------|---------|-------------| | `fixed_delay` | `1.0` | Base delay in seconds added after other calculations | | `per_word_delay` | `0.0` | Extra seconds per whitespace-separated word | | `per_character_delay` | `0.1` | Extra seconds per character (excluding ignored characters) | | `delay_modifier` | `1.0` | Global multiplier on per-word + per-character delays (NOT on fixed_delay). Set via `Dialogic.Settings.autoadvance_delay_modifier` for player-configurable reading speed. | | `await_playing_voice` | `true` | If true, waits for voice audio to finish before advancing | | `ignored_characters_enabled` | `false` | If true, certain characters are excluded from the character count | | `ignored_characters` | `{}` | Dictionary of characters to ignore when counting | **Temporary override** per event via `[aa=x]`: sets `override_delay_for_current_event`, which replaces the entire calculation (but still respects `await_playing_voice`). ```gdscript # Adjust delays dynamically (e.g., for different languages) Dialogic.Inputs.auto_advance.per_word_delay = 0.3 Dialogic.Inputs.auto_advance.per_character_delay = 0.1 # Set a global reading speed modifier (player setting) Dialogic.Settings.autoadvance_delay_modifier = 0.5 # 2x faster ``` ### Text Effect: `[aa]` | Syntax | Behavior | |--------|----------| | `[aa]` | Enables Auto-Advance until the next text event | | `[aa=2]` | Enables Auto-Advance AND sets the delay to 2 seconds (absolute, ignores per_word/per_character) | | `[aa=2?]` | Does NOT enable Auto-Advance, but sets the delay to 2 seconds IF already enabled | ### Signals ```gdscript # Emitted when Auto-Advance should progress (triggers handle_next_event) Dialogic.Inputs.auto_advance.autoadvance # Emitted when Auto-Advance toggles on/off Dialogic.Inputs.auto_advance.toggled.connect(func(enabled: bool): print("Auto-Advance ", "enabled" if enabled else "disabled")) # Force an immediate advance Dialogic.Inputs.auto_advance.autoadvance.emit() ``` ### Querying Progress ```gdscript # Timer progress from 0.0 to 1.0 (returns -1 if not advancing) var progress = Dialogic.Inputs.auto_advance.get_progress() # Time remaining before the next advance var remaining = Dialogic.Inputs.auto_advance.get_time_left() ``` Useful for building an Auto-Advance indicator UI (progress bar or countdown) in your layout layer. ## Manual-Advance Manual-Advance is the default way players progress through timelines — clicking or pressing the `dialogic_default_action` input to move forward event by event. It's managed through `Dialogic.Inputs.manual_advance`, a `DialogicManualAdvance` object. ### Controlling Manual-Advance Two properties control whether the player can advance: | Property | Type | Description | |----------|------|-------------| | `system_enabled` | `bool` (default `true`) | Master switch. When `false`, player input is completely ignored. | | `disabled_until_next_event` | `bool` (default `false`) | Temporarily blocks input for the current event only. Overrides `system_enabled`. | ```gdscript # Disable all player input (e.g., during a cutscene) Dialogic.Inputs.manual_advance.system_enabled = false # Block input just until the next event (e.g., [ns] text effect uses this) Dialogic.Inputs.manual_advance.disabled_until_next_event = true # Check if the player can currently advance if Dialogic.Inputs.manual_advance.is_enabled(): print("Player can advance") # Equivalent to: system_enabled and not disabled_until_next_event ``` ### Input Events Dialogic listens for the input action configured in `dialogic/text/input_action` (default: `dialogic_default_action`). Input is handled via: - **Keyboard/controller** → `_unhandled_input()` (non-mouse events) - **Mouse** → `_input()` or `handle_node_gui_input()` (via InputCatcher/Textbox nodes) **Blocking input temporarily:** Dialogic automatically blocks input for ~100ms after text finishes revealing to prevent accidental double-clicks. Call `Dialogic.Inputs.block_input(time)` to extend this. ```gdscript # Block all input for 0.5 seconds Dialogic.Inputs.block_input(0.5) # Check if input is currently blocked if Dialogic.Inputs.is_input_blocked(): print("Input is blocked") ``` ### Input Action Signals ```gdscript # Priority signal — fires first. Set action_was_consumed = true to swallow the input Dialogic.Inputs.dialogic_action_priority.connect(func(): if some_condition: Dialogic.Inputs.action_was_consumed = true) # Main action signal — fires if action_was_consumed is still false Dialogic.Inputs.dialogic_action.connect(_on_dialogic_action) # Check if the input came from a mouse if Dialogic.Inputs.input_was_mouse_input: print("Mouse click") ``` ## Auto-Skip Auto-Skip rapidly advances through the timeline, bypassing animations, text reveal, and audio. It's useful for quickly navigating already-seen story branches or debugging. Managed through `Dialogic.Inputs.auto_skip`, a `DialogicAutoSkip` object. ### Core Settings | Property | Type | Default | Description | |----------|------|---------|-------------| | `enabled` | `bool` | `false` | Master toggle. Toggling it emits the `toggled` signal. | | `time_per_event` | `float` | `0.1` | Maximum seconds each event may take. Events themselves must respect this — it's not enforced centrally. | | `skip_voice` | `bool` | `true` | If true, voice audio is skipped instead of played. | ### Auto Disable/Enable Conditions | Property | Type | Default | Description | |----------|------|---------|-------------| | `disable_on_user_input` | `bool` | `true` | Disables Auto-Skip when the player presses any Dialogic input action. | | `disable_on_unread_text` | `bool` | `false` | Disables when reaching a text event the player hasn't seen before. Requires History subsystem. | | `enable_on_visited` | `bool` | `false` | Enables Auto-Skip when reaching a previously-visited text event. | ```gdscript # Toggle auto-skip (e.g., from a button) Dialogic.Inputs.auto_skip.enabled = !Dialogic.Inputs.auto_skip.enabled # Debug mode: skip everything including unseen text Dialogic.Inputs.auto_skip.disable_on_unread_text = false Dialogic.Inputs.auto_skip.enabled = true # Reduce time per event for faster skipping Dialogic.Inputs.auto_skip.time_per_event = 0.05 ``` ### Auto-Skip Signal ```gdscript Dialogic.Inputs.auto_skip.toggled.connect(func(is_enabled: bool): if is_enabled: print("Auto-Skip started") else: print("Auto-Skip stopped")) ``` ### Implementing Auto-Skip in Custom Events Auto-Skip behavior must be implemented per event. Two patterns: **Capping time-based operations:** ```gdscript var animation_length: float = 10.0 if Dialogic.Inputs.auto_skip.enabled: animation_length = min(Dialogic.Inputs.auto_skip.time_per_event, animation_length) ``` **Reacting to the toggle for signal-based events:** ```gdscript func _execute() -> void: if Dialogic.Inputs.auto_skip.enabled: finish() # skip instantly return # Normal execution path await some_signal finish() ``` ### Summary: Three Ways to Advance | Method | Trigger | Speed | Player Control | |--------|---------|-------|----------------| | **Manual-Advance** | Player input (`dialogic_default_action`) | Player-paced | Full | | **Auto-Advance** | Timer after text reveal | Configurable delay (per-word/character) | Player toggles on/off | | **Auto-Skip** | Timeline events auto-execute | Time-capped per event | Player toggles on/off; stops on unseen text | ## Styles & Layouts Three systems work together to display timelines: **Dialogic Nodes**, **Layout Scenes**, and **Styles**. ### Concepts - **Dialogic Nodes** (`DialogicNode_*`) — UI control nodes (DialogText, NameLabel, PortraitContainer, ChoiceButton, etc.) that are activated by Dialogic subsystems. They register themselves in groups (`dialogic_set_DialogText`, `dialogic_set_NameLabel`, etc.) so subsystems can find them. - **Layout Scenes** — Reusable scenes containing Dialogic Nodes, other Godot Control nodes, and custom scripts. They provide self-contained UI parts (e.g. `VisualNovel Textbox Scene`, `Glossary Popup Scene`, `Centered Choices Scene`). - **Base layout** extends `DialogicLayoutBase` — the root scene that holds everything. - **Layer layouts** extend `DialogicLayoutLayer` — child scenes added as layers to the base. - **Styles** (`.tres` resources of type `DialogicStyle`) — combine one base layout + list of layer layouts with overridable export settings. Styles can **inherit** from other styles (inherits scenes and default settings). ### Style Structure A `DialogicStyle` resource contains: - `name` — style identifier (used by `[style]` events, `load_style()`) - `inherits` — optional parent style for inheritance - `layer_list` — ordered array of layer IDs - `layer_info` — Dictionary mapping layer ID (empty string `""` for the base) to `DialogicStyleLayer` resources, each holding: - `scene` — `PackedScene` reference - `overrides` — Dictionary of export variable overrides applied at runtime ### Style Inheritance A style can inherit from another. The inheritance chain works as follows: - `get_layer_inherited_list()` returns the **root ancestor's** layer list (layers are always defined by the root). - `get_layer_inherited_info(id)` merges overrides walking **up** the chain — child overrides override ancestor overrides. - `get_inheritance_root()` walks the chain to the topmost style. - `realize_inheritance()` flattens inheritance into the local style, then detaches the parent. ### Style Editor (Dialogic UI) In the Dialogic Style Editor tab you can: - Add new styles (empty or from a premade template) - Rename styles - Add, replace, or remove layers (both premade and custom scenes) - Override export settings on any scene in the style - Make a premade layer or whole style **custom** (copies the scene into your project so you can edit it) - Create inherited styles - Change the default style **Important:** When customizing a premade layout, make sub-resources (fonts, images, scripts) **unique** before modifying them. Resources still inside `addons/dialogic/` may be lost when updating Dialogic. Using "Make Custom" on the layer automatically handles the root script. ### Subsystem API (`Dialogic.Styles`) ```gdscript # Load/switch styles Dialogic.Styles.load_style("StyleName") # load a fresh style (use before Dialogic.start() or select style before _ready) Dialogic.Styles.change_style("StyleName") # switch style mid-dialog, preserving current state (reloads dialog nodes) Dialogic.Styles.preload_style("StyleName") # preload scene resources in background # Query current state Dialogic.Styles.has_active_layout_node() # bool: is a layout node present in the tree? Dialogic.Styles.get_layout_node() # returns the DialogicLayoutBase root node (null if none) Dialogic.Styles.get_current_style() # returns the current style name (empty string if none) # Node lookup within the active layout Dialogic.Styles.get_first_node_in_layout("group_name") # like get_first_node_in_group() but scoped to the layout subtree # Signals Dialogic.Styles.style_changed.connect(_on_style_changed) # emits info: {"style": name, "previous": prev_name} ``` `load_style()` vs `change_style()`: - `load_style()` — use when no layout is active yet (e.g. before `Dialogic.start()`). Creates a fresh layout. - `change_style()` — use mid-dialog. It calls `load_style()` with `state_reload=true`, which reloads the current dialog state into the new layout's nodes after the new scene is ready. This preserves text, portraits, choices, etc. across the style switch. When `load_style()` detects the same scene setup (same inheritance root), it only applies new export overrides without recreating nodes — this is fast and non-disruptive. ### Changing Styles at Runtime **Via timeline event:** ```gdscript [style name="MyStyle"] ``` This calls `Dialogic.Styles.change_style()` and waits one frame for the layout to be ready before continuing. **Via code (pre-start):** ```gdscript Dialogic.Styles.load_style("MyStyle") Dialogic.start("my_timeline") ``` **Per-character style:** Assign a style to a `DialogicCharacter` resource (in the Character editor). When that character speaks, Dialogic temporarily switches to their style (non-base style), then reverts when another character speaks or the base style is restored. ### Creating Custom Layout Scenes To create a custom layout scene from scratch: 1. Create a new scene with a root node extending `DialogicLayoutBase` (for a base) or `DialogicLayoutLayer` (for a layer). 2. Export variables on the root script — they automatically appear as settings in the Style Editor. Use `@export_group` and `@export_subgroup` for organization. Not all variable types are supported. 3. Override `_apply_export_overrides()` to apply exported settings to your nodes. This method is called: - When the style is first loaded - When switching to a different style that uses the same scene (only overrides are re-applied) **Base layout** (`DialogicLayoutBase`) additionally provides: - `add_layer(layer)` — add a `DialogicLayoutLayer` as a child - `get_layers()` — get all layer children - `get_global_setting(setting, default)` — retrieve a setting that layers can share (e.g. color themes) - `_get_persistent_info()` / `_load_persistent_info(info)` — persist and restore data across style changes via `Engine` metadata **Layer layout** (`DialogicLayoutLayer`) additionally provides: - `disabled` export — if `true`, the layer is hidden and processing disabled - `apply_overrides_on_ready` export — if `true`, `_apply_export_overrides()` runs on `_ready()` (automatically set when making a layer custom) - `get_global_setting(setting, default)` — delegates to the parent base **Adding custom scenes to a style:** Use the Style Editor's **Add Layer** or **Replace Layer** buttons above the layer list. Your custom scene appears as an option. ### Creating Custom Dialogic Nodes Each Dialogic Node is a normal Control node with a script that registers itself in a group. Subsystems find their nodes via groups. Customizing a Dialogic node means: 1. Duplicate the existing node scene and modify it. 2. Ensure it still registers itself in the expected group(s). 3. In most cases, reacting to Dialogic signals (or signals of existing nodes) is easier than creating a custom node from scratch. ### Preloading for Performance To avoid lag when a style is first loaded, preload it during a loading screen: ```gdscript # Request threaded preload Dialogic.Styles.preload_style("MyStyle") # Or call on the style resource directly style_resource.prepare() ``` ### Project Settings Key settings under `dialogic/layout/`: - `default_style` — path to the default style resource - `end_behaviour` — 0=free (keep layout), 1=hide (hide on end), 2=keep layout visible - `style_list` — array of all style resource paths - `style_directory` — dictionary mapping style names → paths (built automatically) ### StyleEvent Timeline Shortcode ```gdscript [style name="StyleName"] ``` ### Key Troubleshooting - **Style not found at runtime:** Ensure the style `.tres` file is listed in `dialogic/layout/style_list` in ProjectSettings. The `style_directory` is rebuilt by `DialogicStylesUtil.update_style_directory()`. - **Changes lost on update:** After using "Make Custom", ensure sub-resources (fonts, images, shaders) are saved outside `addons/dialogic/`. Click "Make Unique" on any shared sub-resources. - **Layout not appearing:** `Dialogic.start()` calls `load_style()` automatically if no layout is active. `Dialogic.start_timeline()` does NOT — you must call `Dialogic.Styles.load_style()` first. - **Double layout nodes:** Guard with `if not Dialogic.Styles.has_active_layout_node():` before calling `load_style()`. ## 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.Text.animation_textbox_new_text # emitted when new text starts animating in Dialogic.Text.animation_textbox_show # emitted when textbox show animation starts Dialogic.Text.animation_textbox_hide # emitted when textbox hide animation starts 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. ### Global Data (Slot-Independent) For data that should persist across all save slots (e.g., settings, preferences), use `Dialogic.Settings` or `Dialogic.Save` global info: ```gdscript # Simple: store directly on Settings (auto-saved) Dialogic.Settings.text_speed = 0.5 # Explicit: store arbitrary global key-value pairs Dialogic.Save.set_global_info("player_name", "Miko") var name = Dialogic.Save.get_global_info("player_name", "Unknown") ``` ### Saving Visited Events History Dialogic's History subsystem can persist which text events the player has already seen: ```gdscript # Enable auto-saving visited history on every save Dialogic.History.save_already_visited_history_on_save = true Dialogic.History.save_already_visited_history_on_autosave = true # Manual control Dialogic.History.save_visited_history() Dialogic.History.load_visited_history() Dialogic.History.reset_already_visited_history() ``` ### Slot Management ```gdscript # Check if a slot exists if Dialogic.Save.has_slot("slot_name"): Dialogic.Save.load("slot_name") # Delete a slot Dialogic.Save.delete_slot("slot_name") # Create an empty slot Dialogic.Save.add_empty_slot("slot_name") ``` ### Manual Save/Load (Full State) For custom save pipelines, capture and restore Dialogic's entire state: ```gdscript var state: Dictionary = Dialogic.get_full_state() # ... store/retrieve state however you want ... Dialogic.load_full_state(state) ``` ## Reference Manager Dialogic's **Reference Manager** (toolbar Link button) handles unique identifiers for timelines and characters, and fixes broken references after renames. ### Unique Identifiers Each `.dtl` timeline and `.dch` character gets a **unique identifier** string (initially derived from the filename, but editable). No two resources of the same type can share an identifier. These identifiers are what you use in timeline text (`CharacterName:`, `jump TimelineName/`). In the Reference Manager's second tab, you can double-click any identifier to rename it. Renaming automatically adds an entry to the "Broken References" tab. ### Broken References When you rename a resource, timelines referencing the old identifier break. The Reference Manager's first tab helps fix this: 1. Dialogic may auto-add renames. Review them. 2. Click **Check Selected** to scan all timelines for the references. 3. Review the results, uncheck any false positives. 4. Click **Replace** to apply. > **Warning:** This is a destructive bulk operation on all affected timelines. Commit to version control first. ### Custom Replacements (Advanced) Click the **Plus button** for manual replacements: - **Pure Text** — custom string or regex. Use `(?something)` group to replace only a portion. - **Variable / Portrait / Character / Timeline** — specialized regex for each type. - Optionally **limit to a specific character** (useful for portrait renames). ## Glossary Dialogic's built-in glossary auto-highlights defined terms in dialog text and shows a tooltip with the definition on hover. ### How It Works A glossary is a `.tres` resource with an `entries` array of Dictionaries. Each entry has: - `name` — the primary term - `alternatives` — aliases that also trigger highlighting - `text` — definition shown in the tooltip - `extra` — additional data When a glossary is loaded, Dialogic scans text for matching names/alternatives and applies highlighting automatically. Variables can be used in glossary titles, texts, and extras via `{curly braces}`. ### Accessing Glossaries from Code ```gdscript # Get all glossary file paths var paths: Array = ProjectSettings.get_setting('dialogic/glossary/glossary_files', []) # Load and inspect a glossary var glossary: DialogicGlossary = load(paths[0]) for entry in glossary.entries: print(entry.name, ": ", entry.text) ``` **Note:** Properties prefixed with `_` (like `_translation_id`, `_translation_keys`) are private — use the helper methods on `DialogicGlossary` instead. ### Translation Glossary entries are translatable. Enable translation in Dialogic Settings, then use "Update CSV files". Entry names and alternatives must remain unique across translations, and may not start with `Glossary/`. ## Translation Dialogic supports translating timelines, character names, and glossary entries via CSV files. It uses Godot's built-in `TranslationServer`. ### Setup 1. In Dialogic **Settings → Translation**, enable translation. 2. Choose a **default locale** (the language you write in — used as fallback). 3. Set **translation file mode**: "Per Project" (one CSV) or "Per Timeline" (one CSV per `.dtl`). 4. Set a **translation folder** to keep CSV files organized. ### Workflow ``` 1. Write/Edit timeline → 2. "Update CSV files" → 3. Edit CSV translations ↑ ↓ └────────────────── 4. "Collect translation" ←──────────────┘ ``` **CSV format:** ```csv keys,en,ja Text/1/text,Hello World!,こんにちは世界! ``` - Column headers are locale codes (`en`, `ja`, `fr`, etc.). - Each row's key identifies the translatable unit: `Text//text`, `Character//name`, `Glossary//name`, etc. - Commas in text must be escaped by wrapping in quotes. **Important:** After editing a translated text's original, you MUST regenerate by clicking "Update CSV files" again, or translations won't apply to the changed text. ### Translation IDs in Timelines After enabling translation, Dialogic inserts `#id:` markers at the end of text events: ``` Character: Hello world! #id:14 - Yes, I do! #id:16 ``` These IDs link the text to its CSV row. They are not visible during gameplay. ### Changing Language ```gdscript # Switch to Japanese at runtime TranslationServer.set_locale("ja") ``` ### Testing in Editor Use the "Testing locale" dropdown in Settings → Translation. This is editor-only and may not work in exported builds. ## 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 # Direct property access (preferred) Dialogic.VAR.player_name = "Miko" Dialogic.VAR.coins = 42 # String-based access for dynamic names Dialogic.VAR.set_variable("player_name", "Miko") Dialogic.VAR.set_variable("coins", 42) # Reset all variables to defaults Dialogic.VAR.reset() ``` **Creating timelines in code:** ```gdscript # From strings (follow text syntax) var events: Array = """ Jowan (Surprised): Wow this is interesting! - Yes set {excitement} += 20 - No set {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.