---
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/<Module>/event_<name>.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 | `<Option1/Option2/Option3>` | 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.
