Seven Towers: Usecode document

USECODE C SCRIPTS AND EXECUTE_USECODE_ARRAY INTRINSIC

ABOUT THIS DOCUMENT

This document can be freely distributed as long as its kept mostly intact. Any spelling mistakes can be corrected, but I'd appreciate being notified about them so the quality of the document can be improved...

A lot of the information in this file comes from the Exult source code, specifically from the 'ucparse.yy' and 'ucscriptop.h' files. Any errors are likely my fault, though.

For the most part, this document is a reference for Usecode C scripting commands and for the commands of 'execute_usecode_array' and 'delayed_execute_usecode_array'. I give a few examples of each syntax and I give a small guide about how to convert from 'execute_usecode_array' calls to Usecode C scripts for readability and ease of extension.

Any suggestions will be appreciated!

ABOUT USECODE C SCRIPTING AND THE EXECUTE_USECODE_ARRAY INTRINSIC

'execute_usecode_array' and Usecode C scripts are both used to generate scripted animations in Ultima VII/SI/Exult. They are responsible for the moongates from the Orb of the Moons rising out of the ground, swirl around for a while and sink back into the ground, for example, as well as the scene with Frigidazzi. Item iteractions also tipically have such animations, so it is a good idea to learn a bit of scripting/'execute_usecode_array' if you plan on working with usecode.

It is a good idea to also learn how to convert from 'execute_usecode_array' to Usecode C scripts since the latter are much more readable than the former...for example, in Erethian's usecode (from FoV) has the following 'execute_usecode_array' call:

  1.     (
  2.     item,
  3.     [0x6F, 0x27, 0x0001, 0x01, 0x52, "@An Ailem!", 0x27, 0x0003, 0x70, 0x27, 0x0006]
  4.     );

It is far from obvious what it actually does. Fortunately, we have an alternative to using the 'execute_usecode_array' intrinsic: Usecode C has a set of scripting functions which are converted to 'execute_usecode_array' calls upon compilation. The good point is that Usecode C scripts are much more readable and understandable than 'execute_usecode_array' calls, which only helps writing new usecode.

SCRIPT BLOCKS AND COMMANDS

A bit of nomenclature, for convenience: I will use 'opcode' to mean an element of an usecode array which determines what is to be done; all other elements of the usecode array will be called 'parameters'. An opcode with all its needed parameters will be called a 'command'.

Script blocks also have commands; they are related to opcodes, but there are more opcodes (barely) than there are script commands. I will use 'script commands' (or simply 'commands' when there is no ambiguity) to mean commands from script blocks as opposed to opcodes + parameters.

The basic syntax of a script block is:

script item opt_script_delay script_command

(note to those who already know Usecode C: I have included the ';' in the definition of script_command, so including it above would cause a compile error) for a block with a single command, or

script obj opt_script_delay {script_command_list};

for blocks with one or more commands. In both cases, we have that:

Also in both cases, spaces, tabs and line breaks between commands (but not within a command - there must be at least one tab or space between the root and parameters and between parameters) are ignored. It is good practice then (and helps readability of the code) if you add spaces and identation to clearly delineate each command.

Time now for the commands of the script block. There is (AFAIK) no real table of commands other than what can be gleaned from the ucparse.yy file from the Exult source. For this reason, I went through the trouble of compiling the following list and explaining what they do:

TABLE 1: USECODE C SCRIPT COMMANDS

