Mapping wiki › WAC basics

πŸ“œ WAC scripting basics

WAC is the script language that makes things happen in a Joint Ops map: timed events, weather, messages, AI orders, sounds, effects, win and lose. How a script is laid out, when it runs, what multiplayer players actually see, and the traps that fail without a word.

This page comes from the reverse-engineering of the retail game (the FMJ team and the OpenNova project), checked against real TAC map scripts, and from a live test of every command on a real host with a player joined (13 Oct 2026). The full command list, each with its test result, is on Triggers and Actions.

Tested in a real match (13 Oct 2026)

Every one of the 165 commands was fired on a LAN host (normal retail behaviour, stock map 03TR) with a second player joined, and we recorded what the host and the joining player each saw: object states from both games, plus screenshots from both screens. Results:

ResultCommands
Works, and players see it90
Works on the server only (players do not see it)16
Does nothing, as the game's code says15
Broken3 (teleSSN, fov, sound)
Not proven either way yet41

The result for each command is on its card. The things most likely to catch a mapper out:

Still to prove (the 41): mostly AI aim and distance settings, flashes and lightning (too short to catch in a screenshot so far), sounds (a screenshot cannot hear), fire-button tests, and commands this map has nothing for (doors, location boxes, spawn points). They will be retested and the cards updated.

Files

Who runs it, and when

How a script is written

IF blocks

These forms come from the game's own built-in help:

IF triggers THEN actions END
IF [name] triggers THEN actions END        (a named IF, for reset)
IF triggers THEN actions ELSE actions END
IF triggers THEN actions ELSEIF triggers THEN actions END
IF triggers ENTER actions END              (runs once when the triggers become true)
IF triggers LEAVE actions END              (runs once when they stop being true)
PLOOP actions END                          (once for each human player)
GLOOP group actions END                    (once for each member of a group)
DOSEQ actions NEXT actions [NEXT actions ...] END
DORND actions NEXT actions [NEXT actions ...] END

A misspelled name in reset gives no error: it resets the first IF in the script instead.

Combining triggers

Timing words

All count script passes, about one second each.

TriggerTrue when
neverthis IF has not fired yet ("do it once")
elapse(N)this IF has never fired, or fired N or more seconds ago ("repeat every N")
past(N) / before(N)at least N / fewer than N seconds since the map started
ontick(N)exactly pass N (ontick(0) is the load pass)
previous()the IF before this one (same level) fired more recently than this one
chain(N)as previous, and N seconds have passed since it fired

elapse is true straight away the first time, so it repeats rather than delays. To delay the first time as well, write past(N) and elapse(N). A row of if chain(5) ... endif blocks makes a timed sequence.

DOSEQ and DORND

Each time the block is reached it runs one section and moves to the next section next time. DORND is not random in Joint Ops: it cycles through its sections in order, exactly like DOSEQ. For randomness use random(N) in an IF.

Loops: PLOOP and GLOOP

An IF's memory (never, elapse, ENTER/LEAVE) belongs to the IF, not to each player. PLOOP if onptick(20) and never then ... END fires for the first player only. Use onptick on its own, or a player flag set with psetvar and tested with pisvar. (Worked out from the code, not tested in game.)

Values and variables

You writeMeaning
12, -3, 2.5numbers (whole numbers in the end)
150 in a distance, 150M, 30Fmetres; F = feet
19:45, or 8 in a timetime of day (hours:minutes, or hours)
V0..V255map variables, cleared at every map load
G0..G255global variables (server tools such as WolfRAT use high G numbers to talk to server.wac)
VAR nameyour own named variable (up to 256, names up to 18 characters). Not cleared between maps: set it yourself at the start.
SSNan object's number from MED/Nile; a player is 10000 + slot; player inside PLOOP
G_ FX_ FACE_ SS_ TT_ ANIM_ SSN_ AMMO_prefixes saying what a name is: group, effect, face, sound set, text key, animation, SSN, ammo

Named values

NameWhat it is
ticksnumber of script passes so far (about seconds)
resultthe value of the last command or test
RNDthe last random roll
humansnumber of human players in the game
GameOver, WinVar, LoseVarround over / won / lost
CurTODtime of day in minutes (read only: use TOD to change it)
nightthe night flag of the time-of-day system
accuracyspreadAI aim spread for every AI (can be written)
breathtime, fallmpsbreath time under water, and a falling setting (can be written)
bluekills, greenkillsenemies killed by the player's side: single player only

Setting values: use set(V1, 5), add(V1, 2), sub, inc(V1), dec(V1). The game also accepts V1 = 5, but arithmetic in assignments is unreliable: V1 = 1 + 2 * 3 stores 1, not 7. Keep to one operation per line, or use the commands.

