XScript Compiler
A modern scripting language and compiler for X3 Farnham’s Legacy — write cleaner scripts, catch errors before they reach the game.
⬇ Download Quick StartOverview
XScript is a high-level scripting language that compiles to the native XML format used by X3 Farnham’s Legacy. Rather than working directly in the game’s built-in editor with its limitations, you write scripts in a clean, readable syntax and let the compiler produce correct, optimised XML output.
XScript adds a number of capabilities that aren’t possible in standard X3 scripts:
Object types are tracked through assignments. Calling a ship method on a sector variable produces a warning.
Use function return values directly as arguments or chain with -> without intermediate variables.
$x += 1, ++$count, $x *= 2 — all translated automatically.
#ifdef, #define, #include — conditional compilation and file inclusion.
while($i++), while(arraySize($a) < 10) — increment and function calls in conditions re-evaluated correctly each iteration.
Variables assigned inside a gosub sub are visible to the calling code via a pre-pass.
function test($arg) { return($x); } — local-scoped, with full variable isolation. Call like any built-in function.
sub myHelper() { ... } — readable wrapper for the native label/gosub/endsub pattern, sharing the global variable scope.
Convert existing compiled XML scripts back to readable XScript source — now outputs v0.8 syntax with function main() and sub blocks.
Utils::random(...) — group related functions and constants for cleaner code and better autocomplete.
foreach($item, $array) { ... } — X3 has no native for/foreach; the compiler expands macros to while loops at compile time.
Multiple function signatures share one name — the compiler picks the right one by argument count and type.
IntelliSense, syntax highlighting, hover docs, and one-click compile from the editor.
Download
| File | Description |
|---|---|
XScriptCompiler-0.8.zip | Compiler executable, data builder, and decompiler |
x3fl.xml | XScript definition file — function database, constants, datatypes, namespaces, and macros |
default_data.dat | Pre-built binary data file (requires the game’s Data\ folder to regenerate) |
xscript-x3fl-extension.zip | VS Code extension v1.4.0 — syntax highlighting, IntelliSense, and compiler integration |
Quick Start
1. Set up the folder
Place the compiler and data file in a working folder. If you need to regenerate the data file from scratch, your Data\ folder from the game installation must be present:
XScriptCompiler.exe
default_data.dat
x3fl.xml
Data\
0001-L044.xml
TShips.txt
TDocks.txt
TMissiles.txt
... (other game data files)
2. Write a script
Create a .xs file. Every v0.8 script wraps its body in a function main(...) block:
#DESCRIPTION “My Plugin Script”
#VERSION 1
function main(VARSECTOR $sector)
{
$message = “Hello from XScript”;
incomingMessage($message, PlayerLog::Alert, TRUE);
return(null);
}
3. Compile
The output myscript.xml can be placed directly in your mod’s script folder.
4. Rebuild the data file (optional)
Only needed when x3fl.xml has been updated:
Command Reference
| Command | Purpose |
|---|---|
--load_data <file> |
Load the compiled data file (default_data.dat or x3fl.dat). Required for compile and decompile. |
--compile <file> |
Compile an XScript .xs source file to XML. |
--out <file> |
Output file path. Required for all commands. |
--define:NAME |
Pre-define a symbol for use with #ifdef. Multiple --define: flags are supported. |
--builddata <file> |
Build the binary data file from x3fl.xml and the game’s Data\ folder. |
--decompile <file> |
Decompile a compiled XML script back to XScript source. |
--usenamespace |
When decompiling, emit namespaced calls (e.g. Utils::random) instead of the plain function name, where a namespace mapping exists. |
Language Overview
All statements end with a semicolon. Variables begin with $ and may contain letters, numbers, underscores, and periods. X3 only supports integer values — no floating point.
Script Structure v0.8
Every v0.8 script must wrap its body in a function main(...) block. Optional metadata directives and user-defined functions/subs appear outside it:
#VERSION 1
// Optional: user-defined functions and subs
function DATATYPE_INT clamp(NUMBER $v, NUMBER $lo, NUMBER $hi)
{
if ($v < $lo) { return($lo); }
if ($v > $hi) { return($hi); }
return($v);
}
sub initialise()
{
$count = 0;
}
// Required: main function
function main(VARSECTOR $sector)
{
gosub initialise;
$count = clamp($count, 0, 100);
return(null);
}
User-Defined Functions v0.8
Define your own reusable functions with full variable isolation. All $variables inside a user function are private — the compiler mangles them automatically so they never collide with variables elsewhere in the script.
function DATATYPE_INT getSectorCount(VARSECTOR $sector)
{
if ($sector == NULL) { return(0); }
$count = $sector->shipCount(TRUE);
return($count);
}
// Untyped parameters default to VALUE (accepts anything)
function getDoubled($value)
{
return($value * 2);
}
// Calling user functions
$ships = getSectorCount($sector); // with return value
getDoubled($x); // without
User functions can appear before or after main — forward declarations work automatically via a prepass scan. Recursive calls are allowed but produce a warning.
Sub Blocks v0.8
Subs are the X3-native label:/endsub; pattern in a more readable form. Unlike user functions, subs share the global variable scope — variables assigned inside a sub are visible everywhere.
{
$count = 0;
$flag = TRUE;
}
function main(VARSECTOR $sector)
{
gosub initialise;
$count += 1; // $count is accessible here
return(null);
}
Use endsub; inside a sub body to exit early. The classic label:/endsub; inline syntax also remains valid.
| User function | Sub | |
|---|---|---|
| Variable scope | Private (isolated) | Shared with global |
| Arguments | Declared in (...) | None |
| Return value | Yes, via return($x) | No |
| Call syntax | $r = test($arg); | gosub test; |
Variables and Assignment
$my.variable = “hello”;
$ship = PLAYERSHIP;
$array[0] = 42;
Object Methods and Properties
Use -> to call methods or access properties on an object:
$ship->setCommand(null);
$name = $ship->name; // getter
$ship->name = “My Freighter”; // setter
Nested Function Calls XScript only
Standard X3 scripts require every return value to be assigned to a variable first. XScript allows direct nesting:
$name = $sector->name;
Conditions and While Loops
else if ($value > 50) { … }
else { … }
while ($count < 10)
{
if ($count == 5) continue;
$count += 1;
}
While Loop Expressions v0.6
inc/dec and ++/-- operators can appear directly in a while condition. Function calls in conditions are also re-evaluated correctly on each iteration:
while (arraySize($myArray) < 10) { … }
Constants
$page = TextPage::MiscVoice;
$race = Xenon;
$ship = PLAYERSHIP; $null = NULL;
Labels, Goto and Gosub
Plain labels and jump instructions are still available alongside the new sub block syntax:
$result = $workVar;
goto cleanup;
cleanup:
$count = 0;
doWork:
$workVar = random(10);
endsub;
gosub target before compiling the calling code, so variables assigned inside a sub are correctly typed in the code that follows the gosub. This is handled automatically.
Compound Assignment and Increment
++$count; $count++; —$count; $count—;
// Post-increment in array subscript
$array[$i++] = 10; // uses $i as index, then increments
Chained Assignment v0.7
$x = $y = random(2);
Namespaces v0.7
$flag = RaceFlag::NPC; // namespaced constant
Function Overloads v0.7
$x = random(5, 10); // random(min, max) — 2 arguments, same name
Preprocessor v0.7
Preprocessor directives begin with # and are processed before compilation. They control which code is compiled, set script metadata, allow file inclusion, and provide type hints.
Script Metadata
Metadata directives appear before function main(...):
#VERSION 42
#COMMAND 1234
Datatype Hints v0.7
#datatype $obj DATATYPE_SHIP|DATATYPE_STATION
Defines
#define ADD(a, b) a + b
#define DEBUG // presence-only define for #ifdef
#undef DEBUG
$limit = MAX_COUNT;
$sum = ADD($x, $y);
Multi-line defines v0.7 — a trailing \ continues the define onto the next line.
Conditional Compilation
$platform = 1;
#elseif PLATFORM_LINUX
$platform = 2;
#else
$platform = 0;
#endif
#ifndef DEBUG
$logLevel = 0;
#endif
Comparison operators in #ifdef: ==, !=, >, <, >=, <=. Symbols can also be pre-defined on the command line:
Include Files
#include “common/helpers.xs”
Function Macros v0.7
X3 has no native for/foreach — only while. XScript provides language-level macros that expand to native while loops at compile time:
{
incomingMessage($item->name, PlayerLog::Alert, TRUE);
}
Additional macros can be defined in x3fl.xml under <Macros>.
Error Output
Errors and warnings are printed to stdout with the exact file, line, and column, plus the source line and a caret pointing to the problem:
$result = createSheep(RACE.Argon, $sector, 100)
^
Compile Warning [#2]: [myscript.xs:7:0] – Object ‘$ship’ datatype mismatch
$result = $ship->getSectorName()
^
Errors prevent the output file from being written. Warnings are informational — the script still compiles.
Mod Support
Custom Commands
Third-party mods can add ship commands that aren’t in the base data file. XScript supports these via a prefix notation defined in x3fl.xml:
Rebuilding the Data File
VS Code Integration
A Visual Studio Code extension is included (xscript-x3fl-extension.zip) providing full editor support for .xs files.
Keywords (function, sub, endsub, void), function/sub definition headers, return types, variables, operators, strings, and all preprocessor directives.
function main, function, sub, if, while — structural templates with tab stops that appear in autocomplete.
Autocomplete for all ~2,500 functions, methods, properties, constants, namespaces (Utils::), and function macros (foreach).
Hover over any function, constant, or namespace member to see its description, parameters, and return type.
Compiler errors and warnings appear as underlines directly in the editor and in the Problems panel.
Click the play button in the editor title bar, press Ctrl+Shift+B, or right-click for compile options. Optional compile-on-save.
Installing the Extension
- Extract
xscript-x3fl-extension.zip - Open Visual Studio Code
- Press Ctrl+Shift+X to open the Extensions panel
- Click the … menu (top right) and choose Install from VSIX…
- Select the
.vsixfile and reload when prompted
Configuring the Extension
Place default_data.dat (or x3fl.dat) in your workspace root — the extension loads it automatically on startup.
| Setting | Description |
|---|---|
xscript.compiler.exePath | Full path to XScriptCompiler.exe. |
xscript.compiler.dataFile | Path to default_data.dat. Auto-detected if left blank. |
xscript.compiler.outputDir | Where compiled .xml files are written. Defaults to alongside the source file. |
xscript.compiler.compileOnSave | Set to true to compile automatically on every save. |
xscript.compiler.defines | Array of symbols to pre-define, e.g. ["DEBUG", "PLATFORM_PC"]. |
Version History
0.8 BETA — Current
function main(...)wrapper — all scripts wrap their body in afunction main(...)block; script arguments are declared as typed parameters in the function signature instead ofSetArgument()calls;#DESCRIPTION,#VERSION,#COMMANDdirectives replace the equivalent runtime calls;return(value)at end of main- User-defined local-scoped functions —
function name(params) { ... }with full variable isolation;return($x)exits the function (not the script); forward declarations work automatically; recursion warning; name-collision checking against existing commands sub name() { ... }block syntax — readable wrapper for native label/gosub/endsub; subs share the global variable scope;endsub;for early exit; correct output ordering regardless of source position relative tomain- Pardef-free parameters —
function test($var)defaults untyped parameters toVALUE DATATYPE_*constants in parameter lists — accepted where an exact single-type pardef match exists- Duplicate detection — clear errors for name collisions across labels, subs, and user functions; function names checked against existing script commands
- Decompiler updated — output uses
function main(...)syntax; sub blocks (label:/endsub;groups) are emitted assub name() { ... }outsidemain;sub.label prefix is stripped automatically - VS Code extension v1.4.0 —
function/sub/endsub/voidkeyword highlighting; function and sub definition header patterns; structural snippet completions
0.7 BETA
- Chained (double) assignment —
$x = $y = -1; - Function overloads/aliases — multiple functions sharing a name, resolved by argument count
- Namespaces —
Utils::random(...); namespaced constants (e.g.RaceFlag::NPC) - Function macros —
foreach($item, $array) { ... }and custom macros inx3fl.xml #datatypepreprocessor directive- Multi-line
#definewith trailing\ --usenamespacedecompiler flag- VS Code extension v1.3.0 — namespace/macro autocomplete, hover, signature help
0.6 BETA
- While loop condition re-evaluation —
++/--and function calls inwhile(...)conditions breakandcontinueinside while loops- Nested function calls as arguments and in conditions
- Post-increment in array subscripts
- Full preprocessor system —
#define,#ifdef/#ifndef/#elseif/#else/#endif,#include - Script metadata —
#DESCRIPTION,#VERSION,#COMMAND - Command-line
--define:NAMEflag - VS Code extension v1.2.0 — preprocessor syntax highlighting
0.5 BETA
- Full XScript language — arrays, tables, compound assignment, namespace constants, object methods/properties
- Two-pass compilation for correct type tracking
- Object type propagation
- DataType prefix support for mod-added commands (
SHIPCOMMAND_1000)
