BLSE0.6

Native plugins

Conversations from plugins

On this page 7 sections

(b0.5.48) The same functions as Python's blse.add_dialog, extend_dialog, add_dialog_reply and set_keyvalue (see the Python page for the rules: numbering, the 4-reply limit, conditions and actions, voice files, saves; journal quests are merge files in a mod folder, described there too). Check BLSE_HOST_HAS(host, add_dialog) first: they were added at the end of BLSE_HostAPI.

This page is for plugins that add dialogue: a new conversation for an NPC your mod spawns, new lines in one of the game's conversations, or one more reply the player can pick. Nothing in the game's files is replaced, and several mods can add to the same conversation. To react to conversations or hide and change lines, see Dialogue on the events page.

Rows are .dlg text, exactly as the game's files: one row per line, 13 { } columns (number, male text, female text, next line or # for an NPC line, condition, action, then seven clan columns, the last a Malkavian text). For an NPC line, column 4 is the action the game runs when the line is shown; for a reply, column 4 is its condition and 5 its action. Rows with an empty male text are skipped, as the game does. Conditions and actions are Python code (up to 255 bytes; see the game's limits on the Python page). A plugin that wants C code in a condition adds a Python function with add_python_function and calls it from the condition (blse_native.mymod_test() or mymod_test() in __main__).

Field
int add_dialog(ctx, name, rows) A conversation under a virtual name ("mymod/guard.dlg"); NPCs with that dialogname use it
int extend_dialog(ctx, file, rows) Nodes numbered from 10000 on added to a conversation ("jack_tutorial.dlg")
int add_dialog_reply(ctx, file, npc_line, row, first) One reply row (its number is ignored) offered under NPC line npc_line; first puts it first
int set_keyvalue(ctx, entity, key, value) One keyvalue of an entity, as maps set them: "dialogname" gives an NPC another conversation, "targetname" renames it

All return 1 on success and 0 for bad input; the reason is written to the log under the plugin's name. A plugin's conversations are removed when it declines to load or is disabled after a crash.

C
static const char guard_rows[] =
    "{ 1 }{ Halt. }{ Halt. }{ # }{ }{ }{ }{ }{ }{ }{ }{ }{ }\n"
    "{ 2 }{ A friend. }{ A friend. }{ 3 }{ }{ }{ }{ }{ }{ }{ }{ }{ A fiend. }\n"
    "{ 3 }{ Pass. }{ Pass. }{ # }{ pc.SetQuest(\"MyPlugin_Guard\", 2) }{ }{ }{ }{ }{ }{ }{ }{ }\n"
    "{ 4 }{ Thanks. }{ Thanks. }{ 0 }{ }{ }{ }{ }{ }{ }{ }{ }{ }\n";

if (BLSE_HOST_HAS(host, add_dialog))
    host->add_dialog(host->context, "myplugin/guard.dlg", guard_rows);

Important, before you start:

  • Main thread only. From another thread the functions return 0.
  • Additions apply from the next conversation on: the game reads an NPC's conversation anew each time one starts.
  • Saves remember the conversation's name, not its rows: an NPC given your conversation with set_keyvalue keeps that name in saves, and has nothing to say once your plugin is removed (the game logs "Could not load dialog file").
  • Your conversations go with your plugin: they are removed when it declines to load or is disabled after a crash.

Words used on this page#

Word Meaning
.dlg file The game's file format for one conversation: one row per line, each row 13 columns in { } braces
Row, line number One entry of a conversation; its number (column 1) is how other rows refer to it
NPC line A row whose column 4 is #: something the NPC says
Reply A row that follows an NPC line (by number, up to the next NPC line): something the player can answer. Its column 4 is the NPC line it leads to; 0 ends the conversation. The game shows at most 4 replies
Node An NPC line together with its replies
Condition Python code (column 5 of a reply) deciding whether the reply is shown, run with pc and npc set
Action Python code run when the reply is picked (column 6), or when an NPC line is said (column 5 of an NPC line)
dialogname The keyvalue that names an NPC's conversation file
Virtual name A conversation name that does not need a file ("mymod/guard.dlg"); your rows are used for it

The columns of a row (counted from 1 here; the paragraph near the top of the page counts from 0, so its "column 4" is column 5 below):

Column NPC line Reply
1 Its number Its number
2 Text (male player) Text (male player)
3 Text (female player) Text (female player)
4 # The NPC line it leads to, 0 ends
5 Action when said Condition
6 (unused) Action when picked
7-13 Clan texts; 13 is the Malkavian text Clan texts; 13 is the Malkavian text

The functions#

add_dialog#

