Skip to content

Instantly share code, notes, and snippets.

@DevEarley
Last active August 13, 2025 23:45
Show Gist options
  • Select an option

  • Save DevEarley/7cce1f08b92514237aeca8ae08c9b254 to your computer and use it in GitHub Desktop.

Select an option

Save DevEarley/7cce1f08b92514237aeca8ae08c9b254 to your computer and use it in GitHub Desktop.
    _,.---._                     ,----.    ,-,--.  ,--.--------.  
  ,-.' - ,  `.   .--.-. .-.-. ,-.--` , \ ,-.'-  _\/==/,  -   , -\ 
 /==/ ,    -  \ /==/ -|/=/  ||==|-  _.-`/==/_ ,_.'\==\.-.  - ,-./ 
|==| - .=.  ,  ||==| ,||=| -||==|   `.-.\==\  \    `--`\==\- \    
|==|  : ;=:  - ||==|- | =/  /==/_ ,    / \==\ -\        \==\_ \   
|==|,  '='  ,  ||==|,  \/ - |==|    .-'  _\==\ ,\       |==|- |   
 \==\ _   -    ;|==|-   ,   /==|_  ,`-._/==/\/ _ |      |==|, |   
  '.='.  ,  ; -\/==/ , _  .'/==/ ,     /\==\ - , /      /==/ -/   
    `--`--'' `--`--`..---'  `--`-----``  `--`---'       `--`--`   
   ,-,--.    _,.----.               .=-.-.   _ __   ,--.--------. 
 ,-.'-  _\ .' .' -   \  .-.,.---.  /==/_ /.-`.' ,`./==/,  -   , -\
/==/_ ,_.'/==/  ,  ,-' /==/  `   \|==|, |/==/, -   \==\.-.  - ,-./
\==\  \   |==|-   |  .|==|-, .=., |==|  |==| _ .=. |`--`\==\- \   
 \==\ -\  |==|_   `-' \==|   '='  /==|- |==| , '=',|     \==\_ \  
 _\==\ ,\ |==|   _  , |==|- ,   .'|==| ,|==|-  '..'      |==|- |  
/==/\/ _ |\==\.       /==|_  . ,'.|==|- |==|,  |         |==|, |  
\==\ - , / `-.`.___.-'/==/  /\ ,  )==/. /==/ - |         /==/ -/  
 `--`---'             `--`-`--`--'`--`-``--`---'         `--`--`  

QUEST SCRIPT

█████████ █████████ █████████ █████████ █████████  V.1.1.4 █████████
█████████ █████████ █████████ █████████ █████████ █████████ █████████

TABLE OF CONTENTS

  1. Introduction
  2. Commands
  3. Markers
  4. Predicates
  5. Expressions (new)
  6. INFO & ACTIONS
  7. Comments & Regions
  8. Adding Quest Script to your game
  9. Changelog
  10. Notes

1. Introduction

The purpose of this document is to explain this scripting language and also provide examples of how to implement Quest Script support. If you notice, the chapter for “Adding Quest Script to your game” is last. Usually this chapter is the first in a guide like this. The reason it is last here is because it is the most complicated step and requires a full understanding of Quest Script.

Quest Script was designed to enable modding in BRODUCE. Modders embed the Quest Scripts along with their mesh data and the game runs the scripts after the mod is loaded in at runtime.

Check out QS LSP for VSCode: https://github.com/DevEarley/Quest-Script-Language

2. Commands

Consider the following Quest Script:

