dialogue

package module
v0.0.0-...-fe64702 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 18, 2025 License: MIT Imports: 10 Imported by: 0

README

Dialogue Engine

A VM-based approach to writing game dialogue.

What Is It?

The dialogue engine consists of two parts: a script compiler and a script VM.

The compiler allows you to write dialogue for games in natural, human-readable markdown. It also allows you to drop down into a (very) simple C-like language to handle computations and to interface with custom callbacks you can register to hook with game code.

Speaking of game code, the VM allows you to execute the scripts as small programs attached to your entities, as a kind of AI for speaking characters.

Why?

Simple dialogue is fairly simple to program, but in modern games with lots of story and deeper characters and many choices, it gets suprisingly tricky to program on your own. Add to that the async nature of video games with the herky-jerky stop-start of computing a little, stopping, letting stuff render, come back to incrementally advance things a little more, etc. It gets difficult to reason about.

Luckily there's a programming element which solves this problem: coroutines. Unfortunately, coroutines - especially ones suited for game AI - aren't available in most languages. But VMs can easily function as coroutines, executing code, pausing for a while, and resuming execution, picking back up where it left off. So this package provides one.

Script Format

The script format is 100% valid Markdown (though not all Markdown is valid script!), with some mechanisms to drop down into a programming language.

A script is a collection of dialogue nodes. These nodes are the different sequences of dialogue to be displayed to the user. A script looks like this:

// the first section is the frontmatter. This contains type definitions for external functions.
// These are defined as follows:
extern func1(bool, number, string, null);
extern func2();

// end frontmatter with three backticks.
```

# node1

Here's some text that's going to be displayed to the user.
Just like in HTML, extra whitespace including newlines are
reduced down to render as a single space.

Blocks of text separated by an empty line are displayed to
the user as separate chunks of text, so the user will see
the above paragraph, then this one.

[Markdown links instruct the engine to transition to another node](node2)

# node2

Nodes can be transitioned by links and by giving an option.
Links don't give an option - they just display some text and
the transition.

The following shows what an option looks like. In markdown terms,
it is just an unordered list of links.

- [This goes to node1] (node1)
- [This repeats node2] (node2)
- [An empty line will terminate the list, as it does paragraphs and links] (node3)

# node3

Paragraph text can have inline code interleaved with the plain text.
This is useful to print out the value of a variable like so: `variable1`.
Just use the markdown inline code element. It can also have simple expressions
like so: `3 + 3`

```
// You can also have code blocks.
// These are Markdown fenced code blocks.
// They are a block-level element like a paragraph, link, or option list.
// The language in the code blocks is a simple C-like language.
// Comments are like C single-line comments
// You can assign variables

variable1 = 32 * variable2;

// If statements  and if-else statements are in there as well
// Unlike C, parens are optional
if variable1 > 12 {
  // You can call functions that the vm exposes
  function1(variable1 == variable2);
}

// There's loops, again with optional parens
while variable1 > 12 {
  // preincrement and predecrement are expressions and don't reassign the referenced variable
  variable1 = --variable1
}

// you can transition to other nodes
goto node4;

// you can call the external functions
func1(false, 5, "abc", null);