COMMANDOPCODEDESCRIPTION
continue;0x01Continues script without painting
reset;0x0aResets the script back to the start. Be careful of using this with the nohalt opcode. Also, repeat will only work the first time around.
repeat int_exp script_command;0x0b, 0x0cExecutes the command(s) in script_command and then repeats them a number of times equal to int_exp
repeat int_exp, int_exp script_command;0x0cExecutes the command(s) in script_command and then repeats them a number of times equal to the first int_exp, resetting counter to the second int_exp when the counter reaches 0
nop;0x21Does nothing
nohalt;0x23Script cannot be halted by UI_halt_scheduled and does not prevent the actor from moving on its own
wait while near int_exp;0x24Stays on this opcode until the avatar gets further than int_exp tiles from the script object
wait int_exp;0x27Waits int_exp ticks
wait int_exp minutes;0x28Waits int_exp minutes
wait int_exp hours;0x29Waits int_exp hours
wait while far int_exp;0x2bStays on this opcode until the avatar gets closer than int_exp tiles from the script object
finish;0x2cFinishes script if item is killed?
remove;0x2dDeletes item and halts the script
descent;0x38Decreases lift (z coordinate) by 1
rise;0x39Increases lift (z coordinate) by 1
frame int_exp;0x46Sets the frame for item to int_exp
actor frame int_lit;0x61+int_litSets the frame for an NPC and keeps facing the same direction*
hatch;0x48If item is an egg, activates it
setegg int_exp, int_exp;0x49Changes an egg's activation conditions. The first int_exp sets the criteria, the second sets the distance
next frame;0x4dIncreases current frame by 1, stops at maximum
next frame cycle;0x4eIncreases current frame by 1, wrap to 0 at max
previous frame;0x4fDecreases current frame by 1, stops at 0
previous frame cycle;0x50Decreases current frame by 1, wrap to max at 0
say text_exp;0x52Actor says text_exp
step int_exp[, opt_int_exp];0x53Steps in direction specified by int_exp, with a delta-z optionally specified by opt_int_exp (assumed zero if missing)**
step dir;0x30+dirSteps in direction specified by dir***
music int_exp;0x54Plays music number int_exp
call fun;0x55Calls function fun with event = 2****
call fun, int_lit;0x80Calls function fun with event = int_lit****
speech int_exp;0x56Plays speech track number int_exp
sfx int_exp;0x58Plays sound effect number int_exp
face int_exp;0x59Turns to face direction int_exp**
weather int_exp;0x5aSets weather type to int_exp
hit int_exp;0x78Item attacked, loses int_exp hits
attack;0x7aAttacks using information from previous 'set_to_attack' intrinsic call
resurrect;0x81Brings actor back to life
*For the actor frame int_lit, Usecode C will convert int_lit will into a value between 0-15, so you may want to stick to these values.
**north = 0, northeast = 1, east = 2, etc. up to northwest = 7
***dir can be one of the following: north, ne, east, se, south, sw, west, nw
****A few common values for event:
  • 0: Object on-screen or nearby, called every few seconds
  • 1: Object is double-clicked
  • 2: Scripted, triggered by usecode
  • 3: Egg event
  • 4: Object is used as a weapon
  • 5: An item is readied (worn)
  • 6: An item is unreadied (removed)
  • 7: Died (SI only?)
  • 9: When an NPC wants to talk to you (SI only)

(yes, the ';'s are required) For future convenience, I've added an opcode equivalence for all commands. The parameters on the table have the following meanings:

As far as script blocks go, that is it. There are a few opcodes with no equivalence to Usecode C scripts (but we can bug the Exult devs to include in the future ;-)), but I will defer these to a later section.

To help fix the ideas above, a few exemples are in order:

Orb of the Moons' Moongate animation (the item moongate is assumed to exist). View all frames of shape 779 in Exult Studio to fully see what this script does. This script is slightly different from the original one in BG.

  1. script moongate
  2. {
  3.     //Set the initial frame (when it first appears in the ground):
  4.     frame 0;
  5.  
  6.     //The moongate has 12 frames (0-based); go through them all:
  7.     repeat 10 next frame cycle;;
  8.     //Note that there are TWO ";" in the above statement; one is from the
  9.     //repeat, while the other is from the command. Also, it is important
  10.     //to note that repeat will execute the commands once and then *repeat*
  11.     //them the specified number of times.
  12.  
  13.     //The moongate has fully risen, so now it is time to animate it;
  14.     //frames 4 to 11 can be cycled continuously.
  15.  
  16.     //We will display the full cycle 5 additional times:
  17.     repeat 5
  18.     {
  19.         //Return to the starting frame of the cycle:
  20.         frame 4;
  21.  
  22.         //Go through each frame of the animation. It is done this way
  23.         repeat 5    next frame cycle;;
  24.     };
  25.  
  26.     //Now time to make the moongate disappear; return to the first
  27.     //standing frame:
  28.     frame 4;
  29.  
  30.     //Decrement the frame until frame 0, which is when the moongate is
  31.     //about to vanish:
  32.  
  33.     //Delete the moongate, making it vanish:
  34.     remove;
  35. }