say[Hello World!]???
do[set player name]
do[set pilot name(JACK)]
say[Good morning {PLAYER_NAME}. "{This text is in curly brackets}"]{PILOT_NAME}
choice[THANKS, GOOD MORNING]
[THANKS]
say[Thank you!]Jack
go[END]
[GOOD MORNING]
[Next#1]
say[Good Morning!]Jack
do[wait(1)]
go[END]
[END]
do[show game over]

A Quest script consists of Expressions, Predicates, Commands and Markers. Each line is either a marker or a command. Anything else is treated like a comment and ignored. Commands start with a command name and have their parameters wrapped in square brackets. Markers are wrapped in square brackets and have no command name. They are used almost like function names.

COMMAND[MESSAGE with {EXPRESSIONS}] OPTIONAL also works with {EXPRESSIONS}

do[function name with spaces(param 1,param 2,{EXPRESSIONS work here too})]
IF[FUNCTION CALL]MARKER#2
[MARKER#1]
SAY[#1]
GO[END]
[MARKER#2]
SAY[#2]
GO[END]
[END]

IF[PREDICATE({EXPRESSION})]{EXPRESSION} MARKER#3

[{EXPRESSION} MARKER#3]

Quest Script has the following commands:

  • SAY [MESSAGE] SPEAKER
  • INFO [MESSAGE]
  • DO [FUNCTION NAME(PARAM, LIST)]
  • GO [MARKER]
  • CHOICES [MARKER_1, MARKER_2, MARKER_3, MARKER_4]
  • ACTIONS [MARKER_1, MARKER_2, MARKER_3, MARKER_4]
  • IF [PREDICATE] MARKER

NOTE:

	CHOICE is an alias for CHOICES 
	ACTION is an alias for ACTIONS 

3. Markers

Markers are wrapped in square brackets and have no command name. They are used almost like function names. As a best practice, wrap markers in quotes if you use commas in marker names.

[Hello there.]
[Yes#1]
[Yes#2]
[Yes#Only on tuesdays]
["No, I have not seen them. This is a curly brace }"]
[#4) All of the above#45]
[#4) All of the above#46]

You can make repeat markers identifiable with a # followed by some identifier. These are only used by the scripting engine to identify markers. The text following the # should not be displayed to the end-user. If you need to use a hash symbol in your choice name, you may. The script engine should only ignore the last hash in the marker name.

say["Are you feeling alright?"] Alex
choice[Yes#Second time saying yes,No#2]
...
[Yes#First time saying yes]
...
[Yes#Second time saying yes]
...
[No#1]
...
[No#2]

4. Predicates

Predicates are used inside the IF command. The predicate is a function that always returns a boolean value. Quest Script is only designed to control program flow. The predicates' logic should be in your game's code. Not in the Quest Script.

	IF[func_call(true)] MARKER_NAME

or
	IF[The player has the key] Unlock Door#1

Predicates are any string. In your game logic, set up a match(switch) to handle each predicate that is defined in quest script.

func process_predicate_for_qs(predicate_string):
	var split_predicate = predicate_string.split("(");
	var predicate_params = split_predicate[1].trim_end(")").split(",");
	match(split_predicate[0]):
		"func_call":
			return MY_GLOBAL_AUTOLOAD.func_call(predicate_params[0]);
		"The player has the key":
			return GLOBAL_INV.has_key();
	return false;

5. Expressions

Expressions can be used with any command. The Expressions are defined in your code. When using the SAY command, you pass the Expression function nameby wrapping it in curly brackets {expression} . This function name should defined be in your code and should return a string. You may pass params to an expression function.

say[Hello world!!]
say[{get hello world text}]
say[{get thing to say(hello,world,!,!)}]
Say[Hello {player name}! Wonderful day we are having. Have you seen {pilot}?]Jake

and then in your code...

func process_expressions_for_qs(expression:String):
	if(expression.contains("{") == false):return expression	
	if(expression.contains("{player name}") == true):
		expression = expression.replacen("{player name}",STATE.PLAYER_NAME)
	if(expression.contains("{pilot}") == true):
		expression = expression.replacen("{pilot}",STATE.CURRENT_PILOT)	
	return expression;
	

6. INFO and ACTIONS

The INFO command is similar to SAY. Instead of Speaker Name, there is a Title. Expressions work exactly the same as they do in the SAY command. The INFO box is rendered in a different frame as the SAY command and is not associated with any speaker.

The ACTIONS command is similar to the CHOICES command. like the INFO box, it behaves like the CHOICES command but is rendered in a different frame. It is not associated with a character.

7. Comments & Regions

Comments are made by starting a line with # or //. These will be printed to the console and skipped over. The QS LSP support region folding with #region and #endregion.

# Start of my script

// More Comments

#region start

#endregion

8. Adding Quest Script to your game

Here is a very simple template for a Quest Script implementation :

extends Node

func run_script(script_contents:String):
	pass;
	
func run_script__say(message:String,speaker_name:String):
	pass;

func run_script__info(message:String):
	pass;
	
func run_script__do(func_name_and_value:String):
	pass;
	
func run_script__go(marker_name:String):
	pass;
	
func run_script__choice(choices:Array[String]):
	pass;

func run_script__actions(actions:Array[String]):
	pass;
	
func run_script__if(predicate:String, marker:String):
	pass;

I highly encourage making a second script (or more) for the logic. Limit the script above to simply matching the script-line to the functionality.

9. CHANGELOG

8/13/25

  • Rolled Version to have version parity with QSLSP 1.1.1 > 1.1.4
  • Removed ON and WAIT.
  • Replacements replaced with Expressions
  • Removed character index and replacements from SAY and INFO
  • Removed params from inside the brackets to the right of the closing square bracket ]. This affects SAY and IF.
    • Before SAY[message %s,1,get_character]
    • After SAY[message {expression}]

2/26/25

  • Rolled Version 1.1.0 > 1.1.1
  • Fixed Info Command definition

2/25/25

  • Rolled Version 1.0.2 > 1.1.0
  • Added feature: INFO command
  • Added feature: ACTIONS command
  • Added alias CHOICES for CHOICE
  • Added some info on predicates

2/19/25

  • Rolled Version 1.0.1 > 1.0.2
  • Replaced the Options from the SAY command with Replacements and the REPLACEMENTS_FUNCTION
  • Added some more Marker examples.
  • Updated template.

2/17/25

  • Renamed "WAIT_FOR_SIGNAL[SIGNAL_NAME]" to "ON[SIGNAL_NAME]"
  • Added identifier to markers with the # symbol
  • Removed Buttons. On[] should suffice.
  • Replaced Functions section with section on Options.
  • Replaced the Conditions section with Predicates.
  • Rolled Version 1.0.0 > 1.0.1

10. Notes

8/13/25 I recently rolled out QS LSP for VS Code. Download it here: https://github.com/DevEarley/Quest-Script-Language. I am going to roll the version so it has parity with the LSP. I recently made a game called Mecha MGMT and I heavily rely on QS.

I learned a few lessons on this project. One lesson is that there are a few commands in QS I never want to use. I also learned that I need to add a better way to execute expressions. I am removing WAIT and ON in favor DO. The whole point of QS is to be very easy to use and easy to remember. You can achieve anything you want with the DO command. I have my sights on removing INFO and ACTIONS next - but for now I will leave them alone. They work well in BRODUCE, but I really didn't need them in Mecha MGMT.

Another thing I added in this update are comments and regions. The mission data file is growing all the time. I really don't want to break it into multiple files. So this will help me organize things.

2/25/25 I added "INFO & ACTIONS". These will provide similar functionality to SAY and CHOICES, but will be formatted differently in-game.

It was almost tempting to add a "style" option to SAY and CHOICES but I felt like that would add another step, add friction. I don't mind going wide instead of deep. I think deep is good for normal programming languages, but this is not that. Being a "high-level" scripting language, I prefer to make the script as clear and easy to write as possible. Rolling to 1.1.0 because I am introducing new features. On that topic, here is my logic for the versioning:

X.0.0 - Major (stable) release. Something using QS was shipped. Conceptual changes. 
0.X.0 - New or removed Commands.
0.0.X - Bug fixes or tweaks to existing Commands.

2/19/25 NOTE: I am currently implementing this into my own GODOT project. Many more updates to come in the near future! When I feel it is stable, I will roll the version to 2.0.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment