ACS (Action Coding Script?) is a compiled scripting language that works along side with the Hexen map format. The compiled scripts are stored in a map's BEHAVIOR lump in a binary format decoded by the zdoom/hexen engines. The license the ACS interpreter was released under is not GPL complaint, so there is no ACS support in Odamex. Here is some info I dug which can aid in rewriting the interpreter.
ACS Specifications
ACS File Format
(All numbers are little-endian and 32 bits long) (Pointers are relative to the beginning of the lump)
Bytes 0 - 3 | ACS\0' (0x00534341) |
---|---|
Bytes 4 - 7 | Pointer to script and text pointers |
Bytes 8 -... | Varies |
Pointer Table
The first dword is a count of the number of scripts in the lump. It is immediately followed by an entry for each script in the lump. These entries are of the format:
DWORD 0 This script's number DWORD 1 A pointer to the start of the script DWORD 2 The number of parameters the script accepts
These are then followed by a dword indicating how many different strings are in the script. The remaining dwords contain pointers to each of the strings.
Script Codes
Compiled ACS scripts do not distinguish between their arguments and their local variables. When a script is executed, it's parameters are copied to its first x local variables, where x is the number of parameters the script takes. The ACC compiler will output an error for any script with more than 10 local variables (including parameters), so it's probably safe to allocate enough space for only 10 variables in an ACS interpreter.
There are 64 world variables available (numbered 0-63) that are accessible from scripts executed from any map in a single hub. Each map can also have 32 map variables accessible to all scripts in that map, but not by scripts in other maps.
ACS's internal functions are actually opcodes. Some use arguments on the stack, and others have their arguments immediately following them in the code. For those that use arguments on the stack, the arguments are first pushed on to the stack in sequence, and then the function's opcode is stored. If the function uses a string as an argument, then the string's index is pushed onto the stack as a number. Functions are responsible for popping the arguments passed to them before they return. The first value pushed is the function's first parameter, the second value pushed is the second parameter, etc.
#0: PCD_NOP #1: PCD_TERMINATE #2: PCD_SUSPEND #3: PCD_PUSHNUMBER x #4: PCD_LSPEC1 x #5: PCD_LSPEC2 x #6: PCD_LSPEC3 x #7: PCD_LSPEC4 x #8: PCD_LSPEC5 x #9: PCD_LSPEC1DIRECT x a #10: PCD_LSPEC2DIRECT x a b #11: PCD_LSPEC3DIRECT x a b c #12: PCD_LSPEC4DIRECT x a b c d #13: PCD_LSPEC5DIRECT x a b c d e #14: PCD_ADD #15: PCD_SUBTRACT #16: PCD_MULTIPLY #17: PCD_DIVIDE #18: PCD_MODULUS #19: PCD_EQ #20: PCD_NE #21: PCD_LT #22: PCD_GT #23: PCD_LE #24: PCD_GE #25: PCD_ASSIGNSCRIPTVAR x #26: PCD_ASSIGNMAPVAR x #27: PCD_ASSIGNWORLDVAR x #28: PCD_PUSHSCRIPTVAR x #29: PCD_PUSHMAPVAR x #30: PCD_PUSHWORLDVAR x #31: PCD_ADDSCRIPTVAR x #32: PCD_ADDMAPVAR x #33: PCD_ADDWORLDVAR x #34: PCD_SUBSCRIPTVAR x #35: PCD_SUBMAPVAR x #36: PCD_SUBWORLDVAR x #37: PCD_MULSCRIPTVAR x #38: PCD_MULMAPVAR x #39: PCD_MULWORLDVAR x #40: PCD_DIVSCRIPTVAR x #41: PCD_DIVMAPVAR x #42: PCD_DIVWORLDVAR x #43: PCD_MODSCRIPTVAR x #44: PCD_MODMAPVAR x #45: PCD_MODWORLDVAR x #46: PCD_INCSCRIPTVAR x #47: PCD_INCMAPVAR x #48: PCD_INCWORLDVAR x #49: PCD_DECSCRIPTVAR x #50: PCD_DECMAPVAR x #51: PCD_DECWORLDVAR x #52: PCD_GOTO x #53: PCD_IFGOTO x #54: PCD_DROP #55: PCD_DELAY #56: PCD_DELAYDIRECT x #57: PCD_RANDOM #58: PCD_RANDOMDIRECT x y #59: PCD_THINGCOUNT #60: PCD_THINGCOUNTDIRECT x y #61: PCD_TAGWAIT #62: PCD_TAGWAITDIRECT x #63: PCD_POLYWAIT #64: PCD_POLYWAITDIRECT x #65: PCD_CHANGEFLOOR #66: PCD_CHANGEFLOORDIRECT x y #67: PCD_CHANGECEILING #68: PCD_CHANGECEILINGDIRECT x y #69: PCD_RESTART #70: PCD_ANDLOGICAL #71: PCD_ORLOGICAL #72: PCD_ANDBITWISE #73: PCD_ORBITWISE #74: PCD_EORBITWISE #75: PCD_NEGATELOGICAL #76: PCD_LSHIFT #77: PCD_RSHIFT #78: PCD_UNARYMINUS #79: PCD_IFNOTGOTO x #80: PCD_LINESIDE #81: PCD_SCRIPTWAIT #82: PCD_SCRIPTWAITDIRECT x #83: PCD_CLEARLINESPECIAL #84: PCD_CASEGOTO x y #85: PCD_BEGINPRINT #86: PCD_ENDPRINT #87: PCD_PRINTSTRING #88: PCD_PRINTNUMBER #89: PCD_PRINTCHARACTER #90: PCD_PLAYERCOUNT #91: PCD_GAMETYPE #92: PCD_GAMESKILL #93: PCD_TIMER #94: PCD_SECTORSOUND #95: PCD_AMBIENTSOUND #96: PCD_SOUNDSEQUENCE #97: PCD_SETLINETEXTURE #98: PCD_SETLINEBLOCKING #99: PCD_SETLINESPECIAL #100: PCD_THINGSOUND #101: PCD_ENDPRINTBOLD
- 1: PCD_TERMINATE
Terminates script execution.
- 3: PCD_PUSHNUMBER x
Push x onto the stack.
- 4: PCD_LSPEC1 x
Execute line special x. It takes one argument on the stack.
- 5: PCD_LSPEC2 x
Execute line special x. It takes two arguments on the stack.
- 6: PCD_LSPEC3 x
Execute line special x. It takes three argument on the stack.
- 7: PCD_LSPEC4 x
Execute line special x. It takes four argument on the stack.
- 8: PCD_LSPEC5 x
Execute line special x. It takes five argument on the stack.
- 9: PCD_LSPEC1DIRECT x a
Execute line special x (a).
- 10: PCD_LSPEC2DIRECT x a b
Execute line special x (a, b).
- 11: PCD_LSPEC3DIRECT x a b c
Execute line special x (a, b, c).
- 12: PCD_LSPEC4DIRECT x a b c d
Execute line special x (a, b, c, d).
- 13: PCD_LSPEC5DIRECT x a b c d e
Execute line special x (a, b, c, d, e).
- 14: PCD_ADD
Stack before: int val1 int val2 Stack after: int (val1 + val2)
- 15: PCD_SUBTRACT
Stack before: int val1 int val2 Stack after: int (val1 - val2)
- 16: PCD_MULTIPLY
Stack before: int val1 int val2 Stack after: int (val1 * val2)
- 17: PCD_DIVIDE
Stack before: int val1 int val2 Stack after: int (val1 / val2)
- 18: PCD_MODULUS
Stack before: int val1 int val2 Stack after: int (val1 % val2)
- 19: PCD_EQ
Stack before: int val1 int val2 Stack after: (val1 == val2)
- 20: PCD_NE
Stack before: int val1 int val2 Stack after: (val1 != val2)
- 21: PCD_LT
Stack before: int val1 int val2 Stack after: (val1 < val2)
- 22: PCD_GT
Stack before: int val1 int val2 Stack after: (val1 > val2)
- 23: PCD_LE
Stack before: int val1 int val2 Stack after: (val1 <= val2)
- 24: PCD_GE
Stack before: int val1 int val2 Stack after: (val1 >= val2)
- 25: PCD_ASSIGNSCRIPTVAR x
Stack before: int val Store val in script var x.
- 26: PCD_ASSIGNMAPVAR x
Stack before: int val Store val in map var x.
- 27: PCD_ASSIGNWORLDVAR x
Stack before: int val Store val in world var x.
- 28: PCD_PUSHSCRIPTVAR x
Push value of script var x onto the stack.
- 29: PCD_PUSHMAPVAR x
Push value of map var x onto the stack.
- 30: PCD_PUSHWORLDVAR x
Push value of world var x onto the stack.
- 31: PCD_ADDSCRIPTVAR x
Stack before: int val script var x += val
- 32: PCD_ADDMAPVAR x
Stack before: int val map var x += val
- 33: PCD_ADDWORLDVAR x
Stack before: int val world var x += val
- 34: PCD_SUBSCRIPTVAR x
Stack before: int val script var x -= val
- 35: PCD_SUBMAPVAR x
Stack before: int val map var x -= val
- 36: PCD_SUBWORLDVAR x
Stack before: int val world var x -= val
- 37: PCD_MULSCRIPTVAR x
Stack before: int val script var x *= val
- 38: PCD_MULMAPVAR x
Stack before: int val map var x *= val
- 39: PCD_MULWORLDVAR x
Stack before: int val world var x *= val
- 40: PCD_DIVSCRIPTVAR x
Stack before: int val script var x /= val
- 41: PCD_DIVMAPVAR x
Stack before: int val map var x /= val
- 42: PCD_DIVWORLDVAR x
Stack before: int val world var x /= val
- 43: PCD_MODSCRIPTVAR x
Stack before: int val script var x %= val
- 44: PCD_MODMAPVAR x
Stack before: int val map var x %= val
- 45: PCD_MODWORLDVAR x
Stack before: int val world var x %= val
- 46: PCD_INCSCRIPTVAR x
script var x += 1
- 47: PCD_INCMAPVAR x
map var x += 1
- 48: PCD_INCWORLDVAR x
world var x += 1
- 49: PCD_DECSCRIPTVAR x
script var x -= 1
- 50: PCD_DECMAPVAR x
map var x -= 1
- 51: PCD_DECWORLDVAR x
world varx -= 1
- 52: PCD_GOTO x
Jump to offset x (relative to beginning of object file).
- 53: PCD_IFGOTO x
Stack before: int boolean If the boolean is true, jump to offset x.
- 54: PCD_DROP
Stack before: int anything Pop one number off the stack and discard it.
- 55: PCD_DELAY
Stack before: int tics Pop a number off the stick and delay for that many tics.
- 56: PCD_DELAYDIRECT x
Delay for x tics.
- 57: PCD_RANDOM
Stack before: int minval int maxval Stack after: int random(minval, maxval)
- 58: PCD_RANDOMDIRECT x y
Stack after: int random(x, y)
- 59: PCD_THINGCOUNT
Stack before: int type int tid Stack after: int thingcount(type,tid)
- 60: PCD_THINGCOUNTDIRECT x y
Stack after: int thingcount(x, y)
- 61: PCD_TAGWAIT
Stack before: int tag Pops a tag value off the stack and suspends execution of the current script until all sectors with a matching tag value are inactive.
- 62: PCD_TAGWAITDIRECT x
Waits for all sectors with tag x to become inactive before continuing execution of the current script.
- 63: PCD_POLYWAIT
Stack before: int po Suspends execution of the current script until polyobj po is inactive.
- 64: PCD_POLYWAITDIRECT x
Suspends execution of the current script until polyobj x is inactive.
- 65: PCD_CHANGEFLOOR
Stack before: int tag str flatname Sets the floor flat of all sectors tagged with tag to flatname.
- 66: PCD_CHANGEFLOORDIRECT x y
Sets the floor flat of all sectors tagged with x to y.
- 67: PCD_CHANGECEILING
Stack before: int tag str flatname Sets the ceiling flat of all sectors tagged with tag to flatname.
- 68: PCD_CHANGECEILINGDIRECT x y
Sets the ceiling flat of all sectors tagged with x to y.
- 69: PCD_RESTART
Restart execution of the current script from the beginning.
- 70: PCD_ANDLOGICAL
Stack before: int boolean1 int boolean2 Stack after: (boolean1 && boolean2)
- 71: PCD_ORLOGICAL
Stack before: int boolean1 int boolean2 Stack after: (boolean1 || boolean2)
- 72: PCD_ANDBITWISE
Stack before: int param1 int param2 Stack after int (param1 & param2)
- 73: PCD_ORBITWISE
Stack before: int param1 int param2 Stack after: int (param1 | param2)
- 74: PCD_EORBITWISE
Stack before: int param1 int param2 Stack after: int (param1 ^ param2)
- 75: PCD_NEGATELOGICAL
Stack before: int val Stack after: int (!val)
- 76: PCD_LSHIFT
Stack before: int a int b Stack after: int (a << b)
- 77: PCD_RSHIFT
Stack before: int a int b Stack after: int (a >> b)
- 78: PCD_UNARYMINUS
Stack before: int val Stack after: int (-val)
- 79: PCD_IFNOTGOTO x
Stack before: int boolean If boolean is false, jump to offset x.
- 80: PCD_LINESIDE
Stack after: int lineside lineside is a numerical parameter indicating which side of the line activated this script.
- 81: PCD_SCRIPTWAIT
Stack before: int scr Suspend execution of the current script until script scr has terminated.
- 82: PCD_SCRIPTWAITDIRECT x
Suspend execution of the current script until script x has terminated.
- 83: PCD_CLEARLINESPECIAL
Clears the special of the activating line.
- 84: PCD_CASEGOTO x y
Stack before: int val Stack after: int val (if not taken)
If the val == x, pop it and jump to offset y.
- 85: PCD_BEGINPRINT
Prepare to build a string to output to the screen by creating a new empty working string.
- 86: PCD_ENDPRINT
Output the current working string to the local machine's screen.
- 87: PCD_PRINTSTRING
Stack before: str string Pop a string index off the stack and append it to the current working string.
- 88: PCD_PRINTNUMBER
Stack before: int num Append num to the current working string in its ASCII representation.
- 89: PCD_PRINTCHARACTER
Stack before: int char Append the ASCII character char to the current working string.
- 90: PCD_PLAYERCOUNT
Stack after: int playercount playercount is the number of players in the game.
- 91: PCD_GAMETYPE
Stack after: int gametype gametype represents the current game type.
- 92: PCD_GAMESKILL
Stack after: int gameskill gameskill is the current game skill.
- 93: PCD_TIMER
Stack after: int gametime gametime is the current level time in tics.
- 94: PCD_SECTORSOUND
Stack before: str name int volume Pops two values off the stack and plays a sound in the sector pointed at by this script's activating linedef.
- 95: PCD_AMBIENTSOUND
Stack before: str snd int vol Plays the sound snd at volume vol (0-127) on the local machine.
- 96: PCD_SOUNDSEQUENCE
Stack before: str seq Plays the sound sequence seq in the facing sector.
- 97: PCD_SETLINETEXTURE
Stack before: int line int side int position str texturename
- 98: PCD_SETLINEBLOCKING
Stack before: int line int blocking
- 99: PCD_SETLINESPECIAL
Stack before: int line int special int arg1 int arg2 int arg3 int arg4 int arg5 Sets the line special and args for all matching lines.
- 100: PCD_THINGSOUND
Stack before: int tid str name int volume
Plays a sound at all things marked with tid.
- 101: PCD_ENDPRINTBOLD
Print the current working string to the screen of all computers in the game.