ACS0 instruction set

From DoomWiki.org

This is a listing of the ACS0 instruction set, which is part of the Hexen BEHAVIOR lump. Each script must have a valid stream of ACS0 instructions, which consist of 1 or more 4-byte little-endian integers (or DWORDs). In the vanilla executable, undefined instructions, invalid variable indices, and stack under- or overflows will cause undefined behavior in the interpreter.

Valid ACS0 instructions range from 1 to 7 DWORDs in size, with the opcode as the first 4 bytes and any immediate arguments ("imm") following. The ACS0 interpreter is a pure stack machine, meaning that there are no explicit data registers. General memory access is not provided by the interpreter; only pre-defined arrays of script, map, and world variables are available for reading or writing.

In the vanilla interpreter, integer division and modulus by zero are undefined and unguarded, and will throw a native CPU exception.

Understanding of C programming language operators and syntax is required for some portions of the table below.

Opcodes[edit]

Opcode DWORDs Name Args Stack diff Logic
0 1 NOP - 0 No effect
1 1 Terminate - 0 Stops the script, terminating its thinker
2 1 Suspend - 0 Suspends execution of the script until it is manually resumed
3 2 PushNumber Imm +1 Pushes immediate argument onto the stack
4 2 LSpec1 Imm, Stack -1 Executes immediate argument as line special number, with 1 argument popped from stack
5 2 LSpec2 Imm, Stack x2 -2 Executes immediate argument as line special number, with 2 arguments popped from stack[notes 1]
6 2 LSpec3 Imm, Stack x3 -3 Executes immediate argument as line special number, with 3 arguments popped from stack
7 2 LSpec4 Imm, Stack x4 -4 Executes immediate argument as line special number, with 4 arguments popped from stack
8 2 LSpec5 Imm, Stack x5 -5 Executes immediate argument as line special number, with 5 arguments popped from stack
9 3 LSpec1Direct Imm x2 0 Executes immediate argument 0 as line special number, with immediate argument 1 as args[notes 2]
10 4 LSpec2Direct Imm x3 0 Executes immediate argument 0 as line special number, with immediate arguments 1 - 2 as args
11 5 LSpec3Direct Imm x4 0 Executes immediate argument 0 as line special number, with immediate arguments 1 - 3 as args
12 6 LSpec4Direct Imm x5 0 Executes immediate argument 0 as line special number, with immediate arguments 1 - 4 as args
13 7 LSpec5Direct Imm x6 0 Executes immediate argument 0 as line special number, with immediate arguments 1 - 5 as args
14 1 Add Stack x2 -1 Push(Pop() + Pop())
15 1 Subtract Stack x2 -1 t = Pop(); Push(Pop() - t)
16 1 Multiply Stack x2 -1 Push(Pop() * Pop())
17 1 Divide Stack x2 -1 t = Pop(); Push(Pop() / t)
18 1 Modulus Stack x2 -1 t = Pop(); Push(Pop() % t)
19 1 EQ Stack x2 -1 Compare equality; Push(Pop() == Pop())
Opcode DWORDs Name Args Stack diff Logic
20 1 NE Stack x2 -1 Compare non-equality; Push(Pop() != Pop())
21 1 LT Stack x2 -1 Compare less-than; t = Pop(); Push(Pop() < t)
22 1 GT Stack x2 -1 Compare greater-than; t = Pop(); Push(Pop() > t)
23 1 LE Stack x2 -1 Compare less-than-or-equal; t = Pop(); Push(Pop() <= t)
24 1 GE Stack x2 -1 Compare greater-than-or-equal; t = Pop(); Push(Pop() >= t)
25 2 AssignScriptVar Imm, Stack -1 scriptvars[imm] = Pop()
26 2 AssignMapVar Imm, Stack -1 mapvars[imm] = Pop()
27 2 AssignWorldVar Imm, Stack -1 worldvars[imm] = Pop()
28 2 PushScriptVar Imm +1 Push(scriptvars[imm])
29 2 PushMapVar Imm +1 Push(mapvars[imm])
30 2 PushWorldVar Imm +1 Push(worldvars[imm])
31 2 AddScriptVar Imm, Stack -1 scriptvars[imm] += Pop()
32 2 AddMapVar Imm, Stack -1 mapvars[imm] += Pop()
33 2 AddWorldVar Imm, Stack -1 worldvars[imm] += Pop()
34 2 SubScriptVar Imm, Stack -1 scriptvars[imm] -= Pop()
35 2 SubMapVar Imm, Stack -1 mapvars[imm] -= Pop()
36 2 SubWorldVar Imm, Stack -1 worldvars[imm] -= Pop()
37 2 MulScriptVar Imm, Stack -1 scriptvars[imm] *= Pop()
38 2 MulMapVar Imm, Stack -1 mapvars[imm] *= Pop()
39 2 MulWorldVar Imm, Stack -1 worldvars[imm] *= Pop()
Opcode DWORDs Name Args Stack diff Logic
40 2 DivScriptVar Imm, Stack -1 scriptvars[imm] /= Pop()
41 2 DivMapVar Imm, Stack -1 mapvars[imm] /= Pop()
42 2 DivWorldVar Imm, Stack -1 worldvars[imm] /= Pop()
43 2 ModScriptVar Imm, Stack -1 scriptvars[imm] %= Pop()
44 2 ModMapVar Imm, Stack -1 mapvars[imm] %= Pop()
45 2 ModWorldVar Imm, Stack -1 worldvars[imm] %= Pop()
46 2 IncScriptVar Imm 0 scriptvars[imm] += 1
47 2 IncMapVar Imm 0 mapvars[imm] += 1
48 2 IncWorldVar Imm 0 worldvars[imm] += 1
49 2 DecScriptVar Imm 0 scriptvars[imm] -= 1
50 2 DecMapVar Imm 0 mapvars[imm] -= 1
51 2 DecWorldVar Imm 0 worldvars[imm] -= 1
52 2 Goto Imm 0 Unconditional jump to immediate absolute offset
53 2 IfGoto Imm, Stack -1 Jump to immediate absolute offset if value popped from stack is not zero
54 1 Drop - -1 Pops the stack, discarding the value
55 1 Delay Stack -1 Sets script delay counter to popped value
56 2 DelayDirect Imm 0 Sets script delay counter to immediate operand
57 1 Random Stack x2 -1 high = Pop(); low = Pop(); Push(low + (P_Random() % (high - low + 1)))
58 3 RandomDirect Imm x2 +1 low = imm0; high = imm1; Push(low + (P_Random() % (high - low + 1)))
59 1 ThingCount Stack x2 -1 tid = Pop(); type = Pop(); Push(ThingCount(type, tid))
Opcode DWORDs Name Args Stack diff Logic
60 3 ThingCountDirect Imm x2 +1 type = imm0; tid = imm1; Push(ThingCount(type, tid))
61 1 TagWait Stack -1 Pop stack and set value to script waitValue; set script state to "waiting for tag"
62 2 TagWaitDirect Imm 0 Assign imm0 to script waitValue; set script state to "waiting for tag"
63 1 PolyWait Stack -1 Pop stack and assign value to script waitValue; set script state to "waiting for polyobject"
64 2 PolyWaitDirect Imm 0 Assign imm0 to script waitValue; set script state to "waiting for polyobject"
65 1 ChangeFloor Stack x2 -2 flat = strings[Pop()]; tag = Pop(); ChangeFloor(tag, flat)
66 3 ChangeFloorDirect Imm x2 0 tag = imm0; flat = strings[imm1]; ChangeFloor(tag, flat)
67 1 ChangeCeiling Stack x2 -2 flat = strings[Pop()]; tag = Pop(); ChangeCeiling(tag, flat)
68 3 ChangeCeilingDirect Imm x2 0 tag = imm0; flat = strings[imm1]; ChangeCeiling(tag, flat)
69 1 Restart - 0 Unconditional jump to beginning of script's bytecode
70 1 AndLogical Stack x2  ? Push(Pop() && Pop())[notes 3]
71 1 OrLogical Stack x2  ? Push(Pop() || Pop())[notes 3]
72 1 AndBitwise Stack x2 -1 Push(Pop() & Pop())
73 1 OrBitwise Stack x2 -1 Push(Pop() | Pop())
74 1 EorBitwise Stack x2 -1 Push(Pop() ^ Pop())
75 1 NegateLogical Stack x2 0 Push(!Pop())
76 1 LShift Stack x2 -1 t = Pop(); Push(Pop() << t)
77 1 RShift Stack x2 -1 t = Pop(); Push(Pop() >> t)
78 1 UnaryMinus Stack 0 Push(-Pop())
79 2 IfNotGoto Imm, Stack -1 Jump to immediate absolute offset if popped value is equal to zero
Opcode DWORDs Name Args Stack diff Logic
80 1 LineSide - +1 Pushes side (0 or 1) on which the linedef was activated to start this script (always 0 if not started by a linedef)
81 1 ScriptWait Stack -1 Pop value and assign to script waitValue; set script state to "waiting on script"
82 2 ScriptWaitDirect Imm 0 Assign immediate operand to script waitValue; set script state to "waiting on script"
83 1 ClearLineSpecial - 0 If this script was started from a linedef, that linedef's special will be set to 0.
84 3 CaseGoto Imm x2, Stack 0 or -1 Conditional jump for switch case statements; CaseGoto()
85 1 BeginPrint - 0 Clears print buffer
86 1 EndPrint - 0 If script activator is valid player, send accumulated print buffer to that player; otherwise, send to local client player (consoleplayer)
87 1 PrintString Stack -1 Concatenate strings[Pop()] with print buffer
88 1 PrintNumber Stack -1 Pop value, convert integer to string as by snprintf with "%d" format specifier, and concatenate with print buffer
89 1 PrintCharacter Stack -1 Pop value, convert to ASCII character, and concatenate with print buffer
90 1 PlayerCount - +1 Push number of valid players currently in game
91 1 GameType - +1 Push game type value: 0 == single player, 1 == cooperative, 2 == deathmatch
92 1 GameSkill - +1 Push current game skill value as value from 0 (baby) to 4 (extra hard, ie. Nightmare)
93 1 Timer - +1 Push the amount of tics that have passed in the current level (leveltime)
94 1 SectorSound Stack x2 -2 volume = Pop(); name = strings[Pop()]; SectorSound(name, volume)
95 1 AmbientSound Stack x2 -2 volume = Pop(); name = strings[Pop()]; S_StartSoundAtVolume(NULL, S_GetSoundID(name), volume)
96 1 SoundSequence Stack -1 name = strings[Pop()]; SoundSequence(name)
97 1 SetLineTexture Stack x4 -4 texture = strings[Pop()]; position = Pop(); side = Pop(); lineTag = Pop(); SetLineTexture(lineTag, side, position, texture)
98 1 SetLineBlocking Stack x2 -2 Pop blocking flag (0 or 1), pop line id. Set all linedefs matching the id to blocking or non-blocking depending on flag value (1 or 0).
99 1 SetLineSpecial Stack x7 -7 Pop arguments 4 through 0 in that order; pop special; pop line id. Set all linedefs matching the line id to have the indicated special and arguments.
100 1 ThingSound Stack x3 -3 volume = Pop(); sound = strings[Pop()]; tid = Pop(); ThingSound(tid, sound, volume)
101 1 EndPrintBold - 0 Send the accumulated print buffer to every valid player in the game as a yellow-colored important message
Opcode DWORDs Name Args Stack diff Logic

