United Offensive scripting language ================================================= .. meta:: :description: Source-derived technical reference for the stock Call of Duty: United Offensive GSC scripting language: syntax, values, expressions, functions, threads, waits, notifications, arrays, animation trees, errors, and limits. :keywords: call of duty, united offensive, cod uo, gsc, scripting, script language, threads, waittill, notify, animtree Status and authority -------------------- This document specifies the multiplayer scripting language implemented by the stock Call of Duty: United Offensive compiler and virtual machine. The project's recovered ``STRICT_STOCK`` source is the exclusive authority for the syntax and behavior described here. No online GSC reference, later Call of Duty dialect, or compatibility extension is used as a source. The specification follows the recovered scanner and parser tables, AST builders, bytecode compiler, VM, value and variable systems, notification runtime, animation-tree loader, and stock game-module registries. Maintained non-stock safety checks and compatibility behavior are excluded. A behavior which the stock source does not establish is not generalized from another engine. The language and the game API are separate layers: * this page defines lexical, grammatical, and VM behavior; * :doc:`api` records the names exposed by the stock game module. .. toctree:: :hidden: api Quick example ------------- .. code-block:: c main() { level.round_started = true; level thread monitor_round_end(); } monitor_round_end() { level endon("intermission"); while (level.round_started) { level waittill("player_scored", player, points); player iprintln("score: " + points); } } Source files and program structure ---------------------------------- Script source files use the ``.gsc`` extension. The loader accepts a logical name without the extension and appends ``.gsc``. It canonicalizes logical names by lowercasing them, converting forward slashes to backslashes, and collapsing leading or repeated path separators. A source file contains only top-level function definitions, ``#using_animtree`` directives, and developer-only function definitions. It does not execute top-level statements. .. code-block:: c #using_animtree("american_soldier"); main(arg1, arg2) { // statements } The compiler records every function before emitting any body. Functions may therefore call functions defined later in the same file. Duplicate function names are compile errors. An unresolved local or external function is also a compile error. Names in another script use a backslash-separated logical path and ``::``: .. code-block:: text maps\mp\gametypes\_callbacksetup::codecallback_startgametype(); callback = maps\mp\example::handler; The first form calls a function. The second stores a function value. A cross-file reference schedules that script for loading after the current file has been compiled. Lexical structure ----------------- Character and case rules ~~~~~~~~~~~~~~~~~~~~~~~~ The recovered scanner operates on bytes. Identifiers consist of letters, digits, and underscores, and may begin with an underscore. A token beginning with a digit is scanned as a number first; ``1name`` is therefore not one identifier. Identifiers, function names, field names, and script-path components are interned in lowercase. Keyword recognition itself is case-sensitive and the keyword spellings are lowercase. Use lowercase source spelling consistently; an uppercase spelling can be scanned as an identifier in a position where the parser requires a keyword. The reserved words are: .. code-block:: text return wait thread undefined self level game anim if else while for do switch case default break continue waittill waittillmatch notify endon false true Whitespace and comments ~~~~~~~~~~~~~~~~~~~~~~~ Spaces, tabs, and line breaks separate tokens and are otherwise ignored. Ordinary comments use either form: .. code-block:: c // to the end of the line /* across one or more lines */ ``/#`` and ``#/`` are not ordinary comment tokens. They delimit compilable developer-only code and have the semantics described in `Developer-only code`_. Strings ~~~~~~~ An ordinary string uses double quotes. Prefixing the opening quote with ``&`` creates a localized string value. .. code-block:: c text = "ordinary"; localized = &"MENU_OK"; The scanner recognizes ``\n``, ``\r``, and ``\t``. For every other escape, the backslash is discarded and the following character is retained. For example, ``\"`` produces a quote and ``\\`` produces one backslash. Ordinary and localized strings are distinct VM types. The explicit ``(string)`` cast does not convert a localized string to an ordinary string. Numbers ~~~~~~~ Integer literals are decimal. Hexadecimal notation is not part of this scanner: ``0x10`` is tokenized as integer ``0`` followed by an identifier. Float literals use a decimal point, for example ``0.5``, ``.5``, or ``1.25``. The source scanner does not recognize exponent notation. A trailing point is not a float token, so ``1.`` is invalid as a single numeric literal. A minus immediately before an integer or float literal forms a negative numeric literal. General unary negation is absent: ``-1`` and ``-0.5`` are valid, but ``-value`` is not. Unary plus is also absent. Exact composite tokens ~~~~~~~~~~~~~~~~~~~~~~ The following spellings are each one scanner token: .. code-block:: text (bool) (int) (float) (string) .size #using_animtree #animtree :: Their internal whitespace is significant. ``(int)value`` is a cast, while ``( int )value`` does not parse as one. Likewise, ``array.size`` is the size operator, while ``array . size`` is an ordinary field access named ``size``. Values and types ---------------- Script-visible values ~~~~~~~~~~~~~~~~~~~~~ .. list-table:: :header-rows: 1 :widths: 22 78 * - Type - Meaning * - ``undefined`` - Absence of a value. Unset locals, missing array entries, omitted parameters, and functions without a value result use this type. * - ``int`` - Signed 32-bit integer payload. ``false`` is integer zero and ``true`` is integer one. * - ``float`` - IEEE-754 binary32 value. * - ``string`` - Interned ordinary string. * - ``localized string`` - Interned localized-message string written with ``&"..."``. * - ``vector`` - Three binary32 components. * - ``object`` - Reference to an entity, struct, array, thread object, or another field-bearing VM object. * - ``function`` - Reference to compiled script code. * - ``animation`` - Animation index paired with its animation-tree index. The VM also has implementation types named ``codepos``, ``key/value``, ``stack``, ``thread``, ``entity``, ``struct``, ``array``, ``dead thread``, ``dead entity``, and ``dead object``. Object subtypes are usually carried to script as an ``object`` value; code positions, archived stacks, and dead variants are VM bookkeeping rather than source literals. Literal and predefined values ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. list-table:: :header-rows: 1 :widths: 30 70 * - Source form - Result * - ``undefined`` - Undefined value. * - ``false`` / ``true`` - Integer zero / integer one. * - ``"text"`` - Ordinary string. * - ``&"text"`` - Localized string. * - ``(x, y, z)`` - Vector; each of the three values is cast to float at runtime. * - ``[]`` - New empty array. * - ``%animation_name`` - Animation reference resolved through the file's active animtree. * - ``#animtree`` - Integer handle of the file's active animtree. * - ``::function`` - Same-file function value. * - ``path\file::function`` - Cross-file function value. ``(expression)`` is grouping when it contains one expression. A parenthesized list must contain exactly one or three expressions; the three-expression form constructs a vector. Predefined objects ~~~~~~~~~~~~~~~~~~ ``self`` is the parent object of the current thread. A normal function call without an explicit method object inherits the caller's ``self``. A method call supplies its object as the callee's ``self``. ``level`` and ``game`` are distinct global field-bearing objects created when the script system starts. ``anim`` is the global animation array value used by the script runtime. Their fields persist independently of function-local variables for the life of the script system. Variables, objects, and arrays ------------------------------ References ~~~~~~~~~~ The language has local variables, object fields, and indexed references: .. code-block:: c local = 1; level.phase = "playing"; values[0] = "first"; values["name"] = "value"; Dot access and string indexing address the same name domain on arrays and field objects where that access is supported. Indexed assignment to an ``undefined`` local creates an array automatically. Array keys may be ordinary strings or integers. The valid stock integer-key domain is ``-8257536`` through ``8388607``. Reading a missing array entry produces ``undefined``. Assigning and clearing arrays use copy-on-write when the array is shared; nested arrays are copied recursively while non-array objects remain shared references. Strings and vectors support read-only integer indexing: * ``string[index]`` returns a one-character ordinary string and requires ``0 <= index < strlen(string)``; * ``vector[index]`` returns a float and accepts only indexes 0, 1, and 2; * individual string characters and vector components cannot be changed. Size ~~~~ The exact ``.size`` token applies these rules: .. list-table:: :header-rows: 1 :widths: 28 72 * - Operand - Result * - array object - Number of array entries. * - other object - Integer ``1``. * - ordinary string - Byte length before the terminating NUL. * - any other type - Runtime error. In particular, localized strings are not accepted. Assignment and mutation ~~~~~~~~~~~~~~~~~~~~~~~ Simple assignment uses ``=``. Compound assignment supports: .. code-block:: text |= ^= &= <<= >>= += -= *= /= %= The compound form reads the old value, applies the corresponding binary operator, and writes the result. ``reference++`` and ``reference--`` are statements, not general postfix expressions, and require an integer value. They wrap in the same 32-bit domain as integer addition and subtraction. Expressions ----------- Operator precedence ~~~~~~~~~~~~~~~~~~~ Binary operators are left-associative. The table runs from lowest to highest precedence. .. list-table:: :header-rows: 1 :widths: 12 32 56 * - Level - Operators - Notes * - 1 - ``||`` - Short-circuit logical OR. * - 2 - ``&&`` - Short-circuit logical AND. * - 3 - ``|`` - Integer bitwise OR. * - 4 - ``^`` - Integer bitwise XOR. * - 5 - ``&`` - Integer bitwise AND. * - 6 - ``==`` ``!=`` - Equality and inequality. * - 7 - ``<`` ``>`` ``<=`` ``>=`` - Numeric relational comparison. * - 8 - ``<<`` ``>>`` - Integer shifts. * - 9 - ``+`` ``-`` - Addition, subtraction, vector arithmetic, or concatenation as described below. * - 10 - ``*`` ``/`` ``%`` - Numeric multiplication, division, and remainder. * - 11 - ``!`` ``~`` and exact cast tokens - Unary logical not, integer bitwise not, and explicit casts. Indexing, field access, ``.size``, function selection, and calls bind more tightly than the operators in the table. The language has no conditional ``?:`` operator and no general unary ``+`` or ``-`` operator. Truth conversion and logical operators ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Conditions accept integers directly. Floats are false only when equal to zero. Ordinary strings are converted with the stock decimal integer parser; a zero result is accepted only when the trimmed, optional-sign-prefixed text starts with ``0`` or ``.0``. Other types cannot be used as conditions. ``!`` returns normalized integer zero or one. ``&&`` and ``||`` short-circuit. The right operand is converted to normalized boolean when evaluated. If the left operand short-circuits and is already an integer, its original nonzero or zero integer payload remains the expression result; do not assume that every logical expression produces exactly one or zero. Explicit casts ~~~~~~~~~~~~~~ .. list-table:: :header-rows: 1 :widths: 24 76 * - Cast - Accepted inputs * - ``(bool)`` - Int, float, or ordinary string. The result is integer 0 or 1. * - ``(int)`` - Int, float, or ordinary string. Float conversion truncates according to the stock target conversion; strings use the stock decimal parser. * - ``(float)`` - Float, int, or ordinary string. The result is binary32. * - ``(string)`` - Ordinary string, int, float, or vector. The string-to-number casts use the same zero-text rule as truth conversion. They accept leading whitespace and an optional sign through the stock C conversion routines. Windows and Linux stock builds differ for an extremely small nonzero string value which converts to a nonzero double but narrows to float zero: Windows accepts it, while Linux tests the narrowed float and rejects it unless the text has a zero-literal prefix. Implicit pair conversion ~~~~~~~~~~~~~~~~~~~~~~~~ Binary operators first attempt the stock pair conversion: * int is promoted to float when paired with float; * int, float, or vector is converted to an ordinary string when paired with an ordinary string; * otherwise, unlike types produce an ``unmatching types`` runtime error. This conversion precedes operator validation. It enables string concatenation such as ``"score: " + score``, but it does not make subtraction or relational comparison valid for strings. Arithmetic and bitwise behavior ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. list-table:: :header-rows: 1 :widths: 20 80 * - Operand type - Operations * - int - ``+ - * / %`` and all bitwise/shift operators. Addition, subtraction, and multiplication wrap in 32 bits. Division and remainder use signed integer semantics and reject zero divisors. * - float - ``+ - * /``. Float remainder is not supported; division rejects a zero divisor according to the stock comparison. * - vector - Vector ``+`` and ``-`` only, component by component. * - ordinary string - ``+`` only, producing an interned concatenated string. Shift counts are masked with ``31``. Left shift operates on the 32-bit bit pattern. Right shift is an arithmetic signed shift. Stock signed integer division of ``-2147483648 / -1`` reaches the target CPU's overflowing divide instruction; it is not a defined, recoverable script result. Equality and comparison ~~~~~~~~~~~~~~~~~~~~~~~ Equality is defined after implicit pair conversion: * two ``undefined`` values are equal; * ordinary and localized strings compare their interned handles within their respective type; * vectors compare all three components exactly; * floats are equal when the absolute difference is smaller than the stock epsilon (approximately ``1e-6``); * integers compare their complete 32-bit payload; * objects compare identity; * animations compare their packed tree/index identity. Relational operators accept only int or float after int-to-float promotion. Functions and calls ------------------- Definitions and parameters ~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: c add(left, right) { return left + right; } Parameters and locals belong to the executing function thread. Extra arguments cause ``function called with too many parameters`` when the callee finishes binding its formal parameters. Missing parameters remain ``undefined``. ``return expression;`` supplies a value; ``return;`` and falling off the function body return ``undefined``. The stock VM permits 32 nested script-call frames. The compiler also rejects a function when ``32 * maximum_local_depth + maximum_operand_stack_depth`` is greater than 2047. Built-in argument counts are encoded in one byte; the stock compiler rejects counts greater than or equal to 256, making 255 the actual maximum despite its misleading ``exceeds 256`` diagnostic. Call forms ~~~~~~~~~~ .. list-table:: :header-rows: 1 :widths: 40 60 * - Form - Meaning * - ``function(args)`` - Call a named same-file script function or a stock built-in function. * - ``path\file::function(args)`` - Call a named function in another script. * - ``object function(args)`` - Method-context call; ``object`` becomes the callee's ``self``. A name found in the stock method registry invokes that built-in method. * - ``[[function_value]](args)`` - Call through a function value. * - ``object [[function_value]](args)`` - Call through a function value with explicit ``self``. * - ``thread function(args)`` - Execute the named function as a new child thread using inherited ``self``. * - ``object thread function(args)`` - Execute a child thread with explicit ``self``. The ``thread`` prefix also works with the ``[[function_value]]`` call forms. A new thread begins executing immediately and runs until it returns, waits, or errors. Its expression result is the immediate child execution result when it returns before suspension, otherwise ``undefined``; it is not a stable source- level thread-handle API. Call statements discard their result. Control flow ------------ Conditional and loop statements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The stock statements are conventional in shape: .. code-block:: c if (condition) statement; else statement; while (condition) statement; do statement; while (condition); for (initialization; condition; increment) statement; Any ``for`` clause may be empty. An empty condition is compiled as true. ``break`` is legal in loops and switches. ``continue`` is legal in loops. The runtime has a stock loop watchdog which reports or terminates a potential infinite loop according to developer settings. Switch ~~~~~~ .. code-block:: c switch (value) { case 1: action(); break; case "name": other_action(); break; default: fallback(); break; } Case labels must be integer or ordinary-string literals. Integer cases use the same stock key domain as integer array indexes. Duplicate case values or more than one ``default`` are compile errors. Execution falls through unless terminated with ``break`` or another control transfer. Threads, time, and events ------------------------- Wait ~~~~ ``wait expression;`` converts the expression to float seconds, multiplies by 1000, rounds it with the stock fast-round operation, and schedules the thread on a 24-bit millisecond time key. Negative values and NaN are errors. Values greater than or equal to 16777 seconds are rejected. A wait archives the thread's live stack and locals and resumes it when that time bucket runs. ``wait 0;`` still yields through the scheduler; it is not equivalent to omitting the statement. Notify and waittill ~~~~~~~~~~~~~~~~~~~ Events are attached to field-bearing objects and named by ordinary strings: .. code-block:: c level notify("started", player, mode); level waittill("started", notified_player, notified_mode); For ``notify``, the first argument must evaluate to an ordinary string. Any remaining expressions are event arguments. For ``waittill``, the first argument is the event-name expression. Remaining items are local variable names, not input expressions. When a matching notify arrives, its arguments are appended to the archived frame and assigned to those locals in order. The wait registers on the receiver object and event name, then suspends the current thread. ``waittill`` is one-shot: resumption removes that waiter from the object's event bucket. One notify may wake every waiter registered for that object and name. Waittillmatch ~~~~~~~~~~~~~ .. code-block:: c level waittillmatch("state_changed", expected_player, expected_state); ``waittillmatch`` evaluates and archives its expressions after the event name. On notify, those saved values are compared with the notification arguments in order using normal script equality. The thread resumes only if all saved values match. Unlike ``waittill``, these items are match expressions and are not output local names. The stock bytecode stores the match count in one byte and the dispatcher sign-extends that byte. The stock compiler does not enforce a safe signed-positive count. Programs must keep the match count within 0 through 127; larger counts enter the stock VM's invalid frame-walk behavior rather than defining additional language semantics. Endon ~~~~~ .. code-block:: c self endon("disconnect"); ``endon`` requires an object receiver and an ordinary-string event name. It registers an auxiliary waiter tied to the current thread. A matching notify terminates that thread, including an archived thread suspended in ``wait`` or ``waittill``. Animation trees --------------- ``#using_animtree`` is a top-level directive and its argument must be an identifier string: .. code-block:: c #using_animtree("american_soldier"); The selected tree is reset for each source file. Animation literals and ``#animtree`` require that the file has selected a tree. ``%run`` resolves an animation named ``run`` in that tree and emits an animation value. ``#animtree`` emits the current tree's integer registry index. Animation-tree names are canonicalized like script filenames. The loader forms ``animtrees/.atr`` and resolves referenced animations while the script set is being loaded. Developer-only code ------------------- ``/#`` and ``#/`` delimit code controlled by the developer-script setting. They may surround complete top-level function definitions or statement regions: .. code-block:: text /# debug_dump() { println("debug"); } #/ main() { /# println("entered main"); #/ } When developer scripts are disabled, these regions are removed during compilation. When enabled, their bytecode is retained as developer code. ``print``, ``println``, and ``assert`` are the three developer-only global built-ins in the stock game registry. Outside an explicit developer region, they may be used only as call statements; the compiler marks and conditionally retains that statement as developer code. Inside ``/# ... #/``, the ordinary call-expression rules apply. ``wait``, ``waittill``, and ``waittillmatch`` are not allowed directly inside a developer-only statement region. The stock diagnostic recommends calling a waiting function as a thread. Developer delimiters also cannot directly wrap a ``case``, ``break``, or ``continue`` in a way which crosses the compiler's control-flow boundary, and nested developer function compilation is rejected. Errors and lifetime ------------------- The compiler reports syntax errors, duplicate or missing functions, illegal control-flow placement, invalid literal case values, unresolved scripts and animations, and operand-stack overflow with a source position. The VM reports dynamic type errors at the executing opcode. Important error classes include: * using a non-object as a field or method receiver; * indexing a value with the wrong type or outside its valid domain; * applying an operator to unsupported or unmatching types; * calling a non-function value through ``[[...]]``; * too many function arguments or nested calls; * invalid wait values and invalid event-name types; * invoking a built-in with the wrong number or type of arguments. Values are reference-counted. Strings, vectors, objects, arrays, threads, and archived stacks remain alive while referenced. An entity or thread object can transition to a dead object subtype; later field or method use then fails the object/type checks rather than reviving it. Compact syntax reference ------------------------ The following EBNF is a readable summary of the recovered grammar. It omits the precedence expansion already given in the operator table. ``identifier`` and ``script-path`` are scanner tokens; ``reference`` covers local, field, and indexed lvalues accepted by the compiler. .. code-block:: text script = { using-tree | function | developer-function } ; using-tree = "#using_animtree" "(" string-literal ")" ";" ; function = identifier "(" [ identifiers ] ")" block ; identifiers = identifier { "," identifier } ; developer-function = "/#" function "#/" ; block = "{" { statement } "}" ; statement = ";" | block | developer-block | reference "=" expression ";" | reference compound-assign expression ";" | reference ( "++" | "--" ) ";" | call ";" | "return" [ expression ] ";" | "wait" expression ";" | "if" "(" expression ")" statement [ "else" statement ] | "while" "(" expression ")" statement | "do" statement "while" "(" expression ")" ";" | "for" "(" [ for-item ] ";" [ expression ] ";" [ for-item ] ")" statement | "switch" "(" expression ")" block | "case" ( integer-literal | string-literal ) ":" | "default" ":" | "break" ";" | "continue" ";" | object "waittill" "(" expression { "," identifier } ")" ";" | object "waittillmatch" "(" expression { "," expression } ")" ";" | object "notify" "(" expression { "," expression } ")" ";" | object "endon" "(" expression ")" ";" ; developer-block = "/#" { statement } "#/" ; for-item = reference "=" expression | reference compound-assign expression | reference ( "++" | "--" ) | call ; call = [ object ] [ "thread" ] callable "(" [ expressions ] ")" ; callable = identifier | script-path "::" identifier | "[[" expression "]]" ; function-value = "::" identifier | script-path "::" identifier ; expressions = expression { "," expression } ; reference = identifier | object "." identifier | indexable "[" expression "]" ; compound-assign = "|=" | "^=" | "&=" | "<<=" | ">>=" | "+=" | "-=" | "*=" | "/=" | "%=" ; Stock callable surface ---------------------- The parser treats game functions and methods as normal call syntax. Their names are resolved by the stock game module during compilation. See :doc:`api` for the complete recovered global-function, method, and field registries and their lookup order. Implementation basis -------------------- The specification above is derived from these maintained stock-source areas: .. list-table:: :header-rows: 1 :widths: 34 66 * - Area - Source modules * - Scanner and parser - ``script_yy_runtime.c``, ``script_yy_tokens.c``, and the generated POSIX/Windows scanner and parser tables/builders. * - AST and compilation - ``script_compile_types.h``, ``script_compile_expr.c``, ``script_compile_statements.c``, ``script_compile_load.c``, and ``script_code_emit.c``. * - Runtime values and containers - ``script_vm.cpp``, ``script_value.c``, ``script_variable.c``, and ``script_array.c``. * - Threads and events - ``script_thread.c``, ``script_notify.c``, and the wait/event VM opcodes in ``script_vm.cpp``. * - Animation trees - ``script_anim_runtime.c`` and the animation compiler/runtime paths. * - Game API - The stock registries under ``src/server/game`` summarized in :doc:`api`.