================================================================================ 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: UI_execute_usecode_array ( item, [0x6F, 0x27, 0x0001, 0x01, 0x52, "@An Ailem!", 0x27, 0x0003, 0x70, 0x27, 0x0006] ); 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: * obj can be any object or NPC and is *required*; * opt_script_delay is an optional delay in ticks, and has syntax 'after XX ticks', with XX being the delay in ticks; * script_command is a (script) command which MUST terminate in a ';'; * script_command_list is a list of one or more script_commands, each of which MUST have a terminating ';'. 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 -------------------------------------------------------------------------------- *COMMAND* *OPCODE* *DESCRIPTION* continue; 0x01 Continues script without painting reset; 0x0a Resets 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, 0x0c Executes 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; 0x0c Executes 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; 0x21 Does nothing nohalt; 0x23 Script cannot be halted by UI_halt_scheduled and does not prevent the actor from moving on its own wait while near int_exp; 0x24 Stays on this opcode until the avatar gets further than int_exp tiles from the script object wait int_exp; 0x27 Waits int_exp ticks wait int_exp minutes; 0x28 Waits int_exp minutes wait int_exp hours; 0x29 Waits int_exp hours wait while far int_exp; 0x2b Stays on this opcode until the avatar gets closer than int_exp tiles from the script object finish; 0x2c Finishes script if item is killed? remove; 0x2d Deletes item and halts the script descent; 0x38 Decreases lift (z coordinate) by 1 rise; 0x39 Increases lift (z coordinate) by 1 frame int_exp; 0x46 Sets the frame for item to int_exp actor frame int_lit; 0x61+int_lit Sets the frame for an NPC and keeps facing the same direction* hatch; 0x48 If item is an egg, activates it setegg int_exp, int_exp; 0x49 Changes an egg's activation conditions. The first int_exp sets the criteria, the second sets the distance next frame; 0x4d Increases current frame by 1, stops at maximum next frame cycle; 0x4e Increases current frame by 1, wrap to 0 at max previous frame; 0x4f Decreases current frame by 1, stops at 0 previous frame cycle; 0x50 Decreases current frame by 1, wrap to max at 0 say text_exp; 0x52 Actor says text_exp step int_exp[, opt_int_exp]; 0x53 Steps in direction specified by int_exp, with a delta-z optionally specified by opt_int_exp (assumed zero if missing)** step dir; 0x30+dir Steps in direction specified by dir*** music int_exp; 0x54 Plays music number int_exp call fun; 0x55 Calls function fun with event = 2**** call fun, int_lit; 0x80 Calls function fun with event = int_lit**** speech int_exp; 0x56 Plays speech track number int_exp sfx int_exp; 0x58 Plays sound effect number int_exp face int_exp; 0x59 Turns to face direction int_exp** weather int_exp; 0x5a Sets weather type to int_exp hit int_exp; 0x78 Item attacked, loses int_exp hits attack; 0x7a Attacks using information from previous 'set_to_attack' intrinsic call resurrect; 0x81 Brings 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: * int_lit: Any number; * int_exp: Any number or expression that evaluates to a number (such as constants or even functions); * text_exp: Any string or constant string; * fun: Any function name or number; * script_command: for repeat, a list of one or more commands terminating in a ';'. For more than one command, script_command MUST be enclosed in clurly braces '{' and '}'. 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. script moongate { //Set the initial frame (when it first appears in the ground): frame 0; //The moongate has 12 frames (0-based); go through them all: repeat 10 next frame cycle;; //Note that there are TWO ";" in the above statement; one is from the //repeat, while the other is from the command. Also, it is important //to note that repeat will execute the commands once and then *repeat* //them the specified number of times. //The moongate has fully risen, so now it is time to animate it; //frames 4 to 11 can be cycled continuously. //We will display the full cycle 5 additional times: repeat 5 { //Return to the starting frame of the cycle: frame 4; //Go through each frame of the animation. It is done this way repeat 5 next frame cycle;; }; //Now time to make the moongate disappear; return to the first //standing frame: frame 4; //Decrement the frame until frame 0, which is when the moongate is //about to vanish: repeat 3 previous frame cycle;; //Delete the moongate, making it vanish: remove; } 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. script item //item is the loom { //Set the initial frame: frame 0; //Repeat the following commands 32 times each in sequence. //The loom has 16 frames; starting from frame zero, every //16 repeats returns the loom to frame zero; so the cycle //ends in frame zero. repeat 32 { next frame cycle; continue; sfx 6; }; //After all the above is done, call again the Loom function //to create the cloth: call Loom; //Loom function # is 261 = 0x0105 } //Determine which way the avatar must face: direction = getDirectionDirectionToObject(AVATAR, item); script AVATAR { //Turn the avatar to the appropriate direction: face direction; //The "looming" animation of the avatar, repeated //nine times. repeat 9 { continue; actor frame 6; actor frame 0; wait 1; }; } -------------------------------------------------------------------------------- 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 -------------------------------------------------------------------------------- *COMMAND* *OPCODE* *DESCRIPTION* cont 0x01 Continues script without painting reset 0x0a Resets 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) 0x0b Repeats all commands since offset opcodes/params ago cnt times* repeat2(offset, cnt1, cnt2) 0x0c Loops cnt1 times, each time repeating commands since offset opcodes/params ago cnt2 times* nop 0x21 Does nothing. dont_halt 0x23 Script cannot be halted by UI_halt_scheduled? wait_while_near(dist) 0x24 Waits until avatar gets more than dist tiles away from script object delay_ticks(cnt) 0x27 Waits cnt ticks delay_minutes(cnt) 0x28 Waits cnt minutes delay_hours(cnt) 0x29 Waits cnt hours wait_while_far(dist) 0x2b Waits until avatar gets closer than dist tiles away from script object finish 0x2c Finishes script if item is killed? remove 0x2d Deletes item and halts the script step_n 0x30 Steps towards north step_ne 0x31 Steps towards northeast step_e 0x32 Steps towards east step_se 0x33 Steps towards southeast step_s 0x34 Steps towards south step_sw 0x35 Steps towards southwest step_w 0x36 Steps towards west step_nw 0x37 Steps towards northwest descent 0x38 Decreases lift (z coordinate) by 1 rise 0x39 Increases lift (z coordinate) by 1 frame(frnum) 0x46 Sets the frame for item to frnum egg 0x48 If item is an egg, activates it set_egg(crit, dist); 0x49 Changes an egg's activation conditions: 'crit' is the criteria, 'dist' the distance next_frame_max 0x4d Increases current frame by 1, stops at maximum next_frame 0x4e Increases current frame by 1, wrap to 0 at max prev_frame_min 0x4f Decreases current frame by 1, stops at 0 prev_frame 0x50 Decreases current frame by 1, wrap to max at 0 say(text) 0x52 Actor says text step(dir, dz) 0x53 Steps in direction specified by dir (0-7), changing z coordinate by dz music(tracknum) 0x54 Plays music number tracknum usecode(fun) 0x55 Calls function fun with event = 2 speech(tracknum) 0x56 Plays speech number tracknum sfx(sfxnum) 0x58 Plays sound effect number sfxnum face_dir(dir) 0x59 Turns to face direction dir (0-7) weather(type) 0x5a Sets weather type to type npc_frame 0x61-0x70 Sets the frame for an NPC and keeps facing the same direction (new frame = opcode - 0x61) hit(dam) 0x78 Item attacked, loses dam hits attack 0x7a Attacks using information from previous 'set_to_attack' intrinsic call usecode2(fun, eventid) 0x80 Calls function fun with event = eventid resurrect 0x81 Brings 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: UI_execute_usecode_array ( item, [0x6F, 0x27, 0x0001, 0x01, 0x52, "@An Ailem!", 0x27, 0x0003, 0x70, 0x27, 0x0006] ); 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: UI_execute_usecode_array ( item, [0x6F, 0x27(0x0001), 0x01, 0x52("@An Ailem!"), 0x27(0x0003), 0x70, 0x27(0x0006)] ); 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: script item { actor frame 14; //14 = 0x0E wait 1; continue; say "@An Ailem!"; wait 3; actor frame 15; //15 = 0x0F wait 6; } 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: UI_delayed_execute_usecode_array ( item, [0x23, 0x52, "@I'll follow it.@"], 0x0012 ); '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 script item after 18 ticks //18 = 0x0012 { nohalt; say "@I'll follow it.@"; } 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): UI_execute_usecode_array ( item, [0x46, 0x0000, 0x4E, 0x01, 0x58, 0x0006, 0x0B, 0xFFFC, 0x0020, 0x55, 0x0105] ); direction = getDirectionDirectionToObject(AVATAR, item); UI_execute_usecode_array ( AVATAR, [0x59, direction, 0x01, 0x67, 0x61, 0x27, 0x0001, 0x0B, 0xFFFB, 0x0009] ); (getDirectionDirectionToObject is function 0x827) Separating the opcodes, we get UI_execute_usecode_array ( item, [0x46(0x0000), 0x4E, 0x01, 0x58(0x0006), 0x0B(0xFFFC, 0x0020), 0x55(0x0105)] ); direction = getDirectionDirectionToObject(AVATAR, item); UI_execute_usecode_array ( AVATAR, [0x59(direction), 0x01, 0x67, 0x61, 0x27(0x0001), 0x0B(0xFFFB, 0x0009)] ); Now, in hex, 0xFFFC = -4 (since 0x10000 - 4 = 0xFFFC), and 0xFFFB = -5, so we have (using the friendly names defined above) that UI_execute_usecode_array ( item, [frame(0x0000), next_frame, cont, sfx(0x0006), repeat(-4, 0x0020), usecode(0x0105)] ); direction = getDirectionDirectionToObject(AVATAR, item); UI_execute_usecode_array ( AVATAR, [face_dir(direction), cont, npcframe_6, npcframe_0, delay_ticks(0x0001), repeat(-5, 0x0009)] ); 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: script item //item is the loom { //Set the initial frame: frame 0; //Repeat the following commands 32 times each in sequence. //The loom has 16 frames; starting from frame zero, every //16 repeats returns the loom to frame zero; so the cycle //ends in frame zero. repeat 32 { next frame cycle; continue; sfx 6; }; //After all the above is done, call again the Loom function //to create the cloth: call Loom; //Loom function # is 261 = 0x0105 } //Determine which way the avatar must face: direction = getDirectionDirectionToObject(AVATAR, item); script AVATAR { //Turn the avatar to the appropriate direction: face direction; //The "looming" animation of the avatar, repeated //nine times. repeat 9 { continue; actor frame 6; actor frame 0; wait 1; }; } 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.