Geoff ModeLast update: 23 October 2002
Geoff's homepage ->Jet Set Willy -> Geoff Mode explained
What is "Geoff Mode"?It's a set of changes to the original JSW game engine, which iscorrespondingly known as "Matthew Mode". There are three types ofchange:
You can also look at a disassembly of theGeoff Mode engine.
Cosmetic changesThese don't affect the gameplay, but are the easiest ways to tell ifyou're playing a Geoff Mode game by just looking at it.
The lives counterThree changes here:
- You start with 16 lives, rather than the original 7. Aren't I generous!
- The sprite used for the lives counter is specified in the room data. A similar change was made to good effect in Andrew Broad's "JSW: The Lord of the Rings".
- Each successive "life" sprite shows a different phase, rather than all of them being the same phase.
The time counterThis no longer shows a time, but a 7-digit number which counts downfrom 9999999. This dates from the earliest days of Geoff Mode, and Ican no longer remember why I did it.
The pause colour changesI think there's a difference here, but I can't remember what!
The tuneIn Matthew Mode, the general pitch (i.e. the key) of the tune dropswith each life lost; with sixteen lives, this means that the tune isunlistenable at the beginning. In Geoff Mode, the key depends on theroom number instead (specifically, the room number ANDed with0x1C). If you want to change this, the code takes up 9 bytes at #8B51.
The itemsThese animate through eight colours as in JSWII, not just four as inMatthew Mode.
Functional modificationsThese are either invisible changes which don't affect the gameplay, orbugfixes.
The Block Graphics BugIn Matthew Mode, the basic room layout is generated by drawing theattributes first, then using a CPIR to find the block design:
8D4E DD7E00 LD A, (IX+#00)8D51 21A080 LD HL, #80A08D54 013600 LD BC, #00368D57 EDB1 CPIR
The bug works like this. Suppose your stair block is yellow on black(i.e. the attribute byte is #06), and your liquid (or water) blockcontains a line like this:
[possibly more rows].....##. 06 <------[possibly more rows]
The CPIR will find the byte marked with the arrow and draw the stairblock using the eight bytes following it, meaning that your staircasewon't look right. The solution is to ensure that the attribute bytefor a given block does not appear in the data for an earlier block.
In Geoff Mode, the room-drawing routine has been completely redrawn,and this bug has been slain. The core of the routine is at #8D96,which prints a single block; it starts with HL pointing at the block'sattribute btye (i.e. #80a0 + 9 * block_number) and DE pointing at theaddress of the attributes on the screen.
A side-effect of this is that you can have two different blocks withthe same attributes, and they'll be displayed differently. But they'llboth behave like each other, so if you do this with stairs andfile/plasma blocks, you'll have two sets of deadly staircaseblocks. There's no easy way to fix this.
Data areas have been moved aroundThis was done so that the data areas would be less fragmented. Theareas affected are:
|Data area||Matthew Mode||Geoff Mode|
|Title screen attributes||#9800-#9aff||#b700-#b9ff|
EnhancementsThese changes are, of course, your principal reasons for abandoningMatthew Mode for good.
The patch vectorTwo bytes within the room data specify thelocation of a subroutine which is called every tick, after Willy andall the guardians, items and arrows have been displayed but before theroom data is finally copied to the screen. This subroutine, which maybe different for each room, is known as the "patch vector", fornow-forgotten reasons.
Patch vectors are potentially very powerful; they are used toimplement the teleport, for example. To see what patch vectors arecapable of, have a look at thedisassemblies of the patch vectors in my ownJSW games. The data at locations #85CB to #85E2 is often useful withinpatch vectors; for example, ANDing the "timer" byte at #85CB withsomething and doing a RET NZ afterwards will execute the patch vectoronce every few ticks. This might be useful, for example, if your patchvector reverses the directions of the conveyors.
Because it's a subroutine, it should end with either a RET as usual,or a jump to a subroutine which ends with a RET.
The teleporterThis facility is most conveniently called within a patch vector, likethis example from "Downhill" in "Willy Takes A Trip"
8728 11D55C LD DE, #5CD5872B 0E02 LD C, #02872D C3B287 JP #87B2 ; SEE NOTE BELOW!!!
The teleporter is activated when Willy is at the location given by DE;he is transported to the same location in the room specified byC. Obviously, this should be a different room from the one he'scurrently in, otherwise he'll be teleported to where he is over andover again. It is probably possible to alter the routine so thatyou're transported to a different screen address, but I'll leave thisto someone else to implement.
NOTE: the teleporter has been moved to #8C02 in the latest version ofGeoff Mode, and is likely to stay there for the forseeablefuture. #87B2 is currently empty.
Willy's SpriteAs with the lives counter, the sprite which displays Willy on thescreen is specified in the room data, ratherthan being hardcoded to the Nightmare Room as in Matthew Mode. Thesprite may use the entries within a page in one of the followingmodes:
- The lower four images.
- The upper four images. With this option, and the preceding, Willy will always face in the same direction if you're using eight-position sprites.
- All eight images. If you're using four-position sprites, the sprite will change when Willy changes direction.
- All eight images, in reverse order. With this you can make Willy go backwards.
Ropes are nicerIn Matthew Mode, because of some lazily inefficient programming, thedata for a "rope" guardian takes up sixteen bytes rather than theusual eight. This means that within the roomdata a rope must be followed by a dummy guardian.
In Geoff Mode, the code has been rewritten so that it (1) takes upless space and (2) only needs the usual eight bytes. So you can haveeight ropes in one room.
NOTE: Strange and rather annoying effects have been noticed with somepathological ropes. I don't remember if this was due to a bug in thecode or weird interactions with other data.
Arrows work differentlyIn Matthew Mode, the time of firing of any given "arrow" guardian isspecified within the guardian class, and the arrow reappears every 256ticks. In Geoff Mode, the time of firing is specified within theguardian's start byte in the room data, andreappears every 128 ticks. This means that, in practice, you only needone arrow class for each direction (right and left). You also lose theability to specify the arrow's row at pixel level, but I really don'tthink anyone's going to miss that.
Guardians can wrap aroundInstead of trotting back and forward as in Matthew Mode, guardians canmove in one direction only and "wrap around" at the end of their path.
Horizontal guardians can go fastYes, just like in JSWII, rather than just crawling as in MatthewMode. Actually, speeds of above four times the Matthew Mode crawlaren't terribly useful in practice. See theguardian class format for details.
Horizontal guardians can go diagonallyYes, just like in JSWII again. See the guardianclass format for details.
New room formatAs everybody knows, the data for room N is stored in 256 bytesstarting at #C000 + 256 * N, and is copied to a working area at#8000-#80FF when the room is entered. Changes are highlighted in bluebelow.
|00-7F||Block layout||Each byte specifies a group of 4adjacent blocks, with two bits per block.|
|80-9F||Room name||32 characters, in ASCII.|
|A0||Gas block attribute||AKA "Air".|
|A1-A8||Gas block design|| Specified by "00" inthe block layout.|
|A9||Liquid block attribute||AKA "Water". The ones you canstand on and walk through.|
|AA-B1||Liquid block design||Specified by "01" in the blocklayout.|
|B2||Solid block attribute||AKA "Earth". The ones you can'twalk through, but can stand on.|
|B3-BA||Solid block design||Specified by "10" in the blocklayout.|
|BB||Plasma block attribute||AKA "Fire". The ones thatkill you.|
|BC-C3||Plasma block design||Specified by "10" in the blocklayout.|
|C4||Stair block attribute|
|C5-CC||Stair block design|
|CD||Conveyor block attribute|
|CE-D5||Conveyor block design|
|D6||Conveyor direction||0 = left; 1 = right|
|D7-D8||Location of conveyor left end||inattribute buffer at #5c00-#5dff|
|D9||Conveyor length||Should be <= 32.|
|DA||Stair direction||0 = \; 1 = /|
|DB-DC||Location of stair left end||inattribute buffer at #5c00-#5dff|
|DD||Stair length||Should be <= 16.|
|DE||Border colour|| in bottom 3 bits.|
|DE||Willy on screen||mode, in top 2 bits.|
|DF||Lives counter||top bit = 0 to usebottom half of sprite page, 1 to use top half.|
|E0||Lives counter||sprite page.|
|E1-E8||Item block design|
|E9||Room to left|
|EA||Room to right|
|ED||Willy on screen||sprite page.|
|EE-EF||Address of patch vector|
|F0||Class of first guardian||or #FF if notpresent|
|F1||Start byte of first guardian|
|F2-FF||Data for remaining guardians||inthe same format.|
New guardian class formatThe data for guardian class N is stored in 8 bytes at #bc00 + 8 *N. The bytes have the following meanings.
Byte 0: diiggtttd is the starting dirction of the guardian: 0 = left, 1 =right. This is not meaningful for vertical guardians.
iishould be 00. It holds the current phase of the guardian.
gg is only meaningful for horizontal guardians:
- 00 specifies "purely horizontal".
- 01 moves the guardian one pixel row every tick, i.e. an angle of 26.57 degrees.
- 10 moves the guardian one pixel row every two ticks, i.e. an angle of 14 degrees.
- 11 moves the guardian two pixel rows every tick, i.e. an angle of 45 degrees.
ttt specifies the type of the guardian, as follows:
- 000: not used
- 001: horizontal guardian, non-wraparound
- 010: vertical guardian, non-wraparound
- 011: rope
- 100: arrow
- 101: horizontal guardian, wraparound
- 110: vertical guardian, wraparound
- 111: rope
Byte 1: ppphbcccFor ropes, this is the starting position of the rope within itsswing. For arrows, it is ignored. Otherwise:
pppp specifies the number of phases the guardian is animatedwith. The bits should be 111, 011, 001 or 000, which respectivelyspecify 8, 4, 2 or 1 phases.
h is ignored for vertical and purely horizontal guardians. Fordiagonal guardians, it specifies whether the guardian is moved up (if1) or down (if 0) to start with. It is flipped when non-wraparoundguardians change direction.
b is 1 if the guardian is bright, which looks horrible if theroom's backgrouns isn't black, and 0 otherwise.
ccc is the colour of the guardian.
Byte 2: sssxxxxxNOTE: This byte is taken from the room data,not the guardian data.
For arrows, the top nybble is the y-coordinate of the row (measured bycharacter blocks, not pixels), and the bottom nybble is one-eighth ofthe delay before the arrow appears. For ropes, xxxxx is thex-coordinate of the top pixel, and sss should be000. Otherwise:
sss is the start phase of the guardian. This is ORed with(ppp AND dii) to select the sprite which isdisplayed.
xxxxx is the starting column of the guardian.
Byte 3For ropes, for some reason this should be #74. For arrows it should be#ff if the arrow moves to the right and #20 if it moves to theleft. Otherwise, it specifies 2 * the starting pixel row of horizontalor vertical guardians, or equivalently 16 * the starting charactercell row.
Byte 4For horizontal guardians, it gives the speed, which is negative if theguardian initially moves to the left. For vertical guardians it gives2 * the speed, which is negative if the guardian initially movesupwards. For arrows it is ignored; for ropes it gives the length.
Byte 5For ropes, for some reason this should be #98. For arrows it isignored. Otherwise it specifies the sprite page.
Byte 6For ropes, for some reason this should be #08. For arrows it specifiesthe bit pattern for the top and bottom of the arrow; the middle uses#7e. Otherwise, it gives the number of ticks before the guardian'spath is reset. This byte is decremented once per tick; when it hitszero, it is loaded again from byte 7.
Byte 7For ropes, this gives the period of one swing in ticks; note thatshort ropes with short periods won't appear to swing at all. Forarrows it is ignored. Otherwise, it specifies the length of aguardian's path in ticks.
NoteBytes 6 and 7 have different meanings for horizontal and verticalguardians from Matthew Mode. This change, which follows JSWIIpractice, is necessary because it's otherwise very difficult to workout where to put wraparound diagonal guardians once they've wrappedaround. A side effect of this is that you don't specify a path betweenendpoints as you do in Matthew Mode.
If you're confused...Do what I do! Set the first four entries of your guardian class tableto this:
bc00 00 00 00 00 00 00 00 00 ........bc08 03 22 09 74 20 98 08 36 .".t ..6bc10 84 00 00 00 ff 00 84 00 ........bc18 04 00 00 00 20 00 21 00 .... .!.
These define, respectively:
- a dummy sprite
- a nice long rope
- an arrow moving right
- an arrow moving left