Loom animation. This is the animation of the loom and of the avatar when you double-click a thread then target the loom. The loom is shape 261. This example uses function 0x827, which has been aliased as getDirectionDirectionToObject.

  1. script item            //item is the loom
  2. {
  3.     //Set the initial frame:
  4.     frame 0;
  5.  
  6.     //Repeat the following commands 32 times each in sequence.
  7.     //The loom has 16 frames; starting from frame zero, every
  8.     //16 repeats returns the loom to frame zero; so the cycle
  9.     //ends in frame zero.
  10.     repeat 32
  11.     {
  12.         next frame cycle;
  13.         continue;
  14.         sfx 6;
  15.     };
  16.  
  17.     //After all the above is done, call again the Loom function
  18.     //to create the cloth:
  19.     call Loom;        //Loom function # is 261 = 0x0105
  20. }
  21.  
  22. //Determine which way the avatar must face:
  23. direction = getDirectionDirectionToObject(AVATAR, item);
  24.  
  25. script AVATAR
  26. {
  27.     //Turn the avatar to the appropriate direction:
  28.     face direction;
  29.  
  30.     //The "looming" animation of the avatar, repeated
  31.     //nine times.
  32.     repeat 9
  33.     {
  34.         continue;
  35.         actor frame 6;
  36.         actor frame 0;
  37.         wait 1;
  38.     };
  39. }

TRANSLATING FROM EXECUTE_USECODE_ARRAY CALLS TO USECODE C SCRIPTS

Time now to go about understanding 'execute_usecode_array' calls. The best way to do that is, unsurprisingly, to convert them to script blocks. I will now list the all the opcodes and give them friendly pseudo-names taken from 'ucscriptop.h'.

TABLE 2: UI_EXECUTE_USECODE_ARRAY OPCODES

COMMANDOPCODEDESCRIPTION
cont0x01Continues script without painting
reset0x0aResets the script back to the start. Be careful of using this with the nohalt opcode. Also, repeat will only work the first time around.
repeat(offset, cnt)0x0bRepeats all commands since offset opcodes/params ago cnt times*
repeat2(offset, cnt1, cnt2)0x0cLoops cnt1 times, each time repeating commands since offset opcodes/params ago cnt2 times*
nop0x21Does nothing.
dont_halt0x23Script cannot be halted by UI_halt_scheduled?
wait_while_near(dist)0x24Waits until avatar gets more than dist tiles away from script object
delay_ticks(cnt)0x27Waits cnt ticks
delay_minutes(cnt)0x28Waits cnt minutes
delay_hours(cnt)0x29Waits cnt hours
wait_while_far(dist)0x2bWaits until avatar gets closer than dist tiles away from script object
finish0x2cFinishes script if item is killed?
remove0x2dDeletes item and halts the script
step_n0x30Steps towards north
step_ne0x31Steps towards northeast
step_e0x32Steps towards east
step_se0x33Steps towards southeast
step_s0x34Steps towards south
step_sw0x35Steps towards southwest
step_w0x36Steps towards west
step_nw0x37Steps towards northwest
descent0x38Decreases lift (z coordinate) by 1
rise0x39Increases lift (z coordinate) by 1
frame(frnum)0x46Sets the frame for item to frnum
egg0x48If item is an egg, activates it
set_egg(crit, dist);0x49Changes an egg's activation conditions: 'crit' is the criteria, 'dist' the distance
next_frame_max0x4dIncreases current frame by 1, stops at maximum
next_frame0x4eIncreases current frame by 1, wrap to 0 at max
prev_frame_min0x4fDecreases current frame by 1, stops at 0
prev_frame0x50Decreases current frame by 1, wrap to max at 0
say(text)0x52Actor says text
step(dir, dz)0x53Steps in direction specified by dir (0-7), changing z coordinate by dz
music(tracknum)0x54Plays music number tracknum
usecode(fun)0x55Calls function fun with event = 2
speech(tracknum)0x56Plays speech number tracknum
sfx(sfxnum)0x58Plays sound effect number sfxnum
face_dir(dir)0x59Turns to face direction dir (0-7)
weather(type)0x5aSets weather type to type
npc_frame0x61-0x70Sets the frame for an NPC and keeps facing the same direction (new frame = opcode - 0x61)
hit(dam)0x78Item attacked, loses dam hits
attack0x7aAttacks using information from previous 'set_to_attack' intrinsic call
usecode2(fun, eventid)0x80Calls function fun with event = eventid
resurrect0x81Brings the actor back to life

* For a better explanation of these opcodes, see the examples below.

By looking at the tables, one sees that repeat2 is the only one without a Usecode C script equivalent. The last two opcodes (usecode2 and resurrect) are Exult only - they do not exist in the original games.

In all cases, the parameters are 2-byte hex numbers while the opcodes are single-byte hex numbers. This means that parameters should be written with four digits after the '0x'.

I will now give an example of how to translate between 'execute_usecode_array' and Usecode C scripts. I will begin with the example I picked from Erethian above; namely:

Erethian animation #1:

  1.     (
  2.     item,
  3.     [0x6F, 0x27, 0x0001, 0x01, 0x52, "@An Ailem!", 0x27, 0x0003, 0x70, 0x27, 0x0006]
  4.     );

The first thing I will do is identify parameters and opcodes. As I said, parameters are 2-byte hex numbers; so the opcodes above are 0x6F, 0x01, 0x52, 0x27, 0x70 and 0x27 (again). This means that the above block is executed along the lines of:

  1.     (
  2.     item,
  3.     [0x6F,
  4.      0x27(0x0001),
  5.      0x01,
  6.      0x52("@An Ailem!"),
  7.      0x27(0x0003),
  8.      0x70,
  9.      0x27(0x0006)]
  10.     );

Were (say) 0x27(0x0001) means 'do 0x27 with 0x0001 as parameter'. Now we begin the conversion process: since we are dealing with 'execute_usecode_array', we don't have a delay to worry about. So our script block begins with 'script item'. Looking at Table 1, above, we see that 0x6F (= 0x61 + 0x0E) converts to 'actor frame 0x0E;', 0x27(num) converts to 'wait num;', 0x01 converts to 'continue;', 0x52("@An Ailem!") converts to 'say "@An Ailem!";' and 0x70 becomes 'actor frame 0x0F;'. Thus:

  1. script item
  2. {
  3.     actor frame 14;        //14 = 0x0E
  4.     wait 1;
  5.     continue;
  6.     say "@An Ailem!";
  7.     wait 3;
  8.     actor frame 15;        //15 = 0x0F
  9.     wait 6;
  10. }

That is, Erethian will raise his arms (frame 14 = 0x0E) and keep looking in the same direction, then he waits 1 tick, says "@An Ailem!", waits 3 more ticks, spreads his arms (frame 15 = 0x0F) without changing facing and wait 6 more ticks. The '@', by the way, is an escape character for double quotes (i.e., it will be replaced by a double-quote when it is about to be displayed).

An example of a script with a delay is also found in Erethian's usecode:

Erethian animation #2, delayed usecode:

  1.     (
  2.     item,
  3.     [0x23, 0x52, "@I'll follow it.@"],
  4.     0x0012
  5.     );

'delayed_execute_usecode_array' calls will always convert to scripts with a delay; the delay is equal to the third parameter in the call. Proceeding as above, we have

  1. script item after 18 ticks        //18 = 0x0012
  2. {
  3.     nohalt;
  4.     say "@I'll follow it.@";
  5. }

Simple enough, right? Lets now work with an example involving a loop. For this example, I will take the usecode for a loom (shape 261); in it, we have following usecode:

Loom animation (shape 261):

  1.     (
  2.     item,
  3.     [0x46, 0x0000, 0x4E, 0x01, 0x58, 0x0006, 0x0B, 0xFFFC, 0x0020, 0x55, 0x0105]
  4.     );
  5. direction = getDirectionDirectionToObject(AVATAR, item);
  6.     (
  7.     AVATAR,
  8.     [0x59, direction, 0x01, 0x67, 0x61, 0x27, 0x0001, 0x0B, 0xFFFB, 0x0009]
  9.     );

(getDirectionDirectionToObject is function 0x827) Separating the opcodes, we get

  1.     (
  2.     item,
  3.     [0x46(0x0000),
  4.      0x4E,
  5.      0x01,
  6.      0x58(0x0006),
  7.      0x0B(0xFFFC, 0x0020),
  8.      0x55(0x0105)]
  9.     );
  10. direction = getDirectionDirectionToObject(AVATAR, item);
  11.     (
  12.     AVATAR,
  13.     [0x59(direction),
  14.      0x01,
  15.      0x67,
  16.      0x61,
  17.      0x27(0x0001),
  18.      0x0B(0xFFFB, 0x0009)]
  19.     );

Now, in hex, 0xFFFC = -4 (since 0x10000 - 4 = 0xFFFC), and 0xFFFB = -5, so we have (using the friendly names defined above) that

  1.     (
  2.     item,
  3.     [frame(0x0000),
  4.      next_frame,
  5.      cont,
  6.      sfx(0x0006),
  7.      repeat(-4, 0x0020),
  8.      usecode(0x0105)]
  9.     );
  10. direction = getDirectionDirectionToObject(AVATAR, item);
  11.     (
  12.     AVATAR,
  13.     [face_dir(direction),
  14.      cont,
  15.      npcframe_6,
  16.      npcframe_0,
  17.      delay_ticks(0x0001),
  18.      repeat(-5, 0x0009)]
  19.     );

Since 0x0105 = 261, usecode(0x0105) is calling the loom function again. Time now to work out what the repeat means before proceeding any further. I know that the explanation from the table isn't very enlightening on what the offset means, which is why I chose this example.

The 'prototype' of repeat is repeat(offset, cnt). The offset determines how many opcodes and parameters before the repeat instruction will be repeated, while the cnt indicates how many times they will be repeated. For example, sfx(0x0006) is 1 opcode and 1 parameter, while cont and next_frame are 1 opcode each. Thus, repeat(-4, 0x0020) will repeat the commands next_frame, cont and sfx(0x0006) 32 (=0x0020) times in that order. The repeat(-5, 0x0009) will likewise repeat the commands from cont to delay_ticks(0x0001) (remember that npcframe_0 and npcframe_6 are 1 opcode and no parameters each) 9 (=0x0009) times each. Putting it all together, we end up with:

  1. script item            //item is the loom
  2. {
  3.     //Set the initial frame:
  4.     frame 0;
  5.  
  6.     //Repeat the following commands 32 times each in sequence.
  7.     //The loom has 16 frames; starting from frame zero, every
  8.     //16 repeats returns the loom to frame zero; so the cycle
  9.     //ends in frame zero.
  10.     repeat 32
  11.     {
  12.         next frame cycle;
  13.         continue;
  14.         sfx 6;
  15.     };
  16.  
  17.     //After all the above is done, call again the Loom function
  18.     //to create the cloth:
  19.     call Loom;        //Loom function # is 261 = 0x0105
  20. }
  21.  
  22. //Determine which way the avatar must face:
  23. direction = getDirectionDirectionToObject(AVATAR, item);
  24.  
  25. script AVATAR
  26. {
  27.     //Turn the avatar to the appropriate direction:
  28.     face direction;
  29.  
  30.     //The "looming" animation of the avatar, repeated
  31.     //nine times.
  32.     repeat 9
  33.     {
  34.         continue;
  35.         actor frame 6;
  36.         actor frame 0;
  37.         wait 1;
  38.     };
  39. }

The above code is equivalent to the original loom code with 'execute_usecode_array', and is the same as the one I presented above as an example. All that remains now is to implement the rest of the Loom function and we could even compile it.