ACS0 instruction set

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 integers (or DWORDs). In the vanilla executable, undefined instructions, invalid variable indices, and under- or s 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, meaning that there are no explicit data s. 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 operators and syntax is required for some portions of the table below.

ThingCount
; 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
; 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
; 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
; 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
; 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
; 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
; 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
; 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)