Defender - The Source ===================== Update 03 ========= By Dave Edwards =============== Assuming that you're reading this in order to find out about the ACC Defender program that I'm working on, and that you already have the old source file, then this update is devoted to the latest additions as of 24/10/92. Now, at last, it's possible to die. This is just one of several changes that have occurred, but there would have been more but for circumstances beyond my control (such as a lightning strike blasting my Defender work disc causing a mad scramble for backed-up copies, resulting in my having to write from scratch three days' lost changes). Anyway, read on... Assembly Changes ---------------- None since Update 02: you just bung the code into DevPac or whatever and assemble it. You'll need the 3 include files also, one being just a big list of hardware equates, the other two being the Defender1.i include file & the Defender2.i include file containing all of the title screen stuff, alien handling and mine/missile handling. Also, and this is a ***BIG*** change, I have packed all of the graphics include files into one large file, thus saving both disc directory space (no doubt Mark will now be immensely happy!) & assembly time (since only one file has to be loaded instead of 30!) This file is called graphics.bin, and is included with the other files. Users who want the original graphics files in their original format, see the "Help Wanted" section below for my address, and then send me a blank disc plus stamped, self-addressed jiffy bag for return. Mark your mail with the legend: ACC DEFENDER GRAPHICS FILES so that I can differentiate your mail from DOC DISCS requests, and I'll send you the files in their original format (since there are now more attempts at Defender clones from other ACC members materialising, they might like to use my graphics, so send me a disc & use/abuse the graphics at leisure). Since I'm using an interleaved bitmap, the graphics are saved in the RAW BLIT format. Users of the Kefrens IFF-Converter will be familiar with the RAW BLIT format, as it's provided as one of the standard converter options. If you're really stuck, however, I'll also provide (if requested) an IFF version suitable for loading into DPaintII/III/IV for those in real trouble. Oh, I've also supplied on this ACC disc a utility for constructing big files of bob/sprite graphics from lots of little ones (plus source!), so you can use that to create your own concatenated graphics files! I'll also supply copies of the MakeGraf utility for concatenating graphics files which I've written and sent to Mark (it should be on the same ACC disc as this file, but if you lose it because your younger brother plays with magnets near your discs, then I'll send you the source, executable & DOC file just as I did for Mark). See MakeGrafRT.s, its include files and MakeGraf.DOC for more info! Debugging Routines ------------------ All of the routines from Update 02 still exist. If you want to play with the code, feel free, but take advantage of the debug routines so that if something terrible happens, you'll be able to pinpoint it unless you're unfortunate enough to have the Guru knocking on your door ... even then, so far I've been lucky and it's been pretty obvious where the problem lies when the program commits hara-kiri! Getting Out Of The Game ----------------------- Ok, you've played it for 10 seconds and already you're bored. You now want to get back to the CLI and continue writing Eye Of The Beholder 27. How do you do it? Well, it's a bit harder now. Because the game loop is tidier (you can now die, and start another game), it's harder to get back to the CLI. You now have to press F1 or F2 within the title screens to start playing a game, then hit ESC when the main game screen appears. This will take you back to the CLI quick as a flash! End Of Game Etc --------------- As well as some 'Get Ready' type screens, telling you the attack wave number, there are now some other ancilliary screens. First, there's the endof-wave screen, which tells you that you've completed the attack wave, & how many Bodies you've saved. This, in standard Defender style, also informs the player about bonuses (100 * level completed for each saved Body up to Level 10, in which case the maximum possible bonus for saving all Bodies is 10,000) and I've got it to display this info in proper Defender style too. There's a simple 'Game Over' screen for when you have lost all lives, and now you get back to the start of the game (i.e., the entry title screens) instead of having to get out and re-run the code. Also, the high scores have been implemented! Now, when you get a high score, you can set three initials using the UP, DOWN and FIRE keys. These are taken to be whatever keys/joystick movements etc., that you selected when the joystick/keyboard configuration screen was selected (press F3 during the title sequence to change your keyboard/joystick preferences). Having allowed a player to have his/her own keyboard/joystick preferences, it was merely the sensible thing to do to allow those preferences to be used to alter the three initials on screen. After all, the original Defender arcade machine used UP, DOWN and FIRE for the same job. So, if you're using the default keyboard/joystick setup which I use & set up when the program runs, use joystick up (default UP setting) to change the current letter upward in the alphabet ("A" changes to "B" etc), use joystick down (default DOWN setting) to change the current letter downward in the alphabet ("Z" changes to "Y" etc), and the fire button (default FIRE setting) to move on to the next letter. At the start, the initials are set to three spaces, so you'll have to change the letters to make anything appear. The implementation allows repeating keys (or, for that matter, the joystick to be held constantly in the given position) while letter selection is made. I've used a delay loop using the VBL counter to ensure that the repeat is slow enough for the average user to select letters without too much frustration. Repeat I/O sensing is NOT allowed for the FIRE key, however, and is deliberately killed off to ensure that only one letter at a time is skipped over! Once you've selected your initials, they become the default for future game plays, and reappear if you get more high scores after each new game. You can change them every time you get a new high score, or if you're happy with the current ones, just use FIRE three times in succession. Then, the donkey work. Your score and initials are slotted into the high score tables. If your high score is high enough for both today's greatest scores AND the all time greatest (which they will to begin with, because you can't save your scores to disc & re-load them in afterwards yet!), then your initials and score are slotted into both tables. Later, when I get round to implementing the saving and re-loading of all time high-scores, you should find that some of your "today's" scores don't make it to the "all time" high scores once you've filled up the "all time" high scores with a few truly good high scores! Once again, see the source code and its comments for more details, as they provide the DEFINITIVE description of what the program does! Landscape Changes ----------------- Unfortunately, the crafty Landscape sequence I devised has a latent flaw. It's possible for the landscape to 'fall off' the bottom of the screen thus corrupting whatever memory lies below it (and occasionally waking up the Guru). So, not wanting to do a major rewrite, I thought about how I could use the old table format, but stop the landscape Y position exceeding 255 (on PAL Amigas-NTSC owners will need to do much rewriting of the code to make it work on an NTSC machine-sorry!). Well, I've decided to use the random numbers in a different way. Now, I use the random numbers to select a piece of pre-defined 'scenery' (simply a sequence of bytes representing a particular landscape feature) from a table (why not!). Given that the byte values in the landscape definition table are: _ = $00 \ = $01 / = $02 then landscape 'scenery' can be defined using 6-byte table entries such as: /\ 2,2,2,1,1,1 / \ / \ _ _ \ / 0,1,1,2,2,0 \/ \ / 1,1,0,0,2,2 \__/ Having defined some pieces of 'scenery', I then use a random number to select a piece of scenery, and another to select a place to put it. I have tried to set things up so that one piece of 'scenery' isn't written over another, and so far I think it'll work. The only problem is that the landscape can look a bit sparse in places. The old landscape list formats are the same (list number 1 is a list of 'scenery' bytes as above, and list 2 is a list of blitter vertical offsets for plotting the landscape pieces). While doing this, I then decided to expand the PlayerData structure to contain pointers to landscape lists of this sort. This allowed me to set up two different landscapes for each player, and to change the landscape of one player when moving on to another attack wave, without changing the landscape of the other player. A little bit of jiggery-pokery was required when it came to setting up the landscape lists for two players, but since all of the landscape code used pointer variables in the main variable table, all I needed to do was set them up for the correct player each time (and set them for each player during the initialisation sequence at the start of each new game, thus initialising both sets). Two Players ----------- Since I decided that it would make sense to have two different landscapes for two players, it would also make far more sense to have two different sets of aliens for them too. After a LOT of hard work, I have now implemented this, and so when a two-player game is selected, two lists of alien life forms are created. Actually, this is done as a matter of course, just in case a two-player game is selected, and the one list used if the user selects a one-player game. The PlayerData structures now contain pointers to each of their respective lists of Anim/AO data structures (which has increased memory usage a fair bit). Now the various aliens should behave sensibly. Lander Terrain Following ------------------------ Yes, the Landers now follow the terrain! Basically, the code takes the Lander's x coordinate, and uses this as an index into the Landscape offsets array. The value of the vertical offset used by the landscape plotting routines is then extracted from the relevant array entry, and a similar offset computed from the Lander's Y position. If the two are equal, nothing happens. Otherwise, the Lander is set to move up or down depending upon the state of the comparison. Actually, since the Lander's (x,y) coordinates are those of the top left corner of the Lander graphic, corrections are added to these to reference the centre of the Lander horizontally, and a point 4 pixels below the Lander's legs. The actual corrections applied may differ slightly from these after I 'fine-tuned' them to make the terrain following as aesthetically pleasing as possible. Mines And Missiles ------------------ And now the bad news for those who have been enjoying 'safe' enemies! The code for handling mines and missiles has been implemented! I implemented this using two arrays plus code to access those arrays. The first array is an array of data structures defined by the structure definition (DevPac): rsreset md_XPos rs.w 1 ;x position of object md_YPos rs.w 1 ;y position of object md_XMove rs.w 1 ;x movement of object md_YMove rs.w 1 ;y movement of object md_Count rs.w 1 md_Type rs.b 1 ;object type md_Index rs.b 1 ;index into 2nd array md_Sizeof rs.w 0 and pointed to using the variable MineArray in the main variable table. The second array is an array of bytes, which take the values zero for a free array element, and -1 for an element in use. This second array is pointed to by the MineFree variable in the main variable table. The md_Type field within one of the MineArray elements can take any of three possible values, which depend upon whether that array element is in use by a mine or missile, or is free to be used by other code. The values in question are: _MDF_FREE equ 0 _MDF_MINE equ 1 _MDF_MISSILE equ 2 If the value is not _MDF_FREE, then this value is used to index into the main variable table, where two consecutive long word variables point to the graphic images for a mine and a missile. So when the mine operating code is run, it either encounters a value of _MDF_FREE (and thus moves on to the next array element), or performs the following: lea MineImages(a6),a1 ;ptrs to mine & missile imgs subq.b #1,d0 ;d0 contains md_Type add.w d0,d0 add.w d0,d0 move.l 0(a1,d0.w),a1 ;get image ptr to obtain the correct graphic image. Again, the necessary variables are initialised in the InitVars() routine as needed. So that mine/missile activating code can check quickly if an element is free, the main variable table contains a variable called MineIndex, which is used to index into the MineFree array (a simple array of bytes). When the byte referenced via this index, using code such as: move.l MineFree(a6),a1 ;ptr to free byte array move.w MineIndex(a6),d0 ;index value tst.b 0(a1,d0.w) ;free element? beq.s Free ;skip if so ... Free ... is zero (therefore the first array element is free), then mine/missile activating code can then reference that element directly and set up the required values for a mine or a missile. Since the structure size is 12 bytes, I use code of the following kind to create the pointer to the MineArray element: move.w MineIndex(a6),d0 move.l MineArray(a6),a1 move.w d0,d1 ;this is a quick add.w d1,d0 ;x 12 operation add.w d0,d0 add.w d0,d0 add.w d0,a1 ;this is ptr to md_ element From this point on, the mine/missile activation code can set up the required values. The number of elements in the array is defined by _MA_SIZE, given as an equate in the main program. The DoMines() routine, which handles plotting mines/missiles, collision detection between mines/missiles and the player's ship, and freeing mine/missile entries that have left the playfield, is fully commented as might be expected (I do have a reputation to uphold!). Also available is StopMines() (called at the end of an attack wave or when the player loses a life) which prevents left-over mines/missiles from a previous play session from appearing on the screen when play restarts. Each of the AlienObject SpecialCodes has been edited to include mine/ missile activation code. Each routine uses the MineIndex variable in the main variable table to reference the next array element. If this element is free, then the relevant routine then decides whether or not to fire the missile or lay the mine using code of the following sort: bsr Random ;make random number move.w Seed(a6),d0 ;get it and.w #$F000,d0 ;do this beq.s Firing ;firing if result zero The above code will result in a missile being fired or a mine being laid once every 16 calls (because statistically, the result of ANDing a random number with $F000 will be zero with a probability of 1/16). For a probability of 1/8 (therefore more frequent mine/missile activation), use: and.w #$E000,d0 instead, and for less frequent activation, use (for example, to select 1/64 probability): and.w #$FC00,d0 Note that the various routines use the high-order bits to determine activation frequency, and the low-order bits to determine missile speed & direction. This prevents aliens from always firing mines in the same direction (if you can't see why, try tracing the above code when 'AND #$F000,D0' is replaced by 'AND.W #$000F,D0', and the same low-order bits are then used to set the speed & direction...). Missiles start at the centre of the firing alien, and move in any direction and with a random speed. Mines are laid by Bombers along their flight path (dealt with in an earlier DOC file) and have zero speed. The mine images are copied into a buffer, shifted for pixel alignment and then ORed onto the screen. This allows Bomber mines to appear without new mines scribbling all over the image of the old mines. It also looks prettier when Swarmers fire salvos of the things (see later...). Finally, the duration of a mine or a missile can be controlled. The md_Count field of the MineArray entry is set to a random value, and is then counted down to zero. If it hits zero, then this entry is then freed for reuse by other code. For more information, see the routines LMine(), BMine(), SMine() and XMine() and their associated comments. They all follow a similar scheme, so full comments are attached to LMine() only. BAITERS!!!! ----------- AGH! The dreaded Baiters are alive and active! The InitSet() routine has been adjusted to allow the end four AlienObject structures referenced by BlitPreComp() and friends to be reserved for Baiters. So if you're really unlucky, you'll get four of them! The BaiterOn() routine determines when they appear. Extra entries in the PlayerData structure for each player provide an 'alarm clock' facility. Once approximately four minutes has elapsed in the current attack wave, the first Baiter makes its appearance! From this point on, extra Baiters appear once every 30 seconds until there are four of them in play. Now to make life even harder, the Baiters pursue you, AND they have a top speed almost twice that of your ship! Furthermore, they spit out LOTS of missiles, and when they come sufficiently close to your ship, they match their speed to yours! Then they move vertically, spitting missiles out with gay abandon, until they either collide with you or their missiles get you. It IS possible to escape them for short periods, by judicious use of the Reverse key, followed by stepping on the thrust like there's no tomorrow! But you are hereby warned, they are EXTREMELY difficult to shoot because they are so thin, so don't try double-reverse shooting unless you possess lightning reflexes! However, you'll need to keep performing evasive maneuvers and concentrate on killing off the remaining non-Baiter aliens, since that is the only way that the attack wave will come to an end. Even if you successfully kill the Baiter, another one will replace it in 30 seconds, and if you fail, then you'll have two to contend with. The program allows up to four of them to come alive and taunt you, and they can sometimes fly in a 'cross-pattern' whereby one moves up and one moves down, sandwiching your Defender ship between them in a hail of missiles! Baiter pursuit deserves a bit more coverage. The theory is as follows:the display ranges from X=0 to X=$1FFF, and the current display begins at some X coordinate within the playfield, say X=XD. The position of the ship on the display is given by its Anim_XPos entry in the AO_ structure for the ship which we shall call SX. The TRUE position of the Baiter is XB (its position within the playfield defined by ITS Anim_XPos entry), and therefore its plot position on the display is BX = XB-XD. Note that because of the way the display is handled, the CurrXPos variable in the main variable table contains not XD, but -XD and is ADDED to the Anim_XPos value of scrolling AlienObjects to generate the plotting position for the BlitPreComp() routine. Now the plot position of the Baiter, namely XB-XD, is given by XB+C where C is the value of CurrXPos (=-XD). We then create the difference between the baiter and ship plot positions, namely BX-SX, and then normalise it for the display (using AND.W MaxScrPos(A6)) to force the difference in plot positions to lie somewhere within the playfield (all coordinates are normalised in this way). Note that MaxScrPos(A6) contains the value $1FFF. Once we have ((BX-SX) AND $1FFF) (let us call this DX), this value will be in the range: 0 <= DX <= $0FFF when the Baiter is to the right of the ship on the display (or off it, since this condition holds always), and will be in the range: $1000 <= DX <= $1FFF when the Baiter is to the left of the ship. Hence, we perform the operation: cmp.w #$0FF,d0 ;d0=DX bhi.s ToRight ToLeft ... ToRight ... Now, once we've established where the Baiter is relative to the ship, it is a simple matter of setting the AO_XMove value to make the Baiter move towards the ship. I make this value increase until the AO_XMove value (the velocity of the Baiter) reaches a certain maximum speed, or decrease for negative velocities depending upon where the Baiter is. The absolute value of this velocity is the Baiter's speed, and is set to be greater than the maximum speed of the Defender ship. Thus, even though the Baiter starts off at a position some way away from the ship, it soon catches up. To make life harder, further tests are made to see if the Baiter is 'close to' the ship (in other words, when the value of DX is small). Taking account of the fact that DX is normalised for the display, this involves the comparison of DX with some small distance (call this FX) when the Baiter is to the right of the ship, and comparison of DX with $2000-FX when the Baiter is to the left of the ship. If the Baiter is sufficiently close (i.e., within FX pixels of the ship in either direction) then the Baiter's speed is matched to the Defender ship making the pursuit complete. The vertical movement is set randomly to one pixel up or down, & as the Baiter moves up or down, it spits out missiles when it appears on screen which is usually fatal for the Defender if the Baiter is close by. It is possible to temporarily escape the Baiter (since it can't come to a dead stop) by rapidly reversing direction while increasing the ship's thrust. The Baiter temporarily flies off screen, and begins pursuit once more. BaiterCode() is the routine that sets up the initial position of the Baiter, and for now it also sets the initial horizontal speed to zero. Once a Baiter is active, DoBaiter() handles movement, pursuit and missile firing. As always, see the comments for more information! Since I've provided tips on dealing with the dreaded Baiters, it is only fair that I give some hints on the other creatures too! These now follow... A Bit On Gameplay ----------------- Despite attempts to mimic the original, some things have gone by the wayside. One, sometimes it's impossible to reach a Lander in time when it is kidnapping a Body. This is because of the sheer size of the play area! Also, smart bomb technique with Pods is different. Now, you have to get Pods onto the screen, shoot one, pause briefly and THEN hit the smart bomb. Don't wait too long or some of the swarmers will fly off screen and escape! Landers now follow the terrain, AND spit out missiles. Sometimes, a Lander will be REALLY sneaky and produce a missile that moves with it, hidden behind the Lander! You'll then shoot the Lander and fly into the missile thus left behind if you don't watch for this the sole indication that a Lander is hiding a missile is a slight change in colour at the centre of the Lander caused by ORing the missile image on top of the Lander image (the colours I have chosen for each bitplane combination make it harder since the colour is changed from green to cyan, and you have to squint to see it). Bombers are harder to shoot as they weave about properly. They also now lay mines, and these mines are of varying duration! Some of the mines are of short duration, others last much longer and therefore there is much more risk of flying into them when pursuing a Bomber! Be wary of this! Getting Pods to drift properly was a nightmare (and I still haven't found a neat way of getting around this one). So the Pod drifting is not up to the spec of the arcade original. Deal with these by shooting one, and THEN hitting the Smart Bomb once some Swarmers are alive. If you have lots of Pods on screen, they'll erupt and ALL of the nasties will die if you time it correctly. If not, some Swarmers may be left to haunt you! Mutants are just plain silly! They also spit missiles a lot. Hit from a distance to avoid the frenetic missile activity. Swarmers don't chase you (they will though!). However, they spit out LOTS of missiles! They spit missiles in the same horizontal direction that they are travelling in, but in the OPPOSITE vertical direction, just to make escape from a horde of them that much harder if you fail to Smart Bomb them all after shooting Pods! However, on PAL Amigas, the very bottom of the play area is relatively 'safe' (but not completely!). Those who have rewritten a fair bit of this to work on NTSC Amigas will find that there is NO safe area from the Swarmers unless they write code to provide it! Bug Corrections -------------- Landers now kidnap Bodies properly, and turn into Mutants properly if they reach the top of the screen. I still haven't figured out why the occasional Mutant turns up with an off-screen Y coordinate, because I've tried to prevent that. Now, however, it appears to be VERY occasional. You'll know if it happens to you because the scanner display will be corrupted, and you may find the bitplane moduli/Copper lists corrupted to boot, with the usual spectacular results. Help Wanted! ------------ I'm so busy debugging this damn program, and filling in lots of application forms for employment (and writing specialist, one-off DOC files on request for some people about such things as the Transputer instruction set and assembly syntax), that I haven't the time to play with OctaMED or any of my collection of Trackers. So if anyone has some nice, Defender-ish samples, could they please send them to me! I'll take samples in any form that is compatible with OctaMED. Send on disc to: Dave Edwards 232 Hale Road WIDNES Cheshire WA8 8QA marking your envelope "DEFENDER SAMPLES", and I'll return your disc with some goodies added on for your trouble (e.g., disc repairers & things). Yet To Implement ---------------- For now, the list of 'things to do' comprises:hyperspace (so far ignored completely), allowing 'all time greatest' high scores to be saved to & loaded from disc (not a high priority one, this), and sound. Also, I've yet to implement 'deep space' when all the Bodies disappear, and decent Mutant behaviour (so much yet to do, sigh!). Bugs Still Remaining -------------------- Watch what happens if a Lander takes a Body to the top of the screen. The scanner dots are left behind! BlitPreComp() only handles alien deaths, a Body death is handled elsewhere. I'll fix it later-if you can't wait, fix it yourself! I'm still getting the odd Mutant with bizarre Y coordinates. I don't know why, the code SHOULD stop this (but apparently doesn't always). You can tell if this is the case quite simply-the Mutant can't be shot while its Y coordinate is outside the normal range, and if the Y coordinates are way off the mark, stand by for Amiga FIREWORKS_MODE as the Copper Lists get seriously mutilated... Also, after a change of attack wave, I'm getting weird Swarmer behaviour. When a pod splits, some 'extra' Swarmers appear, but these are NOT in any way normal Swarmers. First, they're dying, and are displayed using the final explosion graphic. Second, they jump about on screen for a brief instant, and then vanish. This ONLY occurs after a move up to a higher attack wave, and as yet I can't figure out why. I should have stopped it by resetting the Swarmer data pointers upon moving to a new level, but this seems to have no effect. That wraps up Update 03. Have fun with it, and as you all know and love: Live fast, code hard & die in a beautiful way Dave Edwards.