TorqueScript
Tribes 2’s scripting language. It looks like C, and that similarity is a trap — it is dynamically typed, everything is a string underneath, and several operators behave differently from their C lookalikes.
All examples in this page are taken from the shipped V12 scripts [script] unless marked otherwise.
Variables
| Sigil | Scope | Example |
|---|---|---|
%name |
Local to the enclosing function | %client, %obj, %amount |
$name |
Global, persists for the process lifetime | $CurrentMission, $Host::BotCount |
| (none) | Not a variable — a bare word is a string literal or an object name | AudioClosest3d |
There is no declaration. Assigning to a name creates it. Reading an unset variable yields the empty
string, which compares equal to "" and to 0.
:: inside a global name is just a naming convention, not a namespace operator:
$pref::Audio::masterVolume = 0.8;
$Host::TeamName[0] = "Storm";
$pref:: globals get special treatment in one place only: export("$pref::*", "prefs/ClientPrefs.cs", False)
writes every matching global out on exit [script].
Everything is a string
Numbers, object IDs, vectors, and booleans are all stored as strings and converted on demand.
%a = "5";
%b = 5;
if (%a == %b) // true — numeric comparison
if (%a $= %b) // true — string comparison, "5" $= "5"
The practical consequence is that you must pick the right comparison operator:
| Operator | Compares | Use for |
|---|---|---|
==, !=, <, >, <=, >= |
Numerically | Numbers |
$=, !$= |
As strings | Strings, object names, datablock names |
Using == on strings is a common and silent bug: "Disc" == "Mortar" is 0 == 0, which is true.
if (%obj.getDataBlock().className $= Armor) // correct
if (%data.getName() !$= "TargetingLaser") // correct
String operators
| Operator | Produces | Example |
|---|---|---|
@ |
Concatenation, no separator | "scripts/" @ %type @ ".cs" |
SPC |
Joined with a space | %first SPC %last |
TAB |
Joined with a tab (\t) |
Building field-separated records |
NL |
Joined with a newline (\n) |
Multi-line text blocks |
TAB and SPC are heavily used — 309 and 293 occurrences respectively in the shipped scripts — because
they are how you construct the delimited strings the engine’s text functions consume.
%text = "<just:center>Please read the following License Agreement carefully."
NL "You must accept this agreement in order to play"
NL "Tribes 2.";
The three-level string data model
This is the single most important idiom in the language. A string is treated as a hierarchy of delimiters:
| Level | Delimiter | Accessors |
|---|---|---|
| Word | space | getWord, getWords, setWord, getWordCount |
| Field | tab | getField, getFields, setField, getFieldCount |
| Record | newline | getRecord, getRecords, getRecordCount |
All are zero-indexed. Usage counts in the shipped scripts: getField 764, getWord 476, getRecord 179.
%result = WONLoginResult(); // returns a tab-separated string
%status = getField( %result, 0 );
%code = getField( %result, 1 );
A position like "-180.737 264.173 73.9045" is just a three-word string:
%x = getWord(%pos, 0);
%z = getWord(%pos, 2);
Transforms are seven words — position (3) then axis-angle rotation (4):
%obj.setTransform("-180.737 264.173 73.9045 0 0 -0.999913 0.0206931");
Arrays
Arrays are syntactic sugar over string concatenation of the name. $Package[3] is the global named
Package3. Multi-dimensional indices are joined with an underscore internally, so $a[1,2] is a1_2.
$Package[$TotalNumberOfPackages] = %this;
$WeaponsHudData[0, bitmapName] = "gui/hud_blaster";
$AmmoIncrement[DiscAmmo] = 5;
Two consequences:
-
There is no array length. The universal idiom is to iterate until you hit an empty entry:
$index = 0; while ($Host::TeamSkin[$index] !$= "") { $TeamSkin[$index] = addTaggedString($Host::TeamSkin[$index]); $index++; } -
Indices can be strings.
$AmmoIncrement[DiscAmmo]is an associative lookup keyed on a datablock name. This is used constantly.
Control flow
C-shaped, with two additions:
if (%a) { } else if (%b) { } else { }
for (%i = 0; %i < %count; %i++) { }
while (%file !$= "") { }
switch (%numericValue) // numeric switch
{
case 0: $ShellBackground = "gui/bg_Hammers.png";
case 1: $ShellBackground = "gui/bg_BloodEagle.png";
default: $ShellBackground = "gui/bg_Bioderm.png";
}
switch$ (%stringValue) // string switch — note the $
{
case "LoadJournal": loadJournal($JournalFile);
case "SaveJournal": saveJournal($JournalFile);
case "PlayJournal": playJournal($JournalFile);
}
case does not fall through — no break needed. case accepts alternatives with or:
case "WS_AuthServ_BadCDKey" or "WS_DBProxyServ_InvalidCDKey":
%msg = "Account Creation Failed - Invalid CD Key.";
The ternary operator works as expected:
%val = $_Camera::movementSpeed $= "" ? 40 : $_Camera::movementSpeed;
There is no do…while — the shipped scripts contain none, and the construct is not supported.
Functions
function functionName(%arg1, %arg2)
{
return %arg1 + %arg2;
}
Arguments are positional and optional — a caller may pass fewer than declared, and the missing ones are empty strings. That is why the shipped signatures are so long:
function messageClient(%client, %msgType, %msgString, %a1, %a2, %a3, %a4, %a5,
%a6, %a7, %a8, %a9, %a10, %a11, %a12, %a13)
Namespaced functions
A function named Class::method is dispatched when you call .method() on an object whose class,
superclass, or datablock class matches:
function Weapon::onUse(%data, %obj) { … }
function WeaponImage::onMount(%this, %obj, %slot) { … }
function DefaultGame::missionLoadDone(%game) { … }
The first parameter is the receiver. Its conventional name varies by context — %this, %obj, %data,
%game, %client — and carries no meaning to the engine. See
SimObjects and namespaces.
Indirect calls
call(%func, %msgType, %msgString, %a1, %a2); // call by name, with args
eval("someCode();"); // compile and run a string
call is used by the message callback system to dispatch to handlers registered by name [script].
eval is used sparingly — GUI button command fields are effectively eval‘d strings.
Tagged strings
Single-quoted strings are tagged strings — the engine interns them and transmits an integer tag over the network instead of the text.
commandToServer( 'MessageSent', %text );
commandToClient( %client, 'CenterPrint', %message, %time, %lines );
You will also see the explicit API:
$TeamName[$index] = addTaggedString($Host::TeamName[$index]);
%message = detag( %msgString );
Rule of thumb: single quotes for anything crossing the network as an identifier; double quotes for everything else. Getting this wrong produces messages that arrive as numbers.
Objects
Creating
new Item()
{
dataBlock = %data.thrownItem;
sourceObject = %obj;
};
new SimGroup (MissionCleanup);
new ScriptObject(Game) {
class = $CurrentMissionType @ "Game";
superClass = DefaultGame;
};
The parenthesised name is optional. Fields inside the braces are set on the new object; any name works, including ones the C++ class has never heard of — see dynamic fields below.
Declaring datablocks
datablock ItemData(Disc)
{
className = Weapon;
shapeFile = "weapon_disc.dts";
image = DiscImage;
mass = 1;
};
datablock PlayerData(LightFemaleHumanArmor) : LightMaleHumanArmor
{
shapeFile = "light_female.dts";
};
The : Parent form copies every field from the parent first. See Datablocks.
Dynamic fields
Any field you assign to an object exists from then on, whether or not the engine knows about it:
%thrownItem.sourceObject = %obj;
%thrownItem.team = %obj.team;
%obj.lastThrowTime[%data] = getSimTime();
%obj.thrownChargeId = 0;
Note %obj.lastThrowTime[%data] — array syntax works on object fields too. This is how most per-object
bookkeeping is done in Tribes 2 mods.
Reading a field that was never set returns "". There is no error, which cuts both ways: typos are silent.
Vector helpers
Vectors are three-word strings, manipulated through console functions:
%eye = %obj.getEyeVector();
%vec = vectorScale(%eye, (%throwStren * 20.0));
%dot = vectorDot("0 0 1", %eye);
%vec = vectorAdd(%vec, vectorScale("0 0 4", 1 - %dot));
%vec = vectorAdd(%vec, vectorScale(%obj.getVelocity(), 0.4));
Also available: vectorSub, vectorNormalize, vectorLen, vectorDist, vectorCross.
See Console functions.
Comments
// line comment
// only. /* … */ block comments are not supported. [inferred] — across the ~200 shipped .cs
files there is not a single block comment; every /* in the corpus is a glob pattern inside a string
literal such as findFirstFile("interiors/*.dif"). Sierra’s own scripters comment out large regions with
runs of //, which is what you should do too.
Gotchas worth memorising
| Trap | What happens | Fix |
|---|---|---|
== on strings |
Both sides numify to 0, comparison is true |
Use $= |
Missing semicolon after }; on a datablock/new/package block |
Parse error, often reported on a later line | Always }; |
/* */ comments |
Not supported | Use // |
| Typo’d field name | Silently creates a new dynamic field | Grep the base scripts for the real spelling |
| Reading an unset variable | Empty string, no warning | Test with !$= "" |
| Reusing a global as a loop counter | $i is process-global; nested use clobbers it |
Use %i |
| Single vs double quotes on network calls | Message arrives as a raw number | 'tag' for identifiers |
Under the community patches
The language is unchanged. Neither patch modifies Tribes2.exe, so the compiler, the syntax, the type
model, and the operator set are exactly as described above. Every idiom on this page works identically.
What the patches add is vocabulary, not grammar: around 30 new console functions registered by
IFC22.dll [binary], and — on RC2a only — a rubyExec / rubyEval bridge into an embedded Ruby
interpreter [patch-script]. Both are ordinary function calls. See
Console functions.
The one thing worth internalising: a call to a function that does not exist produces a console error but does not halt the script. So calling a patch-only function on a vanilla install is noisy rather than fatal — but guard anyway if you support both.
Related
- TorqueScript — V12 Compiler — what this language looks like compiled to
.cs.dso - TorqueScript — V12 Decompiler — reading a
.cs.dsoback to source, with a client-side tool - SimObjects and namespaces — how
Class::methoddispatch resolves - Datablocks — the
datablockdeclaration in depth - Packages —
packageandParent:: - Console functions — the built-in function surface