Notes[edit]

  1. Arguments for stack-based LSpec opcodes are placed in the args array starting from the highest index, meaning they should be pushed in ascending order (ie., 0, 1, 2, 3, 4).
  2. Arguments for immediate LSpecDirect opcodes are placed in the args array in the same order as they occur in the opcode.
  3. 3.0 3.1 As written in the vanilla executable, these operations are both subject to short-circuiting logic. As a result, they may potentially leave the stack in an unpredictable state (either 0 or -1) depending on whether the first value popped is 0 or 1.

Algorithms[edit]

ThingCount[edit]

; return count number of things on level of type, tid, or tid and type
function ThingCount(type, tid) -> integer: 
  if tid and type are both zero: return zero
  if tid is non-zero: 
    if thing type is non-zero: return number of things with tid and type
    else: return number of things with tid
  if tid is zero: 
    return number of things with type

ChangeFloor[edit]

; change all tagged sector floor textures to the indicated flat
; the game will crash if that flat does not exist
function ChangeFloor(tag, flat):
  let flatnum := R_FlatNumForName(flat)
  for sector in sectors:
    if sector.tag = tag:
       sector.floorpic := flatnum

ChangeCeiling[edit]

; change all tagged sector ceiling textures to the indicated flat
; the game will crash if that flat does not exist
function ChangeCeiling(tag, flat):
  let flatnum := R_FlatNumForName(flat)
  for sector in sectors:
    if sector.tag = tag:
       sector.ceilingpic := flatnum