```

# node4

The first node in the script is the dialogue's starting point.
Ending a node without a link or choice will terminate the script.

Todo List

  • unit test the parser
  • unit test the parse tree -> ast translation
  • unit test type checking
  • implement constant folding
  • unit test constant folding
  • implement test dead node elimination
  • unit test dead node elimination
  • 100% unit test coverage everywhere (internal + user-facing)
  • add if with elided else code generation
  • add naked if with reverse logic when consequent is empty
  • add unary minus operator
  • add != operator
  • add modulo operator
  • make infinite loop AST node when while(true){...}
  • add extern func declarations
  • add optional front matter code block
  • implement parent package/public interface
  • implement compiler CLI
  • implement vm cli
  • make link text support inline expressions
  • implement asm deflate/inflate for binary format
  • implement more traditional assembler
  • variable namespaces / differentiating extern and in-script variables
  • add typed variable declarations
  • add builtin function calls (EndDialog, PushOption, ShowOption, ShowLine, EnterNode, ExitNode)
  • user-defined functions
  • relative offset for if/else generated branches
  • basic block IR with accompanying optimizations
  • nail down public API
  • compiler/assembler/linker toolchain
  • exit nodes after choices are made, not before (requires a dynamic global jump)
  • make the language actually C? Lisp?

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Compile

func Compile(options ...CompileArg) (*program.Program, error)

Compile tranlates a Script from text into its executable form. Any reads errors, syntax errors, semantic errors, or write errors will result in an error being returned.

func NoCodeCompression

func NoCodeCompression(ca *CompileArgs)

func NoCodeFolding

func NoCodeFolding(ca *CompileArgs)

func NoDeadCodeElimination

func NoDeadCodeElimination(ca *CompileArgs)

Types

type CompileArg

type CompileArg func(*CompileArgs)

func ASTOutput

func ASTOutput(w io.Writer) CompileArg

func AsmOutput

func AsmOutput(w io.Writer) CompileArg

func CompilerInput

func CompilerInput(r io.Reader) CompileArg

func CompilerOutput

func CompilerOutput(w io.Writer) CompileArg

func ErrOutput

func ErrOutput(w io.Writer) CompileArg

func LexOutput

func LexOutput(w io.Writer) CompileArg

func ParseOutput

func ParseOutput(w io.Writer) CompileArg

func PostProcessOutput

func PostProcessOutput(w io.Writer) CompileArg

func TextOutput

func TextOutput(w io.Writer) CompileArg

type CompileArgs

type CompileArgs struct {
	// contains filtered or unexported fields
}

type ExecutionType

type ExecutionType int

ExecutionType tells the VM whether to suspend ot keep running after a callback is executed.

const (
	// PauseExecution tells the VM to suspend
	PauseExecution ExecutionType = iota
	// ContinueExecution tells the VM to keep running
	ContinueExecution
	// WaitingForChoiceExecution tells the VM we haven't made a choice yet
	WaitingForChoiceExecution
)

type Function

type Function struct {
	// Func is the actual code which handles the events
	Func func(vm *VM, args ...asm.Value) ExecutionType
}

Function is a callback for custom events fired with the Call instruction.

type NewVMOption

type NewVMOption func(*VM) error

NewVMOption is a builder-like function for instantiating a new VM

func HandleEndDialogue

func HandleEndDialogue(handler func(*VM)) NewVMOption

HandleEndDialogue assigns a handler for the EndDialogue event. Pass as an option to NewVM.

func HandleEnterNode

func HandleEnterNode(handler func(*VM, string) ExecutionType) NewVMOption

HandleEnterNode assigns a handler for the EnterNode event. Pass as an option to NewVM.

func HandleExitNode

func HandleExitNode(handler func(*VM, string) ExecutionType) NewVMOption

HandleExitNode assigns a handler for the ExitNode event. Pass as an option to NewVM.

func HandleShowChoice

func HandleShowChoice(handler func(*VM, []string) ExecutionType) NewVMOption

HandleShowChoice assigns a handler for the ShowChoice event. Pass as an option to NewVM.

func HandleShowLine

func HandleShowLine(handler func(*VM, string) ExecutionType) NewVMOption

HandleShowLine assigns a handler for the ShowLine event. Pass as an option to NewVM.

func RegisterCallback

func RegisterCallback(function Function) NewVMOption

RegisterCallback assigns a handler for a custom event which can be fired with the Call instruction.

type VM

type VM struct {
	// contains filtered or unexported fields
}

VM is the virtual machine which runs the instructions generated by the dialogue tree.

func NewVM

func NewVM(program *program.Program, options ...NewVMOption) (*VM, error)

NewVM instantiates a new VM.

func (*VM) Choose

func (vm *VM) Choose(selectedChoice int) error

ChooseAndResume will notify the VM that an option was selected and resumes from the decision point according to the option chosen. Which options are available are given by the ShowChoice event, which is fired at the decision point and puts the VM into a waiting state until an option is chosen.

func (*VM) GetVariable

func (vm *VM) GetVariable(name string) (val asm.Value, exists bool)

GetVariable retrieves a named variable saved by the StoreVariable instruction or by the SetVarableT() series of methods. If the variable does not exist, val is Null and exists is false.

func (*VM) Reset

func (vm *VM) Reset()

Reset stops a VM and clears its variables so that the next time it runs, it will be as if running for the first time.

func (*VM) Resume

func (vm *VM) Resume() error

Resume continues the execution of a paused VM from the point it was paused.

func (*VM) Run

func (vm *VM) Run() error

Run executes the program from its start point. Assigned variables are persisted across runs.

func (*VM) SetVariableBoolean

func (vm *VM) SetVariableBoolean(name string, val bool)

SetVariableBoolean stores val as a boolean under the given name. Name strings are converted into symbol values. Saved variables are persisted across runs.

func (*VM) SetVariableNull

func (vm *VM) SetVariableNull(name string)

SetVariableNull stores a null value under the given name. Name strings are converted into symbol values. Saved variables are persisted across runs.

func (*VM) SetVariableNumber

func (vm *VM) SetVariableNumber(name string, val int)

SetVariableNumber stores val as a number under the given name. Name strings are converted into symbol values. Saved variables are persisted across runs.

func (*VM) SetVariableString

func (vm *VM) SetVariableString(name string, val string)

SetVariableString stores val as a string under the given name. Name strings are converted into symbol values. Saved variables are persisted across runs.

Directories

Path Synopsis
cmd
debug command
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL