_,.---._ ,----. ,-,--. ,--.--------.
,-.' - , `. .--.-. .-.-. ,-.--` , \ ,-.'- _\/==/, - , -\
/==/ , - \ /==/ -|/=/ ||==|- _.-`/==/_ ,_.'\==\.-. - ,-./
|==| - .=. , ||==| ,||=| -||==| `.-.\==\ \ `--`\==\- \
|==| : ;=: - ||==|- | =/ /==/_ , / \==\ -\ \==\_ \
|==|, '=' , ||==|, \/ - |==| .-' _\==\ ,\ |==|- |
\==\ _ - ;|==|- , /==|_ ,`-._/==/\/ _ | |==|, |
'.='. , ; -\/==/ , _ .'/==/ , /\==\ - , / /==/ -/
`--`--'' `--`--`..---' `--`-----`` `--`---' `--`--`
,-,--. _,.----. .=-.-. _ __ ,--.--------.
,-.'- _\ .' .' - \ .-.,.---. /==/_ /.-`.' ,`./==/, - , -\
/==/_ ,_.'/==/ , ,-' /==/ ` \|==|, |/==/, - \==\.-. - ,-./
\==\ \ |==|- | .|==|-, .=., |==| |==| _ .=. |`--`\==\- \
\==\ -\ |==|_ `-' \==| '=' /==|- |==| , '=',| \==\_ \
_\==\ ,\ |==| _ , |==|- , .'|==| ,|==|- '..' |==|- |
/==/\/ _ |\==\. /==|_ . ,'.|==|- |==|, | |==|, |
\==\ - , / `-.`.___.-'/==/ /\ , )==/. /==/ - | /==/ -/
`--`---' `--`-`--`--'`--`-``--`---' `--`--`
█████████ █████████ █████████ █████████ █████████ V.1.1.4 █████████
- By: Alex "Dev" Earley ([email protected])
- Created: 2/4/25
- Last Modified: 8/13/25
█████████ █████████ █████████ █████████ █████████ █████████ █████████
- Introduction
- Commands
- Markers
- Predicates
- Expressions (new)
INFO&ACTIONS- Comments & Regions
- Adding Quest Script to your game
- Changelog
- Notes
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
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
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]
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;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;
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.
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
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.
8/13/25
- Rolled Version to have version parity with QSLSP 1.1.1 > 1.1.4
- Removed
ONandWAIT. Replacementsreplaced withExpressions- Removed character index and replacements from
SAYandINFO - Removed params from inside the brackets to the right of the closing square bracket
]. This affectsSAYandIF.- Before
SAY[message %s,1,get_character] - After
SAY[message {expression}]
- Before
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
Optionsfrom theSAYcommand withReplacementsand theREPLACEMENTS_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
Functionssection with section onOptions. - Replaced the
Conditionssection withPredicates. - Rolled Version 1.0.0 > 1.0.1
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.