C
int add_dialog(void *context, const char *name, const char *rows);

Registers a whole conversation under a virtual name. (b0.5.48)

When to use it: an NPC your mod spawns (spawn_entity with the keyvalue "dialogname"), or an existing NPC you give a new conversation (set_keyvalue).

  • name: 1-200 characters of a-z 0-9 _ . / - and space, ending in .dlg ("mymod/guard.dlg"). No file has to exist.
  • rows: the rows as .dlg text (see the columns above), for example a file your mod ships, read with read_file. Line numbers are your own; a number given twice is refused.
  • Calling it again replaces your rows. When another mod registers the same name, the later registration wins (logged).
  • Returns 1, or 0 with the reason in blse.log (plugin mymod: add_dialog: ...): a bad name, no rows, a row that cannot be read.

extend_dialog#

C
int extend_dialog(void *context, const char *file, const char *rows);

Adds whole nodes (an NPC line and its replies) to an existing conversation, the game's or a virtual one. (b0.5.48)

  • file: matched without regard to case against the end of the game's path ("jack_tutorial.dlg", "main characters/jack_tutorial.dlg", or the full path); up to 259 characters; "*" is not allowed here.
  • rows: numbered from 10000 on (the game's files end below 2000); a lower number is refused.
  • A number another mod already added: the later registration wins (logged). A number the file has keeps the file's row (logged). The same rows again from the same plugin are kept once (b0.5.49).
  • Your new nodes are reached from a reply that leads to them: add one with add_dialog_reply.
  • Returns 1, or 0 with the reason in the log.

add_dialog_reply#

C
int add_dialog_reply(void *context, const char *file, int npc_line, const char *row, int first);

Offers one more reply under an NPC line of a conversation. (b0.5.48)

When to use it: a new question for one of the game's characters, leading to nodes you added with extend_dialog.

  • file: as for extend_dialog.
  • npc_line: the number of the NPC line (1 to 999999).
  • row: exactly one row in .dlg text. Its number is ignored: BLSE numbers the reply itself (after the file's last line, below 10000). Column 4 must be a line number (# is refused: a reply leads somewhere).
  • first: 0 adds the reply only if there is room (the game shows at most 4 replies); 1 puts it first, so a game reply may drop off the end. With several mods: first replies first, then in the order they were added. The same reply again from the same plugin is offered once (b0.5.49).
  • Returns 1, or 0 with the reason in the log.
C
/* Under Jack's line 11: "Seen the guard?", leading to node 10001 added with extend_dialog. */
host->add_dialog_reply(host->context, "jack_tutorial.dlg", 11,
                       "{ 1 }{ Seen the guard? }{ Seen the guard? }{ 10001 }{ }{ }{ }{ }{ }{ }{ }{ }{ }", 0);

set_keyvalue#

C
int set_keyvalue(void *context, uint32_t entity, const char *key, const char *value);

Sets one of an entity's keyvalues as a map does. (b0.5.48)

  • entity: an index or a handle; not the world (index 0).
  • key: the keyvalue's name; value: its text, up to 255 characters. Neither may be NULL.
  • "dialogname" gives an NPC another conversation (a .dlg file or a virtual name); "targetname" renames an entity (scripts then find it by its new name). Keys an entity reads only when it spawns (its model, most settings) change nothing afterwards.
  • Returns 1 or 0. Main thread, while a level runs.
C
host->set_keyvalue(host->context, npc, "dialogname", "myplugin/guard.dlg");

Things the game decides#

  • Limits of conditions and actions: cut at 255 bytes; an action is split at every ; and & (each part runs on its own, so a ; inside a string breaks it); a condition is split at one | or &. For longer code, call a function: a Python mod's, or yours added with add_python_function.
  • Texts: braces and line breaks in texts break the format: do not use them. The game removes text between [ and ] from dialogue lines.
  • Voice: the game looks for sound/character/<name without .dlg>/line<N>_col_<c>.mp3 (or .wav, .vcd), so loose files in a mod folder can voice your lines. Without a file the text is shown.
  • Starting a conversation from code: the game's input, fire_input(context, npc, "StartPlayerDialog", "0", BLSE_NO_ENTITY).
  • Rules and events (hide_dialog_reply, set_dialog_text, dialogue filters, blse.dialog_line, blse.dialog_choice) work with added rows like with the game's; a reply added with add_dialog_reply has the number BLSE gave it.
  • Other ways: a conversation can also be a real .dlg file in a mod folder, and nodes can be added to a game file with a .dlg.append merge file; a reply under one of the game's NPC lines always needs add_dialog_reply. Journal quests are added with merge files: see Quests from mods.