CaseGoto[edit]

; test top of stack for equality with immediate value
; if matches, pop top of stack and set script IP to immediate offset (goto)
; otherwise, leave stack as-is for next case branch or explicit drop instruction
function CaseGoto():
  let value := imm0 
  let offset := imm1 
  if Top() = value: ; peeks top value on stack
    ip := Offset() 
    Drop() ; removes top value from stack

SectorSound[edit]

; play sound by name at volume either globally, or at the script activation
; line's front-sector sound origin if the script activation line is valid
function SectorSound(name, volume):
  let mobj := null
  if ACScript.line is not null:
    mobj := ACScript.line.frontsector.soundorg
  S_StartSoundAtVolume(mobj, S_GetSoundID(name), volume)

SoundSequence[edit]

; Start named sound sequence on the script activation line's front sector.
; SN_StartSequenceName will have no effect if passed mobj = null.
function SoundSequence(name):
  let mobj := null
  if ACScript.line is not null:
    mobj := ACScript.line.frontsector.soundorg
  SN_StartSequenceName(mobj, name)

SetLineTexture[edit]

; Set all linedefs' (with ID matching "tag") textures at indicated position (top, middle, or bottom)
; to the named texture
function SetLineTexture(tag, side, position, texture):
  let texnum := R_TextureNumForName(texture)
  for line in lines:
    if line.id = tag:
      if position = TEXTURE_MIDDLE: ; TEXTURE_MIDDLE = 1
        sides[line.sidenum[side]].midtexture := texnum
      else if position = TEXTURE_BOTTOM: ; TEXTURE_BOTTOM = 2
        sides[line.sidenum[side]].bottomtexture := texnum
      else ; TEXTURE_MIDDLE = 0
        sides[line.sidenum[side]].toptexture := texnum

ThingSound[edit]

; Play the named sound at indicated volume at every thing with the indicated tid
function ThingSound(tid, name, volume):
  let soundid := S_GetSoundID(name)
  for mobj in mobjs:
    if mobj.tid = tid:
      S_StartSoundAtVolume(mobj, soundid, volume)