Copyright © 2025-2026 Candy ArtsHook Functions
Hook functions are functions Candy Dialogue Engine calls at specific steps of Dialogue processing, where your own code runs. They ship empty: write code in the ones you need, and leave the others as they are.
There are two kinds:
- Global hook functions are called by every Engine. Each is a script in Candy_DE/Scripts/User_Data/Functions, listed in the Wizard's Hook Functions tool, with the hook_arguments table: click a name to open its script. Choose the tool in the drop-down at the top of the Wizard's panel.
- UI Element hook functions are written in the script of a UI Element or media player, and only called for that one.
func x_dialogue_end(candy_args: Dictionary, user_args: Array, custom_data: Array):
var engine = candy_args["caller"]
print("A Dialogue ended in ", engine.name)- candy_args: Candy Dialogue Engine's own values, by name. "caller" is always the Engine. The other keys depend on the hook: see the tables below.
- user_args: your own values, which you list in the hook_arguments table (see below).
- custom_data: the Custom Data of the Line the hook is called for: the numbered values every Line can carry in Candy Dialogue Creator. It contains one Array of values per level, such as
[[line values]]or[[line values], [variation values]], and is empty for a hook that isn't called for a Line. See UI, under Hook Functions.
A hook function can use
await: the Engine waits for it to return before it carries on. A hook that runs long therefore delays the Dialogue.
Five hooks are the exception, because nothing can wait for them: x_flag_changed(), x_dialogue_suspended(), x_dialogue_resumed(), x_write_progress() and x_playback_finished(). The Engine calls them and carries on at once.
Global Hook Functions
These are called at the main steps of a Dialogue:| Function | Called | candy_args |
| x_dialogue_start() | When a Dialogue starts, right before its first Line runs. change_game_state() has run and the Engine is shown. Not called by a Jump, which carries on the same Dialogue. | "caller", "conversation", "block", "line": where the Dialogue starts, as passed to start_dialogue(). |
| x_new_line() | When a new Line starts, before its Command runs. Not called for Line Marks and Comments, nor for the Commands run inside another one (by an If, a choice...). | "caller", "line_data": the whole Line, as written in the Dialogue. |
| x_command_start() | Right after, before each Command of the Line runs. Not called for Spoken Lines, which have the three hooks below. | "caller", "line_data", "command": the name of the Command that starts, as the Line writes it, "command_value": that Command's keys. |
| x_new_speech_start() | When a Spoken Line starts, before anything is decided: the Line may still be skipped. | "caller", "line_data" |
| x_new_speech_display() | When a Spoken Line is about to be displayed: its Variation is picked and its speaker is known. Not called for a Line that is skipped (see Dispositions and Variations). | "caller", "line_data", "speaker": the Actor speaking (a Role is replaced with its Actor), "variation": the name of the chosen Variation, "text": its text, with text variables and word substitutions applied. An LLM reply only arrives later. |
| x_speech_advance() | When a Spoken Line is advanced, by the player or by auto-advance, once the UI Element that displayed it has finished with the Line. Not called for a Line that is skipped, nor when the Dialogue is stopped while the Line waits. | "caller", "line_data", "speech": the CandySpeech object of the Line, as it was displayed. It contains the speaker (speaker_ref), the final text (text), the Variation (chosen_variation), the Dialogue Mode (style) and the Line's settings (settings). |
| x_choice_selected() | When a choice is selected, by the player or by a timer, before it switches category and before its Finish transition runs (see Choice List). Not called for an inactive choice. | "caller", "menu": the menu node, "category" and "choice": their names, "button": the button node, "choice_data": the choice's entry in the Choice List's data. |
| x_jump(), x_bridge() | When a Jump or a Bridge command runs, once its target is found, before the Dialogue leaves the current Block. | "caller", "line_data", "conversation", "block", "line": where the Command is, "new_conversation", "new_block", "new_line": its destination. |
| x_return() | When a Return command runs, before the Dialogue leaves the current Block. Not called when a bridged Block reaches its last Line and returns on its own. | "caller", "line_data", "conversation", "block", "line", "levels": the Command's Levels, as a number. |
| x_end() | When an End command runs, before the Dialogue ends. A Dialogue can end in other ways, and x_dialogue_end() is called for all of them. | "caller", "line_data", "conversation", "block", "line" |
| x_flag_changed() | When a Flag command changes a flag (see Flags). | "caller", "flag", "value" |
| x_dialogue_suspended() | When a Dialogue is suspended, once the Engine has paused everything it runs itself (see Suspending Dialogues). | "caller" |
| x_dialogue_resumed() | When a suspended Dialogue is resumed, right before its Lines carry on. | "caller", "ending": true when the suspension is only lifted because the Dialogue is ending or being interrupted. |
| x_dialogue_end() | When a Dialogue ends, at the end of the cleanup (see Ending Dialogues). | "caller" |
x_jump(), x_bridge(), x_return() and x_end() are also called for the transitions a choice menu or an If runs. Such a transition isn't a Line of the Dialogue. "line_data" is then the Line the Engine built for it, and "line" is the index of the Line holding it (the Choice_List or the If). A choice menu's transition has no Custom Data, so custom_data is
[[]].
Each of these has a job of its own, explained on its own page:
| Function | Job |
| change_game_state() | Switches your game to its dialogue state when a Dialogue starts, and back when it ends. See Game State. |
| tts() | Generates the voice of a line with text-to-speech. See TTS. |
| llm_query() | Asks an LLM for a line's text. See LLM. |
| _on_lexicon_clicked() | Decides what happens when a Lexicon keyword is clicked. See Lexicon. |
| _on_lexicon_hovered() | Decides what happens when the mouse enters or leaves a clickable Lexicon keyword. See Lexicon. |
| Function | Called |
| x_write_begun() | When the UI Element starts writing a line's text. |
| x_write_progress() | While the typewriter writes, each time characters appear (see Speech Display). |
| x_write_finished() | When the text is fully written. |
| x_speech_advance() | When the line is advanced, by the player or by auto-advance. |
| x_playback_starting() | When a media player, a voice player, a background layer or a bust is about to play something. |
| x_playback_started() | Right after it started playing. |
| x_playback_finished() | When a media player's playback is over, because the media reached its end or was stopped. Only media players have it (see Media Players). |
| x_initialization_finished() | When a choice menu is built and ready. |
| x_menu_closing() | When a selected choice is about to close a choice menu, while the menu is still shown (see Menu UI). |
var hook_arguments = {
"x_new_line": [
"my literal value", # user_args[0]
"¢globals.player_hp", # user_args[1]
],
}- An entry is passed as written, unless it starts with the CandyRef symbol (default ¢): it's then evaluated each time the hook is called, so the hook always receives the current value.
- A hook with no entry receives an empty Array.
- UI Element hook functions read the same table, by the hook's name.
CandyFunction is loaded when the game starts, sub-folders included. Each function it declares is then known by its own name: x_new_line() is found because a script declares a function of that name, whatever the file is called.
- A script can declare several functions, which groups related ones in one file.
- Several scripts can declare the same hook function: all of them are called, in the order of their file names. The code of separate features can therefore stay in separate files. For a hook that returns a value (tts(), llm_query()), only the first one's value counts, and a warning is printed.
- The scripts are nodes, children of the candy_de autoload: get_tree(), get_node(), timers and signals all work in them.
CandyFunction, and declare your function in it with whatever arguments it needs:
extends CandyFunction
func give_reward(amount: int) -> void:
globals.gold += amountawait candy_de.run_user_function("give_reward", [50])