Never divide by a value that can be 0. In the retail game that is a processor fault, which would crash the host.

Limits

LimitValue
IF blocks (including ELSEIF)1,024 ("Out of IF space")
DOSEQ / DORND blocks1,024 ("Out of DO space")
VAR names256 ("Over Variable Buffersize")
different numbers in the script512 ("Out of num space")
all quoted text together4,096 bytes ("Out of string space")
compiled scriptabout 18 KB ("Over Compile Buffersize": the rest of the file is dropped)
brackets inside each other16
one quoted text63 characters
V and G variables0-255 each
player flags (psetvar / pisvar)0-16
areas a script can testthe first 128 in the map
RUN depth2 levels

Compiler messages

The game keeps only the first message, shown as file (line) message on the game's script debug display. Whatever the message, the script still runs: the broken value becomes 0, so a typo usually means "that line quietly does nothing", or worse, "acts on object 0 or IF 0".

MessageWhat went wrong
Unknown 'WORD'not a command, keyword, variable or number. A "quoted text" on its own, without text, gives this.
Unknown SSNno object in the map has that SSN
Unknown FX / SOUNDSET / AMMO / ANIM / FACE / Groupthe name is not in the game's lists
Wrong Parameterthe wrong kind of value in a slot (a time where a distance belongs)
Missing ENDa block is not closed (often ELSE IF written as two words)
Unexpected END / ELSE / NEXT ...a word in the wrong place
THEN without IF, ELSE without THEN, NEXT without DOa block broken in the middle
No LOOP Nesting!a PLOOP/GLOOP inside another
IF Name already used, Variable Name already usedthe same name twice
Can't run files inside blocks, Unable to run fileRUN problems
Out of ... space, Over ... Buffersizesee limits

Traps with no message at all: LF line endings; a misspelled name in reset; a text key that does not exist; a missing G_ before a group name; extra values after a command.

Multiplayer: what other players see

The script runs only on the host, so what matters is how each command's effect reaches the other players. The game marks every command:

Commands that do nothing, or not what they say

Names in Nile's action list that are not real commands

Example scripts

The SSNs, areas and file names belong to their maps: use your own.

A. Map start, a repair pad and a crane disaster

From TAC's AS-KutuTowerAssault.WAC, shortened.

//map start: runs once on the first pass
if never then
hidessn(3188)          //fallen beam, shown later
killssn(4125)
movefog(200, 0)
fogtype(3)
tod(9:00)              //the original wrote set(tod, 09:00), which does nothing
overcast(45, 0)
ssn2ssn(4138, 4139)    //put a soldier on a gun
ssnuse(4138)
endif

//heli pad repair: every 30 s while the helo sits on the pad
if SSNonSSN(4137, 4136) and elapse(30) then
SSNHP(4137, 8000)
endif

//crane beam drop: a timed sequence, each step after the one above
if ssnride(3405) and never then
ssnwave(2265, "m1rtrev2.wav", 250)
endif
if chain(5) and never then
killssn(3404)
ssnwave(3403, "alarm9.wav", 50)
endif
if chain(5) and never then
ammo2tgt(ARTYRND, 2)
hidessn(2648)
unhidessn(3188)
endif
if chain(4) and never then
removessn(3405)
endif

B. A storm rising after 15 minutes

From TAC's AS-VillaValleyTAC.WAC, shortened (its wind(...) lines are left out: they need the FMJ DLL).

if never() and (OnTick(0)) then    //set the start weather
tod(8)
fogdist(1024)
endif

if Elapse(900) and Past(5) then
farflash()
movefog(20, 50)
overcast(100, 40)
rain(100, 40)
consol("TYPHOON RISING - TAKE CARE!")
endif

if chain(10) then
farflash()
endif
if chain(5) then
flash()
quake(10)
movefog(60, 5)
endif

if chain(5) then                   //clear again
overcast(0, 10)
rain(0, 10)
movefog(1024, 60)
consol("Fog Clear")
endif

C. Seats for every player, and a welcome

The seat part is from TAC's TK-AlQurnahBasin.WAC (made in Nile, fixed by O.M.).

// a player who steps on the pad (133031) is put in the chair (133035)
PLOOP
    if ssnonssn(player, 133031) then
        ssn2ssn(player, 133035)
        ssnuse(player)
    endif
END

// let people out of the chair again
if ssnride(133035) then
    PLOOP
        if ssnonssn(player, 133035) then
            ssnrelease(player)
        endif
    END
endif

// only the player who has just joined sees this
PLOOP
if onptick(20) then
ptext("Welcome to the server!")
endif
END