This is version 1.0 of the_guide.text. Author: Linus Akesson, lairfight@sofhome.net, http://linusworld.cjb.net. THE SOURCE CODE OF STRANDED an adventure in itself. For those of you who don't know what Stranded is, I've included the original archive with the TI-83 binaries. Please consider playing, if not completing, the game before entering the realms of the source code. I hope this guide will be helpful in understanding the inner workings of the game, and wish all my readers a pleasant and interesting journey. Linus Let us begin with the directory layout of this archive. stranded_source (dir) source (dir) txt (dir) txt0.asm txt0.asm.bin txt0.asm.caz txt0.asm.pak txt1.asm txt1.asm.bin txt1.asm.caz txt1.asm.pak txt2.asm txt2.asm.bin txt2.asm.caz txt2.asm.pak txt3.asm txt3.asm.bin txt3.asm.caz txt3.asm.pak txt4.asm txt4.asm.bin txt4.asm.caz txt4.asm.pak txt5.asm txt5.asm.bin txt5.asm.caz txt5.asm.pak txt6.asm txt6.asm.bin txt6.asm.caz txt6.asm.pak txt7.asm txt7.asm.bin txt7.asm.caz txt7.asm.pak txt8.asm txt8.asm.bin txt8.asm.caz txt8.asm.pak txt9.asm txt9.asm.bin txt9.asm.caz txt9.asm.pak txtA.asm txtA.asm.bin txtA.asm.caz txtA.asm.pak txtB.asm txtB.asm.bin txtB.asm.caz txtB.asm.pak txtC.asm txtC.asm.bin txtC.asm.caz txtC.asm.pak bartender_talk bartender_talk.caz bartender_talk.raw bar_cheese bar_cheese.caz bar_cheese.raw bar_nomug bar_nomug.caz bar_nomug.raw bar_rope1 bar_rope1.caz bar_rope1.raw bar_rope2 bar_rope2.caz bar_rope2.raw bar_rope3 bar_rope3.caz bar_rope3.raw bar_rope4 bar_rope4.caz bar_rope4.raw bg_bar bg_bar.caz bg_bar.pak bg_bar.raw bg_bar.raw.caz bg_girl bg_girl.caz bg_girl.pak bg_girl.raw bg_girl.raw.caz bg_osbar bg_osbar.caz bg_osbar.pak bg_osbar.raw bg_osbar.raw.caz bg_oscottage bg_oscottage.caz bg_oscottage.pak bg_oscottage.raw bg_oscottage.raw.caz bg_osgirl bg_osgirl.caz bg_osgirl.pak bg_osgirl.raw bg_osgirl.raw.caz bg_osshop bg_osshop.caz bg_osshop.pak bg_osshop.raw bg_osshop.raw.caz bg_petrol bg_petrol.caz bg_petrol.pak bg_petrol.raw bg_petrol.raw.caz bg_pond bg_pond.caz bg_pond.pak bg_pond.raw bg_pond.raw.caz bg_shop bg_shop.caz bg_shop.pak bg_shop.raw bg_shop.raw.caz bg_yard bg_yard.caz bg_yard.pak bg_yard.raw bg_yard.raw.caz dog_eyes dog_eyes.caz dog_eyes.raw fire1 fire1.caz fire1.raw fire2 fire2.caz fire2.raw gfx_cage gfx_cage.caz gfx_cage.raw gfx_pointer gfx_pointer.caz gfx_pointer.raw girl_left girl_left.caz girl_left.raw girl_talk girl_talk.caz girl_talk.raw inv_branch inv_branch.caz inv_branch.raw inv_branchfire1 inv_branchfire1.caz inv_branchfire1.raw inv_branchfire2 inv_branchfire2.caz inv_branchfire2.raw inv_cage inv_cage.caz inv_cage.raw inv_card inv_card.caz inv_card.raw inv_cheese inv_cheese.caz inv_cheese.raw inv_coins inv_coins.caz inv_coins.raw inv_fishingrod inv_fishingrod.caz inv_fishingrod.raw inv_flower inv_flower.caz inv_flower.raw inv_key inv_key.caz inv_key.raw inv_magglass inv_magglass.caz inv_magglass.raw inv_mouse inv_mouse.caz inv_mouse.raw inv_mug inv_mug.caz inv_mug.raw inv_rope inv_rope.caz inv_rope.raw laughingman_talk laughingman_talk.caz laughingman_talk.raw manright_mglass manright_mglass.caz manright_mglass.raw man_back man_back.caz man_back.raw man_eyes man_eyes.caz man_eyes.raw man_front man_front.caz man_front.raw man_fronttalk man_fronttalk.caz man_fronttalk.raw man_left man_left.caz man_left.raw man_left1 man_left1.caz man_left1.raw man_lefttalk man_lefttalk.caz man_lefttalk.raw man_right man_right.caz man_right.raw man_right1 man_right1.caz man_right1.raw man_rightstretch man_rightstretch.caz man_rightstretch.raw man_rightstretchup man_rightstretchup.caz man_rightstretchup.raw man_righttalk man_righttalk.caz man_righttalk.raw man_staring man_staring.caz man_staring.raw osbar_manleft osbar_manleft.caz osbar_manleft.raw osbar_manright osbar_manright.caz osbar_manright.raw oscott_norope oscott_norope.caz oscott_norope.raw osgirl_door osgirl_door.caz osgirl_door.raw osgirl_flower osgirl_flower.caz osgirl_flower.raw petrolman_talk petrolman_talk.caz petrolman_talk.raw room_girl.asm room_girl.asm.bin room_girl.asm.caz room_girl.asm.pak room_osbar.asm room_osbar.asm.bin room_osbar.asm.caz room_osbar.asm.pak room_oscott.asm room_oscott.asm.bin room_oscott.asm.caz room_oscott.asm.pak room_osgirl.asm room_osgirl.asm.bin room_osgirl.asm.caz room_osgirl.asm.pak room_osshop.asm room_osshop.asm.bin room_osshop.asm.caz room_osshop.asm.pak room_petrol.asm room_petrol.asm.bin room_petrol.asm.caz room_petrol.asm.pak room_pond.asm room_pond.asm.bin room_pond.asm.caz room_pond.asm.pak room_shop.asm room_shop.asm.bin room_shop.asm.caz room_shop.asm.pak room_yard.asm room_yard.asm.bin room_yard.asm.caz room_yard.asm.pak shopkeeper_stretch shopkeeper_stretch.caz shopkeeper_stretch.raw shopkeeper_talk shopkeeper_talk.caz shopkeeper_talk.raw shop_nomglass shop_nomglass.caz shop_nomglass.raw strandedlogo strandedlogo.caz strandedlogo.raw textref.i yard_nobranch yard_nobranch.caz yard_nobranch.raw zstranda.asm zstranda.asm.bin zstranda.asm.caz zstrandb.asm zstrandb.asm.bin zstrandb.asm.caz zstrandc.asm zstrandc.asm.bin zstrandc.asm.caz zstrandc.asm.pak zstrandd.asm zstrandd.asm.bin zstrandd.asm.caz zstrandy.asm zstrandz.asm c-source (dir) tipack.c pics (dir) tiadv1 Stranded.zip the_guide.text In the root, you find this file (the_guide.text) and the original .83p files (Stranded.zip). In the pics directory, you'll find a picture called "tiadv1", which is where I did all my scetching and graphics work. All the small graphics elements have then been cut out of this picture. The picture is in IFF ILBM format. In c-source, you find the ANSI C code of a file compressing utility I wrote. I have written z80 code to uncompress data packed using this utility, and that code can be found in zstrandy.asm. More on this later. In the big directory, "source", there's a lot of stuff. Most of this is graphics, but there is (obviously) source code here too. All source code has been written to work with the assembler I used, which is "caz" for Amiga. This assembler has some pecularities, of which the most notable are: Amiga caz (compared to) TASM defb is the same as .db defw is the same as .dw defm is the same as .db (for many bytes, or strings) I've used "equ" instead of "=" too. Please have a look at the file called: ZSTRANDY.ASM This is the boot code for Stranded, and it is Send(9'd from the basic program. As you can see, it has been optimized to work only with this particular game, and with this particular make of the game. The first directive after the ROM equates and the ORG actually defines the size of the code! You can compare this value (1a60h) with the size of the file ZSTRANDC.ASM.BIN. This is probably the time to talk a bit about the memory layout used in Stranded. Program Code As usual, from 9327h and on. The loader (zstrandy) goes here, but then it moves itself to another location and loads the main code (zstrandc) into this address space. Bitmap Buffers The standard buffer area, PLOTSSCREEN of 768 bytes, is used for the final image. It is then written to the display using _GRBUFCPY several times per second. The background image used for each room is stored in SAVESSCREEN, but occupies only 660 bytes. The rest of SAVESSCREEN may be used to keep object information (more on this later). Data, Variables are stored in the STATVARS area (definitions start at line 184 in zstrandc.asm). Some of these variables hold flags. Other flags are kept in asm_flag1, asm_flag2 and asm_flag3 as defined in TI's include files. Flag definitions start at line 131 in zstrandc.asm. Text Strings Since these are stored in a compressed form, they have to be unpacked before they can be used. The largest block of unpacked text (largest txt*.asm.bin in the source/txt directory) needs to fit into memory somewhere. To solve this, the loader (zstrandy), which has to allocate memory for loading the main code anyway, allocates some more space. This can be seen at lines 14 and 19 of zstrandy.asm. So, zstrandy allocates enough room for zstrandc (which is the main program code) and the largest, unpacked text file. Then it copies zstrandc into this area (which starts at 9327h, the usual program start address), and then it runs it. Remains one question: Where is zstrandc? The observant reader has of course noted that Stranded comes as prgmSTRANDED (a basic program), prgmZSTRANDY (apparently a hex file à la "Send(9") and prgmZSTRANDZ (which is big). This is how it turned out to be in the final release. However, when I was working on this game, I did quite a few complete make-overs when it comes to program layout. The original idea was to have these files: ZSTRANDA bAckground images (and later some object information as well) ZSTRANDB "Bobs", which is an Amiga term and refers to a small block of graphic. Some TI-83 programmers would call it sprites. ZSTRANDC Code. ZSTRABDD Text ("Data", if you want something that starts with a "D"). and then some loader stuff. However, memory became rare and I decided to compress these files and put them in a big all-game file instead. ZSTRANDZ was born. Please have a look at zstrandz.asm: -------------------------------------------------------------------------------- org 2 defw zstranda defw zstrandb defw zstrandd defw zstrandc zstranda: include zstranda.asm.caz zstrandb: include zstrandb.asm.caz zstrandd: include zstrandd.asm.caz zstrandc: include zstrandc.asm.caz -------------------------------------------------------------------------------- All it does is include the other files (A, B, C, and D). Now let's have a quick look at FILENAME ENDINGS. I have used a general scheme, where means .asm Assembly language source code. Can be run through the assembler I used (the Amiga version of caz). .i Assembly language include file. Can also be run through caz. .caz Computer generated source code. Can obviously be run through caz. This is how I included data into the game; since caz had no "incbin" directive, I had to use the normal "include" with converted data files. Have a look at zstrandc.asm.caz and you'll see what I mean. (no ending) These are picture files of the format IFF ILBM. Most of them are quite small, usually not wider than 16 pixels. They are all clips from the big picture file, "pics/tiadv1". .raw Graphics in raw, bitmapped, format. The graphics are stored in the same way you would store a Movax sprite in your source code. They are either 16-bit wide or 96-bit wide depending on if they are used as bobs or backgrounds. The graphics may be masked, but more on this later. .bin Something that has been assembled from an .asm file. Most of the source files in this archive don't contain program code. Instead, they contain data (in the defm (.db) format). The data files usually begin with an "org 0". If I had a file containing just "org 0" and "defb 77", and I'd assemble it, I'd get a file that was one byte long, and that byte would have the value 77 (which would be a capital M, by the way). Such files I have named "*.bin". .pak A packed file. More on the data compression later. .asm.bin Same as .bin, but since I've been writing lazy scripts, this was easier. General rule of thumb: Just look at the last filename ending. .bin.caz A binary file that has been changed into a, sometimes huge, amount of "defm" statements. Same as .caz, really. .asm.bin.caz A file that has been assembled from an .asm file into a .bin file, and then turned into assembly language (if you call "defm" directives assembly language) again. Again, these ugly filename endings come from lazy scripting. .asm.caz Should be .asm.bin.caz, but then, should probably just be ".caz". (You still reading? Cool!) So let us concentrate on the main code. It is written, as an assembly language program, to the file zstrandc.asm. Then I press F9 in my editor and a cute little script comes to life. It saves the file (zstrandc.asm), and assembles it. This creates a file called zstrandc.asm.bin. (The contents of this file is what we want to have at 9327h, so that we can run the game) Next, the script invokes the packer (c-source/tipack.c), which compresses this file into a file called zstrandc.asm.pak. Finally, this packed file is turned into a series of defm statements (using a small utility called bin2caz, which I could have included in the c-source directory but didn't bother to) and the resulting file zstrandc.asm.caz is thus created. Let's see, where were we. Yeah, zstrandy, the loader. The loader has allocated enough memory for the main code (and some extra space) at 9327h. Now it has to find the packed version of zstrandc, and unpack it to 9327h. Please have a look at line 42 in zstrandy.asm. The loader looks up the big file, zstrandz. It adds a small offset to the beginning of the file, and then it fetches a 16-bit word (through _A2POINTHLIND which I'll explain later). What it fetches is line 6 of zstrandz.asm ("defw zstrandc"). This is an offset into zstrandz, that tells the loader where in this file it can find the packed zstrandc. This is a technique I use a lot in Stranded. Data files that begin with "org 0" (or "org 2" in this case, which is just an optimization trick) and then a couple of defw's defining offsets into that file. It is critical to understand how this works to be able to understand how Stranded is layed out. So, the loader has this offset into the data file, and it has the address of the data file. These are added (line 50 of zstrandy.asm), and the resulting address is used as an argument to the Unpack function. As you can see, "call Unpack" and then "jp 9327h" has been optimized into pushing 9327h on the stack and then doing "call Unpack" followed by "ret", which would be the same as "jp Unpack", which would be unnecessary since Unpack comes right below this code. Therefore the call, ret and jp instructions have been commented out. The thing you've all been waiting for..... THE PACKING STUFF! I'm using a rather simple packing algorithm, and I think the easiest way to understand it is by learning how to UNPACK files first. Each packed file begins with a header. WORD PackedSize BYTE 'P' <-- This is always "P", used to make sure this file is packed. Not really necessary, nor used, but wtf. Perhaps it can be used for future expansion. WORD OriginalSize Then follows the packed data bytes, and then follows the packed data bits. This may sound a bit weird at first, but we'll start looking at the data BITS. These are at the very end of the packed file, and are interpreted backwards. The unpacker will find the end of the file (by adding PackedSize to the address of the packed file), and then it'll read bits backwards. The first bit it reads is the command bit. If the command bit is 0, that means READ. If the command bit is 1, that means COPY. The READ command is followed by 3 bits, defining how many bytes should be read. So, when we come across a READ command, we read 3 more bits from the end of the packed data bits, and then we read a certain amount of bytes from the packed data BYTES (starting right after the header and going forwards) and write them to the address to which we're supposed to be unpacking. The value of those three bits can be 0-7, but we add 1 to it. Thus we can read 8 bytes with each READ command. The COPY command is followed a 6-bit position code. This tells the unpacker where in the packed data bytes to start reading. From the current position in the unpacked data byte stream we subtract 2 and then the value of these 6 bits. After the position bits follow 3 bits defining how many bytes should be read from this position. In this way, the unpacker goes through all the commands in the command bits until (OriginalSize) bytes have been unpacked. Let's do an example: 0000: 2800502D 00546865 20726169 6E205370 (.P-.The rain Sp 0010: 73746179 73206D6C 7974706C 2E0A0850 stays mlytpl...P 0020: E1030C56 1256DC61 410E á..V.VÜaA. This file is packed (see the "P" as the third byte?). The first word (0028h) is the size of the packed file. This is specified in TI-83 program style, so the size word itself doesn't count. The size of the file is 2ah, so this is all right. The two bytes at positions 3 and 4 in the file (2dh and 00h) tell us the size of the original, unpacked data. Now we begin the unpacking. Look at the last bit. Actually, let's write the last four bytes in binary, just to clarify. DC61 410E = 1101110001100001 0100000100001110 The last bit is a 0, which means READ. How many bytes do we read? Well, the next three bits are all ones, so read 7+1=8 bytes. The bytes are read from the beginning of the packed data bytes, which start at position 5 in the file. "The rain" This is what we get. Now, we're no longer at position 5 in the file, but at position 5+8=13. We've already used the last 4 bits of the packed data bits, so let's cross them out. DC61 410E = 1101110001100001 010000010000xxxx The next command is also READ. The three bits that follow it form the number 0, so we'll READ 0+1=1 byte. The byte at position 13 in the file is a space (" "), and pnow we're at position 14 in the file. "The rain " DC61 410E = 1101110001100001 01000001xxxxxxxx Now we get a COPY command. The six following bits are 000001 (remember, bits are read backwards). This means that the position from which to copy bytes would be [Where we are now] - 2 - 000001b. Note that bytes are copied from the UNPACKED data. So, place the cursor at the closing quote of "The rain ", and go backwards three steps. This is where we'll start copying. The three following bits are 010, and tell us to copy 2+1=3 bytes. Those bytes would then be "in ". Cool! We've unpacked something. The resulting string is: "The rain in " And the bit stream is down to: DC61 410E = 11011100011000xx xxxxxxxxxxxxxxxx Next command is READ 1+1=2 bytes. The two bytes at position 14 in the file are "Sp", and now we're at position 16. "The rain in Sp" DC61 410E = 1101110001xxxxxx xxxxxxxxxxxxxxxx Time to copy again. The position is 000111b, which is 7+2=9 spaces to the left of the closing quote. The number of bytes to copy is 011b+1=4. "The rain in Spain " DC61 410E = xxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxx Out of bits? No, we just continue backwards from the end of the file. The rest of this unpacking is left as an exercise for the reader. When tipac.c packs a file, it does some kind of try-and-error to find the most efficient compression possible. Note on the packed file header: The header begins with the size of the rest of the packed data. If you wish to compress a program, this word will in effect be placed there by TI-OS, since every program begins with a size word. In that case, you can save 2 bytes by omitting it in your program and giving the address - 2 to the Unpack routine. So, we can finally understand what happens when Stranded is run. * The basic program calls ZSTRANDY. * Memory is allocated, both for the unpacked main code, and some extra memory for text buffers. * The loader looks up ZSTRANDZ, finds the packed zstrandc in it, and unpacks it into the allocated memory. * The unpacked code is run. And now for something completely different. Text. org 0 defw txt_wire_la defw txt_crowbar_la defw txt_skateboard_la defw txt_magnifyingglass_la defw txt_lamp_la defw txt_hammer_la txt_wire_la: defm "A roll of good,",0 defm "strong wire.",0 defm "Only 10 silver",0 defm "coins.",0 defm 0 txt_crowbar_la: defm "Very robust indeed,",0 defm "and only 6 silver",0 defm "coins.",0 defm 0 txt_skateboard_la: defm "Skate or die!",0 defm "The price is 15",0 defm "silver coins.",0 defm 0 txt_magnifyingglass_la: defm "You name it,",0 defm "it enlarges it!",0 defm "Only 3 silver coins.",0 defm 0 txt_lamp_la: defm "I bought this lamp",0 defm "from a fellow called",0 defm "Aladdone or something.",0 defm "Only 15 silver coins.",0 defm 0 txt_hammer_la: defm "Very robust hammer,",0 defm "only 5 silver coins.",0 defm 0 This is source/txt/txt2.asm, one of the 13 text files in the Stranded source code. Each of these text files is compressed individually, so I've tried to collect as much similar text as possible in each file. The phrase " silver coins.", for example, is repeated 3 times in this file, and parts of it are repeated even more often. The file begins with an org 0. This makes sure that e.g. "txt_wire_la" equates to the offset into the file where one would find the text it refers to. (The _la suffix means "look at".) All the 13 text files are compressed, and then the compressed versions are included from zstrandd.asm. This file also contains some other text strings, like "Walk to", that couldn't be compressed because of speed limitations. Therefore there are _two kinds of text strings_ in Stranded. Simple text strings such as "Walk to", "painting", "flower" and "Inventory:". Each one has a number, which is their index into the "objectnames" table starting at line 32 in zstrandd.asm. Packed text strings such as "Well, see you later.". These also have numbers, but the packed text string 03h is not the same as the simple text string 03h. In a packed text string number, the high nibble (the most significant 4 bits) specifies what text file the string is in. The low nibble specifies where in the text file it is. Example: The number of txt_wire_la in the above text file would be 20h, since it is in txt2.asm, and since it is the first item (item number 0) in that file. Now look at source/textref.i. The first set of equates contains the simple text strings. The second set of equates contains the packed ones. Note that packed string 00h isn't an object lookat description, it is a piece of speech. This is because objects with lookat-description 00 don't have any lookat text (i.e. doors and paths). Obviously, calling both simple and packed text string numbers txt_* is very confusing, but it just ended up that way. =) ENTERING A ROOM Some of you probably think that Stranded runs in a standard loop, and has data structures describing the room it's currently in. This is probably how I would design it if I were to rewrite it from scratch. However, that is not quite the way it works. Every room is a piece of code, with an entry point. Every room has a loop, which calls a standard event handling routine. The event handling routine returns a value, saying perhaps that the player wishes to Talk To someone, or Pick Up something. Then the room code handles this event, and goes back into its loop. Please have a look at zstrandc.asm, at line 3708. This is the entry point to the yard with the pile of rubbish, the birdcage and the tree. The first thing we do is set up some data structures. This is done through a routine called LoadRoomDef. LoadRoomDef is an extended LoadRoom, which will set up both the background image and the object table. The original LoadRoom just did the background image (and initialized some variables). Let's do a quick jump to line 2591. This is what an object table looks like. Every object begins with a FLAG byte, saying "this object exists" (1) or "this object doesn't exist" (0). Then the x and y coordinates of the object (where the mouse pointer goes) come. After that, the four objects surrounding this one appear. These are the object numbers, the indexes into the object table, so the first object in the table has number 0 and the next one has number 1 and so on. A -1 in one of these four bytes (up, down, left, right) means that there is no object next to this one, in that direction. Then follows the coordinates the man would walk to to access the object, as well as in which direction the man would face to access it. Directions are 0=left, 1=back, 2=right, 3=front. Then follows the name of the object (a simple text string) and the lookat description of the object (a packed text string). All in all, 12 bytes. In the beginning, all object tables were part of the main code block. But then I figured I'd save some space (quite a lot actually) if I somehow included them in the packed background image. The background image only occupies 660 bytes, and that leaves 768-660=108 bytes of SAVESSCREEN that's free to use. Every object occupies 12 bytes, so the 108 bytes would be able to keep 9 objects. The bar is the only room that has more than 9 objects. This means that when the bar loads, it specifies the room number (and number of background image) in a, and the address of its object table in hl, and calls LoadRoom. All the other rooms have their object tables at SAVESSCREEN+660, so they call LoadRoomDef, which loads hl with SAVESSCREEN+660 and drops through to LoadRoom. Once in LoadRoom, some variables are set up. The object table is scanned to find the first existent object (which is where the mouse pointer would be positioned). If the room is inside the cottage, the background image is filled with dreary blackness (um, sorry, not only the bar has its objects in the main code, the cottage does too), otherwise the room background (and sometimes object table) gets unpacked into SAVESSCREEN. We continue following the room code of the yard, so let's have a look at the background image and object table of that room. LoadRoom looks through ZSTRANDZ (what else) to find ztranda, which is where the room data is located. Please have a look at zstranda.asm. As you can see, ROOM_Yard (equated at the very beginning of zstrandc.asm) is an index into zstranda. Here we find room_yard.asm.caz included, and this is actually room_yard.asm which has been assembled, packed, and then turned into caz-readable form. --- room_yard.asm -------------------------------------------------------------- org 0 include textref.i include bg_yard.raw.caz ; f,xx,yy,up,dn,lt,rt,mx,my,md id ; --------------------------------------------- Yard_Objects: defm 1,19,33,-1,04,01,03,21,42,01 ;00 defb txt_heapofrubbish defb txt_heapofrubbish_la defm 1,08,33,-1,04,-1,00,18,43,00 ;01 defb txt_birdcage defb txt_birdcage_la defm 1,76,26,-1,04,03,-1,76,44,01 ;02 defb txt_tree defb txt_yardtree_la defm 1,67,19,-1,04,00,02,67,41,01 ;03 defb txt_branch defb txt_boringla defm 1,49,54,00,-1,01,02,47,53,03 ;04 defb txt_path defb 0 ;defm 0,0 ;workaround -------------------------------------------------------------------------------- (The workaround at the end remains as a memory from the times when the packer was buggy. There are quite a few of them, spread out in the sources, all commented out.) The first thing we do in this file is include the background image. This is the image in raw format, turned into caz-readable form (so it hasn't been packed yet). This occupies 660 bytes. Then we have 5 objects. The first one is a head of rubbish, at (19,33), existent, and the man would walk to (21,42) and face backwards to access it. There is no object above it ("up" is -1), below it is object #4 (the path), to the left of it is the birdcage and to the right of it is the branch. If the heap of rubbish is the active object, and the player presses the right arrow key, the active object changes to the branch. But if the branch doesn't exist, the active object becomes what is to the right of the branch (the tree). If the tree hadn't been there ("rt" of the branch == -1, or the flag of the tree == 0), nothing would happen when the right arrow key was pressed, even though "rt" of the heap of rubbish wouldn't be -1 itself. So. This file (room_yard.asm) is what is unpacked and put into SAVESSCREEN. Now let's go back to the yard room code (line 3708 in zstrandc.asm). Since the flags in the object table may change (e.g. when an object is picked up), they can't be stored in the packed data. Therefore, the object flags are placed in a small array at line 3758 (the first byte is the number of flags). Right before it, the routine Yard_LoadOF is responsible for loading the flags from the table into the real object table in SAVESSCREEN. So, when a routine in the game wishes to change the existent state of an object, it should change it in this small table and then call Yard_LoadOF. In the same way, the room initialization code should also call Yard_LoadOF. Next, in the room initialization code, the man is set up to be somewhere and look in some direction, and then the room loop is entered. The room loop calls UDHandleInput. This is short of Update Display and Handle Input. Update Display will copy the background image into the graph buffer (PLOTSSCREEN), draw some room specific graphics if necessary, draw the man and the mouse pointer, and draw the black status bar. Handle Input, on the other hand, will check if any key is pressed. If no key is pressed, it will return with the zero flag set. If a key is pressed, it does some default action handling (such as changing the active object when an arrow key is pressed), and returns with zero clear and a return code in the accumulator. The return codes are equated in the beginning of zstrandc.asm and are: HI_Quit equ 1 HI_WalkTo equ 2 HI_TalkTo equ 3 HI_PickUp equ 4 HI_Use equ 5 HI_Inv equ 6 HI_UseWith equ 8 (HI stands for HandleInput.) As you can see on line 3716, if HandleInput returns z, this room code just loops back. However, the room code for outside the bar won't just loop here; instead, it will handle the animation of the laughing man. If the return code is HI_Quit, a "ret" is executed, which will take us through some cleanup code, display the final message on the home screen and quit. If the player wishes to pick something up, we jump to Yard_PickUp. Otherwise, we must set the current active verb back to "Walk to", and loop back. In Yard_PickUp, we check what object the player wishes to pick up. The curren object number (curronum) is the index into this room's object table. Is it 03 (the branch)? Is it 01 (the cage)? If it isn't, call "cantpickup" which will display an appropriate message, and loop back. Otherwise, to pick something up, we must do a couple of things. * Set a flag. These flags are used when UpdateDisplay is running specific graphics code for the inventory. So, if we don't set this flag, the item won't be visible in the inventory. * Set another flag. If the object can be dropped later, the part of UpdateDisplay that is specific for this room might not know whether the object is left in the room or not. Therefore we need two flags. One states that an object is carried (visible in the inventory), and one states that it isn't left in the original room. If the object cannot be dropped, these flags can be one and the same. * Change the flag in the object table. Or rather, change the flag in the small object flag table, and call LoadOF (which, by the way, is short for Load Object Flags). * Change the flag in the invenroty object table. The inventory acts like a room itself, and has its own object table. That table is located at line 2218 in zstrandc.asm. From the beginning, no inventory object exists, so we need a dummy "nothing" object at the end of the table. Since the room entry code always finds the first, existent object, any real inventory object will override this "nothing" object. Now, wasn't the code for the Yard rather small? What happened to the return codes HI_WalkTo and HI_Use? When I try to use something the guy says "I can't do that", and when I ask the guy to walk somewhere, he does. So, where is this magic code located? Let's begin with HI_Use. At line 1265 we find this table: usetable: defm ROOM_Petrol,ROOM_OSCott,ROOM_OSGirl,ROOM_Bar,ROOM_Girl These are all the rooms in which it is possible to Use stuff. The default code in HandleInput always checks tables like this one, to see if it can display an automatic can't-do-that message. There is a usewithtable and a pickuptable too. Walking to objects is done automatically when you try to do anything with them. However, the paths and doors are a bit special, since they actions attached to walking to them (tha action being that you end up in a new room). Originally, each room loop would check for HI_WalkTos, and jump to the Room_entry points accordingly. However, this was soon optimized. Please have a look at line 2160 in zstrandc.asm. This is a table of all the standard doorways in the game. (The only non-standard doorway is the entrance to the cottage, which has room-specific code to it and thus checks for HI_WalkTo.) These standard doorways are entered into the table as follows: BYTE object number BYTE room WORD new room entry point So the default HandleInput code for walking to objects scans through this table. If we're in room ROOM, and try to walk to OBJECT, walk there and then jump to this ENTRY POINT. UpdateDisplay (the beast) UpdateDisplay is where nearly all of the graphics work is done. The first thing it does is to check whether we're displaying the inventory (which is done in a kind of local room loop inside HandleInput). It diverges into two separate code blocks, inventory and non-inventory. NON-INVENTORY DISPLAY: * Copy the background into the graph buffer. * Check what room we're in. EXAMPLE: We're in the yard. (The yard code starts at line 756) * Is the branch taken? If it is, draw a "nobranch" graphic on top of the branch in the background graphic. * Is the cage taken? If it isn't, draw the cage graphic somewhere near the heap of rubbish. (jumping to ud_endroomcode at line 785) * Should we draw the man? (We don't do that in the end-of-game sequence) If so, should we force a particular graphic to be the man? (Such as the eyes in the cottage, or when the man bends down to take something.) Is the man walking? Talking? In what direction? Draw the appropriate man graphics. * This is a bit room specific; when the man stares at the woman the first time he sees her, he stares at her. This staring graphic (which, to save memory, isn't as big as the man, but just covers appropriate parts of his face) must be drawn after the man has been drawn. So, if the man is staring (a flag says so), draw the staring graphics. * Skip to GENERAL DISPLAY STUFF INVENTORY DISPLAY: * Clear the graph buffer. * Write "Inventory:" at the top of the display. * For each possible inventory object: * Is the object carried? (Checking the flags...) If so, draw its graphic. * Is this the branch? Do the animatiom stuff if necessary. * Skip to GENERAL DISPLAY STUFF GENERAL DISPLAY STUFF: * Draw the mouse pointer. * Are there any active text objects? (A text object is what you see above the head of a talking (or laughing) person. There are two independant text objects in Stranded, txt0 and txt1, that can be at any position and contain any, multi-line, text.) Display them if so. * Do the status bar. The text on the status bar is the current verb (which may be an atom ("Walk to") or a composite verb ("Use flower with")) followed by the current object name. * Are we having a conversation? In that case, draw all the lines from the current conversation list, highlighting the selected one. Note: This is the reason why the conversation interface responds so slowly. Every time UpdateDisplay is called, all the lines are written. But if the lines are from different text pages, UpdateDisplay has to unpack several texts every time it is run. * Is the girl present in this room? In that case, display her. Now we've come across a very important part of Stranded that we haven't really discussed so far. The bobs, that is, the small graphics elements that really make the game what it is. Please look at zstrandb.asm. It begins with a big table, where all the offsets into the file are given. (There are equates dealing with the bobs starting at line 241 of zstrandc.asm.) There are 58 bobs in the game, and we'll start by looking at bob #0, the mouse pointer. gfx_pointer: defm 0,0,9 include gfx_pointer.caz The first two bytes are the x and y offsets into the handle point of the bob. Since the mouse pointer has its tip in the upper left corner, it would probably be nice to refer to the mouse pointer using this point. Therefore the handle point of the mouse pointer is at (0,0). (The man, on the other hand, has a handle point that is somewhere on the ground, between his feet. When writing object table definitions, describing where the man would go to access to object, what you write is the point at which you wish the man's handle point to be.) The third byte (9, in the case of the mouse pointer) is the height of the bob. The width of all bobs in Stranded is 16 pixels. (The laughing man consists of two adjacent bobs.) Then follows the graphics data. We'll actually have a look at it; here is gfx_pointer.caz: defm c0h,00h,b0h,00h,4ch,00h,42h,00h,24h,00h,2ah,00h,15h,00h,02h,00h defm 00h,00h,3fh,ffh,0fh,ffh,83h,ffh,81h,ffh,c3h,ffh,c1h,ffh,e8h,ffh defm fdh,ffh,ffh,ffh This is 36 bytes of bitmapped data. The bob is 9x16, so it occupies 9*16=144 bits, which is 18 bytes. Why is the picture twice as big as it needs to be? The answer is simple: The mouse pointer isn't a solid rectangle. It is shaped like an arrow, and so it shouldn't overwrite the areas surrounding that arrow. The first 18 bytes look like this, in binary form: c0h,00h 1100000000000000 b0h,00h 1011000000000000 4ch,00h 0100110000000000 42h,00h 0100001000000000 24h,00h 0010010000000000 2ah,00h 0010101000000000 15h,00h 0001010100000000 02h,00h 0000001000000000 00h,00h 0000000000000000 This is what the pointer would look like against an empty background (such as the inventory display, when you have no belongings). The following 18 bytes are: 3fh,ffh 0011111111111111 0fh,ffh 0000111111111111 83h,ffh 1000001111111111 81h,ffh 1000000111111111 c3h,ffh 1100001111111111 c1h,ffh 1100000111111111 e8h,ffh 1110100011111111 fdh,ffh 1111110111111111 ffh,ffh 1111111111111111 This is the solid shape of an arrow. So, to draw the mouse pointer, we need to AND the second image (the mask) with the graph buffer (leaving the result as the new graph buffer), and then OR the first image with the graph buffer. Then have a look at line 157 of zstrandb.asm, which looks like this: gfx_inv_mglass: defm 90,41,13 include inv_magglass.caz ;solid As you can see from the comment, this image is solid. This means that its mask would be all zeroes. The mask has not been included at all (inv_magglass.caz is only 13x16 bits = 26 bytes big) to save space, but the bob must be drawn using special routines for solid bobs. Something else is curious about the magnifying glass. Why would it have a handle point at (90,41)? That's far outside of the bob! Well, some objects are always located at the same coordinates throughout the game. Good example are the inventory items, and objects like the branch on the tree in the yard. The birdcage, however, appears both next to the heap of rubbish and on the bar floor. * Objects that always appear at the same coordinates have their handle points set so that when you draw them in the bottom right corner of the screen, they appear at the place they're supposed to be in. This way, we don't have to specify coordinates everytime we wish to draw an object somewhere; we simply call a default routine to set up the coordinates of the bottom right corner of the display, and then drop through to the normal draw-a-bob routine. So, drawgfx draws bob number l at coordinates de. drawcorner draws bob number l at the bottom right corner. drawsolid draws bob number l with no mask at coordinates de. drawcornersol draws bob number l with no mask at the bottom right corner. Some utility routines used in the inventory display code: drawinventory3 draws bob number l with no mask at the bottom right corner but only IF (h AND (iy+asm_flag3)) isn't zero. This way, if we set h to 80h (bit 7 set, the other bits clear) and l to a bob number, calling drawinventory3 will only draw the bob if bit 7 of asm_flag3 is set. drawinventory6 Same, but uses another flag byte. Finally, I'm going to explain some cool utility routines I used. Some of them, I found in the ROM, and other I made myself. I hope someone will find them useful. Cool ROM routines _POINTHLIND This routine will add a to hl, and then read the word found at that address. The word is returned in hl. So, if I had a table defw 0 defw 1000 defw 2000 defw 3000 . . and I wished to multiply a by 1000, I'd add a to itself to get an index into the table, and then call _POINTHLIND, and I'd get the result in hl. _A2POINTHLIND When calling _POINTHLIND, it appears that you so often added a to itself first, that a new romcall was added to do it for you. So _A2POINTHLIND will add a to itself, and then drop through to _POINTHLIND. _CLRLP This routine sets b bytes starting at hl to zero. I use it to clear my variables in the dawn of programkind. _strCopy Copies a null-terminated string from hl to de. These are all very useful bread-and-butter routines, and I use them in Stranded whenever I can, to save a few bytes here and there. However, at some point you often realize that you do a certain thing over and over again, and then you rewrite it as yet another small utility routine. These are from zstrandc.asm: setonzresonnz Though a bit cumbersome to pronounce, this routine was quite useful when dealing with the object flags. It checks the zero flag, and if it is set, moves a 1 into (hl). If the zero flag was reset, a 0 is moved into (hl). pointhlind_add As mentioned, I use a lot of word tables at the beginning of files, with offsets into the file. Therefore, I quite often called _A2POINTHLIND to get an offset, and then added the result to the address of the table. This routine will do that. It will call _A2POINTHLIND, and add the result to the address of the table you gave it. That's, actually, all folks. I've explained the layout of the data structures, where in memory all the things go, the basic "program flow architecture" and even some of the implementations. Once you understand how the data is organized, I'm sure it'll be obvious what each routine does (although not necessarily HOW it is done). Feel free to contact me about Stranded, and about its source code. I can be reached at lairfight@softhome.net. However, don't ask me what to do after you've sold the flower to the shop keeper but can't ask the girl out and how do I light up the cottage and what is the pond for, because I think people who don't want to solve problems themselves shouldn't really be playing adventure games in the first place. Instead, ask me if there's a particular routine you can't understand, or a data structure that doesn't make any sense at all, or ideas on better solutions etc. etc. In other words; there's a version number at the top of this text file, and I'd be glad to change it. Best regards, Linus Akesson, coder of Stranded.