; ; Scramble (C) 1981 KONAMI. ; ; Reverse engineering work by Scott Tunstall, Paisley, Scotland. ; Tools used: MAME debugger & Visual Studio Code text editor. ; Date: 28 May 2021. Keep checking for updates. ; ; Please send any questions, corrections and updates to scott.tunstall@ntlworld.com ; ; Thanks go to Phil Murray for misc help and Mark McDougall for his very thorough code review. ; Check out Mark's blog about transcoding this source to the Neo Geo here: http://retroports.blogspot.com/2021/04/scrambled-priorities.html ; ; Be sure to check out my reverse engineering work for Robotron 2084, Galaxian and Berzerk too, ; at http://seanriddle.com/robomame.asm, http://seanriddle.com/galaxian.asm and http://seanriddle.com/berzerk.asm respectively. ; ; ; Finally: ; If you'd like to show appreciation for this work by buying me a coffee, feel free: https://ko-fi.com/scotttunstall ; I'd be equally happy if you donated to Parkinsons UK or Chest Heart And Stroke (CHAS) Scotland. ; Thanks. /* Conventions: NUMBERS ======= The term "@ $" means "at memory address in hexadecimal". e.g. @ $1234 means "refer to memory address 1234" or "program code @ memory location 1234" The term "#$" means "immediate value in hexadecimal". It's a convention I have kept from 6502 days. e.g. #$60 means "immediate value of 60 hex" (96 decimal) If I don't prefix a number with $ or #$ in my comments, treat the value as a decimal number. ARRAYS, LISTS, TABLES ===================== The terms "entry", "slot", "item", "record" when used in an array, list or table context all mean the same thing. I try to be consistent with my terminology but I might not always succeed. "Length" when used in terms of an array refers to how many elements it contains. SizeOf refers to the size in bytes of a structure (as it does in C). Unless I specify otherwise, I all indexes into arrays/lists/tables are zero-based, meaning element [0] is the first element, [1] the second, [2] the third and so on. FLAGS ===== The terms "Clear", "Reset", "Unset" in a flag context all mean the flag is set to zero. A non-zero value in a flag means the flag is "set" - unless I specify otherwise. COORDINATES =========== X,Y refer to the X and Y axis in a Cartesian 2D coordinate system, where X is horizontal and Y is vertical. Like Galaxian, the Scramble monitor is rotated 90 degrees. This means that: a) updating the hardware Y position of a sprite presents itself to the player as changing the horizontal position. To make a sprite appear to move left, you would increment its Y position. To make a sprite appear to move right, you would decrement its Y position. b) updating the hardware X position of a sprite presents itself to the player as changing the vertical position. To make a sprite appear to move up, you would decrement its X position. To make a sprite appear to move down, you would increment its X position. So, when you see code updating the Y coordinate when you would expect X to be updated, or vice versa, you now know why. TODOs: ====== TODOs are for me to look at later, when a piece of code isn't immediately obvious. Anyone who knows me, knows I like leaving TODOs in code :) */ /* Copied from MAME4All documentation: https://github.com/squidrpi/mame4all-pi/blob/master/src/drivers/scramble.cpp Port mappings copied from https://github.com/mamedev/mame/blob/master/src/mame/drivers/scramble.cpp MAIN BOARD: 0000-3fff ROM 4000-47ff RAM 4800-4fff Character RAM (Yes, $4fff, mame4all docs are incorrect) 5000-50ff Object RAM 5000-503f screen attributes 5040-505f sprites 5060-507f bullets 5080-50ff RAM read: 7000 Watchdog Reset (Scramble) 8100 IN0 PORT_BIT( 0x01, IP_ACTIVE_LOW, IPT_JOYSTICK_UP ) PORT_8WAY PORT_COCKTAIL PORT_BIT( 0x02, IP_ACTIVE_LOW, IPT_BUTTON2 ) PORT_BIT( 0x04, IP_ACTIVE_LOW, IPT_SERVICE1 ) PORT_BIT( 0x08, IP_ACTIVE_LOW, IPT_BUTTON1 ) PORT_BIT( 0x10, IP_ACTIVE_LOW, IPT_JOYSTICK_RIGHT ) PORT_8WAY PORT_BIT( 0x20, IP_ACTIVE_LOW, IPT_JOYSTICK_LEFT ) PORT_8WAY PORT_BIT( 0x40, IP_ACTIVE_LOW, IPT_COIN2 ) PORT_BIT( 0x80, IP_ACTIVE_LOW, IPT_COIN1 ) 8101 IN1 PORT_DIPNAME( 0x03, 0x00, "Lives") PORT_DIPLOCATION("SW1:2,1") PORT_DIPSETTING( 0x00, "3" ) PORT_DIPSETTING( 0x01, "4" ) PORT_DIPSETTING( 0x02, "5" ) PORT_DIPSETTING( 0x03, DEF_STR( Free_Play ) ) PORT_BIT( 0x04, IP_ACTIVE_LOW, IPT_BUTTON2 ) PORT_COCKTAIL PORT_BIT( 0x08, IP_ACTIVE_LOW, IPT_BUTTON1 ) PORT_COCKTAIL PORT_BIT( 0x10, IP_ACTIVE_LOW, IPT_JOYSTICK_RIGHT ) PORT_8WAY PORT_COCKTAIL PORT_BIT( 0x20, IP_ACTIVE_LOW, IPT_JOYSTICK_LEFT ) PORT_8WAY PORT_COCKTAIL PORT_BIT( 0x40, IP_ACTIVE_LOW, IPT_START2 ) PORT_BIT( 0x80, IP_ACTIVE_LOW, IPT_START1 ) 8102 IN2 PORT_BIT( 0x01, IP_ACTIVE_LOW, IPT_JOYSTICK_DOWN ) PORT_8WAY PORT_COCKTAIL ; Some examples of Coinage settings and explanation of purpose: ; "A 1/1 B 2/1 C 1/1" means "Slot A: ONE coin / ONE credit. Slot B: TWO coins / ONE credit. Slot C (SERVICE button) : ONE press / ONE credit." ; "A 1/2 B 1/1 C 1/2" means "Slot A: ONE coin / TWO credits. Slot B: ONE coin / ONE credit. Slot C (SERVICE button) : ONE press / TWO credits." PORT_DIPNAME( 0x06, 0x00, "Coinage" PORT_DIPLOCATION("SW1:5,4") PORT_DIPSETTING( 0x00, "Coinage A 1/1 B 2/1 C 1/1") PORT_DIPSETTING( 0x02, "Coinage A 1/2 B 1/1 C 1/2") PORT_DIPSETTING( 0x04, "Coinage A 1/3 B 3/1 C 1/3" ) PORT_DIPSETTING( 0x06, "Coinage A 1/4 B 4/1 C 1/4" ) PORT_DIPNAME( 0x08, 0x00, "Cabinet") PORT_DIPLOCATION("SW1:3") PORT_DIPSETTING( 0x00, "Upright") PORT_DIPSETTING( 0x08, "Cocktail") PORT_BIT( 0x10, IP_ACTIVE_LOW, IPT_JOYSTICK_UP ) PORT_8WAY PORT_BIT( 0x20, IP_ACTIVE_LOW, IPT_CUSTOM ) /* protection bit */ PORT_BIT( 0x40, IP_ACTIVE_LOW, IPT_JOYSTICK_DOWN ) PORT_8WAY PORT_BIT( 0x80, IP_ACTIVE_LOW, IPT_CUSTOM ) /* protection bit */ write: 6801 interrupt enable 6802 coin counter 6803 POUT1 - affects screen background colour (0=black, 1 = blue) - thanks to Phil Murray for telling me about this 6804 stars on 6806 screen vertical flip 6807 screen horizontal flip 8200 To AY-3-8910 port A (commands for the audio CPU) 8201 bit 3 = interrupt trigger on audio CPU bit 4 = disable sound (see scramble_state::scramble_sh_irqtrigger_w in MAME's scramble audio driver) 8202 protection check bits SOUND BOARD: 0000-1fff ROM 8000-83ff RAM I/O ports: read: 20 8910 #2 read 80 8910 #1 read write 10 8910 #2 control20 8910 #2 write 40 8910 #1 control 80 8910 #1 write interrupts: interrupt mode 1 triggered by the main CPU */ ; ; Value in COINAGE_VALUE What it represents ; ====================== ========================= ; 0 Coinage A 1/1 B 2/1 C 1/1 ; 1 Coinage A 1/2 B 1/1 C 1/2 ; 2 Coinage A 1/3 B 3/1 C 1/3 ; 3 Coinage A 1/4 B 4/1 C 1/4 ; ; ** See hardware info above for explanation of coinage settings ** COINAGE_VALUE EQU $4000 COIN_COUNTER EQU $4001 ; Used to coint up to number of coins required to gain a single credit. See $0A80. NUM_CREDITS EQU $4002 ; number of credits. See $0A68 and $0A80. EQU $4003 EQU $4004 ; ; The game follows what I call "scripts". A SCRIPT is a predefined sequence of STAGES (ie: subroutines) that implement an overall goal. ; The whole game is script-driven, from attract mode to the game itself. ; ; The NMI interrupt handler uses SCRIPT_NUMBER ($4005) to identify what script to run and, depending on the script, SCRIPT_STAGE ($400A) to ; determine what subroutine to call to do the work for that stage of the script. When the subroutine has completed its work, ; it increments SCRIPT_STAGE which is akin to, "OK, I'm done; proceed to next stage of script". ; ; For example, a script for HELLO WORLD might be implemented as three stages: ; 1. Display Hello World on screen. Set SCRIPT_STAGE to 2. ; 2. Wait for key. Set SCRIPT_STAGE to 3 after key pressed. ; 3. Terminate program. ; ; The main take-aways from the above are: ; 1. The whole game is driven by the NMI interrupt. ; 2. Script stage and number are really just indexes into jump tables. ; ; see $21F5 for the NMI script handler. SCRIPT_NUMBER EQU $4005 ; 0-based index into pointer table beginning @ $21F9 IS_GAME_IN_PLAY EQU $4006 ; If set to 1, game is in play with a human in control. DEFAULT_PLAYER_LIVES EQU $4007 ; Number of lives you get when starting game. Controlled by dip switches. See $0115 TEMP_COUNTER_4008 EQU $4008 ; temporary counter used for delays, such as waiting before transitioning to next stage of a script TEMP_COUNTER_4009 EQU $4009 ; temporary counter used for delays SCRIPT_STAGE EQU $400A ; Identifies what stage of script [SCRIPT_NUMBER] we are at. See $0E87, $0FD2, $0FEA TEMP_CHAR_RAM_PTR EQU $400B ; pointer to character RAM. Used by screen-related routines (e.g. power on colour test) to remember where to plot characters on next call. CURRENT_PLAYER EQU $400D ; 0 = PLAYER ONE, 1 = PLAYER TWO IS_TWO_PLAYER_GAME EQU $400E ; 0 = One player game, 1 = 2 player game IS_COCKTAIL EQU $400F ; 0 = upright, 1 = Cocktail PORT_STATE_8100 EQU $4010 ; copy of state for memory address 8100 (IN0) ; Value in PORT_STATE_8101 What it means ; ========================= ========================== ; 3 Players lives default to 3 ; 2 Players lives default to 4 ; 1 Players lives default to 5 PORT_STATE_8101 EQU $4011 ; copy of state for memory address 8101 (IN1) ; PORT_STATE_8102 EQU $4012 ; copy of state for memory address 8102 (IN2) PREV_PORT_STATE_8100 EQU $4013 ; holds the previous state of memory address 8100 (IN0) PREV_PORT_STATE_8101 EQU $4014 ; holds the previous state of memory address 8101 (IN1) PREV_PREV_PORT_STATE_8100 EQU $4015 ; holds the previous, previous (!) state of memory address 8100 (IN0) PREV_PREV_PREV_STATE_8100 EQU $4016 ; holds the previous, previous, previous state of memory address 8100 (IN0) BONUS_JET_FOR EQU $4017 ; BCD value representing how many multiples of a thousand required to get a bonus jet, e.g. 10 = 10000 UNPROCESSED_COINS EQU $4018 ; bumps up when coin inserted DRAW_LANDSCAPE_FLAG EQU $4019 ; when set to 1, landscape and ground objects will be drawn & updated. See $020D LANDSCAPE_COLOUR_CHANGE_COUNTER EQU $401B ; ; Object RAM back buffer. ; Colour attributes, scroll offsets and sprite state are held in this buffer and updated by the game. ; When all the updates are complete and ready to be presented on screen to the player, ; the back buffer is copied to the hardware's OBJRAM by an LDIR operation - see $09D2. ; Effectively all colours, scroll and sprites are updated as part of a single operation. ; This back buffering technique is still used today in modern games. ; ; The back buffer is organised thus: ; ; From $4020 - 405f: column scroll and colour attributes. Labelled as OBJRAM_BACK_BUF_ATTRIBUTES in this document. Maps directly to $5000 - $503F. ; Note: Even numbered addresses hold scroll offsets, odd numbered addresses colour attributes. ; From $4060 - 407F: 8 entries of type SPRITE. Labelled as OBJRAM_BACK_BUF_SPRITES in this document. Maps directly to $5040-$505f. ; From $4080 - 409F: 8 entries of type BULLET_SPRITE. Labelled as OBJRAM_BACK_BUF_BULLETS in this document. Maps directly to $5060-$507f. OBJRAM_BACK_BUF EQU $4020 OBJRAM_BACK_BUF_ATTRIBUTES EQU $4020 ; represents a hardware sprite struct SPRITE { BYTE Y; ; Y coordinate BYTE Code; ; Determines animation frame BYTE Colour; ; Core (individual) colour of sprite - other colours shared between all sprites BYTE X; ; X coordinate } - sizeof(SPRITE) is 4 bytes ; OBJRAM_BACK_BUF_SPRITES EQU $4060 ; represents a hardware bullet sprite struct BULLET_SPRITE { BYTE ??? ; Setting this byte in MAME debugger appears to do nothing. BYTE Y; BYTE ??? ; Does nothing. BYTE X; } OBJRAM_BACK_BUF_BULLETS EQU $4080 ; $40C0 to $40FF is reserved for a circular queue. The queue is comprised of byte pairs representing a command and parameter. ; NB: I term the byte pair a *Queue Entry* in the code @$0038 and $01C1. ; ; As 64 bytes are reserved for the queue, that means 32 commands and parameters can be stored. ; ; The memory layout of the queue is quite simple. ; ; $40C0: command A ; $40C1: parameter for command A ; $40C2: command B ; $40C3: parameter for command B ; $40C4: command C ; $40C5: parameter for command C ; ..and so on. ; ; See docs @ $0038 for info about what commands are available, and how to add commands to the queue. ; See docs @ $01C1 for info about how commands are processed. ; CIRC_CMD_QUEUE_PTR_LO EQU $40A0 ; low byte of a pointer to a (hopefully) vacant entry in the circular queue. CIRC_CMD_QUEUE_PROC_LO EQU $40A1 ; low byte of a pointer to the next entry in the circular queue to be processed. CIRC_CMD_QUEUE_START EQU $40C0 CIRC_CMD_QUEUE_END EQU $40FF PLAYER_ONE_SCORE EQU $40A2 ; stored as 3 BCD bytes, 2 digits per byte: $40A2 = last 2 digits of score (tens), $40A3 = 3rd & 4th digits, $40A4 = 1st & 2nd ; e.g. a score of 123456 would be stored like so: ; $40A2: 56 ; $40A3: 34 ; $40A4: 12 PLAYER_TWO_SCORE EQU $40A5 ; stored as 3 BCD bytes, 2 digits per byte: same format & order as PLAYER_ONE_SCORE HI_SCORE EQU $40A8 ; stored as 3 BCD bytes, 2 digits per byte: same format & order as PLAYER_ONE_SCORE MYSTERY_SCORE EQU $40AB ; stored as 3 BCD bytes, 2 digits per byte: same format & order as PLAYER_ONE_SCORE IS_COLUMN_SCROLLING EQU $40B0 ; Unused COLUMN_SCROLL_ATTR_BACKBUF_PTR EQU $40B1 ; Unused PROTECTION_1 EQU $40B8 PROTECTION_PORT_PTR_1 EQU $40B9 ; contains a pointer to $8202, the protection check bits (see memory map at start of document) PROTECTION_PORT_PTR_1_LO EQU $40B9 PROTECTION_PORT_PTR_1_HI EQU $40BA PROTECTION_PORT_PTR_2 EQU $40BB CURRENT_PLAYER_STATE EQU $4100 CURRENT_PLAYER_MISSIONS_COMPLETED EQU $4100 ; Determines how many flags are displayed at the bottom right of the screen. See $0920 EQU $4101 ; TODO: See $08C0 EQU $4102 ; TODO: See $08DF CURRENT_PLAYER_FUEL EQU $4105 ; 0 = Empty, 255 = full tank . See $0880 CURRENT_PLAYER_FUEL_DRAIN_COUNTER EQU $4106 ; Counter affecting fuel consumption rate. Lower = faster. See $1725 and especially $29B5 CURRENT_PLAYER_HAD_EXTRA_LIFE EQU $4107 ; Flag used to check if player can be awarded an extra life. 1 = No, Player already awarded extra life. See $13A7. CURRENT_PLAYER_LIVES EQU $4108 ; Number of lives the current player has. See $08FB CAN_DRAW_LANDSCAPE_1 EQU $4110 ; Used in conjunction with CAN_DRAW_LANDSCAPE_2. Flag required for DRAW_LANDSCAPE to do its thing. See $0793. DISABLE_STARS EQU $4111 ; Flag that determines whether blinking background starfield is disabled . Set to 1 if level has a ceiling. See $16A3 for when set, and $2821 for code that checks flag. IS_MISSION_COMPLETE EQU $4112 ; Flag. Set to $FF to complete the mission and run the MISSION_COMPLETED_SCRIPT (see $127E). See also $22C9 and $232F LANDSCAPE_SCROLL_CONTROL_LATCH EQU $4114 ; This value is used to reload LANDSCAPE_SCROLL_CONTROL_COUNTER when it counts down to 0. See $15AF LANDSCAPE_SCROLL_CONTROL_COUNTER EQU $4115 ; Controls when LANDSCAPE_SCROLL_COUNTER increments. See $15A9 LANDSCAPE_SCROLL_COUNTER EQU $4116 ; Used to determine when to scroll new landscape & objects on and the character RAM locations to plot them to. See $15B5 and $15D7 LANDSCAPE_COLOUR EQU $4117 ; Primary colour of the landscape. See $2810 LANDSCAPE_LAYOUT_PTR EQU $4118 ; Points to next piece of landscape to scroll on. See $15C5 for overview, $15CB and $1854 LANDSCAPE_LAYOUT_PTR_LO EQU $4118 LANDSCAPE_LAYOUT_PTR_HI EQU $4119 ; ; NEXT_GROUND_OBJECT_ID identifies - you guessed - the next ground object (fuel tank, stationary rocket, mystery, base) to scroll onto the screen. ; ; NEXT_GROUND_OBJECT_ID is set by @$163F within the DECODE_LANDSCAPE_FLOOR routine. ; ; Value Ground Object type ; ================================ ; 1 Rocket ; 2 Fuel tank ; 4 Mystery ; 8 Base (I think it looks more like a drilling rig, personally!) ; NEXT_GROUND_OBJECT_ID EQU $411A ; NEXT_GROUND_OBJECT_CHAR_PTR is the character RAM address where the next ground object will be drawn. See GROUND_OBJECT_INIT @ $18E6 NEXT_GROUND_OBJECT_CHAR_PTR EQU $411B ; ; LANDSCAPE_FLAGS are bit flags used to determine: ; * How the landscape looks (see $07CE, $07F1, $081B); ; * What enemies appear (see table below) ; * What ambient noises are played. (See $298E in AMBIENT_SOUND) ; ; ; Flags Value Applies to Level INFLIGHT_ENEMY type ; =================================================================== ; 0 1 Rockets (see $1CA7) ; 2 2 UFOs (see $1D84) ; 1 3 Fireballs (see $1EC6) ; 8 4 Rockets (see $1CAD) ; 4 5 None ; 10 BASE None ; LANDSCAPE_FLAGS EQU $411D CURRENT_PLAYERS_LEVEL EQU $411E ; Determines how many cells in the progress bar are coloured. See @$93E NO_APPARENT_PURPOSE_4130 EQU $4130 ; referenced in code @ $1002, but doesn't do anything PLAYER_ONE_STATE EQU $4140 PLAYER_ONE_LIVES EQU $4148 NO_APPARENT_PURPOSE_4170 EQU $4170 ; referenced in code @ $1002 via LDIR, but doesn't do anything PLAYER_TWO_STATE EQU $4180 PLAYER_TWO_LIVES EQU $4188 EQU $41B0 ; Scramble's game logic doesn't do anything like per-pixel testing to check if a player jet/bullet/bomb sprite has crashed into a hill or cave wall; ; instead it uses an array of LANDSCAPE_EXTENT records (LANDSCAPE_EXTENTS) for collision detection purposes. ; A LANDSCAPE_EXTENT defines pixel boundaries that a player/ player bullet/ player bomb must stay within to stay alive. ; ; Generally speaking, as long as the X coordinate of the player/ bullet / bomb is between CeilingX .. GroundX then no collision is registered. ; : (in pseudocode: bool HitLandscape = (X < CeilingX || X > GroundX); ) ; struct LANDSCAPE_EXTENT { BYTE GroundX; ; ground pixel X limit. BYTE CeilingX; ; ceiling pixel X limit. Is always < GroundX } // sizeof(LANDSCAPE_EXTENT) == 4 bytes ; LANDSCAPE_EXTENTS is an array of 32 LANDSCAPE_EXTENT records: one record per character row of the playfield (including areas scrolled off screen). ; Each LANDSCAPE_EXTENT record requires 2 bytes; the array is thus 64 bytes in size. ; ; See also: ; RESET_LANDSCAPE_EXTENTS ($0FC3) , DECODE_LANDSCAPE_FLOOR ($15CB), DECODE_LANDSCAPE_CEILING ($1659), ; PLAYER_TO_LANDSCAPE_COLLISION_DETECTION ($2113), PLAYER_BULLET_TO_LANDSCAPE_COLLISION_DETECTION ($238F), PLAYER_BOMB_TO_LANDSCAPE_COLLISION_DETECTION ($251E) LANDSCAPE_EXTENTS EQU $41C0 ; ; Scores are stored as 3 BCD bytes, 2 digits per byte: ; e.g. a score of 123456 would be stored like so: ; 56 34 12 ; HI_SCORE_TABLE EQU $4200 HI_SCORE_TABLE_1ST EQU $4200 ; top ranking score HI_SCORE_TABLE_2ND EQU $4203 HI_SCORE_TABLE_3RD EQU $4206 HI_SCORE_TABLE_4TH EQU $4209 HI_SCORE_TABLE_5TH EQU $420C HI_SCORE_TABLE_6TH EQU $420F HI_SCORE_TABLE_7TH EQU $4212 HI_SCORE_TABLE_8TH EQU $4215 HI_SCORE_TABLE_9TH EQU $4218 HI_SCORE_TABLE_10TH EQU $421B ; lowest ranking score HI_SCORE_TABLE_BUFFER EQU $421E ; buffer area used when moving high scores - see $137F CAN_DRAW_LANDSCAPE_2 EQU $4230 ; Used in conjunction with CAN_DRAW_LANDSCAPE_1. See $0793 ; ; In Scramble, a new part of the landscape is rendered offscreen every 2 characters (16 pixels) ready to be scrolled on "Just in time" ; ; LANDSCAPE_GROUND_FIRST_CHAR and LANDSCAPE_GROUND_SECOND_CHAR are the "edge characters" of the new ground to be scrolled on. ; If LANDSCAPE_HAS_CEILING_FLAG = 1 then LANDSCAPE_CEILING_FIRST_CHAR and LANDSCAPE_CEILING_SECOND_CHAR are the "edge characters" of the new ceiling ; to be scrolled on. DRAW_LANDSCAPE @ $0793 will "fill in" any gaps, to make the landscape look solid. ; ; See READ_LANDSCAPE_LAYOUT @$15C5 and DRAW_LANDSCAPE @ $0793 for more detail. ; ; LANDSCAPE_GROUND_FIRST_CHAR EQU $4231 ; ordinal of the first ground character to scroll onto screen. LANDSCAPE_GROUND_FIRST_CHAR_PTR EQU $4232 ; pointer to character RAM where to plot character LANDSCAPE_GROUND_SECOND_CHAR EQU $4234 ; ordinal of the second ground character to scroll onto screen LANDSCAPE_GROUND_SECOND_CHAR_PTR EQU $4235 ; pointer to character RAM where to plot character LANDSCAPE_HAS_CEILING_FLAG EQU $4238 ; Set to 1 if the landscape to be drawn has a ceiling, such as the cave and the maze. ; These fields are only used if LANDSCAPE_HAS_CEILING_FLAG is set to 1 LANDSCAPE_CEILING_FIRST_CHAR EQU $4239 ; ordinal of the first ceiling character to scroll onto screen LANDSCAPE_CEILING_FIRST_CHAR_PTR EQU $423A ; pointer to character RAM where to plot character LANDSCAPE_CEILING_SECOND_CHAR EQU $423C ; ordinal of the first ceiling character to scroll onto screen LANDSCAPE_CEILING_SECOND_CHAR_PTR EQU $423D ; pointer to character RAM where to plot character ; ; $4243 - $425D is reserved for a circular queue of bytes, labelled CIRC_SOUND_CMD_QUEUE below. ; Each byte represents a sound, or a melody, to play. ; ; CIRC_SOUND_CMD_QUEUE_PTR_LO and CIRC_SOUND_CMD_QUEUE_PROC_LO work independently within this circular queue. ; ; Think of CIRC_SOUND_CMD_QUEUE_PTR_LO as the LSB of a pointer to where the next sound command requested will be stored, and ; CIRC_SOUND_CMD_QUEUE_PROC_LO as the LSB of a pointer to the sound command being processed. ; ; If you want to add a sound, call QUEUE_SOUND_COMMAND @ $2877. ; ; The code PROCESS_CIRC_SOUND_CMD_QUEUE @ $2855 will process the entries in the queue. Any entry with a value of $FF is treated as "played". CIRC_SOUND_CMD_QUEUE_PTR_LO EQU $4240 ; low byte of a pointer to a (hopefully) vacant entry in the circular queue. CIRC_SOUND_CMD_QUEUE_PROC_LO EQU $4241 ; low byte of a pointer to the next entry in the circular queue to be processed. IRQTRIGGER_CTRL EQU $4242 ; Used to control $8201 CIRC_SOUND_CMD_QUEUE EQU $4243 CIRC_SOUND_CMD_QUEUE_START EQU $4243 CIRC_SOUND_CMD_QUEUE_END EQU $425D TIMING_VARIABLE EQU $425F ; Perpetually decremented by the NMI handler. Routines use this variable to determine when to execute. ; ; CHAR_BASED_GROUND_OBJECT are "lightweight" (ie: don't require much memory) "projections" of the master GROUND_OBJECT record used for rendering character-based ; objects (targets!!) sitting atop the landscape, i.e.: unlaunched rockets, fuel tanks, mystery, base. ; ; $1F59 details how CHAR_BASED_GROUND_OBJECTs are created. ; $084B details how CHAR_BASED_GROUND_OBJECTS are rendered as a batch to character RAM. ; ; ; Notes: ; ; Value in Code field What it represents ; =================== =================================================================== ; 10 Fuel tank ; 11 100 [points value] ; 12 200 [points value] ; 13 300 [points value] ; 1C Stationary rocket ; 1F Base (I think it looks more like a drilling rig!) animation frame #1 ; 26 Base animation frame #2 ; 2F Base animation frame #3 ; 33 Mystery ; 38 Explosion animation frame #1 ; 39 Explosion animation frame #2 ; 3A Explosion animation frame #3 ; 3B Explosion animation frame #4 ; struct CHAR_BASED_GROUND_OBJECT { BYTE Undrawn; ; Flag. When set to 0 this tells the system to render this object. See $085E. BYTE Code; ; Type of ground object. See table above. Multiply by 4 to get ordinal of first character to plot to char RAM in 2x2. See $0867. BYTE CharRamPtrLo; ; LSB of character RAM address to begin drawing this object. See $086C. BYTE CharRamPtrHi; ; MSB of character RAM address to begin drawing this object. See $086F. } // sizeof(CHAR_BASED_GROUND_OBJECT) == 4 bytes ; CHAR_BASED_GROUND_OBJECTS is a fixed-size 8 element array of type CHAR_BASED_GROUND_OBJECT. It is used by DRAW_ALL_CHARACTER_BASED_GROUND_OBJECTS @ $084B ; to render all ground objects. ; ; Each CHAR_BASED_GROUND_OBJECT requires 4 bytes to hold its state; the array is thus 32 bytes in size. CHAR_BASED_GROUND_OBJECTS EQU $4260 ; ; ; GROUND_OBJECT is important. It holds the main state for a single ground-based destructible target. i.e.: unlaunched rockets, fuel tanks, mystery, base ; (As compared to CHAR_BASED_GROUND_OBJECT which is only used for rendering.) ; ; A new GROUND_OBJECT is spawned by one of the following functions when NEXT_GROUND_OBJECT_ID (see $411A above) contains a nonzero value: ; SPAWN_FUEL_TANK (see $26FA) ; SPAWN_MYSTERY (see $275E) ; SPAWN_ROCKET_ON_GROUND (see $272C) ; SPAWN_BASE (see $2790) ; ; See GROUND_OBJECT_STAGE_OF_LIFE ($18C6) ; ; A GROUND_OBJECT has a 1:1 relationship with a CHAR_BASED_GROUND_OBJECT. ; struct GROUND_OBJECT { 0 BYTE IsActive; ; Active flag. 1 = Active BYTE IsExploding; ; Flag. Set to 1 when object is exploding BYTE StageOfLife; ; Stage of life. See GROUND_OBJECT_STAGE_OF_LIFE @ $18C6 BYTE X; ; X coordinate 4 BYTE Y; ; Y coordinate BYTE ??? BYTE ??? BYTE ??? 8 BYTE ??? BYTE ??? BYTE ??? BYTE ??? 12 BYTE AnimPtrLo ; Low byte of pointer to animation table. See GROUND_OBJECT_INIT@$18E6 and ANIMATE @$13E4. BYTE AnimPtrHi ; high byte of pointer BYTE AnimationCounter BYTE ExplosionCounter ; Determines how long an explosion animation lasts. Higher = longer. See $19AA. 16 BYTE ??? BYTE ??? BYTE Code ; Drives animation of CHAR_BASED_GROUND_OBJECT. Updated by ANIMATE @$13E4, then mapped to CHAR_BASED_GROUND_OBJECT @$1F7A BYTE ??? 20 BYTE ??? BYTE ??? BYTE Colour BYTE ObjectType ; 0 = Rocket, 1 = Fuel, 2 = Mystery, 3 = Base . See $1904 24 BYTE CharRamPtrLo ; LSB of character RAM pointer where this GROUND_OBJECT is drawn. See GROUND_OBJECT_INIT@18E6 BYTE CharRamPtrHi ; MSB of character RAM pointer where this GROUND_OBJECT is drawn BYTE MysteryPointsType ; Only used for "Mystery" object type. Represents points value to display on screen when this object has been shot. 0 = 100pts, 1 = 200pts, 2= 300pts. See $2296 and $1976. BYTE ??? 28 BYTE ??? BYTE ??? BYTE ??? BYTE ??? } // sizeof (GROUND_OBJECT) = 32 bytes ; GROUND_OBJECTS is a fixed-size array of type GROUND_OBJECT. Each GROUND_OBJECT requires 32 bytes to model its state, thus the array is 256 bytes in size. GROUND_OBJECTS EQU $4280 ; ; The PLAYER struct maintains state for the player jet. ; struct PLAYER { 0 BYTE IsActive ; Active flag. 1 = Active BYTE IsExploding ; Flag. Set to 1 when jet is exploding BYTE StageOfLife ; Stage of player [jet's] life. See PLAYER_STAGE_OF_LIFE @ $16E6 for more info BYTE X ; X coordinate 4 BYTE Y ; Y coordinate BYTE ??? BYTE ??? BYTE ??? 8 BYTE ??? BYTE ??? BYTE ??? BYTE ??? 12 BYTE AnimPtrLo ; Low byte of pointer to animation table. See ANIMATE @$13E4 for more info about animation tables. BYTE AnimPtrHi ; high byte of pointer BYTE AnimationCounter ; When this counts down to zero it's time for next animation frame. BYTE ExplosionCounter ; This is used to time how long the jet explosion animation should be shown for. See $1814 and $1847 16 BYTE ??? BYTE ??? BYTE SpriteCode; ; Sprite code (animation frame) to display BYTE ??? 20 BYTE ??? BYTE ??? BYTE Colour; ; Main colour of jet sprite BYTE ??? 24 BYTE ??? BYTE ??? BYTE ??? BYTE ??? 28 BYTE ??? BYTE ??? BYTE ??? BYTE ??? } // sizeof(PLAYER) = 32 bytes ; PLAYERS is a fixed-size two element array of type PLAYER. Each PLAYER requires 32 bytes to model its state, thus the array is 64 bytes in size. PLAYERS EQU $4380 PLAYER_ONE EQU $4380 ; alias of PLAYERS[0] PLAYER_TWO EQU $43A0 ; alias of PLAYERS[1] struct PLAYER_BOMB { 0 BYTE IsActive; ; Active flag. 1 = Active BYTE IsExploding; ; Flag. Set to 1 when bomb is exploding BYTE StageOfLife; ; Stage of bomb's life. See $1A30 and $1A4B for more info BYTE X; ; X coordinate 4 BYTE Y; ; Y coordinate BYTE ??? BYTE ??? BYTE ??? 8 BYTE ??? BYTE ??? BYTE ??? BYTE ??? 12 BYTE AnimPtrLo ; Low byte of pointer to animation table. See ANIMATE @$13E4 for more info about animation tables. BYTE AnimPtrHi ; high byte of pointer to animation table BYTE AnimationCounter ; When this counts down to zero it's time for next animation frame. BYTE ExplosionCounter ; This is used to time how long the bomb explosion animation should be shown for. See $1AB9 and $1AC3 16 BYTE ??? BYTE ??? BYTE SpriteCode; ; Sprite code (animation frame) to display BYTE PathPtrLo ; low byte of pointer to path table (see FOLLOW_PATH @$1578 for more information) 20 BYTE PathPtrHi ; high byte of pointer to path table BYTE ??? BYTE Colour; ; Main colour of sprite BYTE ??? 24 BYTE ??? BYTE ??? BYTE ??? BYTE ??? 28 BYTE ??? BYTE ??? BYTE ??? BYTE ??? } // sizeof(PLAYER_BOMB) = 32 bytes ; PLAYER_BOMBS is a 2 element array of type PLAYER_BOMB. Each PLAYER_BOMB requires 32 bytes to model its state, thus the array is 64 bytes in size. PLAYER_BOMBS EQU $43C0 ; ; This struct holds the state for a flying enemy, specifically one of the following types: a launched rocket, UFO or fireball. ; ; All of the inflight enemies you see on screen at any one time (up to 4) will be of a single type, varying by level. ; Mixing inflight enemy types is not possible. You can't have a mix of rockets and UFOs, for example (which would be fun, I think.) ; ; The game knows what type of enemies are inflight by reading the value of LANDSCAPE_FLAGS ($411D) . ; ; See: ; ROCKET_ANIMATION_AND_MOVEMENT ($1C98) ; UFO_ANIMATION_AND_MOVEMENT ($1D75) ; FIREBALL_ANIMATION_AND_MOVEMENT ($1EC6) struct INFLIGHT_ENEMY { 0 BYTE IsActive; ; Active flag. 1 = Active BYTE IsExploding; ; Flag. Set to 1 when this enemy is exploding. BYTE StageOfLife; ; Stage of enemy's life. See $1A30 for more info. BYTE X; ; X coordinate 4 BYTE Y; ; Y coordinate BYTE ??? BYTE ??? BYTE ??? 8 BYTE ??? BYTE ??? BYTE ??? BYTE ??? 12 BYTE AnimPtrLo ; Low byte of pointer to animation table. See ANIMATE @$13E4 for more info about animation tables. BYTE AnimPtrHi ; high byte of pointer to animation table BYTE AnimationCounter ; When this counts down to zero it's time for next animation frame. BYTE ExplosionCounter ; This is used to time how long the explosion animation should be shown for. See $1AB9 and $1AC3 16 BYTE ??? BYTE ??? BYTE SpriteCode; ; Sprite code (animation frame) to display BYTE PathPtrLo ; low byte of pointer to path table. See FOLLOW_PATH @$1578 for more info on paths 20 BYTE PathPtrHi ; high byte of pointer to path table BYTE ??? BYTE Colour; ; Sprite colour BYTE ??? 24 BYTE ??? BYTE ??? BYTE ??? BYTE ??? 28 BYTE ??? BYTE ??? BYTE ??? BYTE ??? } // sizeof(INFLIGHT_ENEMY) = 32 bytes ; INFLIGHT_ENEMIES is a 4 element array of type INFLIGHT_ENEMY. Each INFLIGHT_ENEMY requires 32 bytes to model its state, thus the array is 128 bytes in size. INFLIGHT_ENEMIES EQU $4400 EQU $4480 struct PLAYER_BULLET { BYTE IsActive; ; Active flag. 1 = Active BYTE X; ; X coordinate BYTE Y; ; Y coordinate } // sizeof(PLAYER_BULLET) = 3 bytes ; PLAYER_BULLETS is a 4 element array of type PLAYER_BULLET. The array is 12 bytes in size. PLAYER_BULLETS EQU $4500 TEMP_COUNTER_4540 EQU $4540 ; counter only used in attract mode script stages ATTRACT_MODE_SCRIPT_STAGE EQU $4541 ; index into jump table @ $0BB1 SCORE_TABLE_CHARS_COUNTER EQU $4542 ; used by DISPLAY_ROW_OF_POINTS_VALUES_FOR_SCORE_TABLE to count down characters left to print on a row. See $0DC8 SCORE_TABLE_ROWS_COUNTER EQU $4543 ; used by ADVANCE_TO_NEXT_ROW_OF_SCORE_TABLE to count rows of points values left to do. See $0DEA SCORE_TABLE_TEXT_PTR EQU $4544 ; pointer to character to read from SCORE_TABLE_TEXT @ $0D6C . See $0DB5 SCORE_TABLE_CHAR_PTR EQU $4546 ; pointer to character RAM where read character will be stored. See $0DBD ; If you want to complete the mission at any time, set IS_MISSION_COMPLETE flag to $FF TEMP_COUNTER_4580 EQU $4580 ; counter only used in stages of the MISSION_COMPLETED_SCRIPT MISSION_COMPLETE_SCRIPT_STAGE EQU $4581 ; Used by MISSION_COMPLETED_SCRIPT (see $127E). index into jump table @ $1282. TEMP_COUNTER_45C0 EQU $45C0 ; counter only used in stages of the HIGH_SCORE_SCRIPT HIGH_SCORE_SCRIPT_STAGE EQU $45C1 ; Used by HIGH_SCORE_SCRIPT (see $12F0). index into jump table @ $12F4 // And now to the code... enjoy. 0000: AF xor a 0001: 32 01 68 ld ($6801),a ; disable interrupt 0004: C3 E1 02 jp $02E1 ; jump to CLEAR_RAM_THEN_JP_0069 0007: FF rst $38 ; Write A to (HL) and A+1 to (HL+1) ; ; Expects: ; A = seed value ; HL = memory address to begin writing values ; DE = value to add to HL after writing done ; ; Returns: ; A is 2 more than it was on entry ; HL = HL + DE + 2 ; 0008: 77 ld (hl),a 0009: 3C inc a 000A: 23 inc hl 000B: 77 ld (hl),a 000C: 3C inc a 000D: 19 add hl,de 000E: C9 ret 000F: FF rst $38 ; ; Fill memory from HL to HL+B with value A. ; ; expects: ; A = value to write ; B = count ; HL = pointer ; 0010: 77 ld (hl),a 0011: 23 inc hl 0012: 10 FC djnz $0010 0014: C9 ret 0015: FF rst $38 0016: FF rst $38 0017: FF rst $38 ; ; Fill memory from HL to HL + B + (C*256) with value A ; 0018: 77 ld (hl),a 0019: 23 inc hl 001A: 10 FC djnz $0018 001C: 0D dec c 001D: 20 F9 jr nz,$0018 001F: C9 ret ; ; Return the byte at HL + A. ; i.e: in BASIC this would be akin to: result = PEEK (HL + A) ; ; expects: ; A = offset ; HL = pointer ; ; returns: ; A = the contents of (HL + A) ; 0020: 85 add a,l ; a+=l 0021: 6F ld l,a 0022: 3E 00 ld a,$00 0024: 8C adc a,h 0025: 67 ld h,a ; effectively: HL = HL + A. Now hl is set to point to byte to read 0026: 7E ld a,(hl) ; load a with contents of (HL) 0027: C9 ret ; ; Jump to instruction in table. ; ; Immediately after the RST 28 call, there must be a table of pointers to code. ; A is a zero-based index into the table. ; ; Expects: ; A = index. Is multiplied by 2 to form an offset into the succeeding table. ; 0028: 87 add a,a ; multiply A by 2. 0029: E1 pop hl ; pop return address off stack into HL 002A: 5F ld e,a 002B: 16 00 ld d,$00 ; extend A into DE. Now DE = offset into table 002D: 19 add hl,de ; Effectively, HL = HL + offset into table 002E: 5E ld e,(hl) ; load E from table 002F: 23 inc hl 0030: 56 ld d,(hl) ; load D from table. Now DE = a pointer to code. 0031: EB ex de,hl ; 0032: E9 jp (hl) ; Jump to code specified by table entry. 0033: FF rst $38 0034: FF rst $38 0035: FF rst $38 0036: FF rst $38 0037: FF rst $38 ; ; Try to insert into the circular command queue located @ $40C0. (CIRC_CMD_QUEUE_START) ; if insert is not possible, exit function immediately. ; ; Expects: ; D is a command number (0..??) ; E is a parameter to pass to the command. ; ; $40A0 (CIRC_CMD_QUEUE_PTR_LO) contains the low byte of a pointer to a (hopefully) free entry in the queue. ; ; Value in D Action it invokes ; ======================================================== ; 0 Does nothing ; 1 Does nothing ; 2: Invokes DISPLAY_HIGH_SCORES_COMMAND ; 3: Invokes UPDATE_PLAYER_SCORE_COMMAND ; 4: Invokes ZERO_SCORE_COMMAND ; 5: Invokes DISPLAY_SCORE_COMMAND ; 6: Invokes PRINT_TEXT ; 7: Invokes HEAD_UP_DISPLAY_COMMAND ; ; ; See also: $01C1 (PROCESS_CIRCULAR_COMMAND_QUEUE) ; ; ALGORITHM: ; 1. Form a pointer to an entry in the circular queue using #$40 as the high byte of the pointer ; and the contents of $40A0 (CIRC_CMD_QUEUE_PTR_LO) as the low byte. ; 2. Read a byte from the queue entry the pointer points to ; 3. IF bit 7 of the byte is unset, then the queue entry is in use, we can't insert. Exit function. ; 4. ELSE: ; 4a) store register DE at the pointer ; 4b) bump pointer to next queue entry ; 5. Exit function QUEUE_COMMAND: 0038: E5 push hl 0039: 26 40 ld h,$40 ; set high byte of address 003B: 3A A0 40 ld a,($40A0) ; read CIRC_CMD_QUEUE_PTR_LO 003E: 6F ld l,a ; set low byte of address. Now HL = pointer to entry in circular queue. 003F: CB 7E bit 7,(hl) ; read byte from address and test bit 7 0041: 28 0E jr z,$0051 ; if bit 7 not set, this entry is already in use, goto $0051 and exit 0043: 72 ld (hl),d ; write DE... 0044: 2C inc l 0045: 73 ld (hl),e 0046: 2C inc l ; ..to (HL) ; has L wrapped around to 0? If so, then we've hit the end of the circular queue 0047: 7D ld a,l ; 0048: FE C0 cp $C0 ; compare low byte of address in HL to #$C0. 004A: 30 02 jr nc,$004E ; if A > #$C0 (192 decimal) then we've not hit the end of the circular queue, goto $004E 004C: 3E C0 ld a,$C0 ; otherwise, A == 0 and we've passed the end of the queue, reset queue pointer high byte to #$C0 (192 decimal) 004E: 32 A0 40 ld ($40A0),a ; update CIRC_CMD_QUEUE_PTR_LO to point to next queue entry 0051: E1 pop hl 0052: C9 ret 0053: FF rst $38 0054: FF rst $38 0055: FF rst $38 0056: FF rst $38 0057: FF rst $38 0058: FF rst $38 0059: FF rst $38 005A: FF rst $38 005B: FF rst $38 005C: FF rst $38 005D: FF rst $38 005E: FF rst $38 005F: FF rst $38 0060: FF rst $38 0061: FF rst $38 0062: FF rst $38 0063: FF rst $38 0064: FF rst $38 0065: FF rst $38 ; NMI 0066: C3 C0 09 jp $09C0 ; jump to NMI_HANDLER ; ; Invoked from CLEAR_RAM_THEN_JP_0069 ; ; ; 0069: 3E 9B ld a,$9B 006B: 32 03 81 ld ($8103),a ; does nothing 006E: 3E 88 ld a,$88 0070: 32 03 82 ld ($8203),a ; does nothing 0073: 3E 08 ld a,$08 0075: 32 42 42 ld ($4242),a ; set IRQTRIGGER_CTRL 0078: 32 01 82 ld ($8201),a ; bit 3 = interrupt trigger on audio CPU 007B: 31 00 48 ld sp,$4800 ; Protection related code. ; It appears the protection chip needs specific values in a specific sequence to be written to it, ; in order to return a specific value indicating that the protection check succeeded (see $00C0) ; ; I wonder if the protection chip would reset the system if it doesn't get the values written to it in the correct sequence? ; Or would it lock the system completely? 007E: 3E 28 ld a,$28 0080: 07 rlca 0081: C6 32 add a,$32 0083: 67 ld h,a 0084: E6 0F and $0F 0086: 6F ld l,a 0087: C6 0D add a,$0D 0089: 77 ld (hl),a ; write $0F to protection 008A: 06 07 ld b,$07 008C: 1E 09 ld e,$09 008E: 4F ld c,a 008F: 57 ld d,a 0090: 10 FC djnz $008E 0092: 70 ld (hl),b ; write 0 to protection ; It appears the scramble hardware sets IX to $421E when the system boots up (ie: hits address 0). 0093: 06 03 ld b,$03 0095: DD 4E 03 ld c,(ix+$03) ; read from $4221 0098: 10 FB djnz $0095 009A: 77 ld (hl),a ; write $0F to protection 009B: 59 ld e,c 009C: 70 ld (hl),b ; write 0 to protection 009D: 06 FA ld b,$FA 009F: 80 add a,b 00A0: 77 ld (hl),a ; write $09 to protection 00A1: 0E 10 ld c,$10 00A3: 06 20 ld b,$20 00A5: 81 add a,c 00A6: 80 add a,b 00A7: 5E ld e,(hl) ; reads $F9 from protection... 00A8: 3E F0 ld a,$F0 00AA: A3 and e ; produces $F0.. 00AB: 2F cpl ; produces $0F.. 00AC: E6 F0 and $F0 ; produces 0. Or should do - if it doesn't, protection kicks in. 00AE: C0 ret nz ; return if protection is triggered. Someone's hacking. 00AF: 26 82 ld h,$82 00B1: 7C ld a,h 00B2: E6 3F and $3F 00B4: 6F ld l,a ; set HL to $8202 (protection) 00B5: 36 0A ld (hl),$0A ; write 10 to protection 00B7: C6 02 add a,$02 00B9: 77 ld (hl),a ; write 4 to protection 00BA: C6 05 add a,$05 00BC: 77 ld (hl),a ; write 9 to protection 00BD: 7E ld a,(hl) ; reads $B9 from protection check 00BE: E6 F0 and $F0 ; produces $B0 00C0: FE B0 cp $B0 ; compare to $B0, which is value if protection check succeeds 00C2: C2 DC 05 jp nz,$05DC ; if protection check does not succeed, corrupt memory state by jumping to text strings, not code.. ; When we get here, the preliminary protection checks have passed. 00C5: 21 C0 40 ld hl,$40C0 ; load HL with CIRC_CMD_QUEUE_START 00C8: 06 40 ld b,$40 ; sizeof(CIRC_CMD_QUEUE) 00CA: 3E FF ld a,$FF 00CC: D7 rst $10 ; fill memory with $FF 00CD: 21 43 42 ld hl,$4243 ; address of CIRC_SOUND_CMD_QUEUE 00D0: 06 1C ld b,$1C ; sizeof(CIRC_SOUND_CMD_QUEUE) 00D2: D7 rst $10 ; fill memory 00D3: 21 43 43 ld hl,$4343 00D6: 22 40 42 ld ($4240),hl ; write to CIRC_SOUND_CMD_QUEUE_PTR_LO and CIRC_SOUND_CMD_QUEUE_PROC_LO 00D9: 3A 00 70 ld a,($7000) ; kick watchdog 00DC: AF xor a 00DD: 32 01 68 ld ($6801),a ; disable interrupts 00E0: 32 05 70 ld ($7005),a ; does nothing 00E3: 32 06 68 ld ($6806),a ; disable screen vertical flip 00E6: 32 07 68 ld ($6807),a ; disable screen horizontal flip 00E9: 21 C0 C0 ld hl,$C0C0 00EC: 22 A0 40 ld ($40A0),hl ; reset CIRC_CMD_QUEUE_PTR_LO and CIRC_CMD_QUEUE_PROC_LO 00EF: 3C inc a 00F0: 32 04 68 ld ($6804),a ; enable stars 00F3: 21 00 48 ld hl,$4800 ; start of character RAM 00F6: 22 0B 40 ld ($400B),hl ; set TEMP_CHAR_RAM_PTR 00F9: 3E 20 ld a,$20 00FB: 32 08 40 ld ($4008),a ; set TEMP_COUNTER_4008 00FE: 3E 10 ld a,$10 0100: 32 17 40 ld ($4017),a ; set BONUS_JET_FOR value to 10 BCD (meaning bonus jet at 10,000 points) ; Read IN2 to determine how many coins needed for credit, and if its a cocktail setup 0103: 3A 02 81 ld a,($8102) ; read IN2 0106: 0F rrca 0107: 47 ld b,a 0108: E6 03 and $03 010A: 32 00 40 ld ($4000),a ; set COINAGE_VALUE 010D: 78 ld a,b 010E: 0F rrca 010F: 0F rrca 0110: E6 01 and $01 0112: 32 0F 40 ld ($400F),a ; set/reset IS_COCKTAIL flag ; Read IN1 so we can find out how many lives the player gets when starting a new game 0115: 3A 01 81 ld a,($8101) ; read IN1 0118: E6 03 and $03 011A: FE 03 cp $03 ; free play? 011C: 28 07 jr z,$0125 ; If we get here, we are not in FREE PLAY mode 011E: C6 03 add a,$03 0120: 32 07 40 ld ($4007),a ; set DEFAULT_PLAYER_LIVES 0123: 18 05 jr $012A ; set FREE PLAY mode 0125: 3E FF ld a,$FF 0127: 32 07 40 ld ($4007),a ; set DEFAULT_PLAYER_LIVES to 255 ; Turn sound off 012A: CD 93 28 call $2893 ; call DISABLE_SOUND ; More protection code - skip to $0165 if you're not interested in this stuff. 012D: AF xor a 012E: 3D dec a 012F: 20 FD jr nz,$012E 0131: CD A2 28 call $28A2 ; call ENABLE_SOUND 0134: 3A D7 07 ld a,($07D7) ; read $23 ("inc hl") from code in ROM 0137: C6 95 add a,$95 0139: 6F ld l,a 013A: E6 0F and $0F 013C: 0F rrca 013D: 0F rrca 013E: 0F rrca 013F: 47 ld b,a ; set B to 1 0140: 0F rrca 0141: 0F rrca 0142: 67 ld h,a ; set HL to $40B8 0143: 70 ld (hl),b ; write calculated value 1 to $40B8 ; Has someone been naughty and changed the KONAMI text to something else?? 0144: 21 9E 05 ld hl,$059E ; point HL to the "K" in text string KONAMI 0147: 7E ld a,(hl) ; get the ordinal for "K" into A 0148: C6 04 add a,$04 ; advance 4 letters in the alphabet, to "O" 014A: 23 inc hl ; bump HL to point to "O" in string table 014B: BE cp (hl) ; compare A ("O") to what should be ("O") 014C: 20 E0 jr nz,$012E ; if they don't match, someone's changed vendor text, put game into infinite loop 014E: 3E 10 ld a,$10 0150: C6 30 add a,$30 0152: 57 ld d,a ; Load D with $40 0153: C6 78 add a,$78 0155: 5F ld e,a ; load E with $B8. Now DE points to PROTECTION_1 0156: 1A ld a,(de) ; read value from PROTECTION_1 (value should be 1 - see $013F) 0157: C6 81 add a,$81 0159: 67 ld h,a ; set H to $82 015A: E6 0F and $0F 015C: 6F ld l,a ; set HL to $8202 - protection 015D: EB ex de,hl 015E: 26 40 ld h,$40 0160: 2E B9 ld l,$B9 ; load HL with address of PROTECTION_PORT_PTR_1 0162: 73 ld (hl),e 0163: 2C inc l 0164: 72 ld (hl),d ; write $8202 to PROTECTION_PORT_PTR_1 ; End of protection code block ; Clear OBJRAM 0165: 21 00 50 ld hl,$5000 ; load HL with OBJRAM address 0168: 01 00 01 ld bc,$0100 ; fill all of OBJRAM 016B: 16 00 ld d,$00 016D: 72 ld (hl),d 016E: 23 inc hl 016F: 0B dec bc 0170: 78 ld a,b 0171: B1 or c ; quick test to check if BC == 0 0172: 20 F9 jr nz,$016D ; if BC!=0, goto $016D ; Fill screen with 8x8 white rectangles 0174: 16 3F ld d,$3F ; ordinal of rectangle character 0176: 21 00 48 ld hl,$4800 ; load HL with address of Character RAM 0179: 01 00 08 ld bc,$0800 ; number of bytes to write to fill screen 017C: 72 ld (hl),d 017D: 3A 00 70 ld a,($7000) ; kick watchdog 0180: 23 inc hl 0181: 0B dec bc 0182: 78 ld a,b 0183: B1 or c ; quick test to check if BC == 0 0184: 20 F6 jr nz,$017C ; if BC!=0, goto $0174 ; Check for 1PT_START1 being depressed. If you hold START down when the game's booting up, ; you'll put the system into an infinite loop. 0186: CD B2 01 call $01B2 ; call CHECK_FOR_1PT_START1_HELD_DOWN 0189: 30 22 jr nc,$01AD ; if held down, goto INFINITE_LOOP 018B: CD B2 01 call $01B2 ; call CHECK_FOR_1PT_START1_HELD_DOWN 018E: 30 1D jr nc,$01AD ; if held down, goto INFINITE_LOOP 0190: 3E 01 ld a,$01 0192: 32 01 68 ld ($6801),a ; enable interrupts ; Reset all scores in hi score table to 10000. 0195: 21 00 42 ld hl,$4200 ; load HL with address of HI_SCORE_TABLE 0198: 06 0A ld b,$0A ; 10 entries in the table 019A: 36 00 ld (hl),$00 019C: 2C inc l 019D: 36 00 ld (hl),$00 019F: 2C inc l 01A0: 36 01 ld (hl),$01 01A2: 2C inc l 01A3: 10 F5 djnz $019A ; reset high score to 10000 as well. 01A5: 21 AA 40 ld hl,$40AA ; load HL with address of last byte of HI_SCORE 01A8: 36 01 ld (hl),$01 ; set high score to be 10000. 01AA: C3 C1 01 jp $01C1 ; jump to PROCESS_CIRCULAR_COMMAND_QUEUE INFINITE_LOOP: 01AD: 3A 00 70 ld a,($7000) ; kick watchdog 01B0: 18 FB jr $01AD ; ; Check if 1PT_START button is held down for a specified amount of time. ; ; Expects: ; BC = counter. The higher the number, the longer 1PT_START should be held down for. ; ; Returns: ; Carry flag set if 1PT_START was held down for BC cycles ; CHECK_FOR_1PT_START1_HELD_DOWN: 01B2: 0B dec bc 01B3: 3A 00 70 ld a,($7000) ; kick watchdog 01B6: 3A 01 81 ld a,($8101) ; read IN1 01B9: 07 rlca ; move IPT_START1 bit into carry 01BA: D0 ret nc ; return if 1PT_START1 is not pressed 01BB: 78 ld a,b 01BC: B1 or c ; Quick check to determine if BC==0 01BD: 20 F3 jr nz,$01B2 ; repeat until BC==0 01BF: 37 scf ; set carry flag to signal that 1PT_START was held down as long as required 01C0: C9 ret ; ; Process the circular command queue starting @ $40C0 (CIRC_CMD_QUEUE_START) ; ; Notes: ; The value in $40A1 (I have named it CIRC_CMD_QUEUE_PROC_LO) is the low byte of a pointer to the first entry in ; the queue to be processed. The MSB of the pointer is always #$40. ; ; In a circular queue, the first entry to be processed is not necessarily the head of the queue. ; The first entry to be processed could be anywhere in the queue. ; PROCESS_CIRCULAR_COMMAND_QUEUE: 01C1: 26 40 ld h,$40 ; high byte of pointer to queue entry be processed 01C3: 3A A1 40 ld a,($40A1) ; read CIRC_CMD_QUEUE_PROC_LO 01C6: C3 1A 04 jp $041A ; $041A reads command number, multiplies it by 2, then jumps back to 01C9 01C9: 30 05 jr nc,$01D0 ; if no carry, then we have a valid command number, goto $01D0 01CB: CD 04 02 call $0204 01CE: 18 F1 jr $01C1 ; process next entry in circular queue 01D0: E6 0F and $0F ; mask in lower nibble 01D2: 4F ld c,a 01D3: 06 00 ld b,$00 ; extend A into BC. BC is now the offset to add to $01F4 (see code @ $01E7) ; indicate that we've read this command, and mark it as free. ; Update CIRC_CMD_QUEUE_PROC_LO to point to next command/parameter pair to process. 01D5: 36 FF ld (hl),$FF ; write #$FF (255 decimal) to first byte of byte pair, to mark it as "free" 01D7: 23 inc hl 01D8: 5E ld e,(hl) ; read parameter value from queue entry into E. 01D9: 36 FF ld (hl),$FF ; write #$FF (255 decimal) to second byte of byte pair, to mark it as "free" 01DB: 2C inc l 01DC: 7D ld a,l 01DD: FE C0 cp $C0 ; is HL == $4100? If so, comparing L (which will be 0) to #$C0 (192 decimal) will set the carry flag. 01DF: 30 02 jr nc,$01E3 ; if carry is not set, then we have not reached the end of the queue ($4100), goto $01E3 01E1: 3E C0 ld a,$C0 ; otherwise, we have reached end of queue. 01E3: 32 A1 40 ld ($40A1),a ; Set CIRC_CMD_QUEUE_PROC_LO to $C0 ($40C0 = start of circular queue) ; BC = offset into jump table @ $01F4 01E6: 7B ld a,e ; Now A = parameter to command 01E7: 21 F4 01 ld hl,$01F4 ; pointer to COMMAND_JUMP_TABLE 01EA: 09 add hl,bc ; now HL = pointer to entry in jump table 01EB: 5E ld e,(hl) 01EC: 23 inc hl 01ED: 56 ld d,(hl) ; DE = pointer read from jump table 01EE: 21 C1 01 ld hl,$01C1 ; return address to go to (entry point of PROCESS_CIRCULAR_COMMAND_QUEUE) 01F1: E5 push hl ; push it onto stack, so when we hit a RET it'll return to $01C1 01F2: EB ex de,hl ; Swap HL and DE round so that HL is pointer read from jump table ; HL = pointer to function, A = parameter to pass to function 01F3: E9 jp (hl) ; jump to code pointed to by (HL) COMMAND_JUMP_TABLE: 01F4: 6A 02 ; $026A (Just a RET instruction) 6B 02 ; $026B (Another RET) 6C 02 ; $026C (DISPLAY_HIGH_SCORES_COMMAND) F1 02 ; $02F1 (UPDATE_PLAYER_SCORE_COMMAND) CC 03 ; $03CC (ZERO_SCORE_COMMAND) E8 03 ; $03E8 (DISPLAY_SCORE_COMMAND) 35 04 ; $0435 (PRINT_TEXT) 0B 07 ; $070B (HEAD_UP_DISPLAY_COMMAND) ; Called from PROCESS_CIRCULAR_COMMAND_QUEUE 0204: 3A 5F 42 ld a,($425F) ; load A with value of TIMING_VARIABLE 0207: 47 ld b,a 0208: E6 0F and $0F 020A: CA 23 02 jp z,$0223 ; if A modulo 16 = 0, goto BLINK_1UP_OR_2UP_TEXT ; If DRAW_LANDSCAPE_FLAG is set, we want to draw the landscape and the ground objects 020D: 21 19 40 ld hl,$4019 ; load HL with address of DRAW_LANDSCAPE_FLAG 0210: CB 46 bit 0,(hl) ; test flag 0212: C8 ret z ; return if flag is not set 0213: E6 03 and $03 0215: CA 80 08 jp z,$0880 ; goto DRAW_REMAINING_PLAYER_FUEL 0218: 3D dec a 0219: CA 4B 08 jp z,$084B ; goto DRAW_ALL_CHARACTER_BASED_GROUND_OBJECTS 021C: 3D dec a 021D: CA 93 07 jp z,$0793 ; goto DRAW_LANDSCAPE 0220: C3 4B 08 jp $084B ; goto DRAW_ALL_CHARACTER_BASED_GROUND_OBJECTS ; ; This code makes the 1UP or 2UP text blink on and off during the game. ; ; Expects: ; B = any number (sourced from TIMING_VARIABLE @ $425F) ; BLINK_1UP_OR_2UP_TEXT: 0223: 11 E0 FF ld de,$FFE0 ; load DE with -32 decimal 0226: 21 E0 48 ld hl,$48E0 ; character RAM address to plot "2UP" at 0229: 3A 0E 40 ld a,($400E) ; read IS_TWO_PLAYER_GAME flag 022C: A7 and a ; test if flag is set 022D: 28 22 jr z,$0251 ; if not a 2 player game, goto DRAW_1UP_ERASE_2UP ; if its a 2 player game we draw "2UP" first, then "1UP". 022F: 36 02 ld (hl),$02 ; plot "2" to character RAM 0231: CD 5B 02 call $025B ; plot "UP" to character RAM 0234: 21 40 4B ld hl,$4B40 0237: CD 59 02 call $0259 ; call DRAW_1UP ; the next thing we do is find out who's playing, so we know what score to blink. 023A: 3A 0D 40 ld a,($400D) ; read CURRENT_PLAYER 023D: A7 and a ; test if A is zero 023E: 21 40 4B ld hl,$4B40 ; address where "1UP" is plotted in character RAM 0241: 28 03 jr z,$0246 ; if A is 0, its player 1, goto $0246 ; player 2 in charge 0243: 21 E0 48 ld hl,$48E0 ; load HL with character RAM address of "2UP" ; we can blink if bit 4 of the value read from TIMING_VARIABLE is set and the game is in play. 0246: CB 60 bit 4,b ; test bit 4 of the value 0248: C8 ret z ; if not set, do nothing 0249: 3A 06 40 ld a,($4006) ; read IS_GAME_IN_PLAY 024C: 0F rrca ; move flag into carry 024D: D0 ret nc ; return if game is not in play 024E: C3 62 02 jp $0262 ; erase text DRAW_1UP_ERASE_2UP: 0251: 21 E0 48 ld hl,$48E0 ; load HL with character RAM address of "2UP" 0254: CD 62 02 call $0262 ; call PLOT_THREE_EMPTY_SPACES to erase "2UP" from screen 0257: 18 DB jr $0234 ; draw "1UP" on screen ; Plot 1UP to character RAM ; ; Expects: ; HL = pointer to character RAM to start plotting from ; DE = offset to add after erasing each character DRAW_1UP: 0259: 36 01 ld (hl),$01 ; "1" 025B: 19 add hl,de 025C: 36 25 ld (hl),$25 ; "U" 025E: 19 add hl,de 025F: 36 20 ld (hl),$20 ; "P" 0261: C9 ret ; Expects: ; HL = pointer to character RAM to start plotting from ; DE = offset to add after erasing each character PLOT_THREE_EMPTY_SPACES: 0262: 3E 10 ld a,$10 ; ordinal for empty space 0264: 77 ld (hl),a ; write to character RAM 0265: 19 add hl,de 0266: 77 ld (hl),a ; write to character RAM 0267: 19 add hl,de 0268: 77 ld (hl),a ; write to character RAM 0269: C9 ret 026A: C9 ret 026B: C9 ret ; ; Display high score table. ; ; ; DISPLAY_HIGH_SCORES_COMMAND: ; Protection: if you're not interested in this, skip to $0283. ; First a sneaky ROM security check. If the ROM checksum doesn't match, memory state will be corrupted and the game will reboot. 026C: 11 B4 00 ld de,$00B4 026F: 21 CC 00 ld hl,$00CC 0272: AF xor a ; clear carry flag 0273: ED 52 sbc hl,de ; HL = $18 0275: 45 ld b,l ; B = $18 0276: 21 B4 00 ld hl,$00B4 ; start of code in ROM to checksum 0279: 86 add a,(hl) ; calculate ROM checksum 027A: 23 inc hl ; 027B: 10 FC djnz $0279 ; repeat until B==0 ; When we get here, if the ROM hasn't been tampered with, A will be $79 027D: 11 79 00 ld de,$0079 0280: BB cp e ; compare A to $79 0281: 20 05 jr nz,$0288 ; if ROM has been tampered with, goto $0288 - corrupt memory ; Now display the - SCORE RANKING - and 1ST, 2ND, 3RD.. labels (but not the scores) for the high score table 0283: 3E 1A ld a,$1A ; ID of -SCORE RANKING- text string (see docs @$0435) 0285: 06 0B ld b,$0B ; there's 11 labels to display, 0287: F5 push af ; Protection: if we come here from the jump @$0281, memory will be corrupted because the jump has skipped over the "push af" ; and the "ld b,$0B" instructions above, meaning the loop will run too many times and the pop af @ $028D will pull things from the ; stack it shouldn't. 0288: C5 push bc 0289: CD 35 04 call $0435 ; call PRINT_TEXT to print heading or high score 028C: C1 pop bc 028D: F1 pop af 028E: 3C inc a ; bump A to ID of next text string to print 028F: 10 F6 djnz $0287 ; repeat until all labels are printed ; OK, when we get here we've displayed the heading & text labels. Now we need to print the scores as well. 0291: 21 87 49 ld hl,$4987 ; load HL with address in character RAM to poke scores to 0294: 11 20 00 ld de,$0020 ; number of characters per ROW 0297: 06 0A ld b,$0A ; 10 high scores to print 0299: DD 21 00 42 ld ix,$4200 ; load IX with address of HI_SCORE_TABLE. IX points to last 2 digits (tens) of high score. ; POKE last 2 digits of this high score to character RAM 029D: DD 7E 00 ld a,(ix+$00) ; get 2 BCD digits of score into A 02A0: 4F ld c,a ; save BCD digits in C register 02A1: E6 0F and $0F ; mask in lower nibble. Now A holds *sixth* (last) digit of high score. 02A3: 77 ld (hl),a ; write digit to character RAM 02A4: 19 add hl,de ; bump HL to character immediately below one just plotted 02A5: 79 ld a,c ; restore BCD digits from C register 02A6: 0F rrca ; move high nibble .. 02A7: 0F rrca 02A8: 0F rrca 02A9: 0F rrca ; .. to lower nibble. 02AA: E6 0F and $0F ; mask in lower nibble. Now A holds *fifth* digit of high score. 02AC: 77 ld (hl),a ; write digit to character RAM 02AD: 19 add hl,de ; bump HL to character immediately below one just plotted ; POKE 3rd and 4th digits of high score to character RAM 02AE: DD 23 inc ix ; bump IX to point to 3rd & 4th digits of current high score 02B0: DD 7E 00 ld a,(ix+$00) ; get 3rd & 4th BCD digits of score into A 02B3: 4F ld c,a ; save BCD digits in C register 02B4: E6 0F and $0F ; mask in lower nibble. Now A holds *fourth* digit of high score. 02B6: 77 ld (hl),a ; write digit to character RAM 02B7: 19 add hl,de ; bump HL to character immediately below one just plotted 02B8: 79 ld a,c ; restore BCD digits from C register 02B9: 0F rrca ; move high nibble .. 02BA: 0F rrca 02BB: 0F rrca 02BC: 0F rrca ; .. to lower nibble. 02BD: E6 0F and $0F ; mask in lower nibble. Now A holds *third* digit of high score. 02BF: 77 ld (hl),a ; write digit to character RAM 02C0: 19 add hl,de ; bump HL to character immediately below one just plotted ; POKE 1st and 2nd digits of high score to character RAM ; I wonder why they didn't make this code a subroutine? It's pretty much a clone of the code above. 02C1: DD 23 inc ix ; bump IX to point to 1st & 2nd digits of current high score 02C3: DD 7E 00 ld a,(ix+$00) ; get 1st & 2nd BCD digits of score into A 02C6: 4F ld c,a ; save BCD digits in C register 02C7: E6 0F and $0F ; mask in lower nibble. Now A holds *second* digit of high score. 02C9: 77 ld (hl),a ; write digit to character RAM 02CA: 19 add hl,de ; bump HL to character immediately below one just plotted 02CB: 79 ld a,c ; restore BCD digits from C register 02CC: 0F rrca ; move high nibble .. 02CD: 0F rrca 02CE: 0F rrca 02CF: 0F rrca ; .. to lower nibble. 02D0: E6 0F and $0F ; mask in lower nibble. Now A holds *first* digit of high score. 02D2: 28 01 jr z,$02D5 ; if first digit of high score is zero, don't bother drawing a zero. Skip to $02D5 02D4: 77 ld (hl),a ; write digit to character RAM ; Adjust HL to point to character two columns down, for next high score. Remember the monitor is rotated 90 degrees. 02D5: 11 62 FF ld de,$FF62 ; load DE with -158 02D8: 19 add hl,de ; bump HL 02D9: 11 20 00 ld de,$0020 02DC: DD 23 inc ix 02DE: 10 BD djnz $029D ; repeat until all entries of high score have been drawn 02E0: C9 ret ; ; Clears RAM from $4000 - $47FF then jumps to $0069 ; ; CLEAR_RAM_THEN_JP_0069: 02E1: 21 00 40 ld hl,$4000 02E4: 11 01 40 ld de,$4001 02E7: 01 00 08 ld bc,$0800 02EA: 36 00 ld (hl),$00 02EC: ED B0 ldir 02EE: C3 69 00 jp $0069 ; Adds points to a player's score. ; ; Expects: ; A = identifies points to be added to current player score ; ; Value in A (decimal) Action ; ==================== ================= ; 0 Add MYSTERY_SCORE ; 1 50 points ; 2 100 points ; 3 150 points ; 4 80 points ; 5 100 points ; 6 200 points ; 7 300 points ; 8 100 points ; 9 200 points ; 10 80 points ; 11 200 points ; 12 10 points ; 13 800 points UPDATE_PLAYER_SCORE_COMMAND: 02F1: A7 and a ; test if A is zero 02F2: 28 48 jr z,$033C ; yes, goto ADD_MYSTERY_SCORE_TO_PLAYER_SCORE ; First update current player's score.. 02F4: 4F ld c,a 02F5: CD 47 03 call $0347 ; call LEA_DE_OF_CURRENT_PLAYER_SCORE 02F8: 87 add a,a ; Multiply A.. 02F9: 81 add a,c ; .. by 3. 02FA: 4F ld c,a 02FB: 06 00 ld b,$00 ; Extend A into BC. Now BC is an offset into POINTS_TABLE 02FD: 21 9B 03 ld hl,$039B ; pointer to POINTS_TABLE 0300: 09 add hl,bc ; now HL points to an entry in the score table. 0301: A7 and a ; clear carry flag 0302: 06 03 ld b,$03 ; Players score is 3 bytes in size 0304: 1A ld a,(de) ; read byte from players score 0305: 8E adc a,(hl) ; add byte from the score table 0306: 27 daa ; ensure that the result is valid BCD 0307: 12 ld (de),a ; update player score 0308: 13 inc de ; bump to next BCD digits in players score 0309: 23 inc hl ; bump to next BCD digits in points table 030A: 10 F8 djnz $0304 ; repeat until all digits in player score have been updated. 030C: D5 push de ; DE points to last address of score + 1 ; Now compare current player's score to high score, and update high score if necessary 030D: 3A 0D 40 ld a,($400D) ; read CURRENT_PLAYER 0310: 0F rrca ; 0311: 30 02 jr nc,$0315 ; if carry flag is not set, its player one in charge 0313: 3E 01 ld a,$01 ; Parameter: 1 = Display player two's score 0315: CD E8 03 call $03E8 ; call DISPLAY_SCORE_COMMAND 0318: D1 pop de 0319: 1B dec de 031A: 21 AA 40 ld hl,$40AA ; point to last byte (first 2 digits) of HI_SCORE 031D: 06 03 ld b,$03 ; Players score is 3 bytes in size 031F: 1A ld a,(de) ; read byte from player score 0320: BE cp (hl) ; compare to byte from high score 0321: D8 ret c ; if byte read is lower than the byte from high score, its not a new high score, so return 0322: 20 05 jr nz,$0329 ; if byte is not the same, then we have a new high score, goto UPDATE_HIGH_SCORE 0324: 1B dec de ; otherwise, bump de 0325: 2B dec hl ; and bump hl 0326: 10 F7 djnz $031F ; repeat until b==0 0328: C9 ret ; Called when the current player's score exceeds the high score. UPDATE_HIGH_SCORE: 0329: CD 47 03 call $0347 ; Call LEA_DE_OF_CURRENT_PLAYER_SCORE. Now DE = pointer to current player score 032C: 21 A8 40 ld hl,$40A8 ; address of HI_SCORE 032F: 06 03 ld b,$03 ; high score occupies 3 bytes 0331: 1A ld a,(de) ; read byte from player score 0332: 77 ld (hl),a ; update byte in high score 0333: 13 inc de ; bump DE to point to next byte in player score 0334: 23 inc hl ; bump HL to point to next byte in high score 0335: 10 FA djnz $0331 0337: 3E 02 ld a,$02 0339: C3 E8 03 jp $03E8 ; call DISPLAY_SCORE_COMMAND ; TODO: This looks like it's adding an arbitrary score to current. ; I suspect this will be the MYSTERY score. ADD_MYSTERY_SCORE_TO_PLAYER_SCORE: 033C: CD 47 03 call $0347 ; call LEA_DE_OF_CURRENT_PLAYER_SCORE 033F: 21 AB 40 ld hl,$40AB ; load HL with address of MYSTERY_SCORE 0342: A7 and a ; clear carry flag so that BCD additions aren't affected 0343: 06 03 ld b,$03 ; player score is 3 bytes in size 0345: 18 BD jr $0304 ; update current player's score with mystery value ; ; Load DE with the [effective] address of the current player's score. ; LEA_DE_OF_CURRENT_PLAYER_SCORE: 0347: F5 push af 0348: 3A 0D 40 ld a,($400D) ; read CURRENT_PLAYER 034B: 11 A2 40 ld de,$40A2 ; address of PLAYER_ONE_SCORE 034E: 0F rrca ; move current player into carry. If its player 2's turn, carry will be set 034F: 30 03 jr nc,$0354 ; if its not player 2's turn, exit 0351: 11 A5 40 ld de,$40A5 ; return address of PLAYER_TWO_SCORE 0354: F1 pop af 0355: C9 ret ; ; Shows head up display, and PLAYER ONE message in middle of screen, indicating its player one's turn. ; DISPLAY_HUD_FOR_PLAYER_ONE: 0356: AF xor a 0357: 32 5F 42 ld ($425F),a ; reset TIMING_VARIABLE 035A: 32 06 68 ld ($6806),a ; disable screen vertical flip 035D: 32 07 68 ld ($6807),a ; disable screen horizontal flip 0360: 32 0D 40 ld ($400D),a 0363: 21 0A 40 ld hl,$400A ; load HL with address of SCRIPT_STAGE 0366: 34 inc (hl) ; advance to next stage in script (REMOVE_SPRITES_AND_DRAW_FLAT_LANDSCAPE @ $107E) 0367: 2D dec l ; bump HL to address of TEMP_COUNTER_4009 0368: 36 96 ld (hl),$96 ; set counter value 036A: 3A 0E 40 ld a,($400E) ; read IS_TWO_PLAYER_GAME flag 036D: 0F rrca ; move flag into carry 036E: 38 25 jr c,$0395 ; if carry is set, its a two player game, need to show player 2's score as well, so goto DISPLAY_PLAYER_TWO_SCORE 0370: 11 00 05 ld de,$0500 ; Command ID: 5 = DISPLAY_SCORE_COMMAND, Param: 0 = Display Player 1 score 0373: FF rst $38 ; call QUEUE_COMMAND 0374: 1E 02 ld e,$02 ; Command ID: 5 = DISPLAY_SCORE_COMMAND, Param: 2 = Display high score 0376: FF rst $38 ; call QUEUE_COMMAND 0377: 14 inc d ; Command ID: 6 = PRINT_TEXT, Param:2 = PLAYER ONE 0378: FF rst $38 ; call QUEUE_COMMAND 0379: 1E 04 ld e,$04 ; Command ID: 6 = PRINT_TEXT, Param:4 = HIGH SCORE 037B: FF rst $38 ; call QUEUE_COMMAND 037C: 11 03 07 ld de,$0703 ; Command ID: 7 = HEAD_UP_DISPLAY_COMMAND, Param:3 = DISPLAY_CURRENT_PLAYER_LIVES 037F: FF rst $38 ; call QUEUE_COMMAND 0380: 1E 00 ld e,$00 ; Command ID: 7 = HEAD_UP_DISPLAY_COMMAND, Param:0 = DISPLAY_MISSIONS_COMPLETED_FLAGS 0382: FF rst $38 ; call QUEUE_COMMAND 0383: 21 40 41 ld hl,$4140 ; copy all of PLAYER_ONE_STATE to CURRENT_PLAYER_STATE 0386: 11 00 41 ld de,$4100 0389: 01 40 00 ld bc,$0040 038C: ED B0 ldir 038E: 2A 1D 41 ld hl,($411D) 0391: 22 18 41 ld ($4118),hl 0394: C9 ret DISPLAY_PLAYER_TWO_SCORE: 0395: 11 01 05 ld de,$0501 ; Command ID: 5 = DISPLAY_SCORE_COMMAND, Param: 1 = Display Player 2 score 0398: FF rst $38 ; call QUEUE_COMMAND 0399: 18 D5 jr $0370 ; now go print player 1's score too ; ; Used by UPDATE_PLAYER_SCORE_COMMAND. ; POINTS_TABLE: 039B: 00 00 00 ; 0 points (unused) 50 00 00 ; 50 points 00 01 00 ; 100 points 50 01 00 ; 150 points 80 00 00 ; 80 points 00 01 00 ; 100 points 00 02 00 ; 200 points 00 03 00 ; 300 points 00 01 00 ; 100 points 00 02 00 ; 200 points 80 00 00 ; 80 points 00 02 00 ; 200 points 10 00 00 ; 10 points 00 08 00 ; 800 points 03C5: 3A 02 40 ld a,($4002) ; read NUM_CREDITS 03C8: A7 and a ; test if zero 03C9: C3 09 26 jp $2609 ; invoke ADVANCE_TO_NEXT_SCRIPT_IF_ZERO_FLAG_UNSET ; ; Zero a given score. Will update score on screen also. ; ; Value in A Action taken ; =========================================== ; 0 Reset player 1's score to 0 ; 1 Reset player 2's score to 0 ; Any other Reset high score to 0 ; ZERO_SCORE_COMMAND: 03CC: F5 push af 03CD: 21 A2 40 ld hl,$40A2 ; load HL with address of PLAYER_ONE_SCORE 03D0: A7 and a 03D1: 28 09 jr z,$03DC 03D3: 21 A5 40 ld hl,$40A5 ; load HL with address of PLAYER_TWO_SCORE 03D6: 3D dec a 03D7: 28 03 jr z,$03DC 03D9: 21 A8 40 ld hl,$40A8 ; load HL with address of HI_SCORE 03DC: 36 00 ld (hl),$00 ; zero... 03DE: 23 inc hl 03DF: 36 00 ld (hl),$00 03E1: 23 inc hl 03E2: 36 00 ld (hl),$00 ; ..high score 03E4: F1 pop af 03E5: C3 E8 03 jp $03E8 ; jump to DISPLAY_SCORE_COMMAND ; Display a score ; ; Value in A Action taken ; ========================================== ; 0 Display player one's score ; 1 Display player two's score ; 2 Display high score DISPLAY_SCORE_COMMAND: 03E8: 21 A4 40 ld hl,$40A4 ; load HL with address of last byte of PLAYER_ONE_SCORE 03EB: DD 21 81 4B ld ix,$4B81 ; pointer to character RAM location for player one's score 03EF: A7 and a ; test if A is zero 03F0: 28 11 jr z,$0403 ; if zero, display player one's score 03F2: 21 A7 40 ld hl,$40A7 ; load HL with address of last byte of PLAYER_TWO_SCORE 03F5: DD 21 21 49 ld ix,$4921 ; pointer to character RAM location for player two's score 03F9: 3D dec a 03FA: 28 07 jr z,$0403 ; if A is now 0 then it was 1 on entry, meaning display player two score, goto $0403 ; Display high score 03FC: 21 AA 40 ld hl,$40AA ; load HL with address of last byte of HI_SCORE 03FF: DD 21 41 4A ld ix,$4A41 ; pointer to character RAM location for high score ; OK, now render score pointed to by HL 0403: 11 E0 FF ld de,$FFE0 ; load DE with $FFE0 (-32 decimal) 0406: 06 03 ld b,$03 ; a score is 3 bytes in size.. 0408: 0E 04 ld c,$04 ; max number of leading zeros that can be skipped. For example, ; when you start the game you have a score of zero. It renders as "00" instead of "000000". ; So this specifies "skip the first 4 zeros in the score, but display the rest" 040A: 7E ld a,(hl) ; read BCD digits from score byte 040B: 0F rrca ; move high nibble (first digit of BCD number)... 040C: 0F rrca 040D: 0F rrca 040E: 0F rrca ; ...into lower nibble (second digit). 040F: CD 20 04 call $0420 ; call PLOT_LOWER_NIB_AS_DIGIT to plot the first digit 0412: 7E ld a,(hl) 0413: CD 20 04 call $0420 ; call PLOT_LOWER_NIB_AS_DIGIT to plot the second digit 0416: 2B dec hl ; bump to *previous* BCD byte 0417: 10 F1 djnz $040A ; do until all BCD digits in score have been drawn 0419: C9 ret 041A: 6F ld l,a ; now HL = pointer to a queue entry 041B: 7E ld a,(hl) ; read command number from queue entry into A. 041C: 87 add a,a ; multiply A by 2 to form an offset into jump table 041D: C3 C9 01 jp $01C9 ; go to PROCESS_CIRCULAR_COMMAND_QUEUE ; Pokes a digit of the score to character RAM. ; ; Expects: ; Lower nibble of A: BCD digit to be plotted as a character on screen ; C = max number of leading zero digits in the score that can be skipped. ; If C is 0, leading zero digits will always be drawn ; IX = pointer to character RAM where digit will be plotted. PLOT_LOWER_NIB_AS_DIGIT: 0420: E6 0F and $0F ; mask in lower nibble 0422: 28 08 jr z,$042C ; if the lower nibble is zero, goto $042C ; OK, we have a nonzero digit. 0424: 0E 00 ld c,$00 ; tell the plot routine to draw all digits, even if they are zero, from now on 0426: DD 77 00 ld (ix+$00),a ; go plot the character 0429: DD 19 add ix,de ; now IX points to character directly above one just plotted 042B: C9 ret ; we have a zero digit. Do we print it, or print a space instead? 042C: 79 ld a,c ; how many zero digits can we skip over? 042D: A7 and a ; test if A is zero 042E: 28 F6 jr z,$0426 ; if we can't skip over any more leading zero digits, then goto $0426 to draw "0". ; Otherwise, we are skipping a leading "0" digit and will print an empty space in its stead.. 0430: 3E 10 ld a,$10 ; ordinal for empty space character 0432: 0D dec c ; decrement count of leading zeros we are allowed to ignore 0433: 18 F1 jr $0426 ; plot empty space to screen ; ; A = index of string to print ; ; Bit 6 set: scroll this text onto screen (unused functionality) ; Bit 7 set: erase this text ; ; Value in A (ANDed with $7F) Text printed ; ============================================================= ; 0 GAME OVER ; 1 PUSH START BUTTON ; 2 PLAYER ONE ; 3 PLAYER TWO ; 4 HIGH SCORE ; 5 CREDIT ; 6 HOW FAR CAN YOU INVADE ; 7 FUEL ; 8 CONGRATULATIONS ; 9 YOU COMPLETED YOUR DUTIES ; A GOOD LUCK NEXT TIME ; B PLAY ; C - SCRAMBLE - ; D - SCORE TABLE - ; E (C) KONAMI 1981 ; F (C) KONAMI 1981 ; 10 (C) KONAMI 1981 ; 11 (C) KONAMI 1981 ; 12 OUR SCRAMBLE SYSTEM ; 13 2 COINS 1 PLAY ; 14 3 COINS 1 PLAY ; 15 1 COIN 2 PLAY ; 16 BONUS JET FOR ; 17 XI000 PTS ; 18 ONE PLAYER ONLY ; 19 ONE OR TWO PLAYERS ; 1A - SCORE RANKING - ; 1B 1ST ; 1C 2ND ; 1D 3RD ; 1E 4TH ; 1F 5TH ; 20 6TH ; 21 7TH ; 22 8TH ; 23 9TH ; 24 10TH PRINT_TEXT: 0435: 87 add a,a ; A = A * 2. This may affect C and PO flags. 0436: F5 push af 0437: 21 A2 04 ld hl,$04A2 ; HL = address of TEXTPTRS 043A: E6 7F and $7F ; mask in bits 0..6. Now A = a value in range of 0..127 043C: 5F ld e,a 043D: 16 00 ld d,$00 ; Extend A into DE 043F: 19 add hl,de ; HL now points to an entry in the TEXTPTRS lookup table. 0440: 5E ld e,(hl) 0441: 23 inc hl 0442: 56 ld d,(hl) ; DE now holds a pointer to a character string to print. 0443: EB ex de,hl ; HL = pointer to character string. DE we don't care about, it will be overwritten. 0444: 5E ld e,(hl) 0445: 23 inc hl 0446: 56 ld d,(hl) ; DE = *HL. Now DE holds character RAM address to print text at 0447: 23 inc hl 0448: EB ex de,hl ; Now HL = pointer to character RAM, DE = pointer to text to print 0449: 01 E0 FF ld bc,$FFE0 ; offset to add to HL after every character write. (-32 in decimal) 044C: F1 pop af 044D: 38 0E jr c,$045D ; if bit 7 of A was set on entry to this function, goto ERASE_TEXT 044F: FA 67 04 jp m,$0467 ; if bit 7 is NOW set, then goto BEGIN_SCROLL_TEXT *unused functionality* 0452: 1A ld a,(de) ; read character to be drawn 0453: FE 3F cp $3F ; is this the string terminator, #$3F? 0455: C8 ret z ; yes, so exit routine 0456: D6 30 sub $30 0458: 77 ld (hl),a ; write character to character RAM 0459: 13 inc de ; bump DE to point to next character 045A: 09 add hl,bc ; Add offset to screen address so that next character is drawn in row above 045B: 18 F5 jr $0452 ; and continue ; DE = pointer to text string to erase from screen ERASE_TEXT: 045D: 1A ld a,(de) 045E: FE 3F cp $3F 0460: C8 ret z 0461: 36 10 ld (hl),$10 ; plot space character 0463: 13 inc de 0464: 09 add hl,bc 0465: 18 F6 jr $045D ; ; ** THIS FUNCTIONALITY IS UNUSED ** ; ; The only reason I'm documenting it is because I've covered near-identical code in Galaxian. ; I wouldn't waste time on it otherwise. ; ; Set text up for scrolling. ; ; HL = pointer to character RAM ; DE = pointer to text string to render ; BEGIN_SCROLL_TEXT: 0467: 22 B5 40 ld ($40B5),hl ; store pointer to character RAM in COLUMN_SCROLL_CHAR_RAM_PTR 046A: ED 53 B3 40 ld ($40B3),de ; now HL = pointer to text string, DE = pointer to character RAM 046E: EB ex de,hl ; store pointer to next char to scroll on in COLUMN_SCROLL_NEXT_CHAR_PTR 046F: 7B ld a,e ; get low byte of character RAM address into A 0470: E6 1F and $1F ; mask in bits 0..4. Effectively A = A mod #$20 (32 decimal). A now represents a column index from 0-31 0472: 47 ld b,a ; save column index in B. 0473: 87 add a,a ; A=A*2. This is because attribute RAM requires 2 bytes per column. 0474: C6 20 add a,$20 ; add $20 (32 decimal) as OBJRAM_BACK_BUF starts at $4020 0476: 6F ld l,a 0477: 26 40 ld h,$40 ; now HL = a pointer to scroll attribute value in OBJRAM_BACK_BUF 0479: 22 B1 40 ld ($40B1),hl ; set COLUMN_SCROLL_ATTR_BACKBUF_PTR 047C: CB 3B srl e 047E: CB 3B srl e 0480: 7A ld a,d 0481: E6 03 and $03 0483: 0F rrca 0484: 0F rrca 0485: B3 or e 0486: E6 F8 and $F8 0488: 4F ld c,a ; C = scroll offset to write to OBJRAM_BACK_BUF 0489: 21 00 48 ld hl,$4800 ; HL = start of character RAM 048C: 78 ld a,b ; restore column index from B (see @$0472) 048D: 85 add a,l 048E: 6F ld l,a ; Add column index to L. Now HL = pointer to column to clear 048F: 11 20 00 ld de,$0020 ; offset to add to HL. $20 (32 decimal) characters per row 0492: 43 ld b,e ; B = count of how many characters need to be cleared by DJNZ loop 0493: 36 10 ld (hl),$10 ; write empty space character 0495: 19 add hl,de ; add offset to HL. Now HL points to same column next row down 0496: 10 FB djnz $0493 0498: 2A B1 40 ld hl,($40B1) ; restore attribute pointer from the stack 049B: 71 ld (hl),c ; write initial scroll offset to OBJRAM_BACK_BUF 049C: 3E 01 ld a,$01 049E: 32 B0 40 ld ($40B0),a ; set IS_COLUMN_SCROLLING flag 04A1: C9 ret ; ; The TEXTPTRS table is a lookup table comprised of pointers to text strings. ; ; The text strings are always organised thus: ; First 2 bytes: pointer to address in character RAM to begin printing text at. ; Subsequent bytes: characters to print, terminated by #$3F (63 decimal) ; ; For example, lets take the first entry in the table, 3E 07. ; ; 3E 07 forms memory address $073E. ; (Note: I suggest you open a memory window in the MAME debugger and view $073E, it'll make this a lot easier to follow.) ; ; The first 2 bytes stored at $073E are $96 and $4A. This forms a character RAM address of $4A96, where the first character will be drawn. ; The subsequent bytes, 47 41 4D 45 40 40 4F 56 45 32 represent the string "GAME OVER" in (mostly) ASCII. $40 is a space character. ; The next byte, $3F, "terminates" the string. (ie: it tells the print routine that nothing more is to be printed.) ; TEXTPTRS: 04A2: 3E 07 ; GAME OVER 4B 07 ; PUSH START BUTTON 5F 07 ; PLAYER ONE 6C 07 ; PLAYER TWO EC 04 ; HIGH SCORE F9 04 ; CREDIT 08 05 ; HOW FAR CAN YOU INVADE 21 05 ; FUEL 28 05 ; CONGRATULATIONS 3A 05 ; YOU COMPLETED YOUR DUTIES 56 05 ; GOOD LUCK NEXT TIME 72 05 ; PLAY 79 05 ; - SCRAMBLE - 88 05 ; - SCORE TABLE - 9A 05 ; (C) KONAMI 1981 9A 05 ; (C) KONAMI 1981 9A 05 ; (C) KONAMI 1981 9A 05 ; (C) KONAMI 1981 AB 05 ; OUR SCRAMBLE SYSTEM C4 05 ; 2 COINS 1 PLAY D5 05 ; 3 COINS 1 PLAY E6 05 ; 1 COIN 2 PLAY F7 05 ; BONUS JET FOR 08 06 ; 000 PTS 12 06 ; ONE PLAYER ONLY 24 06 ; ONE OR TWO PLAYERS 39 06 ; - SCORE RANKING - 4D 06 ; 1ST 60 06 ; 2ND 73 06 ; 3RD 86 06 ; 4TH 99 06 ; 5TH AC 06 ; 6TH BF 06 ; 7TH D2 06 ; 8TH E5 06 ; 9TH F8 06 ; 10TH 04EC: 80 4A 48 49 47 48 40 53 43 4F 52 45 3F 9F 4B 40 .JHIGH@SCORE?.K@ 04FC: 43 52 45 44 49 54 40 40 40 40 40 3F 51 4B 48 4F CREDIT@@@@@?QKHO 050C: 57 40 46 41 52 40 43 41 4E 40 59 4F 55 40 49 4E W@FAR@CAN@YOU@IN 051C: 56 41 44 45 3F 5E 4B 46 55 45 4C 3F CC 4A 43 4F VADE?^KFUEL?.JCO 052C: 4E 47 52 41 54 55 4C 41 54 49 4F 4E 53 3F 6E 4B NGRATULATIONS?nK 053C: 59 4F 55 40 43 4F 4D 50 4C 45 54 45 44 40 59 4F YOU@COMPLETED@YO 054C: 55 52 40 44 55 54 49 45 53 3F 70 4B 47 4F 4F 44 UR@DUTIES?pKGOOD 055C: 40 4C 55 43 4B 40 4E 45 58 54 40 54 49 4D 45 40 @LUCK@NEXT@TIME@ 056C: 41 47 41 49 4E 3F 26 4A 50 4C 41 59 3F A9 4A 5B AGAIN?&JPLAY?.J[ 057C: 40 53 43 52 41 4D 42 4C 45 40 5B 3F C7 4A 5B 40 @SCRAMBLE@[?.J[@ 058C: 53 43 4F 52 45 40 54 41 42 4C 45 40 5B 3F BC 4A SCORE@TABLE@[?.J 059C: 6E 40 4B 4F 4E 41 4D 49 40 40 31 39 38 31 3F 54 n@KONAMI@@1981?T 05AC: 4B 40 4F 55 52 40 53 43 52 41 4D 42 4C 45 40 53 K@OUR@SCRAMBLE@S 05BC: 59 53 54 45 4D 40 02 3F D5 4A 32 40 43 4F 49 4E YSTEM@.?.J2@COIN 05CC: 53 40 31 40 50 4C 41 59 3F D5 4A 33 40 43 4F 49 S@1@PLAY?.J3@COI 05DC: 4E 53 40 31 40 50 4C 41 59 3F D5 4A 31 40 43 4F NS@1@PLAY?.J1@CO 05EC: 49 4E 40 40 32 40 50 4C 41 59 3F 78 4B 42 4F 4E IN@@2@PLAY?xKBON 05FC: 55 53 40 4A 45 54 40 40 46 4F 52 3F 58 49 30 30 US@JET@@FOR?XI00 060C: 30 40 50 54 53 3F D4 4A 4F 4E 45 40 50 4C 41 59 0@PTS?.JONE@PLAY 061C: 45 52 40 4F 4E 4C 59 3F F4 4A 4F 4E 45 40 4F 52 ER@ONLY?.JONE@OR 062C: 40 54 57 4F 40 50 4C 41 59 45 52 53 3F 04 4B 5B @TWO@PLAYERS?.K[ 063C: 40 53 43 4F 52 45 40 52 41 4E 4B 49 4E 47 40 5B @SCORE@RANKING@[ 064C: 3F E7 4A 31 53 54 40 40 40 40 40 40 40 40 40 40 ?.J1ST@@@@@@@@@@ 065C: 50 54 53 3F E9 4A 32 4E 44 40 40 40 40 40 40 40 PTS?.J2ND@@@@@@@ 066C: 40 40 40 50 54 53 3F EB 4A 33 52 44 40 40 40 40 @@@PTS?.J3RD@@@@ 067C: 40 40 40 40 40 40 50 54 53 3F ED 4A 34 54 48 40 @@@@@@PTS?.J4TH@ 068C: 40 40 40 40 40 40 40 40 40 50 54 53 3F EF 4A 35 @@@@@@@@@PTS?.J5 069C: 54 48 40 40 40 40 40 40 40 40 40 40 50 54 53 3F TH@@@@@@@@@@PTS? 06AC: F1 4A 36 54 48 40 40 40 40 40 40 40 40 40 40 50 .J6TH@@@@@@@@@@P 06BC: 54 53 3F F3 4A 37 54 48 40 40 40 40 40 40 40 40 TS?.J7TH@@@@@@@@ 06CC: 40 40 50 54 53 3F F5 4A 38 54 48 40 40 40 40 40 @@PTS?.J8TH@@@@@ 06DC: 40 40 40 40 40 50 54 53 3F F7 4A 39 54 48 40 40 @@@@@PTS?.J9TH@@ 06EC: 40 40 40 40 40 40 40 40 50 54 53 3F F9 4A 31 30 @@@@@@@@PTS?.J10 06FC: 54 48 40 40 40 40 40 40 40 40 40 50 54 53 3F TH@@@@@@@@@PTS? ; ; Value in A What it does ; ========== ======================================== ; 0 Invokes DISPLAY_MISSIONS_COMPLETED_FLAGS ; 1 Invokes DISPLAY_CREDITS ; 2 Invokes DISPLAY_CURRENT_PLAYER_PROGRESS_BAR ; 3 Invokes DISPLAY_CURRENT_PLAYER_LIVES HEAD_UP_DISPLAY_COMMAND: 070B: A7 and a ; test if zero 070C: CA 20 09 jp z,$0920 ; if zero, goto DISPLAY_MISSIONS_COMPLETED_FLAGS 070F: 3D dec a 0710: CA 1A 07 jp z,$071A ; if zero, goto DISPLAY_CREDITS 0713: 3D dec a 0714: CA 3E 09 jp z,$093E ; if zero, goto DISPLAY_CURRENT_PLAYER_PROGRESS_BAR 0717: C3 FB 08 jp $08FB ; default: goto DISPLAY_CURRENT_PLAYER_LIVES DISPLAY_CREDITS: 071A: 3E 05 ld a,$05 ; index of text string "CREDIT" 071C: CD 35 04 call $0435 ; call PRINT_TEXT 071F: 3A 02 40 ld a,($4002) ; read NUM_CREDITS 0722: FE 63 cp $63 ; compare to 99 0724: 38 02 jr c,$0728 ; if A < 99 , skip to $0728 0726: 3E 63 ld a,$63 ; clamp (limit) number of credits to 99 0728: CD 79 07 call $0779 ; call CONVERT_A_TO_BCD 072B: 47 ld b,a ; save credits as BCD in B 072C: E6 F0 and $F0 ; mask in high nibble, which is first digit of BCD 072E: 28 07 jr z,$0737 ; if the first digit is 0, goto $0737. We don't display it. 0730: 0F rrca ; shift high nibble... 0731: 0F rrca 0732: 0F rrca 0733: 0F rrca ; to low nibble.. converting first BCD digit to decimal. 0734: 32 9F 4A ld ($4A9F),a ; Write first digit of credits to character RAM 0737: 78 ld a,b ; get credits as BCD into A again. We preserved it in B @$072B 0738: E6 0F and $0F ; mask in low nibble, which is second digit of BCD. Converts second BCD digit to decimal. 073A: 32 7F 4A ld ($4A7F),a ; Write second digit of credits to character RAM 073D: C9 ret 073E: 96 4A 47 41 4D 45 40 40 4F 56 45 52 3F F1 4A 50 .JGAME@@OVER?.JP 074E: 55 53 48 40 53 54 41 52 54 40 42 55 54 54 4F 4E USH@START@BUTTON 075E: 3F 94 4A 50 4C 41 59 45 52 40 4F 4E 45 3F 94 4A ?.JPLAYER@ONE?.J 076E: 50 4C 41 59 45 52 40 54 57 4F 3F 47 E6 0F C6 00 PLAYER@TWO ; ; Convert value in register A to BCD equivalent ; ; For example, if you pass in $63 (99 decimal) in A, this function will return 99 BCD ; ; Expects: ; A = non BCD value, from 0..99 ; ; Returns: ; A = BCD equivalent ; CONVERT_A_TO_BCD: 0779: 47 ld b,a ; preserve A in B register 077A: E6 0F and $0F ; mask in low nibble 077C: C6 00 add a,$00 ; clears the half carry flag which might affect DAA 077E: 27 daa 077F: 4F ld c,a ; store result in C 0780: 78 ld a,b ; restore A to its original value 0781: E6 F0 and $F0 ; mask in high nibble 0783: 28 0B jr z,$0790 ; if high nibble is zero we don't care, goto $0790 0785: 0F rrca ; shift high nibble... 0786: 0F rrca 0787: 0F rrca 0788: 0F rrca ; ... into lower nibble 0789: 47 ld b,a ; and store in B. 078A: AF xor a ; clear A 078B: C6 16 add a,$16 ; Add 16 hex (which in BCD terms is 16 decimal) to A (so A will progress in BCD from 0->16->32->48... ) 078D: 27 daa 078E: 10 FB djnz $078B ; and repeat until B is 0. 0790: 81 add a,c ; add in value of lower nibble preserved @$256f 0791: 27 daa ; ensure A is a valid BCD number 0792: C9 ret ; and we're out ; ; This routine renders a new part of the landscape offscreen every 16 pixels (or, each time the playfield scrolls 2 character rows' worth) ; ready to be scrolled on "Just in time" . ; ; This routine does not render the enemies on top of the landscape; that is handled by DRAW_ALL_CHARACTER_BASED_GROUND_OBJECTS @ $084B. ; ; NOTES: ; ; Algorithm is roughly: ; IF CAN_DRAW_LANDSCAPE_1 = FALSE OR CAN_DRAW_LANDSCAPE_2 = FALSE THEN EXIT ; Clear 2 rows of characters offscreen ready to be filled with new landscape ; Plot first character for ground ; Fill from beneath this character to bottom of playfield with repeating solid character appropriate for level (brick for maze or solid colour for cave) ; Plot second character for ground ; Fill from beneath this character to bottom of playfield with repeating solid character appropriate for level (brick for maze or solid colour for cave) ; IF landscape doesn't have a ceiling, THEN EXIT ; ELSE ; Plot first character for ceiling ; Fill from "above" this character to "top" of playfield with repeating solid character appropriate for level (brick for maze or solid colour for cave) ; Plot second character for ceiling ; Fill from "above" this character to "top" of playfield with repeating solid character appropriate for level (brick for maze or solid colour for cave) ; END IF ; ; DRAW_LANDSCAPE: ; First check both our flags CAN_DRAW_LANDSCAPE_1 and CAN_DRAW_LANDSCAPE_2. If either flag is unset we can't draw our landscape. 0793: 3A 10 41 ld a,($4110) ; read CAN_DRAW_LANDSCAPE_1 0796: 0F rrca ; move flag into carry 0797: D0 ret nc ; exit if flag not set 0798: 3A 30 42 ld a,($4230) ; read CAN_DRAW_LANDSCAPE_2 079B: 0F rrca ; move flag into carry 079C: D0 ret nc ; exit if flag not set ; OK, both CAN_DRAW_LANDSCAPE_1 and CAN_DRAW_LANDSCAPE_2 flags are set, so we have green light to draw new part of landscape. 079D: 2A 35 42 ld hl,($4235) ; load HL with contents of LANDSCAPE_GROUND_SECOND_CHAR_PTR 07A0: 7D ld a,l ; ensure that HL points to.. 07A1: E6 E0 and $E0 07A3: 6F ld l,a ; ..the very start of a character row 07A4: 11 05 00 ld de,$0005 ; the height of the scores + progress bar is 5 characters 07A7: 19 add hl,de ; now HL points to first character column of landscape to draw ; Preparation work: clear 2 rows of characters to make way for the landscape we're going to draw 07A8: 3E 10 ld a,$10 ; ordinal for empty space 07AA: 06 19 ld b,$19 ; 25 decimal 07AC: D7 rst $10 ; draw 25 empty space characters 07AD: 11 07 00 ld de,$0007 ; offset to add to HL (25 + 7 = 32, which is number of chars per row) 07B0: 19 add hl,de ; bump HL to point to start of next row down 07B1: 06 19 ld b,$19 07B3: D7 rst $10 ; draw 25 empty space characters. ; Plot the first ground character. 07B4: DD 21 30 42 ld ix,$4230 07B8: DD 7E 01 ld a,(ix+$01) ; read LANDSCAPE_GROUND_FIRST_CHAR 07BB: DD 6E 02 ld l,(ix+$02) ; read LSB of LANDSCAPE_GROUND_FIRST_CHAR_PTR 07BE: DD 66 03 ld h,(ix+$03) ; read MSB of LANDSCAPE_GROUND_FIRST_CHAR_PTR. Now HL = character RAM address. 07C1: 77 ld (hl),a ; plot first character ; OK we've plotted the first character. ; We now must fill down to the bottom (in reality, the right hand column) of the scrolling area with a repeating solid character. ; Calculate how many solid characters we need to plot. 07C2: 7D ld a,l ; get LSB of LANDSCAPE_GROUND_FIRST_CHAR_PTR into A 07C3: E6 1F and $1F ; As each row is 32 characters wide, ANDing LSB with 31 will return index of character on row 07C5: 47 ld b,a ; save index in B 07C6: 3E 1D ld a,$1D ; Remaining number of characters to draw = (29 decimal - B) 07C8: 90 sub b 07C9: 28 10 jr z,$07DB ; if result is zero then we don't need to fill in a gap with solid characters, goto $07DB ; A now holds number of solid characters to draw 07CB: 47 ld b,a ; b = number of characters to plot ; what type of solid character to use? 07CC: 0E 39 ld c,$39 ; ordinal of solid "rock" character 07CE: 3A 1D 41 ld a,($411D) ; read LANDSCAPE_FLAGS 07D1: E6 1C and $1C ; Are we on Level 4, 5 or BASE? 07D3: 28 02 jr z,$07D7 ; if not, goto $07D7 07D5: 0E 3D ld c,$3D ; ordinal of solid "brick" character ; Working from left to right, fill in the empty gap between the "edge" character and bottom of scroll area with character ordinal 07D7: 23 inc hl 07D8: 71 ld (hl),c ; plot to character RAM 07D9: 10 FC djnz $07D7 ; repeat until all solid characters plotted ; This code is a repeat of the above ($07B8-07D9) so I won't document it. 07DB: DD 7E 04 ld a,(ix+$04) ; read LANDSCAPE_GROUND_SECOND_CHAR 07DE: DD 6E 05 ld l,(ix+$05) ; read LSB of LANDSCAPE_GROUND_SECOND_CHAR_PTR 07E1: DD 66 06 ld h,(ix+$06) ; read MSB of LANDSCAPE_GROUND_SECOND_CHAR_PTR. Now HL = character RAM address. 07E4: 77 ld (hl),a ; plot first character 07E5: 7D ld a,l 07E6: E6 1F and $1F 07E8: 47 ld b,a 07E9: 3E 1D ld a,$1D 07EB: 90 sub b 07EC: 28 10 jr z,$07FE ; If we have nothing to draw, goto $07FE 07EE: 47 ld b,a 07EF: 0E 39 ld c,$39 ; ordinal of solid "rock" character 07F1: 3A 1D 41 ld a,($411D) ; read LANDSCAPE_FLAGS 07F4: E6 1C and $1C 07F6: 28 02 jr z,$07FA 07F8: 0E D0 ld c,$D0 ; ordinal of solid "brick" character 07FA: 23 inc hl 07FB: 71 ld (hl),c ; plot to character RAM 07FC: 10 FC djnz $07FA ; repeat until all solid characters plotted 07FE: DD 36 00 00 ld (ix+$00),$00 ; clear flag ; Does the landscape have a ceiling that needs to be drawn? 0802: DD CB 08 46 bit 0,(ix+$08) ; test LANDSCAPE_HAS_CEILING_FLAG 0806: C8 ret z ; exit if we don't have a ceiling to draw ; Render the ceiling part of the landscape. ; This code is pretty near identical to the ground rendering code, except this code fills from right to left. 0807: DD 7E 09 ld a,(ix+$09) ; read LANDSCAPE_CEILING_FIRST_CHAR 080A: DD 6E 0A ld l,(ix+$0a) ; read LSB of LANDSCAPE_CEILING_FIRST_CHAR_PTR 080D: DD 66 0B ld h,(ix+$0b) ; read MSB of LANDSCAPE_CEILING_FIRST_CHAR_PTR 0810: 77 ld (hl),a ; plot character to character RAM ; calculate how many solid characters we need to plot to fill in the roof. 0811: 7D ld a,l ; get LSB of LANDSCAPE_CEILING_FIRST_CHAR_PTR into A 0812: E6 1F and $1F ; As each row is 32 characters wide, ANDing LSB with 31 will return index of character on row 0814: D6 05 sub $05 0816: 28 10 jr z,$0828 ; A now holds number of characters to draw 0818: 47 ld b,a ; what type of solid character to use? 0819: 0E 39 ld c,$39 ; ordinal of solid "rock" character 081B: 3A 1D 41 ld a,($411D) ; read LANDSCAPE_FLAGS 081E: E6 1C and $1C ; Are we on Level 4, 5 or BASE? 0820: 28 02 jr z,$0824 ; if not, goto $0824 0822: 0E D0 ld c,$D0 ; ordinal of solid "brick" character ; Working from *right to left*, fill in the empty gap between the "edge" character and top of scroll area with character ordinal 0824: 2B dec hl 0825: 71 ld (hl),c ; plot to character RAM 0826: 10 FC djnz $0824 ; repeat until all solid characters plotted 0828: DD 7E 0C ld a,(ix+$0c) ; read LANDSCAPE_CEILING_SECOND_CHAR 082B: DD 6E 0D ld l,(ix+$0d) ; read LSB of LANDSCAPE_CEILING_SECOND_CHAR_PTR 082E: DD 66 0E ld h,(ix+$0e) ; read MSB of LANDSCAPE_CEILING_SECOND_CHAR_PTR 0831: C3 97 09 jp $0997 ; jump to PLOT_LANDSCAPE_CHAR ; jumped to from $099D.. 0834: 28 10 jr z,$0846 0836: 47 ld b,a 0837: 0E 39 ld c,$39 ; ordinal of solid "rock" character 0839: 3A 1D 41 ld a,($411D) ; read LANDSCAPE_FLAGS 083C: E6 1C and $1C 083E: 28 02 jr z,$0842 0840: 0E 3D ld c,$3D ; ordinal of solid "brick" character 0842: 2B dec hl 0843: 71 ld (hl),c 0844: 10 FC djnz $0842 0846: DD 36 08 00 ld (ix+$08),$00 084A: C9 ret ; ; Renders all stationary rockets, fuel tanks, mystery and bases. ; ; DRAW_ALL_CHARACTER_BASED_GROUND_OBJECTS: 084B: 11 04 00 ld de,$0004 ; sizeof(CHAR_BASED_GROUND_OBJECT) 084E: 06 08 ld b,$08 ; 0850: DD 21 60 42 ld ix,$4260 ; load IX with address of CHAR_BASED_GROUND_OBJECTS array 0854: D9 exx 0855: CD 5E 08 call $085E ; call DRAW_CHARACTER_BASED_GROUND_OBJECT 0858: D9 exx 0859: DD 19 add ix,de ; bump IX to point to next CHAR_BASED_GROUND_OBJECT in array 085B: 10 F7 djnz $0854 ; repeat until B==0 085D: C9 ret ; A ground object is 2x2 characters. This routine plots the characters to character RAM. ; ; IX = pointer to CHAR_BASED_GROUND_OBJECT structure ; DRAW_CHARACTER_BASED_GROUND_OBJECT: 085E: DD CB 00 46 bit 0,(ix+$00) ; read CHAR_BASED_GROUND_OBJECT.Undrawn flag 0862: C8 ret z ; return if ground object has been drawn. 0863: DD 36 00 00 ld (ix+$00),$00 ; clear CHAR_BASED_GROUND_OBJECT.Undrawn flag 0867: DD 7E 01 ld a,(ix+$01) ; read CHAR_BASED_GROUND_OBJECT.Code flag 086A: 87 add a,a 086B: 87 add a,a ; multiply A by 4, to give us ordinal of first character to POKE to character RAM 086C: DD 6E 02 ld l,(ix+$02) ; read CHAR_BASED_GROUND_OBJECT.CharRamPtrLo 086F: DD 66 03 ld h,(ix+$03) ; read CHAR_BASED_GROUND_OBJECT.CharRamPtrHi ; HL is a character RAM address where we start drawing our object. Each object is 2x2 characters in size. ; A is the ordinal of the first character to plot. 0872: 77 ld (hl),a ; plot character 0873: 3C inc a 0874: 23 inc hl 0875: 77 ld (hl),a ; plot character 0876: 3C inc a 0877: 11 1F 00 ld de,$001F ; offset to add to get to next character row 087A: 19 add hl,de 087B: 77 ld (hl),a ; plot character 087C: 3C inc a 087D: 23 inc hl 087E: 77 ld (hl),a ; plot character 087F: C9 ret DRAW_REMAINING_PLAYER_FUEL: 0880: 3A 05 41 ld a,($4105) ; read CURRENT_PLAYER_FUEL 0883: 0F rrca ; divide by 2 0884: 4F ld c,a 0885: E6 78 and $78 ; preserve bits 3-6; 0887: 0F rrca 0888: 0F rrca 0889: 0F rrca 088A: 47 ld b,a ; B = A on entry / 16 ; calculate how many full fuel cells to draw 088B: 3E 0F ld a,$0F 088D: 90 sub b 088E: 21 BE 4A ld hl,$4ABE ; character RAM address to draw at 0891: 11 E0 FF ld de,$FFE0 ; load DE with -32 0894: 04 inc b 0895: 05 dec b 0896: 28 05 jr z,$089D ; draw full fuel cells. B = number of full cells to draw 0898: 36 CB ld (hl),$CB ; draw solid block of fuel 089A: 19 add hl,de ; bump HL to point to character beneath 089B: 10 FB djnz $0898 ; repeat until B==0 ; now calculate remainder of fuel cells to draw 089D: 47 ld b,a 089E: 79 ld a,c 089F: E6 07 and $07 08A1: D9 exx 08A2: 21 B5 08 ld hl,$08B5 ; pointer to FUEL_CELL_CHARS array 08A5: 5F ld e,a 08A6: 16 00 ld d,$00 08A8: 19 add hl,de 08A9: 7E ld a,(hl) ; get ordinal of fuel character to plot 08AA: D9 exx 08AB: 77 ld (hl),a ; plot to character RAM 08AC: 04 inc b 08AD: 05 dec b 08AE: C8 ret z 08AF: 19 add hl,de 08B0: 36 3C ld (hl),$3C ; plot a blue empty square 08B2: 10 FB djnz $08AF 08B4: C9 ret ; These are the ordinals for the characters representing fuel cells ; 3C = blue empty square ; $C4 = fuel cell nearly empty, all the way to... ; $CA = ..fuel cell nearly full FUEL_CELL_CHARS: 08B5: 3C C4 C5 C6 C7 C8 C9 CA ; ; TODO: this code here appears to be uncalled - or it may be for P2 cocktail? ; ; 08BD: CD 80 08 call $0880 ; call DRAW_REMAINING_PLAYER_FUEL 08C0: 3A 01 41 ld a,($4101) 08C3: 21 64 4B ld hl,$4B64 08C6: 11 E0 FF ld de,$FFE0 ; load DE with -32 decimal 08C9: 47 ld b,a 08CA: 3E 18 ld a,$18 08CC: 90 sub b 08CD: 04 inc b 08CE: 05 dec b 08CF: 28 05 jr z,$08D6 08D1: 36 0C ld (hl),$0C 08D3: 19 add hl,de 08D4: 10 FB djnz $08D1 08D6: 47 ld b,a 08D7: A7 and a 08D8: 28 05 jr z,$08DF 08DA: 36 10 ld (hl),$10 08DC: 19 add hl,de 08DD: 10 FB djnz $08DA 08DF: 3A 02 41 ld a,($4102) 08E2: 21 63 4B ld hl,$4B63 08E5: 47 ld b,a 08E6: 3E 18 ld a,$18 08E8: 90 sub b 08E9: 04 inc b 08EA: 05 dec b 08EB: 28 05 jr z,$08F2 08ED: 36 0D ld (hl),$0D 08EF: 19 add hl,de 08F0: 10 FB djnz $08ED 08F2: 47 ld b,a 08F3: A7 and a 08F4: C8 ret z 08F5: 36 10 ld (hl),$10 08F7: 19 add hl,de 08F8: 10 FB djnz $08F5 08FA: C9 ret DISPLAY_CURRENT_PLAYER_LIVES: ; First clear all of the existing spaceships representing lives left at bottom left (as player sees it) of screen 08FB: 21 BF 4B ld hl,$4BBF ; character RAM address of leftmost "life" 08FE: 11 E0 FF ld de,$FFE0 ; load de with -32 0901: 06 0C ld b,$0C ; 12 characters to erase 0903: 36 10 ld (hl),$10 ; ordinal of empty character 0905: 19 add hl,de ; bump HL to point to row above 0906: 10 FB djnz $0903 ; repeat until all ships (if any) are erased ; And now redraw spaceships representing lives left 0908: 21 BF 4B ld hl,$4BBF 090B: 3A 08 41 ld a,($4108) ; read CURRENT_PLAYER_LIVES 090E: A7 and a ; test if current player has zero lives 090F: C8 ret z ; return if true 0910: FE 07 cp $07 ; does player have 7 or more lives? 0912: 38 02 jr c,$0916 ; if player has <7 lives, goto $0916 0914: 3E 06 ld a,$06 ; otherwise, we can draw a max of 6 lives 0916: 47 ld b,a ; B now holds number of spaceships to draw 0917: 36 0A ld (hl),$0A ; plot character for rocket of spaceship 0919: 19 add hl,de ; bump HL to point to character directly above one just plotted 091A: 36 0B ld (hl),$0B ; plot character for cockpit of spaceship 091C: 19 add hl,de ; bump HL to point to character directly above one just plotted 091D: 10 F8 djnz $0917 ; repeat until all spaceships drawn 091F: C9 ret ; ; Draw flags representing the number of missions completed. ; DISPLAY_MISSIONS_COMPLETED_FLAGS: 0920: 3A 00 41 ld a,($4100) ; read CURRENT_PLAYER_MISSIONS_COMPLETED 0923: E6 0F and $0F ; ensure value is from 0..15 0925: 3C inc a ; then add 1 0926: 47 ld b,a ; B is now number of flags to draw (1..16) ; Draw B number of flags 0927: 3E 10 ld a,$10 ; compute how many empty characters need to be drawn 0929: 90 sub b ; empty characters = 16-B 092A: 21 5F 48 ld hl,$485F ; character RAM address to start drawing flags 092D: 11 20 00 ld de,$0020 ; offset to add to HL after plot: there's 32 characters per row 0930: 36 0E ld (hl),$0E ; plot flag character 0932: 19 add hl,de ; bump HL to point to character directly beneath one just plotted 0933: 10 FB djnz $0930 ; repeat until B==0 ; We've drawn the flags. Do we need to pad out the remaining space with empty characters? ; A = count of empty characters that need to be drawn 0935: A7 and a ; set zero flag if A == 0 0936: C8 ret z ; return if A is zero ; draw [A] number of empty space characters in a column starting at HL 0937: 36 10 ld (hl),$10 ; plot an empty space character 0939: 19 add hl,de ; bump HL to point to character directly beneath one just plotted 093A: 3D dec a ; decrement count of empty spaces to be drawn 093B: 20 FA jr nz,$0937 ; repeat until A==0 093D: C9 ret ; ; This routine is responsible for drawing the progress bar directly beneath the player scores and high score. ; ; The progress bar is depicted as a table with 2 rows and 6 columns: ; ; 1ST | 2ND | 3RD | 4TH | 5TH | BASE ; ---------------------------------- ; | | | | | ; ; The first row is the table headings listing the "sections" in the game - think of the element in HTML if that makes more sense. ; The second row are coloured cells that represent the players progress. I suppose these would be like elements. ; Red cells indicate that a section is completed; purple cells indicate that a section is uncompleted. ; DISPLAY_CURRENT_PLAYER_PROGRESS_BAR: ; First draw the table headers (1ST, 2ND , .. BASE ) 093E: DD 21 63 4B ld ix,$4B63 ; address in character RAM of first character to plot 0942: 11 E0 FF ld de,$FFE0 ; offset to add to HL (-32 decimal) after plotting each cell 0945: 21 7F 09 ld hl,$097F ; load HL with address of PROGRESS_BAR_HEADER_TEXT 0948: 06 18 ld b,$18 ; the bar is 24 characters long in total 094A: 7E ld a,(hl) ; read character ordinal 094B: DD 77 00 ld (ix+$00),a ; plot character into character RAM 094E: 23 inc hl ; bump HL to point to next character to draw 094F: DD 19 add ix,de ; bump IX to point to character directly above one just plotted 0951: 10 F7 djnz $094A ; repeat until B==0 ; then draw purple table cells beneath the headers 0953: 21 64 4B ld hl,$4B64 ; character RAM address 0956: 11 E0 FF ld de,$FFE0 ; offset to add to HL (-32 decimal) after plotting each cell 0959: DD 21 A0 09 ld ix,$09A0 ; load IX with address of PROGRESS_BAR_PURPLE_CELLS 095D: 06 18 ld b,$18 ; the cells are 24 characters long in total 095F: DD 7E 00 ld a,(ix+$00) 0962: 77 ld (hl),a 0963: DD 23 inc ix 0965: 19 add hl,de ; bump HL to point to character directly above one just plotted 0966: 10 F7 djnz $095F ; repeat until all cells are purple ; overwrite purple table cells with red to show what section the player is in 0968: 3A 1E 41 ld a,($411E) ; read CURRENT_PLAYERS_LEVEL 096B: 3C inc a ; ensure value is nonzero 096C: 47 ld b,a ; now B indicates how many cells to turn red 096D: 21 64 4B ld hl,$4B64 ; character RAM address 0970: 36 81 ld (hl),$81 ; overwrite purple cell... 0972: 19 add hl,de 0973: 36 82 ld (hl),$82 0975: 19 add hl,de 0976: 36 82 ld (hl),$82 0978: 19 add hl,de 0979: 36 83 ld (hl),$83 ; .. with red. 097B: 19 add hl,de 097C: 10 F2 djnz $0970 ; repeat until B==0 097E: C9 ret ; Ordinals of the characters comprising the header text of the "progress bar". See $093E. PROGRESS_BAR_HEADER_TEXT: 1 S T | 2 N D | 3 R D | 4 T H | 097F: 50 51 52 6D 53 54 55 6D 56 57 55 6D 58 59 5A 6D 5 T H | B A S E 098F: 5B 59 5A 6D 64 65 51 66 ; ; invoked from DRAW_LANDSCAPE. I think the reason this code is separated from the main routine is to make tracing ; logic that bit more difficult for hackers back in the day. I don't see any other reason for doing it. ; PLOT_LANDSCAPE_CHAR: 0997: 77 ld (hl),a 0998: 7D ld a,l 0999: E6 1F and $1F 099B: D6 05 sub $05 099D: C3 34 08 jp $0834 ; Ordinals of the characters comprising the purple part of the progress bar. See $0953 PROGRESS_BAR_PURPLE_CELLS: 09A0: 6E 6F 6F 80 6E 6F 6F 80 6E 6F 6F 80 6E 6F 6F 80 09B0: 6E 6F 6F 80 6E 6F 6F 80 09B8: 6E ld l,(hl) 09B9: 6F ld l,a 09BA: 6F ld l,a 09BB: 80 add a,b 09BC: 6E ld l,(hl) 09BD: 6F ld l,a 09BE: 6F ld l,a 09BF: 80 add a,b ; Non maskable interrupt (NMI) handler NMI_HANDLER: 09C0: F5 push af 09C1: C5 push bc 09C2: D5 push de 09C3: E5 push hl 09C4: 08 ex af,af' 09C5: D9 exx 09C6: F5 push af 09C7: C5 push bc 09C8: D5 push de 09C9: E5 push hl 09CA: DD E5 push ix 09CC: FD E5 push iy 09CE: AF xor a 09CF: 32 01 68 ld ($6801),a ; disable NMI ; update attributes, sprites 09D2: 21 20 40 ld hl,$4020 ; pointer to OBJRAM_BACK_BUF buffer held in RAM 09D5: 11 00 50 ld de,$5000 ; start of screen attribute RAM 09D8: 01 80 00 ld bc,$0080 ; number of bytes to copy from OBJRAM_BACK_BUF 09DB: ED B0 ldir ; update screen & sprites in one go 09DD: 3A 00 70 ld a,($7000) ; kick watchdog 09E0: 3A 15 40 ld a,($4015) ; read PREV_PREV_PORT_STATE_8100 09E3: 32 16 40 ld ($4016),a ; and write to PREV_PREV_PREV_STATE_8100 09E6: 3A 13 40 ld a,($4013) ; read PREV_PORT_STATE_8100 09E9: C3 CA 21 jp $21CA ; jump to rest of NMI handler UNPROCESSED_COINS: 09EC: 21 18 40 ld hl,$4018 ; read UNPROCESSED_COINS 09EF: 7E ld a,(hl) ; Read value 09F0: A7 and a ; test if zero. 09F1: 28 03 jr z,$09F6 ; if zero, goto $09F6 09F3: 35 dec (hl) ; Otherwise, decrement UNPROCESSED_COINS 09F4: 3E 01 ld a,$01 09F6: 32 02 68 ld ($6802),a ; write to coin counter 09F9: 21 10 40 ld hl,$4010 ; pointer to PORT_STATE_8100 value 09FC: 7E ld a,(hl) ; read value 09FD: 2C inc l 09FE: 2C inc l 09FF: 2C inc l ; bump HL to $4013, which is PREV_PORT_STATE_8100 value 0A00: B6 or (hl) ; combine bits set for current state of port 8100 with bits set from previous state 0A01: 2C inc l 0A02: 2C inc l ; bump HL to $4015, which is PREV_PREV_PORT_STATE_8100 value 0A03: 2F cpl ; flip bits 0A04: A6 and (hl) 0A05: 2C inc l ; bump HL to $4016, which is PREV_PREV_PREV_STATE_8100 value 0A06: A6 and (hl) 0A07: E6 C4 and $C4 ; mask in IPT_COIN1, IPT_COIN2 and IPT_SERVICE1 bits 0A09: 28 21 jr z,$0A2C ; if none of them are pressed, goto $0A2C ; if we get here, IPT_COIN1 or IPT_COIN2 or IPT_SERVICE1 bits are set ; If we've inserted a coin, we need to acknowledge it. If we've pressed SERVICE on the other hand, we get free credits 0A0B: 47 ld b,a 0A0C: E6 C0 and $C0 ; mask in IPT_COIN1 or IPT_COIN2 bits 0A0E: 28 05 jr z,$0A15 ; if no coins inserted, must be SERVICE switch that's pressed, goto $0A15 ; Coin has been inserted 0A10: 3E 06 ld a,$06 0A12: 32 18 40 ld ($4018),a ; set UNPROCESSED_COINS ; Update credits and ensure a maximum of 99. 0A15: CD 68 0A call $0A68 ; call UPDATE_CREDITS 0A18: 21 02 40 ld hl,$4002 ; address of NUM_CREDITS 0A1B: 7E ld a,(hl) ; read number of credits 0A1C: FE 63 cp $63 ; compare to 99 (decimal) 0A1E: 38 02 jr c,$0A22 ; if A < 99 decimal, goto $0A22 0A20: 36 63 ld (hl),$63 ; otherwise, clamp number of credits to 99 decimal ; if game is *not* in play, show credit available 0A22: 3A 06 40 ld a,($4006) ; read IS_GAME_IN_PLAY flag 0A25: 0F rrca ; move flag into carry 0A26: 38 04 jr c,$0A2C ; if game is not in play, goto $0A2C 0A28: 11 01 07 ld de,$0701 ; Command ID: 7 = HEAD_UP_DISPLAY_COMMAND, Param: 1 = DISPLAY_CREDITS 0A2B: FF rst $38 ; call QUEUE_COMMAND ; This code here doesn't do anything in the game. 0A2C: 21 03 40 ld hl,$4003 0A2F: 5E ld e,(hl) 0A30: 16 06 ld d,$06 ; load DE with #$0600 0A32: 1A ld a,(de) ; A = $45 0A33: 1C inc e 0A34: 73 ld (hl),e 0A35: 23 inc hl ; bump HL to point to $4004 0A36: 86 add a,(hl) 0A37: 3D dec a 0A38: 77 ld (hl),a 0A39: 3A B0 40 ld a,($40B0) ; read IS_COLUMN_SCROLLING flag 0A3C: 0F rrca ; move flag into carry 0A3D: D0 ret nc ; return if flag not set ; $40B0 is always set to zero so this code is never called. -- BEGIN UNCALLED CODE BLOCK 0A3E: 2A B1 40 ld hl,($40B1) 0A41: 7E ld a,(hl) 0A42: E6 07 and $07 0A44: 20 1B jr nz,$0A61 0A46: EB ex de,hl 0A47: 2A B3 40 ld hl,($40B3) 0A4A: 7E ld a,(hl) 0A4B: FE 3F cp $3F 0A4D: 28 11 jr z,$0A60 0A4F: 23 inc hl 0A50: 22 B3 40 ld ($40B3),hl 0A53: D6 30 sub $30 0A55: 2A B5 40 ld hl,($40B5) 0A58: 77 ld (hl),a 0A59: 01 E0 FF ld bc,$FFE0 0A5C: 09 add hl,bc 0A5D: 22 B5 40 ld ($40B5),hl 0A60: EB ex de,hl 0A61: 35 dec (hl) 0A62: C0 ret nz 0A63: AF xor a 0A64: 32 B0 40 ld ($40B0),a 0A67: C9 ret -- END UNCALLED CODE BLOCK ; ; ; ; ; ; B = bits representing status of IPT_COIN1, IPT_COIN2, IPT_SERVICE1 UPDATE_CREDITS: 0A68: 21 02 40 ld hl,$4002 ; load HL with address of NUM_CREDITS 0A6B: 78 ld a,b 0A6C: E6 84 and $84 ; test if IPT_COIN1 or IPT_SERVICE1 is pressed 0A6E: 28 10 jr z,$0A80 ; if both not pressed, then it must be IPT_COIN2. Goto IPT_COIN2 ; Either IPT_COIN1 or IPT_SERVICE1 is pressed. Award a credit. 0A70: 34 inc (hl) ; increment NUM_CREDITS 0A71: 3A 00 40 ld a,($4000) ; read COINAGE_VALUE 0A74: A7 and a ; test if value is Coinage A 1/1 B 2/1 C 1/1 0A75: C8 ret z ; return if zero 0A76: 34 inc (hl) ; increment NUM_CREDITS 0A77: 3D dec a 0A78: C3 08 1A jp $1A08 ; jumps to a RET Z and a JP $0A7B... 0A7B: 34 inc (hl) ; increment NUM_CREDITS 0A7C: 3D dec a 0A7D: C8 ret z ; if we get here, we get FOUR credits for one coin inserted into IPT_COIN1 0A7E: 34 inc (hl) ; increment credits 0A7F: C9 ret ; ; Called when coin(s) are inserted into the IPT_COIN2 slot. ; IPT_COIN2: 0A80: 3A 00 40 ld a,($4000) ; read COINAGE_VALUE 0A83: 2D dec l ; bump HL to point to COIN_COUNTER 0A84: 34 inc (hl) 0A85: A7 and a ; Test if value is Coinage A 1/1 B 2/1 C 1/1 0A86: 28 08 jr z,$0A90 ; if true, goto IPT_COIN2_TWO_COINS_FOR_ONE_CREDIT 0A88: 3D dec a ; Test if value is Coinage A 1/2 B 1/1 C 1/2 0A89: 28 0A jr z,$0A95 ; if true, goto IPT_COIN2_ONE_COIN_FOR_ONE_CREDIT 0A8B: 3D dec a ; Test if value is Coinage A 1/3 B 3/1 C 1/3 0A8C: 28 0C jr z,$0A9A ; if true, goto IPT_COIN2_THREE_COINS_FOR_ONE_CREDIT 0A8E: 18 0F jr $0A9F ; default: goto IPT_COIN2_FOUR_COINS_FOR_ONE_CREDIT IPT_COIN2_TWO_COINS_FOR_ONE_CREDIT: 0A90: 7E ld a,(hl) ; read COIN_COUNTER 0A91: FE 02 cp $02 ; 2 coins inserted? 0A93: 18 0D jr $0AA2 IPT_COIN2_ONE_COIN_FOR_ONE_CREDIT: 0A95: 7E ld a,(hl) ; read COIN_COUNTER 0A96: FE 01 cp $01 ; 1 coin inserted? 0A98: 18 08 jr $0AA2 IPT_COIN2_THREE_COINS_FOR_ONE_CREDIT: 0A9A: 7E ld a,(hl) ; read COIN_COUNTER 0A9B: FE 03 cp $03 ; 3 coins inserted? 0A9D: 18 03 jr $0AA2 IPT_COIN2_FOUR_COINS_FOR_ONE_CREDIT: 0A9F: 7E ld a,(hl) ; read COIN_COUNTER 0AA0: FE 04 cp $04 ; Carry flag set by one of the CP instructions above 0AA2: D8 ret c ; return if COIN_COUNTER does not match number of coins required for a credit 0AA3: 36 00 ld (hl),$00 ; reset COIN_COUNTER 0AA5: 2C inc l 0AA6: 34 inc (hl) ; increment NUM_CREDITS 0AA7: C9 ret ; ; This is the first script run. It clears the screen, displays scores + high score before switching to ATTRACT_MODE_SCRIPT. ; SCRIPT_ONE: ; clear the screen 0AA8: 2A 0B 40 ld hl,($400B) ; read TEMP_CHAR_RAM_PTR 0AAB: 06 20 ld b,$20 ; number of characters per row 0AAD: 3E 10 ld a,$10 ; ordinal of character (space) 0AAF: D7 rst $10 ; fill memory 0AB0: 22 0B 40 ld ($400B),hl ; update TEMP_CHAR_RAM_PTR 0AB3: 21 08 40 ld hl,$4008 ; load HL with address of TEMP_COUNTER_4008 0AB6: 35 dec (hl) ; decrement value 0AB7: C0 ret nz ; if counter hasn't hit zero, return 0AB8: 2D dec l 0AB9: 2D dec l ; bump HL to point to IS_GAME_IN_PLAY 0ABA: 36 00 ld (hl),$00 ; reset flag ; move onto attract mode 0ABC: 2D dec l ; bump HL to point to SCRIPT_NUMBER 0ABD: 36 01 ld (hl),$01 ; set script number to ATTRACT_MODE_SCRIPT (zero-based index, remember) 0ABF: AF xor a 0AC0: 32 0A 40 ld ($400A),a ; set SCRIPT_STAGE ; display scores 0AC3: 21 E9 0A ld hl,$0AE9 ; load HL with address of COLOUR_ATTRIBUTE_TABLE_0AE9 0AC6: CD D9 0A call $0AD9 ; call SET_COLOUR_ATTRIBUTES_FOR_ENTIRE_SCREEN 0AC9: 11 04 06 ld de,$0604 ; Command ID: 06 = PRINT_TEXT, Param: 4 = HIGH SCORE 0ACC: FF rst $38 ; call QUEUE_COMMAND 0ACD: 11 00 05 ld de,$0500 ; Command ID: 5 = DISPLAY_SCORE_COMMAND, Param: 0 = Display player one's score 0AD0: FF rst $38 ; call QUEUE_COMMAND 0AD1: 1E 02 ld e,$02 ; Command ID: 5 = DISPLAY_SCORE_COMMAND, Param: 2 = Display high score 0AD3: FF rst $38 ; call QUEUE_COMMAND 0AD4: AF xor a 0AD5: 32 14 45 ld ($4514),a 0AD8: C9 ret ; ; Set the colour attributes in OBJRAM_BACK_BUF_ATTRIBUTES. ; ; Expects: ; HL = pointer to 32 bytes which define the colour attributes for each character column. ; SET_COLOUR_ATTRIBUTES_FOR_ENTIRE_SCREEN: 0AD9: 11 20 40 ld de,$4020 ; pointer to OBJRAM_BACK_BUF_ATTRIBUTES 0ADC: 06 20 ld b,$20 ; we're setting attributes for all 32 columns in the row 0ADE: EB ex de,hl ; DE now points to 32 byte attribute list, HL = entry in OBJRAM_BACK_BUF_ATTRIBUTES 0ADF: 36 00 ld (hl),$00 ; reset scroll offset for column in OBJRAM_BACK_BUF_ATTRIBUTES 0AE1: 2C inc l 0AE2: 1A ld a,(de) ; read attribute value from source 0AE3: 77 ld (hl),a ; write attribute value to OBJRAM_BACK_BUF_ATTRIBUTES 0AE4: 2C inc l ; bump HL 0AE5: 13 inc de 0AE6: 10 F7 djnz $0ADF ; repeat until B==0 0AE8: C9 ret COLOUR_ATTRIBUTE_TABLE_0AE9: 0AE9: 00 05 00 07 07 01 06 00 00 00 00 00 00 00 00 00 0AF9: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 06 COLOUR_ATTRIBUTE_TABLE_0B09: 0B09: 00 05 00 00 01 01 06 03 03 04 04 04 04 00 00 00 0B19: 02 02 02 00 00 00 04 04 04 04 04 06 06 06 06 06 COLOUR_ATTRIBUTE_TABLE_0B29: 0B29: 00 05 02 02 02 02 02 06 06 06 06 06 06 06 06 06 0B39: 04 04 04 04 04 04 04 04 04 04 06 06 00 06 06 06 COLOUR_ATTRIBUTE_TABLE_0B49: 0B49: 00 05 06 06 06 06 06 02 00 00 00 00 00 00 00 00 0B59: 00 00 00 00 00 00 00 00 00 00 00 00 00 06 06 06 COLOUR_ATTRIBUTE_TABLE_0B69: 0B69: 00 05 04 04 04 04 04 02 02 02 02 02 02 06 06 06 0B79: 06 06 06 07 07 07 07 07 07 07 07 06 06 06 06 06 COLOUR_ATTRIBUTE_TABLE_0B89: 0B89: 00 05 01 01 01 01 01 01 02 02 03 03 04 04 05 05 0B99: 06 06 07 07 01 01 02 02 01 01 01 01 01 01 01 06 ; ; ; ATTRACT_MODE_SCRIPT handles the attract mode for the game ; ; ATTRACT_MODE_SCRIPT: 0BA9: 21 C5 03 ld hl,$03C5 ; push return address on stack 0BAC: E5 push hl 0BAD: 3A 41 45 ld a,($4541) ; read ATTRACT_MODE_SCRIPT_STAGE 0BB0: EF rst $28 ; call function C5 0B ; $0BC5 (ATTRACT_MODE_INIT) 32 0C ; $0C32 (DISPLAY_CHALLENGE_MESSAGE) A5 0C ; $0CA5 (CLEAR_CHALLENGE_MESSAGE_THEN_DISPLAY_HIGH_SCORES) C7 0C ; $0CC7 (some protection code) F3 0C ; $0CF3 (DISPLAY_SPRITES_FOR_SCORE_TABLE) AE 0D ; $0DAE (DISPLAY_ROW_OF_POINTS_VALUES_FOR_SCORE_TABLE) D7 0D ; $0DD7 (ADVANCE_TO_NEXT_ROW_OF_SCORE_TABLE) F8 0D ; $0DF8 (CLEAR_SCREEN_AND_HIDE_SPRITES_0DF8) 13 0E ; $0E13 (INIT_DEMO) 5C 0E ; $0E5C (DEMO) ; Initialise the attract mode ATTRACT_MODE_INIT: 0BC5: 3E 01 ld a,$01 0BC7: 32 04 68 ld ($6804),a ; Enable stars 0BCA: 3D dec a 0BCB: 32 03 68 ld ($6803),a ; set background to black 0BCE: AF xor a 0BCF: 32 19 40 ld ($4019),a ; clear DRAW_LANDSCAPE_FLAG 0BD2: 21 20 40 ld hl,$4020 ; address of OBJRAM_BACK_BUF 0BD5: 11 21 40 ld de,$4021 0BD8: 01 7F 00 ld bc,$007F 0BDB: 36 00 ld (hl),$00 0BDD: ED B0 ldir ; clear OBJRAM_BACK_BUF 0BDF: 21 02 48 ld hl,$4802 ; address of 3rd character on top row 0BE2: 22 0B 40 ld ($400B),hl ; set TEMP_CHAR_RAM_PTR 0BE5: 21 09 40 ld hl,$4009 ; load HL with address of TEMP_COUNTER_4009 0BE8: 36 20 ld (hl),$20 ; set TEMP_COUNTER_4009 0BEA: 21 41 45 ld hl,$4541 ; load HL with address of ATTRACT_MODE_SCRIPT_STAGE 0BED: 34 inc (hl) ; advance to next stage of script (DISPLAY_CHALLENGE_MESSAGE @ $0C32) 0BEE: AF xor a 0BEF: 32 06 40 ld ($4006),a ; reset IS_GAME_IN_PLAY flag ; protection code - if you're not interested in this, skip to $0C25 0BF2: ED 5B B9 40 ld de,($40B9) ; load DE with PROTECTION_PORT_PTR_1 0BF6: 2A 9E 40 ld hl,($409E) 0BF9: 22 BE 40 ld ($40BE),hl 0BFC: 2C inc l 0BFD: ED 53 BB 40 ld ($40BB),de ; copy PROTECTION_PORT_PTR_1 to PROTECTION_PORT_PTR_2 0C01: 3A 8B 0F ld a,($0F8B) ; load A with #$3E (opcode for LD A,) 0C04: 47 ld b,a 0C05: 47 ld b,a 0C06: 00 nop 0C07: 47 ld b,a 0C08: E6 0F and $0F 0C0A: 3C inc a ; A= #$0F 0C0B: 12 ld (de),a ; write to protection 0C0C: 67 ld h,a 0C0D: AF xor a ; A = 0 0C0E: 12 ld (de),a ; write to protection 0C0F: 44 ld b,h ; B = $#0F 0C10: EB ex de,hl ; HL now is pointer to protection, DE = #$0F01 0C11: 70 ld (hl),b ; write to protection 0C12: 47 ld b,a ; B = 0 0C13: 70 ld (hl),b ; write to protection 0C14: 3A 89 0B ld a,($0B89) ; read first entry of COLOUR_ATTRIBUTE_TABLE_0B89 0C17: C6 04 add a,$04 0C19: 07 rlca 0C1A: 3C inc a ; A = 9 0C1B: 77 ld (hl),a ; write to protection 0C1C: 7E ld a,(hl) ; read from protection 0C1D: E6 F0 and $F0 ; mask in upper nibble 0C1F: 2F cpl 0C20: E6 F0 and $F0 ; if protection is OK, this should set A to 0, and zero flag should be set 0C22: C2 F3 0C jp nz,$0CF3 ; if protection check failed, goto DISPLAY_SPRITES_FOR_SCORE_TABLE ; end protection code 0C25: 06 80 ld b,$80 0C27: 19 add hl,de ; bump HL to point to $9103 0C28: 7E ld a,(hl) ; A = $9B ; IX = $4981 0C29: DD CB 01 5E bit 3,(ix+$01) 0C2D: C8 ret z ; happy path - return if protection check succeeded 0C2E: 21 00 01 ld hl,$0100 0C31: C9 ret ; ; ; Display HOW FAR CAN YOU INVADE OUR SCRAMBLE SYSTEM ; ; DISPLAY_CHALLENGE_MESSAGE: ; first clear everything on screen except scores 0C32: 2A 0B 40 ld hl,($400B) ; load character RAM address from TEMP_CHAR_RAM_PTR 0C35: 06 1E ld b,$1E ; number of characters to erase 0C37: 3E 10 ld a,$10 ; ordinal for empty char 0C39: D7 rst $10 ; fill memory 0C3A: 11 02 00 ld de,$0002 ; offset to add to HL 0C3D: 19 add hl,de 0C3E: 22 0B 40 ld ($400B),hl ; update TEMP_CHAR_RAM_PTR 0C41: 21 09 40 ld hl,$4009 ; load HL with address of TEMP_COUNTER_4009 0C44: 35 dec (hl) 0C45: C0 ret nz ; strange piece of code here. TODO: investigate 0C46: 21 00 41 ld hl,$4100 ; load HL with address of CURRENT_PLAYER_MISSIONS_COMPLETED 0C49: 7E ld a,(hl) 0C4A: 2C inc l 0C4B: 46 ld b,(hl) 0C4C: 2C inc l 0C4D: 4E ld c,(hl) 0C4E: 11 40 41 ld de,$4140 0C51: 78 ld a,b 0C52: 12 ld (de),a 0C53: B1 or c 0C54: 12 ld (de),a ; protection code - if you're not interested in this, skip to $0C74 0C55: 3A B9 40 ld a,($40B9) ; read PROTECTION_PORT_PTR_1_LO 0C58: 6F ld l,a 0C59: ED 5B BA 40 ld de,($40BA) 0C5D: 63 ld h,e ; loads HL with address of protection ($8202) 0C5E: 36 0A ld (hl),$0A ; write to protection 0C60: 3E 0A ld a,$0A 0C62: E6 07 and $07 0C64: 07 rlca ; now A is 4 0C65: 77 ld (hl),a ; write to protection again 0C66: C6 05 add a,$05 ; now A is 9.. 0C68: 77 ld (hl),a ; write to protection again 0C69: 4E ld c,(hl) ; read from protection 0C6A: 79 ld a,c 0C6B: E6 F0 and $F0 ; mask in upper nibble 0C6D: 4F ld c,a 0C6E: 3E B0 ld a,$B0 0C70: B9 cp c ; compare upper nibble to $B0 0C71: C2 F4 22 jp nz,$22F4 ; if they don't match, reset system by going to PLAY_GAME ; end protection code 0C74: 11 10 10 ld de,$1010 0C77: 19 add hl,de ; now HL = $9212 0C78: 7E ld a,(hl) ; ??? no reason why - is $9212 a mirror for a protection port? 0C79: 21 29 0B ld hl,$0B29 ; load HL with address of COLOUR_ATTRIBUTE_TABLE_0B29 0C7C: CD D9 0A call $0AD9 ; call SET_COLOUR_ATTRIBUTES_FOR_ENTIRE_SCREEN 0C7F: AF xor a 0C80: 32 06 68 ld ($6806),a ; disable screen vertical flip 0C83: 32 07 68 ld ($6807),a ; disable screen horizontal flip 0C86: 21 41 45 ld hl,$4541 ; load HL with address of ATTRACT_MODE_SCRIPT_STAGE 0C89: 34 inc (hl) ; advance to next stage of script (CLEAR_CHALLENGE_MESSAGE_THEN_DISPLAY_HIGH_SCORES @ $0CA5) 0C8A: 2D dec l ; bump HL to point to TEMP_COUNTER_4540 0C8B: 36 00 ld (hl),$00 ; set delay before clearing the screen @$0CAC ; Display HOW FAR CAN YOU INVADE OUR SCRAMBLE SYSTEM message 0C8D: 11 01 07 ld de,$0701 ; command ID:7 = HEAD_UP_DISPLAY_COMMAND, Param: 1 = Display credits 0C90: FF rst $38 ; call QUEUE_COMMAND 0C91: 11 06 06 ld de,$0606 ; Command ID:6 = PRINT_TEXT, Param: 6 = HOW FAR CAN YOU INVADE 0C94: FF rst $38 ; call QUEUE_COMMAND 0C95: 11 12 06 ld de,$0612 ; Command ID:6 = PRINT_TEXT, Param: $12 = OUR SCRAMBLE SYSTEM 0C98: FF rst $38 ; call QUEUE_COMMAND 0C99: 11 11 06 ld de,$0611 ; Command ID:6 = PRINT_TEXT, Param: $11 = (C) KONAMI 1981 0C9C: FF rst $38 ; call QUEUE_COMMAND 0C9D: 11 0B 06 ld de,$060B ; Command ID:6 = PRINT_TEXT, Param: $0B = PLAY 0CA0: FF rst $38 ; call QUEUE_COMMAND 0CA1: 1E 0C ld e,$0C ; Command ID:6 = PRINT_TEXT, Param: $0C = - SCRAMBLE - 0CA3: FF rst $38 ; call QUEUE_COMMAND 0CA4: C9 ret CLEAR_CHALLENGE_MESSAGE_THEN_DISPLAY_HIGH_SCORES: 0CA5: 21 40 45 ld hl,$4540 ; load HL with address of TEMP_COUNTER_4540 0CA8: 35 dec (hl) 0CA9: C0 ret nz ; wait until counter reaches zero 0CAA: 2C inc l ; bump HL to point to ATTRACT_MODE_SCRIPT_STAGE 0CAB: 34 inc (hl) ; advance to next stage of script (which is some protection code - skip to DISPLAY_SPRITES_FOR_SCORE_TABLE @$0CF3) ; Remove "HOW FAR CAN YOU INVADE OUR SCRAMBLE SYSTEM" from the screen 0CAC: 21 69 0B ld hl,$0B69 ; load HL with address of COLOUR_ATTRIBUTE_TABLE_0B69 0CAF: CD D9 0A call $0AD9 ; call SET_COLOUR_ATTRIBUTES_FOR_ENTIRE_SCREEN 0CB2: 11 86 06 ld de,$0686 ; Command ID: PRINT_TEXT, Param: $86 = clears HOW FAR CAN YOU INVADE 0CB5: FF rst $38 ; call QUEUE_COMMAND 0CB6: 11 92 06 ld de,$0692 ; Command ID: PRINT_TEXT, Param: $92 = clears OUR SCRAMBLE SYSTEM 0CB9: FF rst $38 ; call QUEUE_COMMAND 0CBA: 11 8B 06 ld de,$068B ; Command ID: PRINT_TEXT, Param: $8B = clears PLAY 0CBD: FF rst $38 ; call QUEUE_COMMAND 0CBE: 11 8C 06 ld de,$068C ; Command ID: PRINT_TEXT, Param: $8C = clears SCRAMBLE 0CC1: FF rst $38 ; call QUEUE_COMMAND ; now display high scores 0CC2: 11 00 02 ld de,$0200 ; Command ID: DISPLAY_HIGH_SCORES_COMMAND 0CC5: FF rst $38 ; call QUEUE_COMMAND 0CC6: C9 ret ; ; Some protection code I can't put a label to ; 0CC7: 21 40 45 ld hl,$4540 ; load HL with address of TEMP_COUNTER_4540 0CCA: 35 dec (hl) 0CCB: C0 ret nz ; wait until counter reaches zero 0CCC: 21 02 48 ld hl,$4802 ; load HL with address of 3rd character, top row, in character RAM 0CCF: 22 0B 40 ld ($400B),hl ; save to TEMP_CHAR_RAM_PTR 0CD2: 21 09 40 ld hl,$4009 ; load HL with address of TEMP_COUNTER_4009 0CD5: 36 20 ld (hl),$20 ; set counter 0CD7: 21 41 45 ld hl,$4541 ; load HL with address of ATTRACT_MODE_SCRIPT_STAGE 0CDA: 34 inc (hl) ; advance to next stage of script (DISPLAY_SPRITES_FOR_SCORE_TABLE @ $0CF3) ;protection code - if not interested, skip rest of this function 0CDB: 3E 80 ld a,$80 0CDD: C6 02 add a,$02 0CDF: 2E 02 ld l,$02 0CE1: 67 ld h,a ; set HL to point to $8202, protection 0CE2: 36 03 ld (hl),$03 ; write to protection 0CE4: 7D ld a,l 0CE5: 3D dec a ; set A to 1 0CE6: 77 ld (hl),a ; write to protection 0CE7: C6 08 add a,$08 ; set A to 9 0CE9: 77 ld (hl),a ; write to protection 0CEA: 46 ld b,(hl) ; read from protection 0CEB: 78 ld a,b 0CEC: E6 F0 and $F0 ; mask in upper nibble 0CEE: FE 40 cp $40 0CF0: 20 D5 jr nz,$0CC7 ; if protection check failed, goto $0CC7 0CF2: C9 ret ; ; Displays all enemy types (as sprites) on the - SCORE TABLE - attract mode screen. ; The associated points values are drawn by DISPLAY_ROW_OF_POINTS_VALUES_FOR_SCORE_TABLE @ $0DAE ; DISPLAY_SPRITES_FOR_SCORE_TABLE: ; clear character RAM so sprites can be shown clearly on black background 0CF3: 2A 0B 40 ld hl,($400B) ; load HL from TEMP_CHAR_RAM_PTR 0CF6: 06 1A ld b,$1A ; count #$1A (26) 0CF8: 3E 10 ld a,$10 ; ordinal for empty space character 0CFA: D7 rst $10 ; plot 26 empty space characters 0CFB: 11 06 00 ld de,$0006 ; each row is 32 characters wide and we've done 26. So we add 6 .. 0CFE: 19 add hl,de ; .. to get HL to point to starting character on next row down 0CFF: 22 0B 40 ld ($400B),hl ; update TEMP_CHAR_RAM_PTR 0D02: 21 09 40 ld hl,$4009 ; load HL with address of TEMP_COUNTER_4009 0D05: 35 dec (hl) 0D06: C0 ret nz ; set the colour palette 0D07: 21 49 0B ld hl,$0B49 ; load HL with address of COLOUR_ATTRIBUTE_TABLE_0B49 0D0A: CD D9 0A call $0AD9 ; call SET_COLOUR_ATTRIBUTES_FOR_ENTIRE_SCREEN ; display the - SCORE TABLE - heading 0D0D: 11 0D 06 ld de,$060D ; Command ID: 6 = PRINT_TEXT, Param:$0D = -SCORE TABLE- 0D10: FF rst $38 ; call QUEUE_COMMAND ; position the sprites for the enemy types 0D11: 21 54 0D ld hl,$0D54 ; load HL with address of SCORE_TABLE_SPRITES 0D14: 11 60 40 ld de,$4060 ; load DE with address of OBJRAM_BACK_BUF_SPRITES 0D17: 01 18 00 ld bc,$0018 0D1A: ED B0 ldir ; set up all sprites in one go 0D1C: 21 6C 0D ld hl,$0D6C ; load HL with address of SCORE_TABLE_TEXT 0D1F: 22 44 45 ld ($4544),hl 0D22: 21 4A 4A ld hl,$4A4A ; address in character RAM 0D25: 22 46 45 ld ($4546),hl ; protection code - if you're not interested in this, skip to $0D46 0D28: ED 5B B9 40 ld de,($40B9) ; read PROTECTION_PORT_PTR_1 0D2C: 3E 0A ld a,$0A 0D2E: 47 ld b,a 0D2F: 0F rrca 0D30: 12 ld (de),a ; write to protection 0D31: EB ex de,hl 0D32: 36 0C ld (hl),$0C ; write to protection 0D34: 36 09 ld (hl),$09 ; write to protection 0D36: 7E ld a,(hl) ; read from protection 0D37: E6 F0 and $F0 ; mask in upper nibble 0D39: 0F rrca 0D3A: FE 30 cp $30 0D3C: C2 14 41 jp nz,$4114 ; jump to somewhere in RAM where there's no code! ; This code doesn't appear to do anything. My guess is that this code exists to slow down "people" (pirates) looking through the code, ; by making them waste time to see if the registers/ flags (carry & zero) were needed elsewhere in any logic. 0D3F: 47 ld b,a 0D40: 48 ld c,b 0D41: 6F ld l,a 0D42: 59 ld e,c 0D43: 07 rlca 0D44: 07 rlca 0D45: 57 ld d,a ; end protection code 0D46: 21 40 45 ld hl,$4540 ; load HL with address of TEMP_COUNTER_4540 0D49: 36 32 ld (hl),$32 0D4B: 2C inc l ; bump HL to point to ATTRACT_MODE_SCRIPT_STAGE 0D4C: 34 inc (hl) ; advance to next stage of script (DISPLAY_ROW_OF_POINTS_VALUES_FOR_SCORE_TABLE) ; set fields required for DISPLAY_ROW_OF_POINTS_VALUES_FOR_SCORE_TABLE routine 0D4D: 2C inc l ; bump HL to point to SCORE_TABLE_CHARS_COUNTER 0D4E: 36 0B ld (hl),$0B ; 11 characters per line 0D50: 2C inc l ; bump HL to point to SCORE_TABLE_ROWS_COUNTER 0D51: 36 06 ld (hl),$06 ; 6 lines 0D53: C9 ret SCORE_TABLE_SPRITES: 0D54: 50 1C 00 4A 50 1E 00 62 50 1A 06 7B 50 10 04 92 0D64: 50 26 05 A9 50 33 01 C2 SCORE_TABLE_TEXT: ; ... 50 PTS 0D6C: 0C 0C 0C 10 10 05 00 10 20 24 23 ; ... 80 PTS 0D77: 0C 0C 0C 10 10 08 00 10 20 24 23 ; ... 100 PTS 0D82: 0C 0C 0C 10 01 00 00 10 20 24 23 ; ... 150 PTS 0D8D: 0C 0C 0C 10 01 05 00 10 20 24 23 ; ... 800 PTS 0D98: 0C 0C 0C 10 08 00 00 10 20 24 23 ; ... MYSTERY 0DA3: 0C 0C 0C 10 1D 29 23 24 15 22 29 ; ; Displays the points value for an enemy on - SCORE TABLE - screen. ; ; This code basically draws a hard-coded text string sourced from SCORE_TABLE_TEXT above, ; with a delay between each character drawn. ; DISPLAY_ROW_OF_POINTS_VALUES_FOR_SCORE_TABLE: ; Has 0DAE: 21 40 45 ld hl,$4540 ; load HL with address of TEMP_COUNTER_4540 0DB1: 35 dec (hl) ; decrement counter 0DB2: C0 ret nz ; return if its not 0DB3: 36 05 ld (hl),$05 ; get character from SCORE_TABLE_TEXT 0DB5: 2A 44 45 ld hl,($4544) ; load HL with contents of SCORE_TABLE_TEXT_PTR 0DB8: 7E ld a,(hl) ; read character to plot 0DB9: 23 inc hl 0DBA: 22 44 45 ld ($4544),hl ; update SCORE_TABLE_TEXT_PTR ; plot ordinal in A to character RAM 0DBD: 2A 46 45 ld hl,($4546) ; load HL with contents of SCORE_TABLE_CHAR_PTR to get pointer to character RAM 0DC0: 77 ld (hl),a ; plot character 0DC1: 11 E0 FF ld de,$FFE0 ; load DE with -32 decimal 0DC4: 19 add hl,de ; bump HL to point to character above 0DC5: 22 46 45 ld ($4546),hl ; set SCORE_TABLE_CHAR_PTR pointer ; count how many characters left to print 0DC8: 21 42 45 ld hl,$4542 ; load HL with address of SCORE_TABLE_CHARS_COUNTER 0DCB: 35 dec (hl) ; decrement number of characters left to print 0DCC: C0 ret nz ; return if we've not done them all yet ; we've printed all 11 characters 0DCD: 36 0B ld (hl),$0B ; reset value of SCORE_TABLE_CHARS_COUNTER to 11. 0DCF: 21 40 45 ld hl,$4540 ; load HL with address of TEMP_COUNTER_4540 0DD2: 36 14 ld (hl),$14 ; delay before plotting next row 0DD4: 2C inc l ; bump HL to point to to ATTRACT_MODE_SCRIPT_STAGE 0DD5: 34 inc (hl) ; advance to next stage of script (ADVANCE_TO_NEXT_ROW_OF_SCORE_TABLE @ $0DD7) 0DD6: C9 ret ; bump SCORE_TABLE_CHAR_PTR to point to the next row on the - SCORE TABLE - screen in preparation for printing enemy points values. ADVANCE_TO_NEXT_ROW_OF_SCORE_TABLE: 0DD7: 21 40 45 ld hl,$4540 ; load HL with address of TEMP_COUNTER_4540 0DDA: 35 dec (hl) ; decrement countdown 0DDB: C0 ret nz ; wait until counter reaches zero 0DDC: 36 01 ld (hl),$01 ; reset countdown value 0DDE: 2C inc l ; bump HL to point to ATTRACT_MODE_SCRIPT_STAGE 0DDF: 35 dec (hl) ; set script stage to DISPLAY_ROW_OF_POINTS_VALUES_FOR_SCORE_TABLE ; calculate character RAM address where next line of score table should be drawn 0DE0: 2A 46 45 ld hl,($4546) ; get character RAM address from SCORE_TABLE_CHAR_PTR 0DE3: 11 63 01 ld de,$0163 0DE6: 19 add hl,de 0DE7: 22 46 45 ld ($4546),hl ; update SCORE_TABLE_CHAR_PTR with new address ; how many rows of the score table do we have left to do? 0DEA: 21 43 45 ld hl,$4543 ; load HL with address of SCORE_TABLE_ROWS_COUNTER 0DED: 35 dec (hl) ; decrement counter 0DEE: C0 ret nz ; exit if counter hasn't hit zero ; we've done all of the rows of the score table 0DEF: 21 40 45 ld hl,$4540 ; load HL with address of TEMP_COUNTER_4540 0DF2: 36 96 ld (hl),$96 ; set delay before 0DF4: 2C inc l ; bump HL to point to ATTRACT_MODE_SCRIPT_STAGE 0DF5: 34 inc (hl) 0DF6: 34 inc (hl) ; set script stage to be CLEAR_SCREEN_AND_HIDE_SPRITES_0DF8 0DF7: C9 ret ; ; ; ; ; CLEAR_SCREEN_AND_HIDE_SPRITES_0DF8: 0DF8: 21 40 45 ld hl,$4540 ; load HL with address of TEMP_COUNTER_4540 0DFB: 35 dec (hl) 0DFC: C0 ret nz ; wait until counter reaches zero 0DFD: 2C inc l ; bump HL to point to ATTRACT_MODE_SCRIPT_STAGE 0DFE: 34 inc (hl) ; increment ATTRACT_MODE_SCRIPT_STAGE 0DFF: 21 60 40 ld hl,$4060 ; load HL with address of OBJRAM_BACK_BUF_SPRITES 0E02: 3E 10 ld a,$10 0E04: 06 18 ld b,$18 0E06: D7 rst $10 ; remove all sprites from screen 0E07: CD 62 11 call $1162 ; call CLEAR_SCREEN_EXCEPT_SCORES_AND_CREDIT 0E0A: CD 83 11 call $1183 ; call CLEAR_ARRAYS_AND_SPRITES 0E0D: 21 E9 0A ld hl,$0AE9 ; load HL with address of COLOUR_ATTRIBUTE_TABLE_0AE9 0E10: C3 D9 0A jp $0AD9 ; jump to SET_COLOUR_ATTRIBUTES_FOR_ENTIRE_SCREEN ; ; Prepare for the demo of the player jet flying over the landscape. ; ; ; INIT_DEMO: 0E13: 21 41 45 ld hl,$4541 ; load HL with address of ATTRACT_MODE_SCRIPT_STAGE 0E16: 34 inc (hl) ; advance to next stage of script (DEMO @ $0E5C) ; reset landscape 0E17: CD C3 0F call $0FC3 ; call RESET_LANDSCAPE_EXTENTS 0E1A: CD A1 10 call $10A1 ; call DRAW_FLAT_STRIP_OF_LAND 0E1D: 3E 01 ld a,$01 0E1F: 32 19 40 ld ($4019),a ; set DRAW_LANDSCAPE_FLAG ; activate player jet for demo 0E22: 21 01 00 ld hl,$0001 0E25: 22 80 43 ld ($4380),hl ; set PLAYERS[0].IsActive to 1, and PLAYERS[0].IsExploding to 0 0E28: 22 A0 43 ld ($43A0),hl ; set PLAYERS[1].IsActive to 1, and PLAYERS[1].IsExploding to 0 0E2B: AF xor a 0E2C: 32 82 43 ld ($4382),a ; set PLAYERS[0].StageOfLife to 0 (see PLAYER_INIT @ $16FE) ; clear player related game state 0E2F: 21 00 41 ld hl,$4100 0E32: 06 40 ld b,$40 0E34: D7 rst $10 ; clear player related game state ; set level 1 to be the landscape for the demo mode 0E35: 21 D0 29 ld hl,$29D0 ; load HL with address of LANDSCAPE_LAYOUT_METADATA_TABLE table 0E38: 11 18 41 ld de,$4118 ; load DE with address of LANDSCAPE_LAYOUT_PTR 0E3B: 7E ld a,(hl) ; read LSB of landscape pointer 0E3C: 12 ld (de),a ; set LSB of LANDSCAPE_LAYOUT_PTR 0E3D: 23 inc hl 0E3E: 13 inc de 0E3F: 7E ld a,(hl) ; read MSB of landscape pointer 0E40: 12 ld (de),a ; set MSB of LANDSCAPE_LAYOUT_PTR 0E41: 23 inc hl 0E42: 7E ld a,(hl) 0E43: 32 1D 41 ld ($411D),a ; set LANDSCAPE_FLAGS ; display fuel remaining bar 0E46: 11 02 07 ld de,$0702 ; Command ID: 07 = HEAD_UP_DISPLAY_COMMAND, Param: 2 = DISPLAY_CURRENT_PLAYER_PROGRESS_BAR 0E49: FF rst $38 ; call QUEUE_COMMAND 0E4A: 3E 20 ld a,$20 0E4C: 32 15 41 ld ($4115),a ; set LANDSCAPE_SCROLL_CONTROL_COUNTER 0E4F: 21 05 41 ld hl,$4105 ; load HL with address of CURRENT_PLAYER_FUEL 0E52: 36 FF ld (hl),$FF ; give player a full tank 0E54: 2C inc l ; bump HL to point to CURRENT_PLAYER_FUEL_DRAIN_COUNTER 0E55: 36 05 ld (hl),$05 ; set fuel drain counter. 0E57: 11 07 06 ld de,$0607 ; Command ID: 06 = PRINT_TEXT, Param: 7 = FUEL 0E5A: FF rst $38 ; call QUEUE_COMMAND 0E5B: C9 ret ; ; Handle the rocket flying over the landscape in demo mode. ; ; DEMO: 0E5C: CD CC 13 call $13CC ; call ANIMATION_AND_MOVEMENT 0E5F: CD 35 1F call $1F35 ; call SCROLL_AND_SPRITES 0E62: CD 36 20 call $2036 ; call COLLISION_DETECTION 0E65: CD 63 25 call $2563 ; call SPAWN_ENEMIES 0E68: CD C2 27 call $27C2 ; call LANDSCAPE_CHANGE ; wait until the player jet's destroyed in the demo 0E6B: 21 80 43 ld hl,$4380 ; load HL with address of PLAYERS[0].IsActive flag 0E6E: 7E ld a,(hl) ; read flag 0E6F: 2C inc l ; bump HL to point to PLAYERS[0].IsExploding flag 0E70: B6 or (hl) ; OR with that flag 0E71: 0F rrca ; move result into carry 0E72: D8 ret c ; return if player is active or is dying ; when jet is destroyed, go back to title page 0E73: 21 41 45 ld hl,$4541 ; load HL with address of ATTRACT_MODE_SCRIPT_STAGE 0E76: 36 00 ld (hl),$00 ; set stage to 0 0E78: C9 ret ADVANCE_TO_NEXT_SCRIPT_IF_ZERO_FLAG_UNSET: 0E79: C8 ret z 0E7A: 21 05 40 ld hl,$4005 ; load HL with address of SCRIPT_NUMBER 0E7D: 34 inc (hl) ; advance to next script 0E7E: AF xor a 0E7F: 32 0A 40 ld ($400A),a ; reset SCRIPT_STAGE to start of script 0E82: C9 ret ; ; ; This script handles the case where credit is inserted and the player must press 1P START or 2P START ; ; CREDIT_INSERTED_SCRIPT: 0E83: 21 3B 0F ld hl,$0F3B ; address of CHECK_IF_1P_START_OR_2P_START_PRESSED 0E86: E5 push hl ; push address on the stack. This method will be called after each script below. 0E87: 3A 0A 40 ld a,($400A) ; read SCRIPT_STAGE 0E8A: EF rst $28 0E8B: 91 0E ; $0E91 (PUSH_START_INIT) E7 0E ; $0EE7 (DISPLAY_PUSH_START_BUTTON) 2D 0F ; $0F2D (DISPLAY_NUMBER_OF_PLAYERS_ALLOWED) PUSH_START_INIT: ; Protection code. If you're not interested in this, skip to $0EB9. 0E91: 53 ld d,e 0E92: 53 ld d,e 0E93: 4A ld c,d 0E94: 53 ld d,e 0E95: AF xor a 0E96: 53 ld d,e 0E97: 53 ld d,e 0E98: 26 02 ld h,$02 0E9A: 5A ld e,d 0E9B: 59 ld e,c ; referenced by $24D1 as part of protection algorithm 0E9C: 2E 79 ld l,$79 0E9E: 59 ld e,c 0E9F: 06 0A ld b,$0A 0EA1: 86 add a,(hl) 0EA2: 59 ld e,c 0EA3: 1E 05 ld e,$05 0EA5: 1E 05 ld e,$05 0EA7: 5A ld e,d 0EA8: 23 inc hl 0EA9: 5A ld e,d 0EAA: 5A ld e,d 0EAB: 10 F4 djnz $0EA1 0EAD: 1E 09 ld e,$09 0EAF: 5A ld e,d 0EB0: FE 1F cp $1F 0EB2: 4B ld c,e 0EB3: 4B ld c,e ; HL should be $0283 when we get here, and zero flag should be set. ; If the zero flag is not set, the game will reset. 0EB4: 4E ld c,(hl) 0EB5: C2 2D F1 jp nz,$F12D ; reset the game. 0EB8: 53 ld d,e ; End of protection code 0EB9: AF xor a 0EBA: 32 19 40 ld ($4019),a ; clear DRAW_LANDSCAPE_FLAG 0EBD: 3E 01 ld a,$01 0EBF: 32 04 68 ld ($6804),a ; enable stars 0EC2: 3D dec a ; set A to 0 0EC3: 32 03 68 ld ($6803),a ; set background to black 0EC6: 21 09 0B ld hl,$0B09 ; load HL with address of COLOUR_ATTRIBUTE_TABLE_0B09 0EC9: CD D9 0A call $0AD9 ; call SET_COLOUR_ATTRIBUTES_FOR_ENTIRE_SCREEN 0ECC: 21 60 40 ld hl,$4060 ; load HL with address of OBJRAM_BACK_BUF_SPRITES 0ECF: 06 40 ld b,$40 0ED1: AF xor a 0ED2: D7 rst $10 ; hides all sprites 0ED3: 32 B0 40 ld ($40B0),a ; reset unused IS_COLUMN_SCROLLING flag 0ED6: 32 06 40 ld ($4006),a ; reset IS_GAME_IN_PLAY flag d0ED9: 21 02 48 ld hl,$4802 ; character RAM address 0EDC: 22 0B 40 ld ($400B),hl ; set TEMP_CHAR_RAM_PTR 0EDF: 21 09 40 ld hl,$4009 ; load HL with address of TEMP_COUNTER_4009 0EE2: 36 10 ld (hl),$10 0EE4: 2C inc l ; bump HL to point to SCRIPT_STAGE 0EE5: 34 inc (hl) ; advance to next stage of script (DISPLAY_PUSH_START_BUTTON below) 0EE6: C9 ret ; ; Displays PUSH START BUTTON and BONUS JET FOR messages on screen. ; ; ; DISPLAY_PUSH_START_BUTTON: ; First lets clear the screen two rows of characters at a time. ; As the screen is rotated it looks like 2 columns are being cleared. 0EE7: 2A 0B 40 ld hl,($400B) ; read TEMP_CHAR_RAM_PTR 0EEA: 06 1D ld b,$1D 0EEC: 3E 10 ld a,$10 0EEE: D7 rst $10 ; plot 29 empty characters 0EEF: 11 03 00 ld de,$0003 ; 0EF2: 19 add hl,de ; bump HL to next row of characters 0EF3: 06 1D ld b,$1D 0EF5: D7 rst $10 ; plot another 29 empty characters 0EF6: 19 add hl,de 0EF7: 22 0B 40 ld ($400B),hl ; update TEMP_CHAR_RAM_PTR 0EFA: 21 09 40 ld hl,$4009 ; load HL with address of TEMP_COUNTER_4009 0EFD: 35 dec (hl) 0EFE: C0 ret nz ; now display PUSH START BUTTON and BONUS JET FOR _____ PTS 0EFF: 2C inc l ; bump HL to point to SCRIPT_STAGE 0F00: 34 inc (hl) ; advance to next stage of script (DISPLAY_NUMBER_OF_PLAYERS_ALLOWED @ $0F2D) 0F01: AF xor a 0F02: 32 06 68 ld ($6806),a ; disable screen vertical flip 0F05: 32 07 68 ld ($6807),a ; disable screen horizontal flip 0F08: 32 0D 40 ld ($400D),a ; set CURRENT_PLAYER 0F0B: 11 01 07 ld de,$0701 ; Command ID: 7 = HEAD_UP_DISPLAY_COMMAND, Param:1 = DISPLAY_CREDITS 0F0E: FF rst $38 ; call QUEUE_COMMAND 0F0F: 11 01 06 ld de,$0601 ; Command ID: 6 = PRINT_TEXT, Param:1 = PUSH START BUTTON 0F12: FF rst $38 ; call QUEUE_COMMAND 0F13: 1E 16 ld e,$16 ; Command ID: 6 = PRINT_TEXT, Param:$16 = BONUS JET FOR 0F15: FF rst $38 ; call QUEUE_COMMAND 0F16: 1C inc e ; Command ID: 6 = PRINT_TEXT, Param:$17 = 000 PTS 0F17: FF rst $38 ; call QUEUE_COMMAND ; Poke 2 digit BCD value into BONUS JET FOR nn000 PTS message on screen 0F18: 3A 17 40 ld a,($4017) ; read BONUS_JET_FOR BCD value 0F1B: 47 ld b,a ; preserve A in B 0F1C: E6 0F and $0F ; mask in lower nibble (2nd digit) 0F1E: 32 78 49 ld ($4978),a ; write digit to character RAM 0F21: 78 ld a,b ; restore A from B 0F22: E6 F0 and $F0 ; mask in upper nibble (1st digit) 0F24: C8 ret z ; if it's zero, don't draw it 0F25: 0F rrca ; move upper nibble... 0F26: 0F rrca 0F27: 0F rrca 0F28: 0F rrca ; ... to lower nibble. 0F29: 32 98 49 ld ($4998),a ; write digit to character RAM 0F2C: C9 ret ; ; Read number of credits. ; If credits available is zero, exit. ; If only 1 credit, display ONE PLAYER ONLY ; Else display ONE OR TWO PLAYERS ; DISPLAY_NUMBER_OF_PLAYERS_ALLOWED: 0F2D: 3A 02 40 ld a,($4002) ; read NUM_CREDITS 0F30: A7 and a ; test if we have any credits 0F31: C8 ret z ; return if no credits 0F32: 3D dec a ; decrement credit count. If we only have 1 credit, zero flag will now be set. 0F33: 11 18 06 ld de,$0618 ; Command ID: 6 = PRINT_TEXT, Param:$18 = ONE PLAYER ONLY 0F36: 28 01 jr z,$0F39 ; if zero flag set, we only have 1 credit, so can only have one player game, goto $0F39 0F38: 1C inc e ; bump param to display ONE OR TWO PLAYERS 0F39: FF rst $38 ; call QUEUE_COMMAND 0F3A: C9 ret ; ; Check if the player has pushed the 1 Player or 2 Player start buttons. ; ; CHECK_IF_1P_START_OR_2P_START_PRESSED: 0F3B: 3A 11 40 ld a,($4011) ; read PORT_STATE_8101 0F3E: CB 7F bit 7,a ; test if 1P START button is pressed 0F40: C2 7B 0F jp nz,$0F7B ; if button pressed, goto $0F7B 0F43: CB 77 bit 6,a ; test if 2P START button is pressed 0F45: C8 ret z ; return if 2P START button is not pressed ; Called when 2P START button is pressed 0F46: 3A 02 40 ld a,($4002) ; read NUM_CREDITS 0F49: FE 02 cp $02 ; have we at least 2 credits for a 2 player game? 0F4B: D8 ret c ; return if <2 0F4C: D6 02 sub $02 ; deduct 2 credits from available credit 0F4E: 32 02 40 ld ($4002),a ; update NUM_CREDITS 0F51: 21 00 01 ld hl,$0100 ; will set CURRENT_PLAYER to 0 and IS_TWO_PLAYER_GAME flag to 1 START_GAME: 0F54: 22 0D 40 ld ($400D),hl ; write to CURRENT_PLAYER and IS_TWO_PLAYER_GAME flag 0F57: AF xor a 0F58: 32 0A 40 ld ($400A),a ; set SCRIPT_STAGE to 0 0F5B: 3E 03 ld a,$03 0F5D: 32 05 40 ld ($4005),a ; set SCRIPT_NUMBER to 3 0F60: 3E 01 ld a,$01 0F62: 32 06 40 ld ($4006),a ; set IS_GAME_IN_PLAY flag 0F65: 11 04 06 ld de,$0604 ; Command ID: 6 = PRINT_TEXT, Param:4 = HIGH SCORE 0F68: FF rst $38 ; call QUEUE_COMMAND 0F69: CD 91 0F call $0F91 0F6C: CD 13 29 call $2913 ; call QUEUE_GAME_START_MUSIC 0F6F: 11 00 04 ld de,$0400 ; Command ID: 4 = ZERO_SCORE_COMMAND, Param:0 = Zero player 1's score 0F72: FF rst $38 ; call QUEUE_COMMAND 0F73: 3A 0E 40 ld a,($400E) ; read IS_TWO_PLAYER_GAME flag 0F76: 0F rrca ; move flag into carry 0F77: D0 ret nc ; return if its not a 2 player game 0F78: 1C inc e ; Command ID: 4 = ZERO_SCORE_COMMAND, Param:1 = Zero player 2's score 0F79: FF rst $38 ; call QUEUE_COMMAND 0F7A: C9 ret ; Called when 1P START button is pressed 0F7B: 3A 02 40 ld a,($4002) ; read NUM_CREDITS 0F7E: A7 and a ; test if any credits have been inserted 0F7F: 28 0A jr z,$0F8B ; if no 0F81: 3D dec a 0F82: 32 02 40 ld ($4002),a ; update NUM_CREDITS 0F85: 21 00 00 ld hl,$0000 ; will set CURRENT_PLAYER to 0 and IS_TWO_PLAYER_GAME flag to 0 0F88: C3 54 0F jp $0F54 ; jump to START_GAME 0F8B: 3E 01 ld a,$01 0F8D: 32 05 40 ld ($4005),a ; set SCRIPT_NUMBER to 1 0F90: C9 ret ; ; ; ; ; 0F91: AF xor a ; clear player state and landscape state from 4100-41ff 0F92: 21 00 41 ld hl,$4100 0F95: 47 ld b,a 0F96: D7 rst $10 ; ; clear landscape related flags 0F97: 21 30 42 ld hl,$4230 0F9A: 06 10 ld b,$10 0F9C: D7 rst $10 ; clear landscape related flags ; hide all sprites 0F9D: 21 60 40 ld hl,$4060 ; load HL with address of OBJRAM_BACK_BUF_SPRITES 0FA0: 06 40 ld b,$40 0FA2: D7 rst $10 ; hide all sprites 0FA3: 21 60 42 ld hl,$4260 ; load HL with address of CHAR_BASED_GROUND_OBJECTS array 0FA6: 01 02 B0 ld bc,$B002 0FA9: DF rst $18 ; fill memory 0FAA: 32 19 40 ld ($4019),a ; clear DRAW_LANDSCAPE_FLAG ; zero any state held for both players 0FAD: 21 00 41 ld hl,$4100 ; load HL with address of CURRENT_PLAYER_STATE 0FB0: 11 01 41 ld de,$4101 0FB3: 01 C0 00 ld bc,$00C0 0FB6: 36 00 ld (hl),$00 0FB8: ED B0 ldir ; clear all player state 0FBA: 3A 07 40 ld a,($4007) ; read DEFAULT_PLAYER_LIVES 0FBD: 32 48 41 ld ($4148),a ; set PLAYER_ONE_LIVES 0FC0: 32 88 41 ld ($4188),a ; set PLAYER_TWO_LIVES ; ; Reset the LANDSCAPE_EXTENTS to default values. ; RESET_LANDSCAPE_EXTENTS: 0FC3: 21 C0 41 ld hl,$41C0 ; load HL with address of LANDSCAPE_EXTENTS 0FC6: 11 28 C9 ld de,$C928 ; D = #$C9 (ground), E = $28 (ceiling) 0FC9: 06 20 ld b,$20 ; 32 rows to do 0FCB: 72 ld (hl),d ; set LANDSCAPE_EXTENT.GroundX 0FCC: 2C inc l 0FCD: 73 ld (hl),e ; set LANDSCAPE_EXTENT.CeilingX 0FCE: 2C inc l 0FCF: 10 FA djnz $0FCB ; repeat until all rows done 0FD1: C9 ret ; ; ; This script handles PLAYER ONE's game. ; ; PLAYER_ONE_GAME_SCRIPT: 0FD2: 3A 0A 40 ld a,($400A) ; read SCRIPT_STAGE 0FD5: EF rst $28 0FD6: 02 10 18 1A ; $1A18: CLEAR_SCREEN_1A18 56 03 ; $0356: DISPLAY_HUD_FOR_PLAYER_ONE 7E 10 ; $107E: REMOVE_SPRITES_AND_DRAW_FLAT_LANDSCAPE BA 10 ; $10BA: GAME_INIT F4 22 ; $22F4: PLAY_GAME 1D 11 ; $111D: PLAYER_ONE_KILLED AB 11 ; $11AB: CHECK_IF_SHOULD_SWITCH_TO_PLAYER_TWO 7E 12 ; $127E: MISSION_COMPLETED_SCRIPT F0 12 ; $12F0: HIGH_SCORE_SCRIPT ; ; ; This script handles PLAYER TWO's game. ; ; PLAYER_TWO_GAME_SCRIPT: 0FEA: 3A 0A 40 ld a,($400A) 0FED: EF rst $28 0FEE: 02 10 18 1A ; $1A18: CLEAR_SCREEN_1A18 39 10 ; $1039: DISPLAY_HUD_FOR_PLAYER_TWO 7E 10 ; $107E: REMOVE_SPRITES_AND_DRAW_FLAT_LANDSCAPE BA 10 ; $10BA: GAME_INIT F4 22 ; $22F4: PLAY_GAME 09 12 ; $1209: PLAYER_TWO_KILLED 3F 12 ; $123F: CHECK_IF_SHOULD_SWITCH_TO_PLAYER_ONE 7E 12 ; $127E: MISSION_COMPLETED_SCRIPT F0 12 ; $12F0: HIGH_SCORE_SCRIPT ; ; ; ; ; I don't know what this code is for but I'm sure its redundant. I suspect it was a part of some ; protection scheme that was removed before production. Skip to $1017. ; Begin redundant code 1002: 21 30 41 ld hl,$4130 ; load HL with address of NO_APPARENT_PURPOSE_4130 1005: 11 70 41 ld de,$4170 ; load DE with address of NO_APPARENT_PURPOSE_4170 1008: 01 08 00 ld bc,$0008 100B: ED B0 ldir ; copy 8 bytes 100D: 7E ld a,(hl) ; HL = $4138 100E: B1 or c 100F: 12 ld (de),a 1010: 2C inc l 1011: 14 inc d 1012: 7E ld a,(hl) 1013: 4F ld c,a 1014: 1A ld a,(de) 1015: B0 or b 1016: 12 ld (de),a ; write 0 to $4278 - for no reason whatsoever. ; End redundant code 1017: AF xor a 1018: 32 19 40 ld ($4019),a ; clear DRAW_LANDSCAPE_FLAG 101B: 21 60 40 ld hl,$4060 ; load HL with address of OBJRAM_BACK_BUF_SPRITES 101E: 06 40 ld b,$40 ; sizeof(OBJRAM_BACK_BUF_SPRITES) + sizeof(OBJRAM_BACK_BUF_BULLETS) 1020: D7 rst $10 ; fill memory 1021: 21 0A 40 ld hl,$400A ; load HL with address of SCRIPT_STAGE 1024: 34 inc (hl) ; advance to next stage of script (CLEAR_SCREEN_1A18 @ $1A18) 1025: 2D dec l 1026: 36 20 ld (hl),$20 1028: 21 00 48 ld hl,$4800 ; load HL with start of character RAM 102B: 22 0B 40 ld ($400B),hl ; write to TEMP_CHAR_RAM_PTR 102E: CD 83 11 call $1183 ; call CLEAR_ARRAYS_AND_SPRITES 1031: 3E 01 ld a,$01 1033: 32 06 40 ld ($4006),a ; set IS_GAME_IN_PLAY flag. 1036: C3 C3 0F jp $0FC3 ; jump to RESET_LANDSCAPE_EXTENTS ; ; Shows head up display, and PLAYER TWO message in middle of screen, indicating its player two's turn. ; DISPLAY_HUD_FOR_PLAYER_TWO: 1039: AF xor a 103A: 32 5F 42 ld ($425F),a ; reset TIMING_VARIABLE 103D: 3A 0F 40 ld a,($400F) ; read IS_COCKTAIL flag 1040: 0F rrca ; move flag into carry 1041: 30 08 jr nc,$104B ; if not cocktail, goto $104B ; flip the screen for player 2 if its a cocktail cabinet 1043: 3E 01 ld a,$01 1045: 32 06 68 ld ($6806),a ; set screen vertical flip 1048: 32 07 68 ld ($6807),a ; set screen horizontal flip ; 104B: 3E 01 ld a,$01 104D: 32 0D 40 ld ($400D),a ; set CURRENT_PLAYER to be 1 (PLAYER TWO) 1050: 21 0A 40 ld hl,$400A ; load HL with address of SCRIPT_STAGE 1053: 34 inc (hl) ; advance to next stage of script (REMOVE_SPRITES_AND_DRAW_FLAT_LANDSCAPE @ $107E) 1054: 2D dec l ; bump HL to point to TEMP_COUNTER_4009 1055: 36 96 ld (hl),$96 ; set counter value ; Display head up display 1057: 11 00 05 ld de,$0500 ; Command ID: 5 = DISPLAY_SCORE_COMMAND, Param:0 = Display player 1's score 105A: FF rst $38 ; call QUEUE_COMMAND 105B: 1C inc e ; Command ID: 5 = DISPLAY_SCORE_COMMAND, Param:1 = Display player 2's score 105C: FF rst $38 ; call QUEUE_COMMAND 105D: 1C inc e ; Command ID: 5 = DISPLAY_SCORE_COMMAND, Param:2 = Display high score 105E: FF rst $38 ; call QUEUE_COMMAND 105F: 11 03 06 ld de,$0603 ; Command ID: 6 = PRINT_TEXT, Param:3 = PLAYER TWO 1062: FF rst $38 ; call QUEUE_COMMAND 1063: 1C inc e ; Command ID: 6 = PRINT_TEXT, Param:4 = HIGH SCORE 1064: FF rst $38 ; call QUEUE_COMMAND 1065: 11 03 07 ld de,$0703 ; Command ID: 7 = HEAD_UP_DISPLAY_COMMAND, Param:3 = DISPLAY_CURRENT_PLAYER_LIVES 1068: FF rst $38 ; call QUEUE_COMMAND 1069: 1E 00 ld e,$00 ; Command ID: 7 = HEAD_UP_DISPLAY_COMMAND, Param:0 = DISPLAY_MISSIONS_COMPLETED_FLAGS 106B: FF rst $38 ; call QUEUE_COMMAND ; Copy player two state to current player state. Now current player = player two. 106C: 21 80 41 ld hl,$4180 ; address of PLAYER_TWO_STATE 106F: 11 00 41 ld de,$4100 ; address of CURRENT_PLAYER_STATE 1072: 01 40 00 ld bc,$0040 1075: ED B0 ldir ; copy state 1077: 2A 1D 41 ld hl,($411D) ; read LANDSCAPE_FLAGS 107A: 22 18 41 ld ($4118),hl ; set LANDSCAPE_LAYOUT_PTR 107D: C9 ret ; ; The player's turn is almost ready to begin. ; ; We need to remove any existing sprites from the screen as they are from the previous turn, and then draw the flat landscape that you ; see at the start of every level. ; REMOVE_SPRITES_AND_DRAW_FLAT_LANDSCAPE: 107E: 21 09 40 ld hl,$4009 ; load HL with address of TEMP_COUNTER_4009 1081: 35 dec (hl) 1082: C0 ret nz 1083: 36 20 ld (hl),$20 ; set TEMP_COUNTER_4009 1085: 2C inc l ; bump HL to point to SCRIPT_STAGE 1086: 34 inc (hl) ; advance to next stage of script (GAME_INIT @ $10BA) 1087: 11 82 06 ld de,$0682 ; Command ID: 6 = PRINT_TEXT, Param:$82 = erase text PLAYER ONE 108A: FF rst $38 ; call QUEUE_COMMAND 108B: 1E 07 ld e,$07 ; Command ID: 6 = PRINT_TEXT, Param:$7 = FUEL 108D: FF rst $38 ; call QUEUE_COMMAND 108E: CD 62 11 call $1162 ; call CLEAR_SCREEN_EXCEPT_SCORES_AND_CREDIT 1091: CD 83 11 call $1183 ; call CLEAR_ARRAYS_AND_SPRITES 1094: CD C3 0F call $0FC3 ; call RESET_LANDSCAPE_EXTENTS 1097: AF xor a 1098: 32 19 40 ld ($4019),a ; clear DRAW_LANDSCAPE_FLAG 109B: 21 60 40 ld hl,$4060 ; load HL with address of OBJRAM_BACK_BUF_SPRITES 109E: 06 40 ld b,$40 10A0: D7 rst $10 ; fill memory ; Draw flat strip of land you see when starting a level. DRAW_FLAT_STRIP_OF_LAND: 10A1: 21 19 48 ld hl,$4819 ; character RAM address to start drawing from 10A4: 11 1C 00 ld de,$001C ; offset to add to HL to get to next character row after plotting characters 10A7: 06 1E ld b,$1E ; number of rows to fill 10A9: 3E 36 ld a,$36 ; ordinal for rugged ground character 10AB: 0E 39 ld c,$39 ; ordinal for solid block character 10AD: 77 ld (hl),a ; plot rugged part of ground 10AE: 2C inc l 10AF: 71 ld (hl),c ; plot solid block 10B0: 2C inc l 10B1: 71 ld (hl),c ; plot solid block 10B2: 2C inc l 10B3: 71 ld (hl),c ; plot solid block 10B4: 2C inc l 10B5: 71 ld (hl),c ; plot solid block 10B6: 19 add hl,de 10B7: 10 F4 djnz $10AD 10B9: C9 ret ; ; Initialise player jet, select landscape, draw HUD ready for player to start turn ; ; ; GAME_INIT: 10BA: 21 09 40 ld hl,$4009 ; load HL with address of TEMP_COUNTER_4009 10BD: 35 dec (hl) 10BE: C0 ret nz ; wait until counter reaches zero 10BF: 36 0A ld (hl),$0A ; set TEMP_COUNTER_4009 to 10 10C1: 2C inc l ; bump HL to point to SCRIPT_STAGE 10C2: 34 inc (hl) ; advance to next stage of script (PLAY_GAME @ $22F4) 10C3: 3E 01 ld a,$01 10C5: 32 19 40 ld ($4019),a ; set DRAW_LANDSCAPE_FLAG 10C8: 21 01 00 ld hl,$0001 10CB: 22 80 43 ld ($4380),hl ; set PLAYERS[0].IsActive and reset PLAYERS[0].IsExploding flags 10CE: 22 A0 43 ld ($43A0),hl ; set PLAYERS[1].IsActive and reset PLAYERS[1].IsExploding flags 10D1: AF xor a 10D2: 32 82 43 ld ($4382),a ; reset PLAYERS[0].StageOfLife (see PLAYER_INIT @ $16FE) ; player starting level, update lives remaining 10D5: 21 08 41 ld hl,$4108 ; load HL with address of CURRENT_PLAYER_LIVES 10D8: 35 dec (hl) ; reduce number of lives 10D9: 11 03 07 ld de,$0703 ; Command ID: 7 = HEAD_UP_DISPLAY_COMMAND, Param:3 = DISPLAY_CURRENT_PLAYER_LIVES 10DC: FF rst $38 ; call QUEUE_COMMAND 10DD: 21 10 41 ld hl,$4110 10E0: 11 11 41 ld de,$4111 10E3: 01 0D 00 ld bc,$000D 10E6: 36 00 ld (hl),$00 10E8: ED B0 ldir ; select landscape depending on current player's level. 10EA: 21 D0 29 ld hl,$29D0 ; load HL with address of LANDSCAPE_LAYOUT_METADATA_TABLE table 10ED: 3A 1E 41 ld a,($411E) ; read CURRENT_PLAYERS_LEVEL 10F0: 47 ld b,a ; 10F1: 87 add a,a ; Multiply CURRENT_PLAYERS_LEVEL.. 10F2: 80 add a,b ; .. by 3 and store result in A. 10F3: 5F ld e,a 10F4: 16 00 ld d,$00 ; Extend A into DE. Now DE is an offset into the LANDSCAPE_LAYOUT_METADATA_TABLE 10F6: 19 add hl,de ; bump HL to point to layout metadata. 10F7: 7E ld a,(hl) 10F8: 32 18 41 ld ($4118),a ; set LANDSCAPE_LAYOUT_PTR_LO 10FB: 23 inc hl 10FC: 7E ld a,(hl) 10FD: 32 19 41 ld ($4119),a ; set LANDSCAPE_LAYOUT_PTR_HI 1100: 23 inc hl 1101: 7E ld a,(hl) 1102: 32 1D 41 ld ($411D),a ; set LANDSCAPE_FLAGS ; Update progress bar 1105: 11 02 07 ld de,$0702 ; Command ID: 7 = HEAD_UP_DISPLAY_COMMAND, Param:2 = DISPLAY_CURRENT_PLAYER_PROGRESS_BAR 1108: FF rst $38 ; call QUEUE_COMMAND 1109: 3E 08 ld a,$08 110B: 32 15 41 ld ($4115),a ; set LANDSCAPE_SCROLL_CONTROL_COUNTER 110E: 3E FF ld a,$FF 1110: 32 05 41 ld ($4105),a ; set CURRENT_PLAYER_FUEL to max (full tank) 1113: 3E 05 ld a,$05 1115: 32 06 41 ld ($4106),a ; set CURRENT_PLAYER_FUEL_DRAIN_COUNTER rate 1118: 11 00 07 ld de,$0700 ; Command ID: 7 = HEAD_UP_DISPLAY_COMMAND, Param:0 = DISPLAY_MISSIONS_COMPLETED_FLAGS 111B: FF rst $38 ; call QUEUE_COMMAND 111C: C9 ret ; See also $1209 (PLAYER_TWO_KILLED) PLAYER_ONE_KILLED: ; how many lives does player one have left? 111D: 3A 08 41 ld a,($4108) ; read CURRENT_PLAYER_LIVES 1120: A7 and a ; test if zero (game over) 1121: 20 28 jr nz,$114B ; if not game over, goto PLAYER_ONE_NOT_GAME_OVER ; Player one's out of lives, so display GAME OVER PLAYER ONE 1123: CD 62 11 call $1162 ; call CLEAR_SCREEN_EXCEPT_SCORES_AND_CREDIT 1126: CD 83 11 call $1183 ; call CLEAR_ARRAYS_AND_SPRITES 1129: 11 00 06 ld de,$0600 ; Command ID: 6 = PRINT_TEXT, Param:0 = GAME OVER 112C: FF rst $38 ; call QUEUE_COMMAND 112D: 1E 02 ld e,$02 ; Command ID: 6 = PRINT_TEXT, Param:2 = PLAYER ONE 112F: FF rst $38 ; call QUEUE_COMMAND 1130: 3E 01 ld a,$01 1132: 32 04 68 ld ($6804),a ; enable stars 1135: 3D dec a 1136: 32 03 68 ld ($6803),a ; set background to black ; Now let player one see if they have a high score 1139: 21 C0 45 ld hl,$45C0 ; load HL with address of TEMP_COUNTER_45C0 113C: 36 96 ld (hl),$96 113E: 2C inc l ; bump HL to point to HIGH_SCORE_SCRIPT_STAGE 113F: 36 00 ld (hl),$00 ; set HIGH_SCORE_SCRIPT_STAGE to 0 (start) 1141: 3E 09 ld a,$09 1143: 32 0A 40 ld ($400A),a ; set SCRIPT_STAGE to 9 (see HIGH_SCORE_SCRIPT @ $12F0) 1146: AF xor a 1147: 32 19 40 ld ($4019),a ; clear DRAW_LANDSCAPE_FLAG 114A: C9 ret ; Player one has some lives left. Is it a two player game? If not, return player one to start of level they died in. ; If it is a two player game, jump to a routine that checks if its player two's turn (they might be out of lives so can't continue.) PLAYER_ONE_NOT_GAME_OVER: ; First check if this is a two player game 114B: 3A 0E 40 ld a,($400E) ; read IS_TWO_PLAYER_GAME flag 114E: 0F rrca ; move flag into carry 114F: 38 09 jr c,$115A ; if a two player game goto $115A ; its a one player game. 1151: 21 0A 40 ld hl,$400A ; load HL with address of SCRIPT_STAGE 1154: 36 03 ld (hl),$03 ; set SCRIPT_STAGE to 3 (see REMOVE_SPRITES_AND_DRAW_FLAT_LANDSCAPE @ $107E) 1156: 2D dec l ; bump HL to point to TEMP_COUNTER_4009 1157: 36 32 ld (hl),$32 ; set TEMP_COUNTER_4009 1159: C9 ret ; its a two player game. 115A: 21 0A 40 ld hl,$400A ; load HL with address of SCRIPT_STAGE 115D: 34 inc (hl) ; advance to next stage of script (CHECK_IF_SHOULD_SWITCH_TO_PLAYER_TWO @ $11BA) 115E: 2D dec l ; bump HL to point to TEMP_COUNTER_4009 115F: 36 32 ld (hl),$32 ; set TEMP_COUNTER_4009 1161: C9 ret ; ; ; Clear all text, except scores and available credits, from the screen. ; ; CLEAR_SCREEN_EXCEPT_SCORES_AND_CREDIT: 1162: 21 03 48 ld hl,$4803 ; load HL with address of 4th character on top row 1165: 11 05 00 ld de,$0005 ; first clear screen 1168: 0E 20 ld c,$20 ; 32 rows to do 116A: 3E 10 ld a,$10 ; ordinal of empty space character 116C: 06 1B ld b,$1B ; 27 characters to erase per row 116E: 77 ld (hl),a ; plot empty space character 116F: 23 inc hl ; bump HL to point to next character on same row 1170: 10 FC djnz $116E ; repeat until all characters erased 1172: 19 add hl,de ; bump HL to point to first character on *next* row to erase 1173: 0D dec c ; decrement counter of rows left to do 1174: 20 F6 jr nz,$116C ; if nonzero, there's more rows to be erased, goto $116C ; now set attributes and scroll offsets 1176: 21 2A 40 ld hl,$402A ; point to scroll offset for 4th character in OBJRAM_BACK_BUF 1179: 06 19 ld b,$19 ; do 25 columns 117B: AF xor a 117C: 77 ld (hl),a ; reset scroll offset 117D: 2C inc l 117E: 77 ld (hl),a ; reset colour attribute to black 117F: 2C inc l 1180: 10 FA djnz $117C 1182: C9 ret ; Clears (fills with 0) the following arrays: ; GROUND_OBJECTS ; PLAYERS ; PLAYER_BOMBS ; INFLIGHT_ENEMIES ; PLAYER_BULLETS ; OBJRAM_BACK_BUF_SPRITES (will hide all sprites) ; OBJRAM_BACK_BUF_BULLETS (will hide all bullets) CLEAR_ARRAYS_AND_SPRITES: 1183: 21 80 42 ld hl,$4280 ; load HL with address of GROUND_OBJECTS 1186: 11 81 42 ld de,$4281 1189: 01 A0 02 ld bc,$02A0 118C: 36 00 ld (hl),$00 ; clear GROUND_OBJECTS array 118E: ED B0 ldir 1190: 21 60 42 ld hl,$4260 ; load HL with address of CHAR_BASED_GROUND_OBJECTS array 1193: 11 61 42 ld de,$4261 1196: 01 1F 00 ld bc,$001F ; sizeof(CHAR_BASED_GROUND_OBJECTS) -1 1199: 36 00 ld (hl),$00 119B: ED B0 ldir ; clear CHAR_BASED_GROUND_OBJECTS array 119D: 21 60 40 ld hl,$4060 ; load HL with address of OBJRAM_BACK_BUF_SPRITES 11A0: 11 61 40 ld de,$4061 11A3: 01 3F 00 ld bc,$003F ; sizeof(OBJRAM_BACK_BUF_SPRITES) + sizeof(OBJRAM_BACK_BUF_BULLETS) -1 11A6: 36 00 ld (hl),$00 11A8: ED B0 ldir ; clear OBJRAM_BACK_BUF_SPRITES & OBJRAM_BACK_BUF_BULLETS 11AA: C9 ret CHECK_IF_SHOULD_SWITCH_TO_PLAYER_TWO: ; Skip to $11BA. I think this is dead code. ; I've put a breakpoint on reads from $41B0, $41B1 and $41B2 and have found this is the only code that references these addresses. ; I've NOPed out 11AB - 11B9 and no adverse effects occurred during the game. ; My conclusion is that this is dead protection code. At best its wasting CPU cycles for some timing reason I can't fathom. 11AB: 21 B0 41 ld hl,$41B0 11AE: 7E ld a,(hl) 11AF: F6 03 or $03 11B1: 77 ld (hl),a 11B2: 2C inc l 11B3: F6 09 or $09 11B5: 77 ld (hl),a 11B6: 2C inc l 11B7: F6 0C or $0C 11B9: 77 ld (hl),a ; 11BA: 21 09 40 ld hl,$4009 ; load HL with address of TEMP_COUNTER_4009 11BD: 35 dec (hl) 11BE: C0 ret nz ; wait until the counter times out ; read number of lives player one has left. 11BF: 3A 08 41 ld a,($4108) ; read CURRENT_PLAYER_LIVES 11C2: A7 and a ; test if zero 11C3: 20 10 jr nz,$11D5 ; if player one has some lives left, goto $11D5 ; player one has no lives left. Check if its a two player game. 11C5: 3A 0E 40 ld a,($400E) ; read IS_TWO_PLAYER_GAME flag. 11C8: 0F rrca ; move flag into carry 11C9: 38 19 jr c,$11E4 ; if its a two player game, goto $11E4 ; Its a one player game and the player has no lives left: go back to the HOW FAR CAN YOU INVADE OUR SCRAMBLE SYSTEM intro. 11CB: 3E 01 ld a,$01 11CD: 32 05 40 ld ($4005),a ; set SCRIPT_NUMBER to 1 11D0: AF xor a 11D1: 32 41 45 ld ($4541),a ; set ATTRACT_MODE_SCRIPT_STAGE to 0 ($0BC5) 11D4: C9 ret ; player one has some lives left, how about player two? 11D5: 3A 88 41 ld a,($4188) ; read PLAYER_TWO_LIVES 11D8: A7 and a ; test if zero 11D9: 20 19 jr nz,$11F4 ; if player two has some lives left, goto $11F4 ; player one has some lives left, but player two doesn't (or its not a two player game) so let player one continue. 11DB: 21 0A 40 ld hl,$400A ; load HL with address of SCRIPT_STAGE 11DE: 36 03 ld (hl),$03 ; set SCRIPT_STAGE to be REMOVE_SPRITES_AND_DRAW_FLAT_LANDSCAPE ($107E) 11E0: 2D dec l ; bump HL to point to TEMP_COUNTER_4009 11E1: 36 01 ld (hl),$01 11E3: C9 ret ; player one has no lives left and its a two player game. So do we switch to PLAYER TWO's game? 11E4: 3A 88 41 ld a,($4188) ; read PLAYER_TWO_LIVES 11E7: A7 and a ; test if zero 11E8: 20 0A jr nz,$11F4 ; player two has some lives left, switch to his game ; player one has no lives left and neither does player two. Go back to the HOW FAR CAN YOU INVADE OUR SCRAMBLE SYSTEM intro. 11EA: 3E 01 ld a,$01 11EC: 32 05 40 ld ($4005),a ; set SCRIPT_NUMBER to 1 11EF: AF xor a 11F0: 32 41 45 ld ($4541),a ; set ATTRACT_MODE_SCRIPT_STAGE to 0 ($0BC5) 11F3: C9 ret ; We're about to switch over to player two, but we need to preserve player one's progress. ; Copy current game state over to player one's state 11F4: 21 00 41 ld hl,$4100 ; CURRENT_PLAYER_STATE 11F7: 11 40 41 ld de,$4140 ; PLAYER_ONE_STATE 11FA: 01 40 00 ld bc,$0040 11FD: ED B0 ldir ; switch to player TWO. 11FF: AF xor a 1200: 32 0A 40 ld ($400A),a ; set SCRIPT_STAGE to 0 1203: 3E 04 ld a,$04 1205: 32 05 40 ld ($4005),a ; switch over to PLAYER_TWO_GAME_SCRIPT which handles player two 1208: C9 ret PLAYER_TWO_KILLED: ; how many lives does player two have remaining? 1209: 3A 08 41 ld a,($4108) ; read CURRENT_PLAYER_LIVES 120C: A7 and a ; test if zero (game over) 120D: 20 28 jr nz,$1237 ; if not game over goto PLAYER_TWO_NOT_GAME_OVER ; Player two's out of lives, so display GAME OVER PLAYER TWO 120F: CD 62 11 call $1162 ; call CLEAR_SCREEN_EXCEPT_SCORES_AND_CREDIT 1212: CD 83 11 call $1183 ; call CLEAR_ARRAYS_AND_SPRITES 1215: 11 00 06 ld de,$0600 ; Command ID: 6 = PRINT_TEXT, Param:0 = GAME OVER 1218: FF rst $38 ; call QUEUE_COMMAND 1219: 1E 03 ld e,$03 ; Command ID: 6 = PRINT_TEXT, Param:3 = PLAYER TWO 121B: FF rst $38 ; call QUEUE_COMMAND 121C: 3E 01 ld a,$01 121E: 32 04 68 ld ($6804),a ; enable stars 1221: 3D dec a 1222: 32 03 68 ld ($6803),a ; set background to black ; Now let player two see if they have a high score 1225: 21 C0 45 ld hl,$45C0 ; load HL with address of TEMP_COUNTER_45C0 1228: 36 96 ld (hl),$96 122A: 2C inc l ; bump HL to point to HIGH_SCORE_SCRIPT_STAGE 122B: 36 00 ld (hl),$00 ; set stage to 0 122D: 3E 09 ld a,$09 122F: 32 0A 40 ld ($400A),a ; set SCRIPT_STAGE to 9 (HIGH_SCORE_SCRIPT @ $12F0) 1232: AF xor a 1233: 32 19 40 ld ($4019),a ; clear DRAW_LANDSCAPE_FLAG 1236: C9 ret ; Player two has some lives left ; Switch to player one if possible PLAYER_TWO_NOT_GAME_OVER: 1237: 21 0A 40 ld hl,$400A ; load HL with address of SCRIPT_STAGE 123A: 34 inc (hl) ; advance to next stage of script (CHECK_IF_SHOULD_SWITCH_TO_PLAYER_ONE @ $123F) 123B: 2D dec l ; bump HL to point to TEMP_COUNTER_4009 123C: 36 64 ld (hl),$64 ; set counter value 123E: C9 ret ; ; This code is almost identical to that in CHECK_IF_SHOULD_SWITCH_TO_PLAYER_TWO (see $11BA) ; ; CHECK_IF_SHOULD_SWITCH_TO_PLAYER_ONE: 123F: 21 09 40 ld hl,$4009 ; load HL with address of TEMP_COUNTER_4009 1242: 35 dec (hl) 1243: C0 ret nz ; wait until the counter times out ; read number of lives player two has left. 1244: 3A 08 41 ld a,($4108) ; read CURRENT_PLAYER_LIVES 1247: A7 and a ; test if zero 1248: 20 10 jr nz,$125A ; if not zero, goto $125A ; player two has no lives left. Check if player one has any lives left. 124A: 3A 48 41 ld a,($4148) ; read PLAYER_ONE_LIVES 124D: A7 and a ; test if zero 124E: 20 0A jr nz,$125A ; if player one has lives left, goto $125A ; Its a two player game and neither player has any lives left: go back to the HOW FAR CAN YOU INVADE OUR SCRAMBLE SYSTEM intro. 1250: 3E 01 ld a,$01 1252: 32 05 40 ld ($4005),a ; set SCRIPT_NUMBER to 1 1255: AF xor a 1256: 32 41 45 ld ($4541),a ; clear ATTRACT_MODE_SCRIPT_STAGE 1259: C9 ret ; player two has some lives left, how about player one? 125A: 3A 48 41 ld a,($4148) ; read PLAYER_ONE_LIVES 125D: A7 and a ; test if zero 125E: 20 09 jr nz,$1269 ; player one has some lives left, switch to his game ; player two has some lives left, but player one doesn't so let player two continue. 1260: 21 0A 40 ld hl,$400A ; load HL with address of SCRIPT_STAGE 1263: 36 03 ld (hl),$03 ; set SCRIPT_STAGE to be GAME_INIT ($10BA) 1265: 2D dec l ; bump HL to point to TEMP_COUNTER_4009 1266: 36 01 ld (hl),$01 1268: C9 ret ; We're about to switch over to player one, but we need to preserve player two's progress. ; Copy current game state over to player two's state 1269: 21 00 41 ld hl,$4100 ; CURRENT_PLAYER_STATE 126C: 11 80 41 ld de,$4180 ; PLAYER_TWO_STATE 126F: 01 40 00 ld bc,$0040 1272: ED B0 ldir ; switch to player ONE. 1274: AF xor a 1275: 32 0A 40 ld ($400A),a ; set SCRIPT_STAGE to 0 1278: 3E 03 ld a,$03 127A: 32 05 40 ld ($4005),a ; switch over to PLAYER_ONE_GAME_SCRIPT which handles player one 127D: C9 ret ; ; This script executes when the player completes their mission ; MISSION_COMPLETED_SCRIPT: 127E: 3A 81 45 ld a,($4581) ; read MISSION_COMPLETE_SCRIPT_STAGE 1281: EF rst $28 ; invoke function mapping to script stage 1282: 88 12 ; $1288 (COOL_OFF) AB 12 ; $12AB (DISPLAY_CONGRATULATIONS) C6 12 ; $12C6 (AWARD_EXTRA_LIFE_AND_MISSION_FLAG) ; ; The mission is complete, but let the game continue for a second or so. ; I'll call this the "cool off period" ; COOL_OFF: 1288: CD CC 13 call $13CC ; call ANIMATION_AND_MOVEMENT 128B: CD 35 1F call $1F35 ; call SCROLL_AND_SPRITES 128E: CD 36 20 call $2036 ; call COLLISION_DETECTION 1291: CD C2 27 call $27C2 ; call LANDSCAPE_CHANGE ; count down until cool off period is over 1294: 21 80 45 ld hl,$4580 ; load HL with address of TEMP_COUNTER_4580 1297: 35 dec (hl) ; decrement counter 1298: C0 ret nz ; exit if cool off period isn't over yet 1299: 36 05 ld (hl),$05 ; set value of TEMP_COUNTER_4580 129B: 2C inc l ; bump HL to point to MISSION_COMPLETE_SCRIPT_STAGE 129C: 34 inc (hl) ; advance to next stage of script (DISPLAY_CONGRATULATIONS @ $12AB) 129D: 3E 01 ld a,$01 129F: 32 04 68 ld ($6804),a ; enable stars 12A2: 3E 00 ld a,$00 12A4: 32 03 68 ld ($6803),a ; set background to black 12A7: 32 19 40 ld ($4019),a ; clear DRAW_LANDSCAPE_FLAG 12AA: C9 ret ; ; Displays "CONGRATULATIONS YOU COMPLETED YOUR DUTIES GOOD LUCK NEXT TIME" ; DISPLAY_CONGRATULATIONS: 12AB: 21 80 45 ld hl,$4580 ; load HL with address of TEMP_COUNTER_4580 12AE: 35 dec (hl) 12AF: C0 ret nz 12B0: 2C inc l ; bump HL to point to MISSION_COMPLETE_SCRIPT_STAGE 12B1: 34 inc (hl) ; advance to next stage of script (which is AWARD_EXTRA_LIFE_AND_MISSION_FLAG @ $12C6) 12B2: CD 62 11 call $1162 ; call CLEAR_SCREEN_EXCEPT_SCORES_AND_CREDIT 12B5: 11 08 06 ld de,$0608 ; command: PRINT_TEXT, parameter: 8 = CONGRATULATIONS 12B8: FF rst $38 ; call QUEUE_COMMAND 12B9: 1C inc e ; command: PRINT_TEXT, parameter: 9 = YOU COMPLETED YOUR DUTIES 12BA: FF rst $38 ; call QUEUE_COMMAND 12BB: 1C inc e ; command: PRINT_TEXT, parameter: $0A = GOOD LUCK NEXT TIME 12BC: FF rst $38 ; call QUEUE_COMMAND 12BD: 21 89 0B ld hl,$0B89 ; load HL with address of COLOUR_ATTRIBUTE_TABLE_0B89 12C0: CD D9 0A call $0AD9 ; call SET_COLOUR_ATTRIBUTES_FOR_ENTIRE_SCREEN 12C3: C3 83 11 jp $1183 ; jump to CLEAR_ARRAYS_AND_SPRITES ; ; Award extra life, add a "mission complete" flag and return to 1ST level ; AWARD_EXTRA_LIFE_AND_MISSION_FLAG: ; Wait a short while until player has read the congratulations message 12C6: 21 80 45 ld hl,$4580 ; load HL with address of TEMP_COUNTER_4580 12C9: 35 dec (hl) ; decrement counter value 12CA: C0 ret nz ; wait until value reaches zero ; award extra life 12CB: 21 08 41 ld hl,$4108 ; load HL with address of CURRENT_PLAYER_LIVES 12CE: 34 inc (hl) ; give player an extra life 12CF: 21 00 41 ld hl,$4100 ; load HL with address of CURRENT_PLAYER_MISSIONS_COMPLETED ; update missions completed flags on HUD 12D2: 34 inc (hl) ; increment number of missions completed 12D3: 11 00 07 ld de,$0700 ; command: HEAD_UP_DISPLAY_COMMAND, parameter: 0 = DISPLAY_MISSIONS_COMPLETED_FLAGS 12D6: FF rst $38 ; call QUEUE_COMMAND 12D7: AF xor a 12D8: 32 5F 42 ld ($425F),a ; reset TIMING_VARIABLE ; and restart game from level one 12DB: 21 0A 40 ld hl,$400A ; load HL with address of SCRIPT_STAGE 12DE: 36 03 ld (hl),$03 ; set SCRIPT_STAGE to 3 (see GAME_INIT @ $10BA) 12E0: 2D dec l ; bump HL to point to TEMP_COUNTER_4009 12E1: 36 0A ld (hl),$0A ; set counter 12E3: CD C3 0F call $0FC3 ; call RESET_LANDSCAPE_EXTENTS 12E6: AF xor a 12E7: 32 1E 41 ld ($411E),a ; set CURRENT_PLAYERS_LEVEL back to start level 12EA: 21 E9 0A ld hl,$0AE9 ; load HL with address of COLOUR_ATTRIBUTE_TABLE_0AE9 12ED: C3 D9 0A jp $0AD9 ; jump to SET_COLOUR_ATTRIBUTES_FOR_ENTIRE_SCREEN HIGH_SCORE_SCRIPT: 12F0: 3A C1 45 ld a,($45C1) ; read HIGH_SCORE_SCRIPT_STAGE 12F3: EF rst $28 ; call appropriate stage of script 12F4: FA 12 ; $12FA (REMOVE_GAME_OVER_MESSAGE) 0E 13 ; $130E (HIGHLIGHT_HIGH_SCORE) 2D 13 ; $132D (SWITCH_TO_OTHER_PLAYER) REMOVE_GAME_OVER_MESSAGE: 12FA: 21 C0 45 ld hl,$45C0 ; load HL with address of TEMP_COUNTER_45C0 12FD: 35 dec (hl) ; decrement counter 12FE: C0 ret nz ; return if counter hasn't reached zero. 12FF: 2C inc l ; bump HL to point to HIGH_SCORE_SCRIPT_STAGE 1300: 34 inc (hl) ; advance to next stage of the GAME OVER script (which is HIGHLIGHT_HIGH_SCORE @ $130E) 1301: 11 80 06 ld de,$0680 ; Command ID: 6 = PRINT_TEXT, Parameter: 80 = Clear GAME OVER text 1304: FF rst $38 ; call QUEUE_COMMAND 1305: 1E 82 ld e,$82 ; Parameter 82 = Clear PLAYER ONE text 1307: FF rst $38 ; call QUEUE_COMMAND 1308: 21 69 0B ld hl,$0B69 ; load HL with address of COLOUR_ATTRIBUTE_TABLE_0B69 130B: C3 D9 0A jp $0AD9 ; jump to SET_COLOUR_ATTRIBUTES_FOR_ENTIRE_SCREEN ; ; If the user's score makes it into the high score table, highlight it in white. ; HIGHLIGHT_HIGH_SCORE: 130E: CD 46 13 call $1346 ; call GET_HIGH_SCORE_INDEX 1311: 7D ld a,l ; get index of high score into A. 1312: FE 0A cp $0A ; compare to 10. 1314: 28 0B jr z,$1321 ; if 10, then that means the player didn't get a high score, goto $1321 ; Player has a high score. Calculate the address of the colour attribute for the player's high score, then set attribute to white to highlight score. 1316: 87 add a,a ; multiply index of high score.. 1317: 87 add a,a ; .. by 4. 1318: 5F ld e,a 1319: 16 00 ld d,$00 ; save result in DE 131B: 21 2F 40 ld hl,$402F 131E: 19 add hl,de ; add result to $402F (OBJRAM_BACK_BUF_ATTRIBUTES) 131F: 36 00 ld (hl),$00 ; set row containing new high score to WHITE ; Now display high scores 1321: 11 00 02 ld de,$0200 ; command ID: 2 = DISPLAY_HIGH_SCORES_COMMAND 1324: FF rst $38 ; call QUEUE_COMMAND ; Establish a short delay. 1325: 21 C0 45 ld hl,$45C0 ; load HL with address of TEMP_COUNTER_45C0 1328: 36 80 ld (hl),$80 132A: 2C inc l ; bump HL to point to HIGH_SCORE_SCRIPT_STAGE 132B: 34 inc (hl) ; advance to next stage of the GAME OVER script 132C: C9 ret ; ; After showing the player's high score, switch to the other player if possible. ; ; SWITCH_TO_OTHER_PLAYER: 132D: 21 C0 45 ld hl,$45C0 ; load HL with address of TEMP_COUNTER_45C0 1330: 35 dec (hl) ; decrement counter 1331: C0 ret nz ; return if counter hasn't reached zero. 1332: 21 0A 40 ld hl,$400A ; load HL with address of SCRIPT_STAGE 1335: 36 07 ld (hl),$07 ; set SCRIPT_STAGE to 7 (either CHECK_IF_SHOULD_SWITCH_TO_PLAYER_ONE@$123F or CHECK_IF_SHOULD_SWITCH_TO_PLAYER_TWO @$11AB). 1337: 2D dec l ; bump HL to point to TEMP_COUNTER_4009 1338: 36 64 ld (hl),$64 ; set TEMP_COUNTER_4009 to 100. 133A: C9 ret ; ; Called when rocket has been shot. This code was possibly split from CHECK_IF_PLAYER_BOMB_HIT_ROCKET ; to make it harder to follow for hackers. ; ; IY = pointer to PLAYER_BOMB struct ; CHECK_IF_PLAYER_BOMB_HIT_ROCKET_CONTINUED_1: 133B: FD 36 02 06 ld (iy+$02),$06 ; set PLAYER_BOMB.StageOfLife to 6 (see PLAYER_BOMB_EXPLOSION_INIT @ $1A4B) 133F: 11 0A 03 ld de,$030A ; Command ID: 3 = UPDATE_PLAYER_SCORE_COMMAND, Param: $0A = 80 points 1342: FF rst $38 ; call QUEUE_COMMAND 1343: C3 17 25 jp $2517 ; Check if the player has a high score. If they do, then overwrite the relevant high score in the high score table, ; and return the index to the calling function. ; ; Returns: ; L = zero-based index of high score that was overwritten with player's score. ; if L = 10 that means player did not get a high score. GET_HIGH_SCORE_INDEX: 1346: 01 1E 00 ld bc,$001E ; $1E = 30 decimal. Divide by 3 to give 10 - number of entries in high score table 1349: 11 03 00 ld de,$0003 ; sizeof(bytes required for score) 134C: 6A ld l,d ; set l to 0 134D: DD 21 A2 40 ld ix,$40A2 ; load IX with address of PLAYER_ONE_SCORE 1351: 3A 0D 40 ld a,($400D) ; read CURRENT_PLAYER 1354: 0F rrca ; if carry is set, player two is our current player 1355: 30 02 jr nc,$1359 ; if carry is not set, player one is current, goto $1359 1357: DD 19 add ix,de ; bump IX to point to PLAYER_TWO_SCORE ; IX = pointer to current player's score 1359: FD 21 00 42 ld iy,$4200 ; load IY with address of HI_SCORE_TABLE_1ST ; Compare first two digits of score to first two digits of high score 135D: DD 7E 02 ld a,(ix+$02) ; read first two digits of player's score 1360: FD BE 02 cp (iy+$02) ; compare against first two digits of high score entry 1363: 20 0F jr nz,$1374 ; if they don't match, ; Compare third & fourth digits 1365: DD 7E 01 ld a,(ix+$01) ; read third and fourth digits of players score 1368: FD BE 01 cp (iy+$01) 136B: 20 07 jr nz,$1374 136D: DD 7E 00 ld a,(ix+$00) 1370: FD BE 00 cp (iy+$00) 1373: C8 ret z 1374: 30 09 jr nc,$137F ; if digits of player's score >= digits of high score, goto REPLACE_HIGH_SCORE_WITH_PLAYERS_SCORE 1376: FD 19 add iy,de ; bump IY to point to next score in table (going from top score, to 2nd top, 3rd top etc) 1378: 2C inc l ; increment index of high score 1379: 0D dec c 137A: 0D dec c 137B: 0D dec c 137C: C8 ret z 137D: 18 DE jr $135D ; ; IX = pointer to players score (3 BCD digits) ; IY = pointer to high score entry to overwrite with players score ; BC = number of bytes to move in high score table ; L = zero-based index of high score that will be overwritten (0=1ST, 1=2ND, 2=3RD and so on) REPLACE_HIGH_SCORE_WITH_PLAYERS_SCORE: 137F: 7D ld a,l 1380: 21 1D 42 ld hl,$421D ; pointer to tens digits of HI_SCORE_TABLE_10TH 1383: 11 20 42 ld de,$4220 ; pointer to HI_SCORE_TABLE_BUFFER 1386: ED B8 lddr ; shift scores down 1388: 6F ld l,a ; now overwrite high score entry with player score 1389: DD 7E 00 ld a,(ix+$00) 138C: FD 77 00 ld (iy+$00),a 138F: DD 7E 01 ld a,(ix+$01) 1392: FD 77 01 ld (iy+$01),a 1395: DD 7E 02 ld a,(ix+$02) 1398: FD 77 02 ld (iy+$02),a 139B: C9 ret ; Add 10 points to players score every 64 ticks of the TIMING_VARIABLE. GAIN_POINTS_JUST_FOR_STAYING_ALIVE: 139C: 3A 5F 42 ld a,($425F) ; read TIMING_VARIABLE 139F: E6 3F and $3F ; if the modulus of TIMING_VARIABLE divided by 64 != 0.. 13A1: C0 ret nz ; ..then return. 13A2: 11 0C 03 ld de,$030C ; Command ID: 3 = UPDATE_PLAYER_SCORE_COMMAND, Param: $0C = 10 points 13A5: FF rst $38 ; call QUEUE_COMMAND 13A6: C9 ret CHECK_IF_EXTRA_LIFE_SHOULD_BE_AWARDED: 13A7: 3A 07 41 ld a,($4107) ; read CURRENT_PLAYER_HAD_EXTRA_LIFE 13AA: A7 and a ; test flag 13AB: C0 ret nz ; exit if player has already been awarded an extra life ; Find out what player is playing 13AC: 21 A4 40 ld hl,$40A4 ; load HL with address of first 2 digits of PLAYER ONE's score 13AF: 3A 0D 40 ld a,($400D) ; read CURRENT_PLAYER 13B2: 0F rrca 13B3: 30 03 jr nc,$13B8 ; if no carry, PLAYER ONE is playing, goto $13B8 ; player two is playing 13B5: 21 A7 40 ld hl,$40A7 ; load HL with address of first 2 digits of PLAYER TWO's score ; If the first two digits of the player's score aren't zero, then the player has hit 10000 points and a new life can be awarded. 13B8: 7E ld a,(hl) ; read first two digits 13B9: A7 and a ; test if zero 13BA: C8 ret z ; exit if first two digits are zero. No extra life for you! 13BB: CD C4 24 call $24C4 ; do a protection check and then play a "life awarded!" sound. 13BE: 21 08 41 ld hl,$4108 ; load HL with address of CURRENT_PLAYER_LIVES 13C1: 34 inc (hl) ; award player an extra life! 13C2: 11 03 07 ld de,$0703 ; Command ID: 7 = HEAD_UP_DISPLAY_COMMAND, Param:3 = DISPLAY_CURRENT_PLAYER_LIVES 13C5: FF rst $38 ; call QUEUE_COMMAND 13C6: 3E 01 ld a,$01 13C8: 32 07 41 ld ($4107),a ; set CURRENT_PLAYER_HAD_EXTRA_LIFE flag. Player will not get another extra life. 13CB: C9 ret ANIMATION_AND_MOVEMENT: 13CC: CD A5 15 call $15A5 ; call SCROLL 13CF: CD AE 18 call $18AE ; call GROUND_OBJECT_ANIMATION_AND_MOVEMENT 13D2: CD D0 16 call $16D0 ; call PLAYER_ANIMATION_AND_MOVEMENT 13D5: CD 30 1A call $1A30 ; call PLAYER_BOMB_ANIMATION_AND_MOVEMENT 13D8: CD 98 1C call $1C98 ; call ROCKET_ANIMATION_AND_MOVEMENT 13DB: CD 75 1D call $1D75 ; call UFO_ANIMATION_AND_MOVEMENT 13DE: CD C6 1E call $1EC6 ; call FIREBALL_ANIMATION_AND_MOVEMENT 13E1: C3 AA 16 jp $16AA ; call MOVE_PLAYER_BULLETS ; ; This function animates an entity. ; ; IX = pointer to a PLAYER/ PLAYER_BOMB/ GROUND_OBJECT/ INFLIGHT_ENEMY struct. ; ; Before this method is called, the AnimPtrLo (IX + $0C) and AnimPtrHi (IX + $0D) fields must form a pointer to a record within an "animation table". ; An animation table describes the colours and sprite codes required for an animation. It does *not* define how the entity moves (see FOLLOW_PATH @$1578 for more info.) ; ; Each entry in the animation table requires 3 bytes: ; * Byte 0: the core colour for the animation frame; ; * Byte 1: the "sprite code" (number) for the animation frame to display; ; * Byte 2: the delay before showing the next frame. ; ; A colour byte of $FF is special: it marks the end of the animation sequence (the "end of animation marker"); ; The subsequent two bytes are a pointer to the next animation table to use. ; ; All animation tables have the suffix _ANIMATION_TABLE. e.g. PLAYER_ONE_JET_ANIMATION_TABLE. ANIMATE: 13E4: DD 7E 0E ld a,(ix+$0e) ; read AnimationCounter value 13E7: A7 and a 13E8: 28 05 jr z,$13EF ; if AnimationCounter has counted down to zero goto CHANGE_COLOUR_AND_SPRITE_CODE 13EA: 3D dec a ; 13EB: DD 77 0E ld (ix+$0e),a ; otherwise decrement delay value 13EE: C9 ret ; change colour and sprite code (animation frame) of entity CHANGE_COLOUR_AND_SPRITE_CODE: 13EF: DD 6E 0C ld l,(ix+$0c) ; read AnimPtrLo 13F2: DD 66 0D ld h,(ix+$0d) ; read AnimPtrHi. Now HL contains a pointer to animation information 13F5: 7E ld a,(hl) ; read colour byte 13F6: FE FF cp $FF ; is this the end of animation marker byte? (see docs @ $1860) 13F8: 28 15 jr z,$140F ; yes, goto END_OF_ANIMATION_SEQUENCE 13FA: DD 77 16 ld (ix+$16),a ; set Colour 13FD: 23 inc hl ; 13FE: 7E ld a,(hl) ; 13FF: DD 77 12 ld (ix+$12),a ; write to SpriteCode / CharCode 1402: 23 inc hl 1403: 7E ld a,(hl) 1404: DD 77 0E ld (ix+$0e),a ; write to AnimationCounter 1407: 23 inc hl 1408: DD 75 0C ld (ix+$0c),l 140B: DD 74 0D ld (ix+$0d),h ; update pointer to animation information 140E: C9 ret ; We have encountered the "end of animation marker" byte ($FF). The next two bytes we read from (HL) form a pointer to the next animation table we must use. END_OF_ANIMATION_SEQUENCE: 140F: 23 inc hl ; bump HL. Now HL is a pointer to a pointer [to an animation table] 1410: 7E ld a,(hl) ; read low byte of animation table address 1411: DD 77 0C ld (ix+$0c),a ; set AnimPtrLo 1414: 23 inc hl 1415: 7E ld a,(hl) ; read high byte of animation table address 1416: DD 77 0D ld (ix+$0d),a ; set AnimPtrHi 1419: 18 C9 jr $13E4 ; jump to ANIMATE to read animation info. ; ; The code from $141B-1577 is unused. I've put breakpoints on some of the entry points and played through the game, they are never called. ; /*** START UNUSED CODE ***/ 141B: DD E5 push ix 141D: E1 pop hl 141E: 3E 07 ld a,$07 1420: 85 add a,l 1421: 6F ld l,a 1422: DD 36 0A 00 ld (ix+$0a),$00 1426: DD 7E 05 ld a,(ix+$05) 1429: DD BE 03 cp (ix+$03) 142C: 28 04 jr z,$1432 142E: 38 1E jr c,$144E 1430: 18 56 jr $1488 1432: DD 7E 06 ld a,(ix+$06) 1435: DD BE 04 cp (ix+$04) 1438: 28 0E jr z,$1448 143A: 38 06 jr c,$1442 143C: 36 00 ld (hl),$00 143E: 2C inc l 143F: 36 01 ld (hl),$01 1441: C9 ret 1442: 36 00 ld (hl),$00 1444: 2C inc l 1445: 36 FF ld (hl),$FF 1447: C9 ret 1448: 36 00 ld (hl),$00 144A: 2C inc l 144B: 36 00 ld (hl),$00 144D: C9 ret 144E: DD 7E 06 ld a,(ix+$06) 1451: DD BE 04 cp (ix+$04) 1454: 28 2C jr z,$1482 1456: 38 15 jr c,$146D 1458: 36 FF ld (hl),$FF 145A: 2C inc l 145B: 36 01 ld (hl),$01 145D: DD 7E 03 ld a,(ix+$03) 1460: DD 96 05 sub (ix+$05) 1463: 47 ld b,a 1464: DD 7E 06 ld a,(ix+$06) 1467: DD 96 04 sub (ix+$04) 146A: 4F ld c,a 146B: 18 55 jr $14C2 146D: 36 FF ld (hl),$FF 146F: 2C inc l 1470: 36 FF ld (hl),$FF 1472: DD 7E 03 ld a,(ix+$03) 1475: DD 96 05 sub (ix+$05) 1478: 47 ld b,a 1479: DD 7E 04 ld a,(ix+$04) 147C: DD 96 06 sub (ix+$06) 147F: 4F ld c,a 1480: 18 40 jr $14C2 1482: 36 FF ld (hl),$FF 1484: 2C inc l 1485: 36 00 ld (hl),$00 1487: C9 ret 1488: DD 7E 06 ld a,(ix+$06) 148B: DD BE 04 cp (ix+$04) 148E: 28 2C jr z,$14BC 1490: 38 15 jr c,$14A7 1492: 36 01 ld (hl),$01 1494: 2C inc l 1495: 36 01 ld (hl),$01 1497: DD 7E 05 ld a,(ix+$05) 149A: DD 96 03 sub (ix+$03) 149D: 47 ld b,a 149E: DD 7E 06 ld a,(ix+$06) 14A1: DD 96 04 sub (ix+$04) 14A4: 4F ld c,a 14A5: 18 1B jr $14C2 14A7: 36 01 ld (hl),$01 14A9: 2C inc l 14AA: 36 FF ld (hl),$FF 14AC: DD 7E 05 ld a,(ix+$05) 14AF: DD 96 03 sub (ix+$03) 14B2: 47 ld b,a 14B3: DD 7E 04 ld a,(ix+$04) 14B6: DD 96 06 sub (ix+$06) 14B9: 4F ld c,a 14BA: 18 06 jr $14C2 14BC: 36 01 ld (hl),$01 14BE: 2C inc l 14BF: 36 00 ld (hl),$00 14C1: C9 ret 14C2: 79 ld a,c 14C3: B8 cp b 14C4: 28 16 jr z,$14DC 14C6: 38 0B jr c,$14D3 14C8: DD 36 09 00 ld (ix+$09),$00 14CC: CD E5 14 call $14E5 14CF: DD 77 0B ld (ix+$0b),a 14D2: C9 ret 14D3: DD 36 09 01 ld (ix+$09),$01 14D7: 78 ld a,b 14D8: 41 ld b,c 14D9: 4F ld c,a 14DA: 18 F0 jr $14CC 14DC: DD 36 09 01 ld (ix+$09),$01 14E0: DD 36 0B FF ld (ix+$0b),$FF 14E4: C9 ret 14E5: AF xor a 14E6: 67 ld h,a 14E7: 68 ld l,b 14E8: 57 ld d,a 14E9: 59 ld e,c 14EA: 06 08 ld b,$08 14EC: CB FF set 7,a 14EE: 07 rlca 14EF: 29 add hl,hl 14F0: A7 and a 14F1: ED 52 sbc hl,de 14F3: 38 03 jr c,$14F8 14F5: 10 F5 djnz $14EC 14F7: C9 ret 14F8: 19 add hl,de 14F9: CB 87 res 0,a 14FB: 10 EF djnz $14EC 14FD: C9 ret 14FE: DD 7E 04 ld a,(ix+$04) 1501: DD BE 06 cp (ix+$06) 1504: 28 4A jr z,$1550 1506: DD 7E 03 ld a,(ix+$03) 1509: DD BE 05 cp (ix+$05) 150C: 28 58 jr z,$1566 150E: DD CB 09 46 bit 0,(ix+$09) 1512: 28 1E jr z,$1532 1514: DD 7E 07 ld a,(ix+$07) 1517: DD 86 03 add a,(ix+$03) 151A: DD 77 03 ld (ix+$03),a 151D: DD 7E 0B ld a,(ix+$0b) 1520: DD 86 0A add a,(ix+$0a) 1523: DD 77 0A ld (ix+$0a),a 1526: D0 ret nc 1527: DD 7E 08 ld a,(ix+$08) 152A: DD 86 04 add a,(ix+$04) 152D: DD 77 04 ld (ix+$04),a 1530: A7 and a 1531: C9 ret 1532: DD 7E 08 ld a,(ix+$08) 1535: DD 86 04 add a,(ix+$04) 1538: DD 77 04 ld (ix+$04),a 153B: DD 7E 0B ld a,(ix+$0b) 153E: DD 86 0A add a,(ix+$0a) 1541: DD 77 0A ld (ix+$0a),a 1544: D0 ret nc 1545: DD 7E 07 ld a,(ix+$07) 1548: DD 86 03 add a,(ix+$03) 154B: DD 77 03 ld (ix+$03),a 154E: A7 and a 154F: C9 ret 1550: DD 7E 03 ld a,(ix+$03) 1553: DD BE 05 cp (ix+$05) 1556: 28 0C jr z,$1564 1558: 30 05 jr nc,$155F 155A: DD 34 03 inc (ix+$03) 155D: A7 and a 155E: C9 ret 155F: DD 35 03 dec (ix+$03) 1562: A7 and a 1563: C9 ret 1564: 37 scf 1565: C9 ret 1566: DD 7E 04 ld a,(ix+$04) 1569: DD BE 06 cp (ix+$06) 156C: 30 05 jr nc,$1573 156E: DD 34 04 inc (ix+$04) 1571: A7 and a 1572: C9 ret 1573: DD 35 04 dec (ix+$04) 1576: A7 and a 1577: C9 ret /*** END UNUSED CODE ***/ ; ; Makes the entity move along a table-driven path. ; ; Expects: ; IX = pointer to PLAYER_BOMB or INFLIGHT_ENEMY structure ; ; PathPtrLo (IX + $13) and PathPtrHi (IX+$14) form a pointer to an entry in a path table which is basically a table of 2D vectors. ; ; Each entry in the table comprises of 2 bytes: ; Byte 0: a signed delta (which I will call XDelta in the code below) to be added to the X coordinate ; Byte 1: a signed delta (which I will call YDelta)to be added to the Y coordinate ; ; An XDelta of #$80 is a special case. This means that the end of the path has been reached, and that the 2 following bytes ; in the table should be treated as a pointer to the next path to follow. This allows paths to be "chained" but I haven't ; seen any paths that reference anything except themselves. ; FOLLOW_PATH: 1578: DD 6E 13 ld l,(ix+$13) ; read PathPtrLo 157B: DD 66 14 ld h,(ix+$14) ; read PathPtrHi. Now HL = pointer to a path 157E: 7E ld a,(hl) ; read byte from path 157F: FE 80 cp $80 ; is this a marker byte? 1581: 20 0C jr nz,$158F ; if not, goto UPDATE_XY_FROM_PATH ; we've got a marker byte. Next 2 bytes are a pointer to our next path. 1583: 23 inc hl 1584: 7E ld a,(hl) 1585: DD 77 13 ld (ix+$13),a ; set PathPtrLo 1588: 23 inc hl 1589: 7E ld a,(hl) 158A: DD 77 14 ld (ix+$14),a ; set PathPtrHi 158D: 18 E9 jr $1578 ; goto FOLLOW_PATH ; Update X and Y coordinates of the object using our deltas UPDATE_XY_FROM_PATH: 158F: DD 86 03 add a,(ix+$03) ; X = X + XDelta 1592: DD 77 03 ld (ix+$03),a ; update X 1595: 23 inc hl 1596: 7E ld a,(hl) 1597: DD 86 04 add a,(ix+$04) ; Y = Y + YDelta 159A: DD 77 04 ld (ix+$04),a ; update Y 159D: 23 inc hl 159E: DD 75 13 ld (ix+$13),l ; update PathPtrLo 15A1: DD 74 14 ld (ix+$14),h ; update PathPtrHi 15A4: C9 ret ; ; ; ! IMPORTANT ! ; ; Scrolls on a new part of the landscape every 16 pixels (2 character rows) ; ; LANDSCAPE_SCROLLING: 15A5: DD 21 10 41 ld ix,$4110 15A9: DD 7E 05 ld a,(ix+$05) ; read LANDSCAPE_SCROLL_CONTROL_COUNTER 15AC: A7 and a ; test if zero 15AD: 20 12 jr nz,$15C1 ; if not zero, goto UPDATE_SCROLL_CONTROL_COUNTER which decrements it ; LANDSCAPE_SCROLL_CONTROL_COUNTER is zero. 15AF: DD 7E 04 ld a,(ix+$04) ; read LANDSCAPE_SCROLL_CONTROL_LATCH value 15B2: DD 77 05 ld (ix+$05),a ; set LANDSCAPE_SCROLL_CONTROL_COUNTER to the latch value ; Every 16th pixel we need to scroll a new part of the landscape on. 15B5: DD 7E 06 ld a,(ix+$06) ; read LANDSCAPE_SCROLL_COUNTER 15B8: E6 0F and $0F ; 15BA: CC C5 15 call z,$15C5 ; if LANDSCAPE_SCROLL_COUNTER modulo 16 == 0, call READ_LANDSCAPE_LAYOUT 15BD: DD 34 06 inc (ix+$06) ; increment LANDSCAPE_SCROLL_COUNTER 15C0: C9 ret UPDATE_SCROLL_CONTROL_COUNTER: 15C1: DD 35 05 dec (ix+$05) ; decrement LANDSCAPE_SCROLL_CONTROL_COUNTER 15C4: C9 ret ; ; A LANDSCAPE_LAYOUT defines the landscape and ground objects (targets!) for a level. ; ; A landscape layout is comprised of multiple records. Each record defines 2 character columns (16 pixels) of a level, specifically: ; * the ordinals of the characters to be scrolled on (the "sharp edges" of the landscape, so to speak); ; * the "height(s)" to draw the characters at; ; * any ground objects (targets) to be rendered. ; ; Each record can be either 6 bytes or 9 bytes in size; 9 bytes if a ceiling needs to be rendered. ; IMPORTANT: A level layout may comprise a mix of both 6 and 9 byte records. ; ; The record structure is as follows: ; Byte 0: used to compute LANDSCAPE_GROUND_FIRST_CHAR_PTR (see $15FD) ; Byte 1: sets LANDSCAPE_GROUND_FIRST_CHAR (see $160A) ; Byte 2: used to compute LANDSCAPE_GROUND_SECOND_CHAR_PTR (see $15E4) ; Byte 3: sets LANDSCAPE_GROUND_SECOND_CHAR (see $15F3) ; ; This is where things become a little more complex. Byte 4 determines if this record is going to be 6 or 9 bytes in size. ; If Byte 4 is zero: ; * this record is 6 bytes in size and no ceiling needs to be rendered: ; * Byte 5 sets NEXT_GROUND_OBJECT_ID (see $164A); ; ; Else If Byte 4 is nonzero: ; This record is 9 bytes in size because a ceiling needs to be rendered, and: ; * Byte 4 is used to compute LANDSCAPE_CEILING_FIRST_CHAR_PTR (see $167B) ; * Byte 5 is stored in LANDSCAPE_CEILING_FIRST_CHAR (see $1688) ; * Byte 6 is used to compute LANDSCAPE_CEILING_SECOND_CHAR_PTR (see $1664) ; * Byte 7 sets LANDSCAPE_CEILING_SECOND_CHAR (see $1671) ; * Byte 8 sets NEXT_GROUND_OBJECT_ID (see $1645) ; ; ; See also: ; Landscape layout definitions @ $29E2, $2DD3, $31C4, $3465, $3856, $3C47. READ_LANDSCAPE_LAYOUT: 15C5: CD CB 15 call $15CB ; call DECODE_LANDSCAPE_FLOOR 15C8: C3 59 16 jp $1659 ; jump to DECODE_LANDSCAPE_CEILING DECODE_LANDSCAPE_FLOOR: 15CB: FD 2A 18 41 ld iy,($4118) ; load IY with contents of LANDSCAPE_LAYOUT_PTR 15CF: 3E 01 ld a,$01 15D1: 32 10 41 ld ($4110),a ; set CAN_DRAW_LANDSCAPE_1 15D4: 32 30 42 ld ($4230),a ; set CAN_DRAW_LANDSCAPE_2 ; Calculate where in screen RAM to plot two characters of ground 15D7: DD 7E 06 ld a,(ix+$06) ; read LANDSCAPE_SCROLL_COUNTER 15DA: 2F cpl ; flip bits 15DB: E6 F0 and $F0 15DD: 47 ld b,a ; save result in B for use later - see $1610 15DE: 6F ld l,a 15DF: 26 12 ld h,$12 15E1: 29 add hl,hl 15E2: 29 add hl,hl 15E3: E5 push hl ; save character RAM address on stack ; compute LANDSCAPE_GROUND_SECOND_CHAR_PTR and LANDSCAPE_GROUND_SECOND_CHAR 15E4: FD 7E 02 ld a,(iy+$02) 15E7: E6 F8 and $F8 15E9: 0F rrca 15EA: 0F rrca 15EB: 0F rrca 15EC: 5F ld e,a 15ED: 16 00 ld d,$00 15EF: 19 add hl,de ; HL is now an address in character RAM 15F0: 22 35 42 ld ($4235),hl ; set LANDSCAPE_GROUND_SECOND_CHAR_PTR 15F3: FD 7E 03 ld a,(iy+$03) 15F6: 32 34 42 ld ($4234),a ; set LANDSCAPE_GROUND_SECOND_CHAR 15F9: E1 pop hl ; Bump HL to point to next character row 15FA: 1E 20 ld e,$20 ; 32 bytes per character row 15FC: 19 add hl,de ; compute LANDSCAPE_GROUND_FIRST_CHAR_PTR and LANDSCAPE_GROUND_FIRST_CHAR 15FD: FD 7E 00 ld a,(iy+$00) 1600: E6 F8 and $F8 1602: 0F rrca 1603: 0F rrca 1604: 0F rrca 1605: 5F ld e,a ; E = character offset on row 1606: 19 add hl,de 1607: 22 32 42 ld ($4232),hl ; set LANDSCAPE_GROUND_FIRST_CHAR_PTR 160A: FD 7E 01 ld a,(iy+$01) 160D: 32 31 42 ld ($4231),a ; set LANDSCAPE_GROUND_FIRST_CHAR ; Now record the ground height in the relevant LANDSCAPE_EXTENT record. 1610: 78 ld a,b 1611: 0F rrca 1612: 0F rrca 1613: 5F ld e,a 1614: 21 C0 41 ld hl,$41C0 ; load HL with address of LANDSCAPE_EXTENTS 1617: 19 add hl,de 1618: FD 7E 02 ld a,(iy+$02) 161B: 77 ld (hl),a ; set LANDSCAPE_EXTENT.GroundX 161C: 2C inc l ; $28 is the default for when the landscape has no ceiling (ie: not cave or maze) 161D: 36 28 ld (hl),$28 ; set LANDSCAPE_EXTENT.CeilingX 161F: 2C inc l ; bump HL to point to next LANDSCAPE_EXTENT record 1620: FD 7E 00 ld a,(iy+$00) 1623: 77 ld (hl),a ; set LANDSCAPE_EXTENT.GroundX 1624: 2C inc l 1625: 36 28 ld (hl),$28 ; set LANDSCAPE_EXTENT.CeilingX 1627: AF xor a 1628: 32 38 42 ld ($4238),a ; reset LANDSCAPE_HAS_CEILING_FLAG 162B: 32 11 41 ld ($4111),a ; reset DISABLE_STARS flag ; do we have a ceiling to render? 162E: 2A 18 41 ld hl,($4118) ; load HL with contents of LANDSCAPE_LAYOUT_PTR 1631: 1E 09 ld e,$09 ; sizeof(record) that contains ceiling data 1633: FD 7E 04 ld a,(iy+$04) 1636: A7 and a ; test if byte 4 is nonzero 1637: 20 02 jr nz,$163B ; if nonzero, we have a ceiling, goto $163B 1639: 1E 06 ld e,$06 ; sizeof(record) that does not contain ceiling data 163B: 19 add hl,de 163C: 22 18 41 ld ($4118),hl ; update LANDSCAPE_LAYOUT_PTR ; identify what type of ground object we are going to scroll onto screen 163F: FD 7E 04 ld a,(iy+$04) 1642: A7 and a 1643: 28 05 jr z,$164A 1645: FD 7E 08 ld a,(iy+$08) 1648: 18 03 jr $164D 164A: FD 7E 05 ld a,(iy+$05) 164D: 32 1A 41 ld ($411A),a ; set NEXT_GROUND_OBJECT_ID ; Establish where next CHAR_BASED_GROUND_OBJECT will be plotted 1650: 2A 35 42 ld hl,($4235) ; load HL with contents of LANDSCAPE_GROUND_SECOND_CHAR_PTR 1653: 2B dec hl 1654: 2B dec hl 1655: 22 1B 41 ld ($411B),hl ; set NEXT_GROUND_OBJECT_CHAR_PTR 1658: C9 ret ; ; ; ; ; DECODE_LANDSCAPE_CEILING: 1659: FD 7E 04 ld a,(iy+$04) 165C: A7 and a 165D: C8 ret z ; Calculate where in screen RAM to plot two characters for ceiling 165E: 68 ld l,b 165F: 26 12 ld h,$12 1661: 29 add hl,hl 1662: 29 add hl,hl 1663: E5 push hl ; compute LANDSCAPE_GROUND_SECOND_CHAR_PTR and LANDSCAPE_GROUND_SECOND_CHAR 1664: FD 7E 06 ld a,(iy+$06) 1667: E6 F8 and $F8 1669: 0F rrca 166A: 0F rrca 166B: 0F rrca 166C: 5F ld e,a 166D: 19 add hl,de 166E: 22 3D 42 ld ($423D),hl ; set LANDSCAPE_CEILING_SECOND_CHAR_PTR 1671: FD 7E 07 ld a,(iy+$07) 1674: 32 3C 42 ld ($423C),a ; set LANDSCAPE_CEILING_SECOND_CHAR 1677: E1 pop hl ; bump HL to point to next row 1678: 1E 20 ld e,$20 167A: 19 add hl,de ; compute LANDSCAPE_CEILING_FIRST_CHAR_PTR and LANDSCAPE_CEILING_FIRST_CHAR 167B: FD 7E 04 ld a,(iy+$04) 167E: E6 F8 and $F8 1680: 0F rrca 1681: 0F rrca 1682: 0F rrca 1683: 5F ld e,a 1684: 19 add hl,de 1685: 22 3A 42 ld ($423A),hl ; set LANDSCAPE_CEILING_FIRST_CHAR_PTR 1688: FD 7E 05 ld a,(iy+$05) 168B: 32 39 42 ld ($4239),a ; set LANDSCAPE_CEILING_FIRST_CHAR ; Now record the ceiling depth in the relevant LANDSCAPE_EXTENT record. ; The ground height for the same record will be set at $1610 168E: 78 ld a,b 168F: 0F rrca 1690: 0F rrca 1691: 5F ld e,a 1692: 21 C0 41 ld hl,$41C0 ; load HL with address of LANDSCAPE_EXTENTS 1695: 19 add hl,de 1696: 2C inc l 1697: FD 7E 06 ld a,(iy+$06) 169A: 77 ld (hl),a ; set LANDSCAPE_EXTENT.CeilingX 169B: 2C inc l 169C: 2C inc l 169D: FD 7E 04 ld a,(iy+$04) 16A0: 77 ld (hl),a ; set LANDSCAPE_EXTENT.CeilingX ; If the maze has a ceiling then we don't show starfield 16A1: 3E 01 ld a,$01 16A3: 32 11 41 ld ($4111),a ; set DISABLE_STARS flag 16A6: 32 38 42 ld ($4238),a ; set LANDSCAPE_HAS_CEILING_FLAG 16A9: C9 ret ; ; Update any active player bullets. ; When bullet goes off screen, deactivate it. ; MOVE_PLAYER_BULLETS: 16AA: DD 21 00 45 ld ix,$4500 ; load IX with address of PLAYER_BULLETS 16AE: 11 03 00 ld de,$0003 ; sizeof(PLAYER_BULLET) 16B1: 06 04 ld b,$04 ; player can have a maximum of 4 bullets at once 16B3: CD BB 16 call $16BB 16B6: DD 19 add ix,de 16B8: 10 F9 djnz $16B3 16BA: C9 ret 16BB: DD CB 00 46 bit 0,(ix+$00) ; read PLAYER_BULLET.IsActive flag 16BF: C8 ret z 16C0: DD 7E 02 ld a,(ix+$02) ; read PLAYER_BULLET.Y 16C3: D6 03 sub $03 ; subtract 3 pixels - to player, bullet will appear to move right 16C5: DD 77 02 ld (ix+$02),a ; update PLAYER_BULLET.Y 16C8: FE 1F cp $1F ; has bullet gone offscreen? 16CA: D0 ret nc ; return if not 16CB: DD CB 00 86 res 0,(ix+$00) ; otherwise clear PLAYER_BULLET.IsActive flag 16CF: C9 ret ; ; Read the joystick state and animate and move the player jet accordingly ; ; This code does not read the state of the SHOOT or BOMB buttons - see SPAWN_PLAYER_BULLET @$257F and SPAWN_PLAYER_BOMB @ $26B1 for that. ; PLAYER_ANIMATION_AND_MOVEMENT: ; Protection code - if not interested, skip to $16DA 16D0: 00 nop 16D1: 00 nop 16D2: 78 ld a,b 16D3: 4F ld c,a 16D4: 2A B9 40 ld hl,($40B9) ; read PROTECTION_PORT_PTR_1 16D7: 36 00 ld (hl),$00 ; write to protection 16D9: 00 nop ; End protection code 16DA: DD 21 80 43 ld ix,$4380 ; load IX with address of PLAYERS[0].IsActive flag 16DE: DD 7E 00 ld a,(ix+$00) ; read flag 16E1: DD B6 01 or (ix+$01) ; OR with PLAYERS[0].IsExploding flag 16E4: 0F rrca ; if either flag is set, carry will be set after rrca 16E5: D0 ret nc ; return if player is not active and not dying PLAYER_STAGE_OF_LIFE: 16E6: DD 7E 02 ld a,(ix+$02) ; read PLAYER.StageOfLife 16E9: EF rst $28 16EA: FE 16 ; PLAYER_INIT 25 17 ; PLAYER_ANIMATE D4 17 ; PLAYER_OUT_OF_FUEL 07 18 ; $1807: just a RET 08 18 ; $1808: just a RET 09 18 ; $1809: just a RET 0A 18 ; PLAYER_EXPLOSION_INIT 25 18 ; PLAYER_EXPLOSION_ANIMATE 5E 18 ; $185E: just a RET 5F 18 ; $185F: just a RET ; ; ; Initialise important player state such as screen position and animation table ; ; PLAYER_INIT: 16FE: DD 36 03 58 ld (ix+$03),$58 ; set PLAYERS[0].X 1702: DD 36 23 58 ld (ix+$23),$58 ; set PLAYERS[1].X 1706: DD 36 04 D0 ld (ix+$04),$D0 ; set PLAYERS[0].Y 170A: DD 36 24 E0 ld (ix+$24),$E0 ; set PLAYERS[1].Y 170E: 21 60 18 ld hl,$1860 ; load HL with address of PLAYER_ONE_JET_ANIMATION_TABLE 1711: 22 8C 43 ld ($438C),hl ; set PLAYERS[0].AnimPtr to point to PLAYER_ONE_JET_ANIMATION_TABLE 1714: 21 6F 18 ld hl,$186F ; load HL with address of PLAYER_TWO_JET_ANIMATION_TABLE 1717: 22 AC 43 ld ($43AC),hl ; set PLAYERS[1].AnimPtr to point to PLAYER_TWO_JET_ANIMATION_TABLE 171A: DD 36 0E 00 ld (ix+$0e),$00 ; set PLAYERS[0].AnimationCounter to 0 171E: DD 36 2E 00 ld (ix+$2e),$00 ; set PLAYERS[1].AnimationCounter to 0 1722: DD 34 02 inc (ix+$02) ; advance to next stage of life ; Drain player's fuel, and if not out of fuel animate the jet and read joystick controls to move PLAYER_ANIMATE: 1725: 21 06 41 ld hl,$4106 ; load HL with address of CURRENT_PLAYER_FUEL_DRAIN_COUNTER 1728: 35 dec (hl) ; decrement counter 1729: 20 0A jr nz,$1735 ; if counter hasn't hit zero, goto ANIMATE_AND_MOVE_PLAYER 172B: C3 B5 29 jp $29B5 ; jump to SET_CURRENT_PLAYER_FUEL_DRAIN_COUNTER to reset counter depending on level. Eventually jumps to $172E... ; HL = address of CURRENT_PLAYER_FUEL ($4105) 172E: 35 dec (hl) ; reduce amount of fuel left 172F: 20 04 jr nz,$1735 ; if not run out of fuel, goto $1735 1731: DD 34 02 inc (ix+$02) ; player has run out of fuel. increment PLAYER[].StageOfLife 1734: C9 ret 1735: DD 21 A0 43 ld ix,$43A0 ; address of PLAYER_TWO 1739: CD E4 13 call $13E4 ; call ANIMATE 173C: DD 21 80 43 ld ix,$4380 ; address of PLAYER_ONE 1740: CD E4 13 call $13E4 ; call ANIMATE 1743: CD 49 17 call $1749 ; call PLAYER_MOVE_VERTICAL 1746: C3 9E 17 jp $179E ; call PLAYER_MOVE_HORIZONTAL ; ; Read joystick state and move the player jet up or down the screen accordingly, if required. ; ; Expects: ; IX = pointer to PLAYER structure ; PLAYER_MOVE_VERTICAL: 1749: 3A 06 40 ld a,($4006) ; read IS_GAME_IN_PLAY 174C: 0F rrca ; move flag into carry 174D: D0 ret nc ; return if game is not in play 174E: 3A 0D 40 ld a,($400D) ; read CURRENT_PLAYER 1751: 0F rrca ; set carry if its player 2 1752: 38 33 jr c,$1787 ; if player 2, goto $1787 ; Player 1 is in charge. Read vertical joystick state. CHECK_IF_PLAYER_ONE_JOYSTICK_PUSHED_UP_OR_DOWN: 1754: 3A 12 40 ld a,($4012) ; read PORT_STATE_8102 1757: 06 00 ld b,$00 1759: CB 67 bit 4,a ; read IPT_JOYSTICK_UP 175B: 28 02 jr z,$175F ; if joystick not pushed up, goto $175F 175D: CB C0 set 0,b 175F: CB 77 bit 6,a ; read IPT_JOYSTICK_DOWN 1761: 28 02 jr z,$1765 ; if joystick not pushed down, goto $1765 1763: CB C8 set 1,b ; Bit 0 of B set: joystick pushed UP. Bit 1 set: joystick pushed DOWN. 1765: 78 ld a,b CHECK_IF_JOYSTICK_PUSHED_UP: 1766: 0F rrca ; move joystick UP flag into carry 1767: 30 0E jr nc,$1777 ; if joystick not pushed up, goto CHECK_IF_JOYSTICK_PUSHED_DOWN ; joystick has been pushed up. Can we move the player jet up? 1769: DD 7E 03 ld a,(ix+$03) ; read PLAYER.X 176C: 3D dec a ; tentatively decrement X coordinate 176D: FE 38 cp $38 ; has the jet flown as far up as it can go? 176F: D8 ret c ; yes, so return ; move the player jet up 1770: DD 77 03 ld (ix+$03),a ; otherwise update PLAYER.X 1773: DD 35 23 dec (ix+$23) 1776: C9 ret CHECK_IF_JOYSTICK_PUSHED_DOWN: 1777: 0F rrca ; move joystick DOWN flag into carry 1778: D0 ret nc ; if joystick not pushed down, return ; Joystick has been pushed down. Can we move the player jet down? 1779: DD 7E 03 ld a,(ix+$03) ; read PLAYER.X 177C: 3C inc a ; tentatively increment X coordinate 177D: FE D8 cp $D8 ; has the jet flown as far down as it can go? 177F: D0 ret nc ; yes, so return ; Move the player jet down 1780: DD 77 03 ld (ix+$03),a ; otherwise update PLAYER.X 1783: DD 34 23 inc (ix+$23) 1786: C9 ret ; ; Player 2 is in charge. Read joystick state. ; CHECK_IF_PLAYER_TWO_JOYSTICK_PUSHED_UP_OR_DOWN: 1787: 3A 12 40 ld a,($4012) ; read PORT_STATE_8102 178A: 06 00 ld b,$00 178C: CB 47 bit 0,a ; test IPT_JOYSTICK_DOWN bit 178E: 28 02 jr z,$1792 ; if player is not pushing stick down, goto $1792 1790: CB C8 set 1,b 1792: 3A 10 40 ld a,($4010) ; read PORT_STATE_8100 1795: CB 47 bit 0,a ; test IPT_JOYSTICK_UP bit 1797: 28 02 jr z,$179B ; if player is not pushing stick up, goto $179B 1799: CB C0 set 0,b ; Bit 0 of B set: joystick pushed UP. Bit 1 set: joystick pushed DOWN. 179B: 78 ld a,b 179C: 18 C8 jr $1766 ; jump to CHECK_IF_JOYSTICK_PUSHED_UP ; ; Read joystick state and move the player jet left or right accordingly, if required. ; ; Expects: ; IX = pointer to PLAYER structure ; PLAYER_MOVE_HORIZONTAL: 179E: 3A 06 40 ld a,($4006) ; read IS_GAME_IN_PLAY 17A1: 0F rrca ; move flag into carry 17A2: D0 ret nc ; return if game is not in play 17A3: 3A 0D 40 ld a,($400D) ; read CURRENT_PLAYER 17A6: 0F rrca ; set carry if its player 2 17A7: 38 26 jr c,$17CF ; if player 2, goto CHECK_IF_PLAYER_TWO_JOYSTICK_PUSHED_LEFT_OR_RIGHT ; Player 1's in charge, so read controller 1 bits 17A9: 3A 10 40 ld a,($4010) ; read PORT_STATE_8100 CHECK_IF_JOYSTICK_PUSHED_LEFT: 17AC: 07 rlca 17AD: 07 rlca 17AE: 07 rlca ; move IPT_JOYSTICK_LEFT bit into carry 17AF: 30 0E jr nc,$17BF ; if joystick is not being pushed left, goto CHECK_IF_JOYSTICK_PUSHED_RIGHT ; joystick is being pushed left. Can we move the player left? 17B1: DD 7E 04 ld a,(ix+$04) ; read PLAYER.Y 17B4: 3C inc a ; tentatively increment Y coordinate 17B5: FE D0 cp $D0 ; has the jet flown as far left as it can go? 17B7: D0 ret nc ; yes, so return 17B8: DD 77 04 ld (ix+$04),a ; otherwise update PLAYER.Y 17BB: DD 34 24 inc (ix+$24) 17BE: C9 ret CHECK_IF_JOYSTICK_PUSHED_RIGHT: 17BF: 07 rlca ; move 1PT_JOYSTICK_RIGHT bit into carry 17C0: D0 ret nc ; exit if joystick is not being pushed right ; joystick is being pushed right. Can we move the player right? 17C1: DD 7E 04 ld a,(ix+$04) ; read PLAYER.Y 17C4: 3D dec a ; tentatively decrement Y coordinate 17C5: FE 80 cp $80 ; has the jet flown as far right as it can go? 17C7: D8 ret c ; yes, so return 17C8: DD 77 04 ld (ix+$04),a ; otherwise update PLAYER.Y 17CB: DD 35 24 dec (ix+$24) 17CE: C9 ret ; Player 2's in charge, so read controller 2 bits CHECK_IF_PLAYER_TWO_JOYSTICK_PUSHED_LEFT_OR_RIGHT: 17CF: 3A 11 40 ld a,($4011) ; read PORT_STATE_8101 17D2: 18 D8 jr $17AC ; goto CHECK_IF_JOYSTICK_PUSHED_LEFT ; ; The player's run out of fuel. ; Make the player's jet lose altitude and crash into the ground. ; ; Expects: ; IX = pointer to PLAYER structure ; PLAYER_OUT_OF_FUEL: 17D4: DD 21 A0 43 ld ix,$43A0 ; load IX with address of PLAYERS[1] 17D8: CD E4 13 call $13E4 ; call ANIMATE 17DB: DD 21 80 43 ld ix,$4380 ; load IX with address of PLAYERS[0] 17DF: CD E4 13 call $13E4 ; call ANIMATE 17E2: DD 34 03 inc (ix+$03) ; increment PLAYER.X 17E5: DD 34 23 inc (ix+$23) 17E8: DD 7E 03 ld a,(ix+$03) ; read PLAYER.X 17EB: FE F0 cp $F0 ; has plane hit the ground yet? 17ED: D8 ret c ; return if not ; Plane's hit the ground and going to explode 17EE: DD 36 00 00 ld (ix+$00),$00 ; clear PLAYERS[0].IsActive 17F2: DD 36 01 01 ld (ix+$01),$01 ; set PLAYERS[0].IsExploding 17F6: DD 36 02 06 ld (ix+$02),$06 ; set PLAYERS[0].StageOfLife to 6 (see PLAYER_EXPLOSION_INIT @ $180A) 17FA: DD 36 20 00 ld (ix+$20),$00 ; clear PLAYERS[1].IsActive 17FE: DD 36 21 01 ld (ix+$21),$01 ; set PLAYERS[1].IsExploding 1802: DD 36 22 06 ld (ix+$22),$06 ; set PLAYERS[1].StageOfLife to 6 (see PLAYER_EXPLOSION_INIT @ $180A) 1806: C9 ret 1807: C9 ret 1808: C9 ret 1809: C9 ret PLAYER_EXPLOSION_INIT: 180A: 21 7E 18 ld hl,$187E ; load HL with address of PLAYER_ONE_EXPLOSION_ANIMATION_TABLE 180D: 22 8C 43 ld ($438C),hl 1810: DD 36 0E 00 ld (ix+$0e),$00 1814: DD 36 0F 6F ld (ix+$0f),$6F ; set PLAYER.ExplosionCounter 1818: 21 96 18 ld hl,$1896 ; load HL with address of PLAYER_TWO_EXPLOSION_ANIMATION_TABLE 181B: 22 AC 43 ld ($43AC),hl 181E: DD 36 2E 00 ld (ix+$2e),$00 1822: DD 34 02 inc (ix+$02) ; bump StageOfLife PLAYER_EXPLOSION_ANIMATE: 1825: 3A 5F 42 ld a,($425F) ; read TIMING_VARIABLE 1828: E6 03 and $03 182A: CC 54 18 call z,$1854 ; if timing variable is a multiple of 4, call CYCLE_LANDSCAPE_COLOUR 182D: DD 21 A0 43 ld ix,$43A0 1831: CD E4 13 call $13E4 ; call ANIMATE 1834: DD 21 80 43 ld ix,$4380 1838: CD E4 13 call $13E4 ; call ANIMATE 183B: 3A 15 41 ld a,($4115) ; read LANDSCAPE_SCROLL_CONTROL_COUNTER 183E: A7 and a ; test if zero 183F: 20 06 jr nz,$1847 ; if not zero, goto CHECK_IF_PLAYER_EXPLOSION_FINISHED ; These 2 lines of code are never called. From tinkering I've found they are meant to scroll the jet explosion animation off screen. 1841: DD 34 04 inc (ix+$04) 1844: DD 34 24 inc (ix+$24) ; Have we finished exploding yet? CHECK_IF_PLAYER_EXPLOSION_FINISHED: 1847: DD 35 0F dec (ix+$0f) ; decrement PLAYER.ExplosionCounter 184A: C0 ret nz ; if the explosion counter hasn't reached zero, the explosion animation hasn't finished, so exit ; explosion animation is complete 184B: DD 36 01 00 ld (ix+$01),$00 ; reset PLAYER.IsExploding flag 184F: DD 36 21 00 ld (ix+$21),$00 1853: C9 ret ; ; The player has been killed. Cycle the landscape colours. ; CYCLE_LANDSCAPE_COLOUR: 1854: 3A 17 41 ld a,($4117) ; read LANDSCAPE_COLOUR 1857: 3C inc a ; increment it to next colour 1858: E6 07 and $07 ; clamp colour to value between 0 and 7 185A: 32 17 41 ld ($4117),a ; set LANDSCAPE_COLOUR 185D: C9 ret 185E: C9 ret 185F: C9 ret PLAYER_ONE_JET_ANIMATION_TABLE: 1860: 06 28 05 ; colour = 06, code = $28, delay = $05 06 2A 05 ; colour = 06, code = $2A, delay = $05 06 2C 05 ; colour = 06, code = $2C, delay = $05 06 2E 05 ; colour = 06, code = $2E, delay = $05 FF ; end of animation marker 60 18 ; pointer to PLAYER_ONE_JET_ANIMATION_TABLE ($1860) - this animation is cyclic PLAYER_TWO_JET_ANIMATION_TABLE: 186F: 00 27 05 ; colour = 0, code = $27, delay = $05 00 29 05 ; colour = 0, code = $29, delay = $05 00 2B 05 ; colour = 0, code = $2B, delay = $05 00 2D 05 ; colour = 0, code = $2D, delay = $05 FF ; end of animation marker 6F 18 ; pointer to PLAYER_TWO_JET_ANIMATION_TABLE ($186F) - this animation is cyclic PLAYER_ONE_EXPLOSION_ANIMATION_TABLE: 187E: 04 3C 10 04 3D 10 04 3C 10 04 3D 10 04 3C 10 04 3D 10 04 3E 10 FF ; end of animation marker 7E 18 ; pointer to PLAYER_ONE_EXPLOSION_ANIMATION_TABLE ($187E) - this animation is cyclic PLAYER_TWO_EXPLOSION_ANIMATION_TABLE: 1896: 04 BC 10 04 BD 10 04 BC 10 04 BD 10 04 BC 10 04 BD 10 04 BE 10 FF ; end of animation marker 96 18 ; pointer to PLAYER_TWO_EXPLOSION_ANIMATION_TABLE ($1896) - this animation is cyclic ; ; Handles all GROUND_OBJECTS from cradle to grave, ie: initialisation/ animation/ explosion / death /deletion from screen. ; ; GROUND_OBJECT_ANIMATION_AND_MOVEMENT: ; Protection check 18AE: 2A B9 40 ld hl,($40B9) 18B1: 36 0F ld (hl),$0F ; end protection check 18B3: DD 21 80 42 ld ix,$4280 ; load IX with address of GROUND_OBJECTS 18B7: 11 20 00 ld de,$0020 ; sizeof(GROUND_OBJECT) 18BA: 06 08 ld b,$08 18BC: D9 exx 18BD: CD C6 18 call $18C6 ; call GROUND_OBJECT_STAGE_OF_LIFE 18C0: D9 exx 18C1: DD 19 add ix,de 18C3: 10 F7 djnz $18BC 18C5: C9 ret GROUND_OBJECT_STAGE_OF_LIFE: 18C6: DD 7E 00 ld a,(ix+$00) ; read GROUND_OBJECT.IsActive flag 18C9: DD B6 01 or (ix+$01) ; combine with GROUND_OBJECT.IsExploding flag 18CC: 0F rrca ; move result into carry 18CD: D0 ret nc ; return if neither flag is set 18CE: DD 7E 02 ld a,(ix+$02) ; read GROUND_OBJECT.StageOfLife 18D1: EF rst $28 18D2: E6 18 ; GROUND_OBJECT_INIT 2F 19 ; GROUND_OBJECT_ANIMATE 47 19 ; $1947: RET 48 19 ; GROUND_OBJECT_DELETE 62 19 ; $1962: RET 63 19 ; $1963: RET 64 19 ; GROUND_OBJECT_EXPLOSION_INIT A7 19 ; GROUND_OBJECT_EXPLOSION_ANIMATE C4 19 ; $19C4: RET C5 19 ; $19C5: RET ; ; Initialise a GROUND_OBJECT. ; ; ; IX = pointer to vacant GROUND_OBJECT structure GROUND_OBJECT_INIT: ; get pointer to character RAM address where the ground object should be drawn 18E6: 2A 1B 41 ld hl,($411B) ; read NEXT_GROUND_OBJECT_CHAR_PTR 18E9: DD 75 18 ld (ix+$18),l ; set GROUND_OBJECT.CharRamPtrLo 18EC: DD 74 19 ld (ix+$19),h ; set GROUND_OBJECT.CharRamPtrHi ; calculate screen coordinates for GROUND_OBJECT to use in collision detection 18EF: 3A 16 41 ld a,($4116) ; read LANDSCAPE_SCROLL_COUNTER 18F2: E6 0F and $0F 18F4: C6 F8 add a,$F8 18F6: DD 77 04 ld (ix+$04),a ; set GROUND_OBJECT.Y 18F9: 7D ld a,l 18FA: E6 1F and $1F 18FC: 07 rlca 18FD: 07 rlca 18FE: 07 rlca 18FF: C6 08 add a,$08 1901: DD 77 03 ld (ix+$03),a ; set GROUND_OBJECT.X ; Identify the animation table the GROUND_OBJECT needs 1904: DD 7E 17 ld a,(ix+$17) ; read GROUND_OBJECT.ObjectType 1907: A7 and a 1908: 28 15 jr z,$191F ; if GROUND_OBJECT.ObjectType==0, goto GROUND_OBJECT_ROCKET_INIT 190A: 3D dec a 190B: 28 08 jr z,$1915 ; if GROUND_OBJECT.ObjectType==1, goto GROUND_OBJECT_FUEL_TANK_INIT 190D: 3D dec a 190E: 28 0A jr z,$191A ; if GROUND_OBJECT.ObjectType==2, goto GROUND_OBJECT_MYSTERY_INIT 1910: 21 D8 19 ld hl,$19D8 ; otherwise, the object type is "Base" (the only animated ground object) 1913: 18 0D jr $1922 GROUND_OBJECT_FUEL_TANK_INIT: 1915: 21 CC 19 ld hl,$19CC ; load HL with address of GROUND_OBJECT_FUEL_TANK_ANIMATION_TABLE 1918: 18 08 jr $1922 GROUND_OBJECT_MYSTERY_INIT: 191A: 21 D2 19 ld hl,$19D2 ; load HL with address of GROUND_OBJECT_MYSTERY_ANIMATION_TABLE 191D: 18 03 jr $1922 GROUND_OBJECT_ROCKET_INIT: 191F: 21 C6 19 ld hl,$19C6 ; load HL with address of GROUND_OBJECT_ROCKET_ANIMATION_TABLE ; Main part of GROUND_OBJECT_INIT - invoked for all ground object types ; HL = pointer to animation table for the ground object 1922: DD 75 0C ld (ix+$0c),l ; set GROUND_OBJECT.AnimPtrLo 1925: DD 74 0D ld (ix+$0d),h ; set GROUND_OBJECT.AnimPtrHi 1928: DD 36 0E 00 ld (ix+$0e),$00 ; set GROUND_OBJECT.AnimationCounter 192C: DD 34 02 inc (ix+$02) ; set GROUND_OBJECT.StageOfLife to 2 (GROUND_OBJECT_ANIMATE) ; IX = pointer to GROUND_OBJECT GROUND_OBJECT_ANIMATE: 192F: CD E4 13 call $13E4 ; call ANIMATE ; keep the ground object's Y coordinate in sync with landscape. 1932: 3A 15 41 ld a,($4115) ; read LANDSCAPE_SCROLL_CONTROL_COUNTER 1935: A7 and a 1936: C0 ret nz 1937: DD 34 04 inc (ix+$04) ; increment GROUND_OBJECT.Y ; has the ground object scrolled off the screen? 193A: DD 7E 04 ld a,(ix+$04) 193D: C6 14 add a,$14 193F: FE 08 cp $08 1941: D0 ret nc ; ground object has scrolled off the screen. Set stage of life to be 3 (GROUND_OBJECT_DELETE) 1942: DD 36 02 03 ld (ix+$02),$03 1946: C9 ret 1947: C9 ret ; IX = pointer to GROUND_OBJECT to be deleted from screen. GROUND_OBJECT_DELETE: 1948: DD 6E 18 ld l,(ix+$18) ; read GROUND_OBJECT.CharRamPtrLo 194B: DD 66 19 ld h,(ix+$19) ; read GROUND_OBJECT.CharRamPtrHi ; now HL = address in character RAM 194E: 3E 10 ld a,$10 ; ordinal of empty character 1950: 77 ld (hl),a ; erase 1st character 1951: 23 inc hl 1952: 77 ld (hl),a ; erase 2nd character 1953: 11 1F 00 ld de,$001F ; Add 31 to bump HL to character RAM line below 1956: 19 add hl,de 1957: 77 ld (hl),a ; erase 3rd character 1958: 23 inc hl 1959: 77 ld (hl),a ; erase 4th character 195A: AF xor a 195B: DD 77 00 ld (ix+$00),a ; clear GROUND_OBJECT.IsActive 195E: DD 77 01 ld (ix+$01),a ; clear GROUND_OBJECT.IsExploding 1961: C9 ret 1962: C9 ret 1963: C9 ret GROUND_OBJECT_EXPLOSION_INIT: ; First find out what type of object is exploding, then pick the correct animation table. 1964: DD 7E 17 ld a,(ix+$17) ; read GROUND_OBJECT.ObjectType 1967: 3D dec a 1968: 28 05 jr z,$196F ; if GROUND_OBJECT.ObjectType==1, goto GROUND_OBJECT_EXPLOSION_INIT_FUEL_TANK 196A: 3D dec a 196B: 28 09 jr z,$1976 ; if GROUND_OBJECT.ObjectType==2, goto GROUND_OBJECT_EXPLOSION_INIT_MYSTERY 196D: 18 23 jr $1992 ; otherwise, use the default explosion animation. GROUND_OBJECT_EXPLOSION_INIT_FUEL_TANK: 196F: 21 F3 19 ld hl,$19F3 ; address of FUEL_TANK_EXPLOSION_ANIMATION_TABLE 1972: 3E 3F ld a,$3F ; counter for how long explosion should last. Higher = longer 1974: 18 21 jr $1997 ; ; The "Mystery" has been hit. Instead of an explosion animation, we display a points value. ; GROUND_OBJECT_EXPLOSION_INIT_MYSTERY: 1976: DD 46 1A ld b,(ix+$1a) ; read GROUND_OBJECT.MysteryPointsType (see also: MYSTERY_SHOT_AWARD_RANDOM_PTS @$2296) 1979: 3E 4F ld a,$4F ; counter for how long points value should be displayed. Higher = longer 197B: 05 dec b 197C: 28 05 jr z,$1983 ; if GROUND_OBJECT.MysteryPointsType == 1, goto GROUND_OBJECT_EXPLOSION_INIT_MYSTERY_DISPLAY_200PTS 197E: 05 dec b 197F: 28 07 jr z,$1988 ; if GROUND_OBJECT.MysteryPointsType == 2, goto GROUND_OBJECT_EXPLOSION_INIT_MYSTERY_DISPLAY_300PTS 1981: 18 0A jr $198D ; goto GROUND_OBJECT_EXPLOSION_INIT_MYSTERY_DISPLAY_100PTS ; Display "200PTS" GROUND_OBJECT_EXPLOSION_INIT_MYSTERY_DISPLAY_200PTS: 1983: 21 0C 1A ld hl,$1A0C ; load HL with address of MYSTERY_200PTS_ANIMATION_TABLE 1986: 18 0F jr $1997 ; Display "300PTS" GROUND_OBJECT_EXPLOSION_INIT_MYSTERY_DISPLAY_300PTS: 1988: 21 12 1A ld hl,$1A12 ; load HL with address of MYSTERY_300PTS_ANIMATION_TABLE 198B: 18 0A jr $1997 ; Display "100PTS" GROUND_OBJECT_EXPLOSION_INIT_MYSTERY_DISPLAY_100PTS: 198D: 21 02 1A ld hl,$1A02 ; load HL with address of MYSTERY_100PTS_ANIMATION_TABLE 1990: 18 05 jr $1997 1992: 21 E4 19 ld hl,$19E4 ; load HL with address of DEFAULT_EXPLOSION_ANIMATION_TABLE 1995: 3E 3F ld a,$3F ; Main part of GROUND_OBJECT_EXPLOSION_INIT - invoked for all ground object types ; HL = pointer to animation table for the exploding ground object 1997: DD 75 0C ld (ix+$0c),l ; set GROUND_OBJECT.AnimPtrLo 199A: DD 74 0D ld (ix+$0d),h ; set GROUND_OBJECT.AnimPtrHi 199D: DD 36 0E 00 ld (ix+$0e),$00 ; set GROUND_OBJECT.AnimationCounter 19A1: DD 77 0F ld (ix+$0f),a ; set GROUND_OBJECT.ExplosionCounter 19A4: DD 34 02 inc (ix+$02) ; set GROUND_OBJECT.StageOfLife to 2 (GROUND_OBJECT_ANIMATE) ; ; Animate the exploding GROUND_OBJECT. ; ; IX = pointer to GROUND_OBJECT GROUND_OBJECT_EXPLOSION_ANIMATE: 19A7: CD E4 13 call $13E4 ; call ANIMATE ; has the explosion counter counted down to zero? If so, the explosion animation is done, remove this ground object from screen. 19AA: DD 35 0F dec (ix+$0f) ; decrement GROUND_OBJECT.ExplosionCounter 19AD: 28 10 jr z,$19BF ; if counter has reached zero, explosion has burnt itself out, goto GROUND_OBJECT_EXPLOSION_DONE ; keep explosion's Y coordinate in sync with landscape scroll. 19AF: 3A 15 41 ld a,($4115) ; read LANDSCAPE_SCROLL_CONTROL_COUNTER 19B2: A7 and a 19B3: C0 ret nz 19B4: DD 34 04 inc (ix+$04) ; increment GROUND_OBJECT.Y ; has the explosion scrolled off the screen? 19B7: DD 7E 04 ld a,(ix+$04) 19BA: C6 14 add a,$14 19BC: FE 08 cp $08 19BE: D0 ret nc ; Explosion's either burnt out or gone off screen. GROUND_OBJECT_EXPLOSION_DONE: 19BF: DD 36 02 03 ld (ix+$02),$03 ; set GROUND_OBJECT.StageOfLife to 3 (GROUND_OBJECT_DELETE) to delete this object 19C3: C9 ret 19C4: C9 ret 19C5: C9 ret ; The rocket, fuel tank and MYSTERY are all cyclic animations with one frame of animation. GROUND_OBJECT_ROCKET_ANIMATION_TABLE: 19C6: 02 1C 10 ; colour = 02, code = $1C, delay = $10 FF ; end of animation marker C6 19 ; pointer to $19C6, GROUND_OBJECT_ROCKET_ANIMATION_TABLE GROUND_OBJECT_FUEL_TANK_ANIMATION_TABLE: 19CC: 02 10 10 ; colour = 02, code = $10, delay = $10 FF ; end of animation marker CC 19 ; pointer to $19CC, GROUND_OBJECT_FUEL_TANK_ANIMATION_TABLE GROUND_OBJECT_MYSTERY_ANIMATION_TABLE: 19D2: 02 33 10 ; colour = 02, code = $33, delay = $10 FF ; end of animation marker D2 19 ; pointer to $19D2, GROUND_OBJECT_MYSTERY_ANIMATION_TABLE GROUND_OBJECT_BASE_ANIMATION_TABLE: 19D8: 00 2F 06 ; colour = 0, code = $2F, delay = $06 00 26 06 ; colour = 0, code = $26, delay = $06 00 1F 06 ; colour = 0, code = $1F, delay = $06 FF ; end of animation marker D8 19 ; pointer to $19D8, GROUND_OBJECT_BASE_ANIMATION_TABLE DEFAULT_EXPLOSION_ANIMATION_TABLE: 19E4: 02 38 10 02 39 10 02 3A 10 02 3B 10 FF ; end of animation marker E4 19 ; Note how the fuel tank explosion animation (for when you hit unlaunched rockets) is identical to the default explosion animation above. FUEL_TANK_EXPLOSION_ANIMATION_TABLE: 19F3: 02 38 10 ; colour = 02, code = $38, delay = $10 02 39 10 02 3A 10 02 3B 10 FF ; end of animation marker F3 19 ; pointer to $19F3, FUEL_TANK_EXPLOSION_ANIMATION_TABLE MYSTERY_100PTS_ANIMATION_TABLE: 1A02: 02 11 50 ; colour = 02, code = $11, delay = $50 FF ; end of animation marker 02 1A ; pointer to $1A02, MYSTERY_100PTS_ANIMATION_TABLE ; called by $0A78 1A08: C8 ret z 1A09: C3 7B 0A jp $0A7B ; Animation table for "200PTS" displayed when Mystery shot MYSTERY_200PTS_ANIMATION_TABLE: 1A0C: 02 12 50 ; colour = 02, code = 12, delay = 50 FF ; end of animation marker 0C 1A ; pointer to $1A0C, MYSTERY_200PTS_ANIMATION_TABLE ; Animation table for "300PTS" displayed when Mystery shot MYSTERY_300PTS_ANIMATION_TABLE: 1A12: 02 13 50 ; colour = 02, code = 13, delay = 50 FF ; end of animation marker 12 1A ; pointer to $1A12, MYSTERY_300PTS_ANIMATION_TABLE ; ; Clear specified number of character rows and then increment SCRIPT_STAGE ; As the screen is rotated 90 degrees, it will appear as if a single column of characters is being cleared ; from right to left, one by one. ; ; value in TEMP_COUNTER_4009 = number of character rows left to clear ; CLEAR_SCREEN_1A18: 1A18: 2A 0B 40 ld hl,($400B) ; read TEMP_CHAR_RAM_PTR 1A1B: 06 20 ld b,$20 ; number of characters per row 1A1D: 3E 10 ld a,$10 ; ordinal of character (space) 1A1F: D7 rst $10 ; fill memory 1A20: 22 0B 40 ld ($400B),hl ; update TEMP_CHAR_RAM_PTR 1A23: 21 09 40 ld hl,$4009 ; load HL with address of TEMP_COUNTER_4009 1A26: 35 dec (hl) 1A27: C0 ret nz 1A28: 2C inc l ; bump HL to point to SCRIPT_STAGE 1A29: 34 inc (hl) ; increment SCRIPT_STAGE 1A2A: 21 E9 0A ld hl,$0AE9 ; load HL with address of COLOUR_ATTRIBUTE_TABLE_0AE9 1A2D: C3 D9 0A jp $0AD9 ; jump to SET_COLOUR_ATTRIBUTES_FOR_ENTIRE_SCREEN PLAYER_BOMB_ANIMATION_AND_MOVEMENT: ; first, some protection code.. jump to $1A38 if not interested 1A30: 3E 41 ld a,$41 1A32: 07 rlca 1A33: 67 ld h,a 1A34: 2E 02 ld l,$02 ; make HL = $8202, IO port for protection chip 1A36: 36 0F ld (hl),$0F ; write to protection chip ; end of protection code 1A38: DD 21 C0 43 ld ix,$43C0 ; load IX with address of PLAYER_BOMBS array 1A3C: 11 20 00 ld de,$0020 ; sizeof (PLAYER_BOMB) 1A3F: 06 02 ld b,$02 ; max number of player bombs on screen you can have 1A41: D9 exx 1A42: CD 4B 1A call $1A4B ; call PLAYER_BOMB_STAGE_OF_LIFE 1A45: D9 exx 1A46: DD 19 add ix,de 1A48: 10 F7 djnz $1A41 1A4A: C9 ret ; ; Expects: ; IX = pointer to PLAYER_BOMB struct ; PLAYER_BOMB_STAGE_OF_LIFE: 1A4B: DD 7E 00 ld a,(ix+$00) ; read PLAYER_BOMB.IsActive flag 1A4E: DD B6 01 or (ix+$01) ; combine with PLAYER_BOMB.IsExploding flag 1A51: 0F rrca ; if either flags were set, carry flag will now be set 1A52: D0 ret nc ; exit if bomb is neither active nor exploding. ; OK, we have a bomb that's either active or exploding. What stage of its "life" is it at? ; Jump to routine most appropriate for its stage of life. 1A53: DD 7E 02 ld a,(ix+$02) 1A56: EF rst $28 1A57: 6B 1A ; $1A6B ; PLAYER_BOMB_INIT 94 1A ; $1A94 ; PLAYER_BOMB_ANIMATE A8 1A ; $1AA8 ; RET instruction A9 1A ; $1AA9 ; RET instruction AA 1A ; $1AAA ; RET instruction AB 1A ; $1AAB ; RET instruction AC 1A ; $1AAC ; PLAYER_BOMB_EXPLOSION_INIT C0 1A ; $1AC0 ; PLAYER_BOMB_EXPLOSION_ANIMATE D6 1A ; $1AD6 ; RET instruction D7 1A ; $1AD7 ; RET instruction PLAYER_BOMB_INIT: ; first position the bomb below the player jet 1A6B: 3A 83 43 ld a,($4383) ; read PLAYERS[0].X 1A6E: C6 04 add a,$04 1A70: DD 77 03 ld (ix+$03),a ; set PLAYER_BOMB.X 1A73: 3A 84 43 ld a,($4384) ; read PLAYERS[0].Y 1A76: C6 08 add a,$08 1A78: DD 77 04 ld (ix+$04),a ; set PLAYER_BOMB.Y ; define animation frames for bomb 1A7B: 21 D8 1A ld hl,$1AD8 ; load HL with address of PLAYER_BOMB_ANIMATION_TABLE 1A7E: DD 75 0C ld (ix+$0c),l ; set PLAYER_BOMB.AnimPtrLo 1A81: DD 74 0D ld (ix+$0d),h ; set PLAYER_BOMB.AnimPtrHi 1A84: DD 36 0E 00 ld (ix+$0e),$00 ; set PLAYER_BOMB.AnimationCounter ; define flightpath of bomb 1A88: 21 FF 1A ld hl,$1AFF ; load HL with address of PLAYER_BOMB_PATH 1A8B: DD 75 13 ld (ix+$13),l ; set PLAYER_BOMB.PathPtrLo 1A8E: DD 74 14 ld (ix+$14),h ; set PLAYER_BOMB.PathPtrHi 1A91: DD 34 02 inc (ix+$02) ; advance PLAYER_BOMB.StageOfLife ; ; Expects: ; IX = pointer to PLAYER_BOMB structure ; PLAYER_BOMB_ANIMATE: 1A94: CD E4 13 call $13E4 ; call ANIMATE 1A97: CD 78 15 call $1578 ; call FOLLOW_PATH ; has the bomb gone down the screen as far as it can? 1A9A: DD 7E 03 ld a,(ix+$03) ; read PLAYER_BOMB.X 1A9D: FE F0 cp $F0 1A9F: D8 ret c ; return if <#$F0 ; bomb's hit the ground. Make it explode! 1AA0: AF xor a 1AA1: DD 77 00 ld (ix+$00),a ; clear PLAYER_BOMB.IsActive 1AA4: DD 77 01 ld (ix+$01),a ; clear PLAYER_BOMB.IsExploding 1AA7: C9 ret 1AA8: C9 ret 1AA9: C9 ret 1AAA: C9 ret 1AAB: C9 ret ; ; ; ; PLAYER_BOMB_EXPLOSION_INIT: 1AAC: 21 F0 1A ld hl,$1AF0 ; load HL with address of PLAYER_BOMB_EXPLOSION_ANIMATION_TABLE 1AAF: DD 75 0C ld (ix+$0c),l ; write LSB to PLAYER_BOMB.AnimPtrLo 1AB2: DD 74 0D ld (ix+$0d),h ; write MSB to PLAYER_BOMB.AnimPtrLo 1AB5: DD 36 0E 00 ld (ix+$0e),$00 ; set PLAYER_BOMB.AnimationCounter 1AB9: DD 36 0F 23 ld (ix+$0f),$23 ; set PLAYER_BOMB.ExplosionCounter 1ABD: DD 34 02 inc (ix+$02) ; advance to next stage of life PLAYER_BOMB_EXPLOSION_ANIMATE: 1AC0: CD E4 13 call $13E4 ; call ANIMATE 1AC3: DD 35 0F dec (ix+$0f) ; decrement PLAYER_BOMB.ExplosionCounter 1AC6: 20 05 jr nz,$1ACD ; if counter hasn't reached zero goto CHECK_IF_PLAYER_BOMB_EXPLOSION_SHOULD_SCROLL_TOO ; explosion animation has completed. 1AC8: AF xor a 1AC9: DD 77 01 ld (ix+$01),a ; reset PLAYER_BOMB.IsExploding flag 1ACC: C9 ret ; The only time a bomb explosion won't scroll off the screen is when the player jet is hit. CHECK_IF_PLAYER_BOMB_EXPLOSION_SHOULD_SCROLL_TOO: 1ACD: 3A 15 41 ld a,($4115) ; read LANDSCAPE_SCROLL_CONTROL_COUNTER 1AD0: A7 and a ; test if zero 1AD1: C0 ret nz ; return if not ; Scroll the explosion off screen 1AD2: DD 34 04 inc (ix+$04) ; increment PLAYER_BOMB.Y 1AD5: C9 ret 1AD6: C9 ret 1AD7: C9 ret PLAYER_BOMB_ANIMATION_TABLE: 1AD8: 06 21 04 ; colour = 06, code = 21, delay = 04 06 22 04 06 21 04 06 22 04 06 23 08 06 24 08 06 25 FE FF ; end of animation marker D8 1A ; pointer to $1AD8, PLAYER_BOMB_ANIMATION_TABLE PLAYER_BOMB_EXPLOSION_ANIMATION_TABLE: 1AF0: 06 38 09 ; colour = 06, code = 38, delay = 09 06 39 09 06 3A 09 06 3B 09 FF ; end of animation marker F0 1A ; pointer to $1AF0, PLAYER_BOMB_EXPLOSION_ANIMATION_TABLE ; ; This defines the path that a player bomb takes. ; ; See FOLLOW_PATH ($1578) for description of the table. ; PLAYER_BOMB_PATH: 1AFF: 00 00 ; X delta = 0, Y delta = 0 01 00 ; X delta = 1, Y delta = 0 00 FF ; X delta = 0, Y delta = -1 (remember, bytes are signed) ; .. you get the idea.. Now here's the rest of the deltas, from $1B05: 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 01 FF 00 FF 00 FF 00 FF 01 FF 00 FF 00 FF 01 FF 00 FF 01 FF 01 FF 00 FF 01 FF 01 FF 01 FF 01 00 01 00 01 FF 01 FF 01 00 01 FF 01 00 01 FF 01 00 01 00 01 FF 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 80 ; marker byte specifying "end of path" FF 1A ; pointer to $1AFF, PLAYER_BOMB_PATH - path repeats itself ; ; ; ; ; ROCKET_ANIMATION_AND_MOVEMENT: ; protection code: if not interested skip to $1CA7 1C98: 2A B9 40 ld hl,($40B9) ; read PROTECTION_PORT_PTR_1 1C9B: 36 00 ld (hl),$00 ; write to protection 1C9D: 3E 12 ld a,$12 1C9F: 0F rrca 1CA0: 77 ld (hl),a ; write to protection 1CA1: 4E ld c,(hl) ; read from protection 1CA2: 36 0A ld (hl),$0A ; write to protection 1CA4: C6 FB add a,$FB 1CA6: 77 ld (hl),a ; write to protection ; end protection code ; Are we on a landscape with flying rockets? 1CA7: 3A 1D 41 ld a,($411D) ; read LANDSCAPE_FLAGS 1CAA: A7 and a ; are we on level 1? 1CAB: 28 03 jr z,$1CB0 ; yes, goto $1CAB 1CAD: FE 08 cp $08 ; are we on level 4? 1CAF: C0 ret nz ; Call ROCKET_STAGE_OF_LIFE for each INFLIGHT_ENEMY 1CB0: DD 21 00 44 ld ix,$4400 ; load IX with address of INFLIGHT_ENEMIES 1CB4: 11 20 00 ld de,$0020 ; sizeof(INFLIGHT_ENEMY) 1CB7: 06 04 ld b,$04 ; max number of INFLIGHT_ENEMIES on screen at one time 1CB9: D9 exx 1CBA: CD C3 1C call $1CC3 ; call ROCKET_STAGE_OF_LIFE 1CBD: D9 exx 1CBE: DD 19 add ix,de ; bump IX to point to next INFLIGHT_ENEMY 1CC0: 10 F7 djnz $1CB9 ; repeat until all rockets are done 1CC2: C9 ret ROCKET_STAGE_OF_LIFE: 1CC3: DD 7E 00 ld a,(ix+$00) ; read INFLIGHT_ENEMY.IsActive flag 1CC6: DD B6 01 or (ix+$01) ; combine with INFLIGHT_ENEMY.IsExploding flag 1CC9: 0F rrca ; move result into carry 1CCA: D0 ret nc ; exit if neither active nor exploding 1CCB: DD 7E 02 ld a,(ix+$02) 1CCE: EF rst $28 1CCF: E3 1C ; $1CE3 - ROCKET_INIT FC 1C ; $1CFC - ROCKET_ANIMATE 28 1D ; $1D28 - RET instruction 29 1D ; $1D29 - RET instruction 2A 1D ; $1D2A - RET instruction 2B 1D ; $1D2B - RET instruction 2C 1D ; $1D2C - ROCKET_EXPLOSION_INIT 40 1D ; $1D40 - ROCKET_EXPLOSION_ANIMATE 56 1D ; $1D56 - RET instruction 57 1D ; $1D57 - RET instruction ; Prepare to launch a rocket ROCKET_INIT: 1CE3: 21 58 1D ld hl,$1D58 ; load HL with address of ROCKET_ANIMATION_TABLE 1CE6: DD 75 0C ld (ix+$0c),l ; set INFLIGHT_ENEMY.AnimPtrLo 1CE9: DD 74 0D ld (ix+$0d),h ; set INFLIGHT_ENEMY.AnimPtrHi 1CEC: DD 36 0E 00 ld (ix+$0e),$00 ; set INFLIGHT_ENEMY.AnimationCounter 1CF0: 21 70 1D ld hl,$1D70 ; load HL with address of ROCKET_PATH 1CF3: DD 75 13 ld (ix+$13),l ; set INFLIGHT_ENEMY.PathPtrLo 1CF6: DD 74 14 ld (ix+$14),h ; set INFLIGHT_ENEMY.PathPtrHi 1CF9: DD 34 02 inc (ix+$02) ; advance to next stage of life. ROCKET_ANIMATE: 1CFC: CD E4 13 call $13E4 ; call ANIMATE 1CFF: CD 78 15 call $1578 ; call FOLLOW_PATH 1D02: 3A 17 41 ld a,($4117) ; read LANDSCAPE_COLOUR 1D05: DD 77 16 ld (ix+$16),a ; ensure colour is in sync with player 1D08: 3A 15 41 ld a,($4115) ; read LANDSCAPE_SCROLL_CONTROL_COUNTER 1D0B: A7 and a 1D0C: 20 03 jr nz,$1D11 1D0E: DD 34 04 inc (ix+$04) ; increment INFLIGHT_ENEMY.Y - this will make the rocket scroll with the scenery ; has the rocket gone off the left hand of screen (as player sees it)? 1D11: DD 7E 04 ld a,(ix+$04) ; read INFLIGHT_ENEMY.Y 1D14: FE F0 cp $F0 1D16: 38 08 jr c,$1D20 ; if Y < 240, rockets not gone off side of screen, goto $1D20 ; rocket's gone off screen. Free up its slot in INFLIGHT_ENEMIES 1D18: AF xor a 1D19: DD 77 00 ld (ix+$00),a ; reset INFLIGHT_ENEMY.IsActive 1D1C: DD 77 01 ld (ix+$01),a ; reset INFLIGHT_ENEMY.IsExploding 1D1F: C9 ret ; has this rocket flown up screen as far as it can go? 1D20: DD 7E 03 ld a,(ix+$03) ; read INFLIGHT_ENEMY.X 1D23: FE 28 cp $28 ; if X>=$28 (40 decimal) .. 1D25: D0 ret nc ; .. don't do anything ; rocket gone up screen as far as it can go, deactivate it 1D26: 18 F0 jr $1D18 1D28: C9 ret 1D29: C9 ret 1D2A: C9 ret 1D2B: C9 ret ROCKET_EXPLOSION_INIT: 1D2C: 21 61 1D ld hl,$1D61 ; load HL with address of ROCKET_EXPLOSION_ANIMATION_TABLE 1D2F: DD 75 0C ld (ix+$0c),l ; set INFLIGHT_ENEMY.AnimPtrLo 1D32: DD 74 0D ld (ix+$0d),h ; set INFLIGHT_ENEMY.AnimPtrHi 1D35: DD 36 0E 00 ld (ix+$0e),$00 ; set INFLIGHT_ENEMY.AnimationCounter 1D39: DD 36 0F 3F ld (ix+$0f),$3F ; set INFLIGHT_ENEMY.ExplosionCounter 1D3D: DD 34 02 inc (ix+$02) ; advance to next stage of life (ROCKET_EXPLOSION_ANIMATE) ROCKET_EXPLOSION_ANIMATE: 1D40: CD E4 13 call $13E4 ; call ANIMATE 1D43: DD 35 0F dec (ix+$0f) ; decrement INFLIGHT_ENEMY.ExplosionCounter 1D46: 20 05 jr nz,$1D4D ; if explosion hasn't finished, goto $1D4D 1D48: AF xor a 1D49: DD 77 01 ld (ix+$01),a ; reset INFLIGHT_ENEMY.IsExploding to terminate the explosion 1D4C: C9 ret ; Check if we need to scroll the explosion in time with the landscape 1D4D: 3A 15 41 ld a,($4115) ; read LANDSCAPE_SCROLL_CONTROL_COUNTER 1D50: A7 and a ; test if zero 1D51: C0 ret nz ; if not zero, then not time to scroll the rocket ; scroll exploding rocket off screen 1D52: DD 34 04 inc (ix+$04) ; increment INFLIGHT_ENEMY.Y 1D55: C9 ret 1D56: C9 ret 1D57: C9 ret ROCKET_ANIMATION_TABLE: 1D58: 00 1D 10 ; colour = 0, sprite code = $1D, delay before next frame = $10 00 1E 10 ; colour = 0, sprite code = $1E, delay before next frame = $10 FF ; end of animation marker 58 1D ; pointer to first animation ($1D58) - this animation is cyclic ROCKET_EXPLOSION_ANIMATION_TABLE: 1D61: 06 38 05 ; colour = 6, sprite code = $38, delay before next frame = $05 06 39 05 06 3A 05 06 3B 05 FF ; end of animation marker 61 1D ; pointer to first animation ($1D61) - this animation is cyclic ; Rocket only flies straight up. ROCKET_PATH: 1D70: FF 00 ; XDelta = -1, YDelta = 0 80 ; end of path marker 70 1D ; pointer to start ($1D70) UFO_ANIMATION_AND_MOVEMENT: ; Protection related code. If not interested, skip to $1D84 1D75: 3E 41 ld a,$41 1D77: 07 rlca 1D78: 67 ld h,a 1D79: 2E 02 ld l,$02 ; HL = $8202 (protection) 1D7B: 36 09 ld (hl),$09 ; write to protection 1D7D: 7E ld a,(hl) ; read from protection 1D7E: E6 F0 and $F0 1D80: FE B0 cp $B0 1D82: 20 F1 jr nz,$1D75 ; end of protection code ; Are we on a landscape with UFOs? 1D84: 3A 1D 41 ld a,($411D) ; read LANDSCAPE_FLAGS 1D87: FE 02 cp $02 ; are flags set for level 2 (UFOs) ? 1D89: C0 ret nz ; exit if not ; call UFO_STAGE_OF_LIFE for each INFLIGHT_ENEMY 1D8A: DD 21 00 44 ld ix,$4400 ; load IX with address of INFLIGHT_ENEMIES 1D8E: 11 20 00 ld de,$0020 ; sizeof(INFLIGHT_ENEMY) 1D91: 06 04 ld b,$04 ; max number of INFLIGHT_ENEMIES on screen at one time 1D93: D9 exx 1D94: CD 9D 1D call $1D9D ; call UFO_STAGE_OF_LIFE 1D97: D9 exx 1D98: DD 19 add ix,de ; bump IX to point to next INFLIGHT_ENEMY 1D9A: 10 F7 djnz $1D93 1D9C: C9 ret ; Expects: ; IX = pointer to INFLIGHT_ENEMY structure ; UFO_STAGE_OF_LIFE: 1D9D: DD 7E 00 ld a,(ix+$00) ; read INFLIGHT_ENEMY.IsActive flag 1DA0: DD B6 01 or (ix+$01) ; OR with INFLIGHT_ENEMY.IsExploding flag 1DA3: 0F rrca ; move result into carry 1DA4: D0 ret nc ; exit if ufo is neither active nor exploding 1DA5: DD 7E 02 ld a,(ix+$02) ; read INFLIGHT_ENEMY.StageOfLife 1DA8: EF rst $28 1DA9: BD 1D ; $1DBD - UFO_INIT D6 1D ; $1DD6 - UFO_AND_FIREBALL_ANIMATE EA 1D ; $1DEA - RET instruction EB 1D ; $1DEB - RET instruction EC 1D ; $1DEC - RET instruction ED 1D ; $1DED - RET instruction EE 1D ; $1DEE - UFO_AND_FIREBALL_EXPLOSION_INIT 02 1E ; $1E02 - UFO_AND_FIREBALL_EXPLOSION_ANIMATE 18 1E ; $1E18 - RET instruction 19 1E ; $1E19 - RET instruction UFO_INIT: 1DBD: 21 1A 1E ld hl,$1E1A ; load HL with address of UFO_ANIMATION_TABLE 1DC0: DD 75 0C ld (ix+$0c),l ; set INFLIGHT_ENEMY.AnimPtrLo 1DC3: DD 74 0D ld (ix+$0d),h ; set INFLIGHT_ENEMY.AnimPtrHi 1DC6: DD 36 0E 00 ld (ix+$0e),$00 ; set INFLIGHT_ENEMY.AnimationCounter 1DCA: 21 2F 1E ld hl,$1E2F ; load HL with address of UFO_PATH 1DCD: DD 75 13 ld (ix+$13),l ; set INFLIGHT_ENEMY.PathPtrLo 1DD0: DD 74 14 ld (ix+$14),h ; set INFLIGHT_ENEMY.PathPtrHi 1DD3: DD 34 02 inc (ix+$02) ; advance to next stage of life UFO_AND_FIREBALL_ANIMATE: 1DD6: CD E4 13 call $13E4 ; call ANIMATE 1DD9: CD 78 15 call $1578 ; call FOLLOW_PATH ; has this entity gone offscreen? 1DDC: DD 7E 04 ld a,(ix+$04) ; read INFLIGHT_ENEMY.Y 1DDF: FE F0 cp $F0 1DE1: D8 ret c ; if Y < 240, UFO/fireball has not gone off side of screen ; entity has gone off screen, deactivate it. 1DE2: AF xor a 1DE3: DD 77 00 ld (ix+$00),a ; reset INFLIGHT_ENEMY.IsActive 1DE6: DD 77 01 ld (ix+$01),a ; reset INFLIGHT_ENEMY.IsExploding 1DE9: C9 ret 1DEA: C9 ret 1DEB: C9 ret 1DEC: C9 ret 1DED: C9 ret ; ; ; ; ; UFO_AND_FIREBALL_EXPLOSION_INIT: 1DEE: 21 20 1E ld hl,$1E20 ; load HL with address of UFO_EXPLOSION_ANIMATION_TABLE 1DF1: DD 75 0C ld (ix+$0c),l ; set INFLIGHT_ENEMY.AnimPtrLo 1DF4: DD 74 0D ld (ix+$0d),h ; set INFLIGHT_ENEMY.AnimPtrHi 1DF7: DD 36 0E 00 ld (ix+$0e),$00 ; set INFLIGHT_ENEMY.AnimationCounter 1DFB: DD 36 0F 2B ld (ix+$0f),$2B ; set INFLIGHT_ENEMY.ExplosionCounter 1DFF: DD 34 02 inc (ix+$02) ; increment INFLIGHT_ENEMY.StageOfLife to UFO_AND_FIREBALL_EXPLOSION_ANIMATE UFO_AND_FIREBALL_EXPLOSION_ANIMATE: 1E02: CD E4 13 call $13E4 ; call ANIMATE 1E05: DD 35 0F dec (ix+$0f) ; decrement INFLIGHT_ENEMY.ExplosionCounter 1E08: 20 05 jr nz,$1E0F ; if counter hasn't reached zero goto CHECK_IF_PLAYER_BOMB_EXPLOSION_SHOULD_SCROLL_TOO ; explosion animation has completed. 1E0A: AF xor a 1E0B: DD 77 01 ld (ix+$01),a ; reset INFLIGHT_ENEMY.IsExploding flag 1E0E: C9 ret ; If the player's hit, don't scroll the UFO explosion off screen. CHECK_IF_UFO_EXPLOSION_SHOULD_SCROLL_TOO: 1E0F: 3A 15 41 ld a,($4115) ; read LANDSCAPE_SCROLL_CONTROL_COUNTER 1E12: A7 and a ; test if zero 1E13: C0 ret nz ; return if not ; Scroll the explosion off screen 1E14: DD 34 04 inc (ix+$04) ; increment INFLIGHT_ENEMY.Y 1E17: C9 ret 1E18: C9 ret 1E19: C9 ret ; The poor UFO only has one animation frame! UFO_ANIMATION_TABLE: 1E1A: 05 1A 10 ; colour 5, sprite code = $1A, delay before frame change = $10 FF ; end of animation marker 1A 1E ; pointer to first animation ($1E1A) - this animation is cyclic UFO_EXPLOSION_ANIMATION_TABLE: 1E20: 04 38 0B ; colour 4, sprite code = $38, delay before frame change = $0B 04 39 0B 04 3A 0B 04 3B 0B FF ; end of animation marker 20 1E ; pointer to first animation of this table ($1E20) - this animation is cyclic UFO_PATH: 1E2F: FF 00 ; XDelta = -1, YDelta = 0 FE 00 ; XDelta = -2, YDelta = 0 FE 00 FE 00 FE 00 FE 00 FE 00 FE 00 FE 00 FE 02 FE 00 FE 02 FE 00 FE 02 FE 02 FE 02 FE 02 00 02 00 02 02 02 02 02 02 02 02 02 02 02 02 02 02 00 02 02 02 00 02 02 02 00 02 02 02 00 02 00 02 00 02 00 02 00 02 02 02 00 02 00 02 00 02 02 02 00 02 00 02 00 02 00 02 02 02 00 02 02 02 00 02 00 02 02 02 02 02 02 00 02 00 02 00 02 FE 02 FE 02 FE 02 FE 02 FE 00 FE 02 FE 00 FE 02 FE 00 FE 02 FE 00 FE 02 FE 00 FE 00 FE 00 FE 00 FE 00 FE 00 1EC3: 80 ; marker byte specifying "end of path" 2F 1E ; pointer to $1E2F, UFO_PATH ; Are we on a landscape with fireballs? FIREBALL_ANIMATION_AND_MOVEMENT: 1EC6: ld a,($411D) ; read LANDSCAPE_FLAGS 1EC9: FE 01 cp $01 1ECB: C0 ret nz ; call FIREBALL_STAGE_OF_LIFE for each INFLIGHT_ENEMY 1ECC: DD 21 00 44 ld ix,$4400 ; load IX with address of INFLIGHT_ENEMIES 1ED0: 11 20 00 ld de,$0020 ; sizeof(INFLIGHT_ENEMY) 1ED3: 06 04 ld b,$04 ; max number of INFLIGHT_ENEMIES on screen at one time 1ED5: D9 exx 1ED6: CD DF 1E call $1EDF ; call FIREBALL_STAGE_OF_LIFE 1ED9: D9 exx 1EDA: DD 19 add ix,de ; bump IX to point to next INFLIGHT_ENEMY structure 1EDC: 10 F7 djnz $1ED5 ; repeat until b==0 1EDE: C9 ret ; ; Expects: ; IX = pointer to INFLIGHT_ENEMY structure ; FIREBALL_STAGE_OF_LIFE: 1EDF: DD 7E 00 ld a,(ix+$00) ; read INFLIGHT_ENEMY.IsActive flag 1EE2: DD B6 01 or (ix+$01) ; combine with INFLIGHT_ENEMY.IsExploding flag 1EE5: 0F rrca ; move result into carry 1EE6: D0 ret nc ; exit if neither active nor exploding 1EE7: DD 7E 02 ld a,(ix+$02) 1EEA: EF rst $28 1EEB: FF 1E ; $1EFF - FIREBALL_INIT D6 1D ; $1DD6 - UFO_AND_FIREBALL_ANIMATE EA 1D ; $1DEA - RET instruction EB 1D ; $1DEB - RET instruction EC 1D ; $1DEC - RET instruction ED 1D ; $1DED - RET instruction EE 1D ; $1DEE - UFO_AND_FIREBALL_EXPLOSION_INIT 02 1E ; $1E02 - UFO_AND_FIREBALL_EXPLOSION_ANIMATE 18 1E ; $1E18 - RET instruction 19 1E ; $1E19 - RET instruction FIREBALL_INIT: 1EFF: 21 1B 1F ld hl,$1F1B ; load HL with address of FIREBALL_ANIMATION_TABLE 1F02: DD 75 0C ld (ix+$0c),l ; set INFLIGHT_ENEMY.AnimPtrLo 1F05: DD 74 0D ld (ix+$0d),h ; set INFLIGHT_ENEMY.AnimPtrHi 1F08: DD 36 0E 00 ld (ix+$0e),$00 ; set INFLIGHT_ENEMY.AnimationCounter 1F0C: 21 30 1F ld hl,$1F30 ; load HL with address of FIREBALL_PATH 1F0F: DD 75 13 ld (ix+$13),l ; set INFLIGHT_ENEMY.PathPtrLo 1F12: DD 74 14 ld (ix+$14),h ; set INFLIGHT_ENEMY.PathPtrHi 1F15: DD 34 02 inc (ix+$02) ; advance to next stage of life (which is UFO_AND_FIREBALL_ANIMATE) 1F18: C3 D6 1D jp $1DD6 ; jump to UFO_AND_FIREBALL_ANIMATE FIREBALL_ANIMATION_TABLE: 1F1B: 00 35 05 ; colour 9, sprite code = $35, delay before frame change = $05 00 36 05 00 37 05 00 30 05 00 37 05 00 36 05 FF ; end of animation marker 1B 1F ; pointer to first animation of this table ($1F1B) - this animation is cyclic FIREBALL_PATH: 1F30: 00 04 ; XDelta = 0, YDelta = 4 80 ; end of path marker 30 1F ; pointer to start of path ($1F30) ; ; Handle the scrolling and positioning/colour of sprites. ; ; SCROLL_AND_SPRITES: 1F35: CD 44 1F call $1F44 ; call SET_PLAYFIELD_SCROLL_OFFSET_AND_COLOUR_ATTRIBUTES 1F38: CD 59 1F call $1F59 ; call MAP_GROUND_OBJECTS_TO_CHAR_BASED_GROUND_OBJECTS 1F3B: CD 8A 1F call $1F8A ; call SET_PLAYER_JET_SPRITE_POS_CODE_COLOUR 1F3E: CD 94 1F call $1F94 ; call SET_INFLIGHT_ENEMIES_SPRITE_POS_CODE_COLOUR 1F41: C3 E3 1F jp $1FE3 ; jump to SET_BULLETS_SPRITE_POSITIONS ; ; Sets the scroll offsets and colour attributes for the playfield only. ; SET_PLAYFIELD_SCROLL_OFFSET_AND_COLOUR_ATTRIBUTES: 1F44: 21 2A 40 ld hl,$402A ; pointer to scroll offset in OBJRAM_BACK_BUF 1F47: 06 19 ld b,$19 1F49: 3A 16 41 ld a,($4116) ; read LANDSCAPE_SCROLL_COUNTER 1F4C: ED 44 neg 1F4E: 4F ld c,a 1F4F: 3A 17 41 ld a,($4117) ; read LANDSCAPE_COLOUR 1F52: 71 ld (hl),c ; set scroll offset 1F53: 2C inc l 1F54: 77 ld (hl),a ; set colour attribute 1F55: 2C inc l 1F56: 10 FA djnz $1F52 1F58: C9 ret ; ; ; The GROUND_OBJECT is the main structure but CHAR_BASED_GROUND_OBJECT is its representation on screen. ; We need to "project" data from GROUND_OBJECT into a CHAR_BASED_GROUND_OBJECT so that the game can ; render the ground objects properly. ; ; Tech note: A projection is taking a structure and mapping it to a different structure. ; See: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/projection-operations ; ; MAP_GROUND_OBJECTS_TO_CHAR_BASED_GROUND_OBJECTS: 1F59: 21 60 42 ld hl,$4260 ; address of CHAR_BASED_GROUND_OBJECTS array 1F5C: DD 21 80 42 ld ix,$4280 ; address of GROUND_OBJECTS array 1F60: 11 20 00 ld de,$0020 ; sizeof(GROUND_OBJECT) 1F63: 06 08 ld b,$08 ; max number of ground objects on screen at one time 1F65: CD 6D 1F call $1F6D ; call MAP_GROUND_OBJECT_TO_CHAR_BASED_GROUND_OBJECT 1F68: DD 19 add ix,de ; bump IX to point to next GROUND_OBJECT 1F6A: 10 F9 djnz $1F65 ; repeat until all GROUND_OBJECTS are done 1F6C: C9 ret ; HL = pointer to target CHAR_BASED_GROUND_OBJECT structure ; IX = pointer to source GROUND_OBJECT structure MAP_GROUND_OBJECT_TO_CHAR_BASED_GROUND_OBJECT: 1F6D: DD 7E 00 ld a,(ix+$00) ; read GROUND_OBJECT.IsActive 1F70: DD B6 01 or (ix+$01) ; combine with GROUND_OBJECT.IsExploding 1F73: 0F rrca 1F74: 36 00 ld (hl),$00 ; reset CHAR_BASED_GROUND_OBJECT.Undrawn flag 1F76: D0 ret nc ; exit if ground object is not active or exploding 1F77: 36 01 ld (hl),$01 ; set CHAR_BASED_GROUND_OBJECT.Undrawn flag 1F79: 2C inc l 1F7A: DD 7E 12 ld a,(ix+$12) ; read GROUND_OBJECT.Code 1F7D: 77 ld (hl),a ; set CHAR_BASED_GROUND_OBJECT.Code 1F7E: 2C inc l 1F7F: DD 7E 18 ld a,(ix+$18) ; read GROUND_OBJECT.CharRamPtrLo 1F82: 77 ld (hl),a ; set CHAR_BASED_GROUND_OBJECT.CharRamPtrLo 1F83: 2C inc l 1F84: DD 7E 19 ld a,(ix+$19) ; read GROUND_OBJECT.CharRamPtrHi 1F87: 77 ld (hl),a ; set CHAR_BASED_GROUND_OBJECT.CharRamPtrHi 1F88: 2C inc l 1F89: C9 ret ; ; Set the position and colour of the player jet and player bomb sprites. ; SET_PLAYER_JET_AND_BOMBS_SPRITE_POS_CODE_COLOUR: 1F8A: DD 21 80 43 ld ix,$4380 ; load IX with address of PLAYER 1F8E: FD 21 60 40 ld iy,$4060 ; load IY with address of OBJRAM_BACK_BUF_SPRITES 1F92: 18 12 jr $1FA6 ; jump to SET_SPRITE_POS_CODE_COLOUR SET_INFLIGHT_ENEMIES_SPRITE_POS_CODE_COLOUR: 1F94: DD 21 00 44 ld ix,$4400 ; load IX with address of INFLIGHT_ENEMIES 1F98: FD 21 70 40 ld iy,$4070 ; pointer to sprite in OBJRAM_BACK_BUF_SPRITES 1F9C: 18 08 jr $1FA6 ; jump to SET_SPRITE_POS_CODE_COLOUR ; Unsure if this code is called. 1F9E: DD 21 80 44 ld ix,$4480 1FA2: FD 21 70 40 ld iy,$4070 ; pointer inside OBJRAM_BACK_BUF_SPRITES ; ; Set the position, code (animation frame) and colour of a sprite. ; ; IX = pointer to PLAYER or INFLIGHT_ENEMY structure. ; IY = pointer to a SPRITE structure in OBJRAM_BACK_BUF_SPRITES ; SET_SPRITE_POS_CODE_COLOUR: 1FA6: 01 08 04 ld bc,$0408 ; B = 4 (number of sprites to set position and colour), C =8 1FA9: DD 7E 00 ld a,(ix+$00) ; read Active flag 1FAC: DD B6 01 or (ix+$01) ; OR with Dying/Exploding flag 1FAF: 0F rrca ; move result into carry 1FB0: 30 27 jr nc,$1FD9 ; if entity is neither active or dying/exploding, goto $1FD9 ; entity is active or exploding, so sprite needs to be setup with correct position, colour and animation frame (code). 1FB2: DD 7E 16 ld a,(ix+$16) ; read sprite colour value 1FB5: FD 77 02 ld (iy+$02),a ; write to SPRITE.Colour 1FB8: DD 7E 03 ld a,(ix+$03) ; read X coordinate 1FBB: 91 sub c ; subtract 8 1FBC: FD 77 03 ld (iy+$03),a ; write to SPRITE.X 1FBF: DD 7E 04 ld a,(ix+$04) ; read Y coordinate 1FC2: 2F cpl ; flip bits 1FC3: 91 sub c ; subtract 8 1FC4: FD 77 00 ld (iy+$00),a ; write to SPRITE.Y 1FC7: DD 7E 12 ld a,(ix+$12) ; read sprite code 1FCA: FD 77 01 ld (iy+$01),a ; write to SPRITE.Code 1FCD: 11 20 00 ld de,$0020 1FD0: DD 19 add ix,de 1FD2: 1E 04 ld e,$04 ; sizeof(SPRITE) 1FD4: FD 19 add iy,de ; bump to next SPRITE record 1FD6: 10 D1 djnz $1FA9 ; repeat until 4 sprites done 1FD8: C9 ret ; if the entity is not active then place its sprite offscreen. 1FD9: FD 36 00 F8 ld (iy+$00),$F8 1FDD: FD 36 03 F8 ld (iy+$03),$F8 1FE1: 18 EA jr $1FCD ; ; ; Position sprites for all player bullets. ; ; SET_BULLETS_SPRITE_POSITIONS: 1FE3: DD 21 80 40 ld ix,$4080 ; load IX with address of OBJRAM_BACK_BUF_BULLETS 1FE7: FD 21 00 45 ld iy,$4500 ; load IY with address of PLAYER_BULLETS 1FEB: 06 07 ld b,$07 1FED: CD FB 1F call $1FFB 1FF0: 11 04 00 ld de,$0004 ; sizeof(BULLET_SPRITE) 1FF3: DD 19 add ix,de 1FF5: 1D dec e ; set DE to be 3, which is sizeof(PLAYER_BULLET) 1FF6: FD 19 add iy,de ; bump IY to point to next PLAYER_BULLET 1FF8: 10 F3 djnz $1FED 1FFA: C9 ret ; IX = pointer to BULLET_SPRITE structure ; IY = pointer to PLAYER_BULLET structure 1FFB: FD CB 00 46 bit 0,(iy+$00) ; test PLAYER_BULLET.IsActive flag 1FFF: 28 23 jr z,$2024 ; if bullet is not active 2001: FD 7E 02 ld a,(iy+$02) ; read PLAYER_BULLET.Y 2004: 2F cpl 2005: DD 77 01 ld (ix+$01),a ; set BULLET_SPRITE.Y 2008: FD 7E 01 ld a,(iy+$01) ; read PLAYER_BULLET.X 200B: C6 05 add a,$05 ; add 5 to value to get real X coordinate 200D: DD 77 03 ld (ix+$03),a ; set BULLET_SPRITE.X 2010: 3A 0F 40 ld a,($400F) ; read IS_COCKTAIL flag 2013: 0F rrca ; move flag into carry 2014: 30 06 jr nc,$201C ; if not a cocktail cabinet goto $201C ; cocktail cabinet 2016: 3A 0D 40 ld a,($400D) ; read CURRENT_PLAYER 2019: 0F rrca ; carry will be set if PLAYER TWO is playing 201A: 38 11 jr c,$202D ; if player two is playing, ; must be a hardware quirk that requires bullet X coordinate to be flipped? 201C: DD 7E 03 ld a,(ix+$03) ; read BULLET_SPRITE.X 201F: 2F cpl ; X = 255 - X 2020: DD 77 03 ld (ix+$03),a ; update BULLET_SPRITE.X 2023: C9 ret ; Player bullet is not active, so position bullet sprite offscreen. 2024: DD 36 01 00 ld (ix+$01),$00 ; set BULLET_SPRITE.Y 2028: DD 36 03 00 ld (ix+$03),$00 ; set BULLET_SPRITE.X 202C: C9 ret ; Called when IS_COCKTAIL is true and PLAYER TWO playing. 202D: DD 7E 03 ld a,(ix+$03) 2030: D6 0D sub $0D 2032: DD 77 03 ld (ix+$03),a 2035: C9 ret ; !IMPORTANT ROUTINE! ; Handles all collision detection in the game. ; COLLISION_DETECTION: 2036: CD 5E 20 call $205E ; call PLAYER_TO_UFO_COLLISION_DETECTION 2039: CD C2 20 call $20C2 ; call PLAYER_TO_FIREBALL_COLLISION_DETECTION 203C: CD DE 20 call $20DE ; call PLAYER_TO_GROUND_OBJECT_COLLISION_DETECTION 203F: CD F4 20 call $20F4 ; call PLAYER_TO_ROCKET_COLLISION_DETECTION 2042: CD 13 21 call $2113 ; call PLAYER_TO_LANDSCAPE_COLLISION_DETECTION 2045: CD 66 21 call $2166 ; call PLAYER_BULLET_TO_UFO_COLLISION_DETECTION 2048: CD 17 22 call $2217 ; call PLAYER_BULLET_TO_GROUND_OBJECT_COLLISION_DETECTION 204B: CD D6 22 call $22D6 ; call PLAYER_BULLET_TO_ROCKET_COLLISION_DETECTION 204E: CD 8F 23 call $238F ; call PLAYER_BULLET_TO_LANDSCAPE_COLLISION_DETECTION 2051: CD C9 23 call $23C9 ; call PLAYER_BOMB_TO_UFO_COLLISION_DETECTION 2054: CD 34 24 call $2434 ; call PLAYER_BOMB_TO_GROUND_OBJECT_COLLISION_DETECTION 2057: CD 94 24 call $2494 ; call PLAYER_BOMB_TO_ROCKET_COLLISION_DETECTION 205A: CD 1E 25 call $251E ; call PLAYER_BOMB_TO_LANDSCAPE_COLLISION_DETECTION 205D: C9 ret PLAYER_TO_UFO_COLLISION_DETECTION: 205E: 3A 80 43 ld a,($4380) ; read PLAYERS[0].IsActive flag 2061: 0F rrca ; move flag into carry 2062: D0 ret nc ; return if player is not active 2063: 3A 1D 41 ld a,($411D) ; read LANDSCAPE_FLAGS 2066: FE 02 cp $02 ; are UFOs on this level? 2068: C0 ret nz ; exit if not 2069: DD 21 00 44 ld ix,$4400 ; load IX with address of INFLIGHT_ENEMIES 206D: 11 20 00 ld de,$0020 ; sizeof(INFLIGHT_ENEMY) 2070: 06 04 ld b,$04 ; max number of UFOs on screen at one time 2072: CD 7A 20 call $207A ; call CHECK_IF_PLAYER_COLLIDED_WITH_OBJECT 2075: DD 19 add ix,de 2077: 10 F9 djnz $2072 2079: C9 ret ; Check if the player jet has collided with an inflight enemy or a ground object. ; ; Expects: ; IX = pointer to INFLIGHT_ENEMY or GROUND_OBJECT structure ; CHECK_IF_PLAYER_COLLIDED_WITH_OBJECT: 207A: DD CB 00 46 bit 0,(ix+$00) 207E: C8 ret z 207F: 21 83 43 ld hl,$4383 ; load HL with address of PLAYERS[0].X 2082: 7E ld a,(hl) ; read PLAYERS[0].X 2083: DD 96 03 sub (ix+$03) 2086: C6 06 add a,$06 2088: FE 0D cp $0D 208A: D0 ret nc 208B: 2C inc l ; bump HL to point to PLAYERS[0].Y 208C: 7E ld a,(hl) ; read PLAYERS[0].Y 208D: C6 04 add a,$04 208F: DD 96 04 sub (ix+$04) 2092: C6 0D add a,$0D 2094: FE 19 cp $19 2096: D0 ret nc ; Player jet has hit an inflight enemy or ground object 2097: DD CB 00 86 res 0,(ix+$00) ; clear IsActive flag 209B: DD CB 01 C6 set 0,(ix+$01) ; set IsExploding flag 209F: DD 36 02 06 ld (ix+$02),$06 20A3: 2D dec l 20A4: 2D dec l ; bump HL to point to PLAYER_ONE.StageOfLife 20A5: 36 06 ld (hl),$06 ; set stage of life to 6 (PLAYER_EXPLOSION_INIT) 20A7: 2D dec l 20A8: 36 01 ld (hl),$01 ; set PLAYER_ONE.IsExploding 20AA: 2D dec l 20AB: 36 00 ld (hl),$00 ; reset PLAYER_ONE.IsActive 20AD: 21 A0 43 ld hl,$43A0 ; load HL with address of PLAYER_TWO 20B0: 36 00 ld (hl),$00 ; reset PLAYER_TWO.IsActive 20B2: 2C inc l 20B3: 36 01 ld (hl),$01 ; set PLAYER_TWO.IsExploding flag 20B5: DD 36 17 00 ld (ix+$17),$00 ; set GROUND_OBJECT.ObjectType to 0, so that player doesn't get points for crashing into object (thanks Mark) 20B9: 3E FF ld a,$FF 20BB: 32 15 41 ld ($4115),a ; set LANDSCAPE_SCROLL_CONTROL_COUNTER 20BE: CD F3 28 call $28F3 ; call QUEUE_PLAYER_HIT_OBJECT_SOUND 20C1: C9 ret PLAYER_TO_FIREBALL_COLLISION_DETECTION: 20C2: 3A 80 43 ld a,($4380) ; read PLAYERS[0].IsActive flag 20C5: 0F rrca ; move flag into carry 20C6: D0 ret nc ; return if player is not active 20C7: 3A 1D 41 ld a,($411D) ; read LANDSCAPE_FLAGS 20CA: FE 01 cp $01 ; are fireballs on this level? 20CC: C0 ret nz ; exit if not ; Call CHECK_IF_PLAYER_COLLIDED_WITH_OBJECT for each INFLIGHT_ENEMY 20CD: DD 21 00 44 ld ix,$4400 ; load IX with address of INFLIGHT_ENEMIES 20D1: 11 20 00 ld de,$0020 ; sizeof(INFLIGHT_ENEMY) 20D4: 06 04 ld b,$04 ; max number of INFLIGHT_ENEMIES on screen at one time 20D6: CD 7A 20 call $207A ; call CHECK_IF_PLAYER_COLLIDED_WITH_OBJECT 20D9: DD 19 add ix,de 20DB: 10 F9 djnz $20D6 20DD: C9 ret PLAYER_TO_GROUND_OBJECT_COLLISION_DETECTION: 20DE: 3A 80 43 ld a,($4380) ; read PLAYERS[0].IsActive flag 20E1: 0F rrca ; move flag into carry 20E2: D0 ret nc ; return if player is not active ; Call CHECK_IF_PLAYER_COLLIDED_WITH_OBJECT for each GROUND_OBJECT 20E3: DD 21 80 42 ld ix,$4280 ; load HL with address of GROUND_OBJECTS 20E7: 11 20 00 ld de,$0020 ; sizeof(GROUND_OBJECT) 20EA: 06 08 ld b,$08 ; maximum of 8 ground objects on screen 20EC: CD 7A 20 call $207A ; call CHECK_IF_PLAYER_COLLIDED_WITH_OBJECT 20EF: DD 19 add ix,de 20F1: 10 F9 djnz $20EC 20F3: C9 ret PLAYER_TO_ROCKET_COLLISION_DETECTION: 20F4: 3A 80 43 ld a,($4380) ; read PLAYERS[0].IsActive flag 20F7: 0F rrca ; move flag into carry 20F8: D0 ret nc ; return if player is not active ; are we on a rocket level? 20F9: 3A 1D 41 ld a,($411D) ; read LANDSCAPE_FLAGS 20FC: A7 and a ; is this level 1, can rockets attack the player? 20FD: 28 03 jr z,$2102 20FF: FE 08 cp $08 ; is this level 4, can rockets attack the player? 2101: C0 ret nz ; Call CHECK_IF_PLAYER_COLLIDED_WITH_OBJECT for each INFLIGHT_ENEMY 2102: DD 21 00 44 ld ix,$4400 ; load IX with address of INFLIGHT_ENEMIES 2106: 11 20 00 ld de,$0020 ; sizeof(INFLIGHT_ENEMY) 2109: 06 04 ld b,$04 ; max number of INFLIGHT_ENEMIES on screen at one time 210B: CD 7A 20 call $207A ; call CHECK_IF_PLAYER_COLLIDED_WITH_OBJECT 210E: DD 19 add ix,de 2110: 10 F9 djnz $210B 2112: C9 ret PLAYER_TO_LANDSCAPE_COLLISION_DETECTION: 2113: 3A 80 43 ld a,($4380) ; read PLAYERS[0].IsActive flag 2116: 0F rrca ; move flag into carry 2117: D0 ret nc ; return if player is not active 2118: 3A 16 41 ld a,($4116) ; read LANDSCAPE_SCROLL_COUNTER 211B: 47 ld b,a ; get Y coordinate of jet (horizontal as player sees it) and use it to compute the first LANDSCAPE_EXTENT record to check player's X position against. 211C: 3A 84 43 ld a,($4384) ; read PLAYERS[0].Y 211F: D6 04 sub $04 2121: 90 sub b 2122: E6 F8 and $F8 2124: 0F rrca 2125: 0F rrca 2126: C6 C0 add a,$C0 2128: 6F ld l,a 2129: 26 41 ld h,$41 ; HL is now a pointer into LANDSCAPE_EXTENTS array ; get X coordinate of jet (vertical as player sees it) 212B: 06 03 ld b,$03 ; jet body is 3 characters wide. 212D: 3A 83 43 ld a,($4383) ; read PLAYERS[0].X 2130: C6 03 add a,$03 2132: 5F ld e,a ; E = PLAYERS[0].X+3 2133: D6 06 sub $06 2135: 57 ld d,a ; D = PLAYERS[0].X-3 ; Check if jet is within height parameters. 2136: 7E ld a,(hl) ; read LANDSCAPE_EXTENT.GroundX 2137: BB cp e ; 2138: 38 10 jr c,$214A ; if E > ground pixel height then player jet has hit the ground. Goto KILL_PLAYER 213A: 2C inc l 213B: 7E ld a,(hl) ; read LANDSCAPE_EXTENT.CeilingX 213C: BA cp d 213D: 30 0B jr nc,$214A ; if D <= ceiling pixel height then player jet has hit the ceiling. Goto KILL_PLAYER 213F: 2C inc l 2140: 28 03 jr z,$2145 ; if HL = $4200 then the end of the LANDSCAPE_EXTENTS array has been reached. 2142: 10 F2 djnz $2136 ; Working from nose of jet backwards to the tail, repeat until 3 characters worth of space has been checked for collision. 2144: C9 ret ; Reset HL to point to start of LANDSCAPE_EXTENTS array 2145: 2E C0 ld l,$C0 ; LSB of LANDSCAPE_EXTENTS array address 2147: 10 ED djnz $2136 2149: C9 ret KILL_PLAYER: 214A: 21 80 43 ld hl,$4380 ; load HL with address of PLAYER_ONE 214D: 36 00 ld (hl),$00 ; reset PLAYERS[0].IsActive 214F: 2C inc l 2150: 36 01 ld (hl),$01 ; set PLAYERS[0].IsExploding 2152: 2C inc l 2153: 36 06 ld (hl),$06 ; set PLAYERS[0].StageOfLife 2155: 21 A0 43 ld hl,$43A0 ; load HL with address of PLAYER_TWO 2158: 36 00 ld (hl),$00 ; reset PLAYERS[1].IsActive 215A: 2C inc l 215B: 36 01 ld (hl),$01 ; set PLAYERS[1].IsExploding 215D: 3E FF ld a,$FF 215F: 32 15 41 ld ($4115),a ; set LANDSCAPE_SCROLL_CONTROL_COUNTER 2162: CD F3 28 call $28F3 ; call QUEUE_PLAYER_HIT_OBJECT_SOUND 2165: C9 ret PLAYER_BULLET_TO_UFO_COLLISION_DETECTION: 2166: 3A 1D 41 ld a,($411D) ; read LANDSCAPE_FLAGS 2169: FE 02 cp $02 ; are UFOs enabled? 216B: C0 ret nz ; exit if not. 216C: FD 21 00 45 ld iy,$4500 ; load IY with address of PLAYER_BULLETS array 2170: 06 04 ld b,$04 ; max number of bullets on screen. 2172: 11 20 00 ld de,$0020 ; sizeof(INFLIGHT_ENEMY) 2175: CD 81 21 call $2181 ; call CHECK_IF_PLAYER_BULLET_HIT_UFOS 2178: FD 23 inc iy 217A: FD 23 inc iy 217C: FD 23 inc iy ; bump IY to point to next PLAYER_BULLET structure 217E: 10 F5 djnz $2175 2180: C9 ret ; IY = pointer to PLAYER_BULLET structure CHECK_IF_PLAYER_BULLET_HIT_UFOS: 2181: FD CB 00 46 bit 0,(iy+$00) ; test PLAYER_BULLET.IsActive flag 2185: C8 ret z ; return if player bullet is not active 2186: DD 21 00 44 ld ix,$4400 ; load IX with address of INFLIGHT_ENEMIES 218A: 0E 04 ld c,$04 ; max number of INFLIGHT_ENEMIES on screen at one time 218C: D9 exx 218D: CD 97 21 call $2197 2190: D9 exx 2191: DD 19 add ix,de 2193: 0D dec c 2194: 20 F6 jr nz,$218C 2196: C9 ret ; IX = pointer to INFLIGHT_ENEMY structure ; IY = pointer to PLAYER_BULLET structure CHECK_IF_PLAYER_BULLET_HIT_UFO: 2197: DD CB 00 46 bit 0,(ix+$00) ; test INFLIGHT_ENEMY.IsActive flag 219B: C8 ret z ; if enemy is not active, return 219C: FD 7E 01 ld a,(iy+$01) ; read PLAYER_BULLET.X 219F: DD 96 03 sub (ix+$03) ; subtract INFLIGHT_ENEMY.X 21A2: C6 03 add a,$03 21A4: FE 07 cp $07 21A6: D0 ret nc 21A7: FD 7E 02 ld a,(iy+$02) ; read PLAYER_BULLET.Y 21AA: DD 96 04 sub (ix+$04) ; subtract INFLIGHT_ENEMY.Y 21AD: C6 04 add a,$04 21AF: FE 09 cp $09 21B1: D0 ret nc ; Player bullet has hit a UFO. 21B2: DD 36 00 00 ld (ix+$00),$00 ; reset INFLIGHT_ENEMY.IsActive flag 21B6: DD 36 01 01 ld (ix+$01),$01 ; set INFLIGHT_ENEMY.IsExploding flag 21BA: DD 36 02 06 ld (ix+$02),$06 ; set INFLIGHT_ENEMY.StageOfLife to 6 (see UFO_AND_FIREBALL_EXPLOSION_INIT @ $1DEE) 21BE: FD 36 00 00 ld (iy+$00),$00 ; reset PLAYER_BULLET.IsActive flag 21C2: 11 08 03 ld de,$0308 ; Command ID: 3 = UPDATE_PLAYER_SCORE_COMMAND, Param:8 = 100 pts 21C5: FF rst $38 ; call QUEUE_COMMAND 21C6: CD EB 28 call $28EB ; call QUEUE_UFO_DEATH_SOUND 21C9: C9 ret ; ; Rest of the NMI handler - called from $09E9. ; 21CA: 32 15 40 ld ($4015),a ; write to PREV_PREV_PORT_STATE_8100 21CD: 2A 10 40 ld hl,($4010) ; read PORT_STATE_8100 and PORT_STATE_8101 21D0: 22 13 40 ld ($4013),hl ; and write to PREV_PORT_STATE_8100 and PREV_PORT_STATE_8101 21D3: 21 12 40 ld hl,$4012 ; load HL with address of PORT_STATE_8102 21D6: 3A 02 81 ld a,($8102) ; read IN2 21D9: 2F cpl 21DA: 77 ld (hl),a ; write to PORT_STATE_8102 21DB: 2B dec hl 21DC: 3A 01 81 ld a,($8101) ; read IN1 21DF: 2F cpl 21E0: 77 ld (hl),a ; write to PORT_STATE_8101 21E1: 2B dec hl 21E2: 3A 00 81 ld a,($8100) ; read IN0 21E5: 2F cpl 21E6: 77 ld (hl),a ; write to PORT_STATE_8100 21E7: 21 5F 42 ld hl,$425F ; pointer to TIMING_VARIABLE 21EA: 35 dec (hl) ; decrement value 21EB: CD EC 09 call $09EC ; call UNPROCESSED_COINS 21EE: CD 55 28 call $2855 ; call PROCESS_CIRC_SOUND_CMD_QUEUE 21F1: 21 03 22 ld hl,$2203 ; address of NMI_CLEANUP 21F4: E5 push hl ; ; invoke script #SCRIPT NUMBER (where SCRIPT_NUMBER is zero-based) 21F5: 3A 05 40 ld a,($4005) ; read SCRIPT_NUMBER 21F8: EF rst $28 21F9: A8 0A ; $0AA8 (SCRIPT_ONE) A9 0B ; $0BA9 (ATTRACT_MODE_SCRIPT) 83 0E ; $0E83 (CREDIT_INSERTED_SCRIPT) D2 0F ; $0FD2 (PLAYER_ONE_GAME_SCRIPT) EA 0F ; $0FEA (PLAYER_TWO_GAME_SCRIPT) NMI_CLEANUP: 2203: FD E1 pop iy 2205: DD E1 pop ix 2207: E1 pop hl 2208: D1 pop de 2209: C1 pop bc 220A: F1 pop af 220B: D9 exx 220C: 08 ex af,af' 220D: E1 pop hl 220E: D1 pop de 220F: C1 pop bc 2210: 3E 01 ld a,$01 2212: 32 01 68 ld ($6801),a ; re-enable interrupts 2215: F1 pop af 2216: C9 ret ; ; Checks if player bullet has hit any ground based targets ; PLAYER_BULLET_TO_GROUND_OBJECT_COLLISION_DETECTION: 2217: FD 21 00 45 ld iy,$4500 ; load IY with address of PLAYER_BULLETS array 221B: 06 04 ld b,$04 ; max number of player bullets on screen at one time 221D: 11 20 00 ld de,$0020 ; sizeof(GROUND_OBJECT) 2220: CD 2C 22 call $222C 2223: FD 23 inc iy 2225: FD 23 inc iy 2227: FD 23 inc iy ; bump IY to point to next PLAYER_BULLET 2229: 10 F5 djnz $2220 222B: C9 ret ; IY = pointer to PLAYER_BULLET 222C: FD CB 00 46 bit 0,(iy+$00) ; test PLAYER_BULLET.IsActive flag 2230: C8 ret z ; return if bullet is not active 2231: DD 21 80 42 ld ix,$4280 ; load IX with address of GROUND_OBJECTS array 2235: 0E 08 ld c,$08 ; length of GROUND_OBJECTS array 2237: D9 exx 2238: CD 42 22 call $2242 ; call CHECK_IF_PLAYER_BULLET_HIT_GROUND_OBJECT 223B: D9 exx 223C: DD 19 add ix,de ; bump IX to point to next GROUND_OBJECT 223E: 0D dec c ; C holds count of how many ground objects left to check 223F: 20 F6 jr nz,$2237 ; repeat until bullet checked for collision against all ground objects 2241: C9 ret ; IX = pointer to GROUND_OBJECT ; IY = pointer to PLAYER_BULLET CHECK_IF_PLAYER_BULLET_HIT_GROUND_OBJECT: 2242: DD CB 00 46 bit 0,(ix+$00) ; test GROUND_OBJECT.IsActive 2246: C8 ret z ; exit if object is not active 2247: FD 7E 01 ld a,(iy+$01) ; read PLAYER_BULLET.X 224A: DD 96 03 sub (ix+$03) ; subtract GROUND_OBJECT.X 224D: C6 07 add a,$07 224F: FE 0F cp $0F 2251: D0 ret nc 2252: FD 7E 02 ld a,(iy+$02) ; read PLAYER_BULLET.Y 2255: DD 96 04 sub (ix+$04) ; subtract GROUND_OBJECT.Y 2258: C6 04 add a,$04 225A: FE 09 cp $09 225C: D0 ret nc ; Ground object has been shot. Deactivate the bullet and make the ground object explode. 225D: DD 36 00 00 ld (ix+$00),$00 ; clear GROUND_OBJECT.IsActive 2261: DD 36 01 01 ld (ix+$01),$01 ; set GROUND_OBJECT.IsExploding 2265: DD 36 02 06 ld (ix+$02),$06 ; set GROUND_OBJECT.StageOfLife to 6 (see GROUND_OBJECT_EXPLOSION_INIT @ $1964) 2269: FD 36 00 00 ld (iy+$00),$00 ; clear PLAYER_BULLET.IsActive ; read the type of ground object then award required amount of points to player AWARD_POINTS_FOR_DESTROYING_GROUND_OBJECT: 226D: DD 7E 17 ld a,(ix+$17) ; read GROUND_OBJECT.ObjectType 2270: A7 and a ; test if zero (rocket) 2271: 28 08 jr z,$227B ; it's a rocket, goto ROCKET_DESTROYED_AWARD_50_PTS 2273: 3D dec a 2274: 28 0D jr z,$2283 ; fuel tank, goto FUEL_DESTROYED_ADD_FUEL_AND_AWARD_50_PTS 2276: 3D dec a 2277: 28 1D jr z,$2296 ; mystery, goto MYSTERY_SHOT_AWARD_RANDOM_PTS 2279: 18 4E jr $22C9 ; Base, goto BASE_SHOT_AWARD_800_PTS_AND_COMPLETE_GAME ; We've shot a rocket. Award player 50 points. ROCKET_DESTROYED_AWARD_50_PTS: 227B: 11 01 03 ld de,$0301 ; Command ID: 3 = UPDATE_PLAYER_SCORE_COMMAND, Param:1 = 50 pts 227E: FF rst $38 ; call QUEUE_COMMAND 227F: CD FB 28 call $28FB ; call QUEUE_ROCKET_EXPLOSION_SOUND 2282: C9 ret ; We've shot a fuel tank, add fuel to jet and award 150 pts FUEL_DESTROYED_ADD_FUEL_AND_AWARD_50_PTS: 2283: 21 05 41 ld hl,$4105 ; address of CURRENT_PLAYER_FUEL 2286: 7E ld a,(hl) ; read fuel left 2287: C6 30 add a,$30 ; add 48 decimal (3 full blocks) to it 2289: 30 02 jr nc,$228D ; if there's no overflow after this addition, goto $228D 228B: 3E FF ld a,$FF ; otherwise, clamp to maximum amount of fuel you can have 228D: 77 ld (hl),a ; update CURRENT_PLAYER_FUEL 228E: 11 03 03 ld de,$0303 ; Command ID: 3 = UPDATE_PLAYER_SCORE_COMMAND, Param:3 = 150 pts 2291: FF rst $38 ; call QUEUE_COMMAND 2292: CD CE 28 call $28CE ; call QUEUE_EXPLOSION_SOUND_1 2295: C9 ret ; We've shot a MYSTERY object. The number of points we are awarded is based on a pseudo-random number from 0..3. MYSTERY_SHOT_AWARD_RANDOM_PTS: 2296: ED 5F ld a,r ; read the refresh register 2298: E6 03 and $03 ; convert value read into number from 0-3. 229A: A7 and a ; is the number zero? 229B: 28 08 jr z,$22A5 ; if so, goto MYSTERY_SHOT_AWARD_100_PTS 229D: 3D dec a ; was the original number 1? 229E: 28 05 jr z,$22A5 ; if so, goto MYSTERY_SHOT_AWARD_100_PTS 22A0: 3D dec a ; was the original number 2? 22A1: 28 0E jr z,$22B1 ; if so, goto MYSTERY_SHOT_AWARD_200_PTS 22A3: 18 18 jr $22BD ; the original number must have been 3 - goto MYSTERY_SHOT_AWARD_300_PTS MYSTERY_SHOT_AWARD_100_PTS: 22A5: DD 36 1A 00 ld (ix+$1a),$00 ; set GROUND_OBJECT.MysteryPointsType to 0 22A9: 11 05 03 ld de,$0305 ; Command ID: 3 = UPDATE_PLAYER_SCORE_COMMAND, Param:5 = 100 pts 22AC: FF rst $38 ; call QUEUE_COMMAND 22AD: CD D6 28 call $28D6 ; call QUEUE_EXPLOSION_SOUND_DUPLICATE 22B0: C9 ret MYSTERY_SHOT_AWARD_200_PTS: 22B1: DD 36 1A 01 ld (ix+$1a),$01 ; set GROUND_OBJECT.MysteryPointsType to 1 22B5: 11 06 03 ld de,$0306 ; Command ID: 3 = UPDATE_PLAYER_SCORE_COMMAND, Param:6 = 200 pts 22B8: FF rst $38 ; call QUEUE_COMMAND 22B9: CD D6 28 call $28D6 ; call QUEUE_EXPLOSION_SOUND_DUPLICATE 22BC: C9 ret MYSTERY_SHOT_AWARD_300_PTS: 22BD: DD 36 1A 02 ld (ix+$1a),$02 ; set GROUND_OBJECT.MysteryPointsType to 2 22C1: 11 07 03 ld de,$0307 ; Command ID: 3 = UPDATE_PLAYER_SCORE_COMMAND, Param:7 = 300 points 22C4: FF rst $38 ; call QUEUE_COMMAND 22C5: CD D6 28 call $28D6 ; call QUEUE_EXPLOSION_SOUND_DUPLICATE 22C8: C9 ret ; Shot a "Base" - not only do you get 800 points, you also complete the mission. BASE_SHOT_AWARD_800_PTS_AND_COMPLETE_GAME: 22C9: 11 0D 03 ld de,$030D ; Command ID: 3 = UPDATE_PLAYER_SCORE_COMMAND, Param:$0D = 800 points!!! 22CC: FF rst $38 ; call QUEUE_COMMAND 22CD: CD D6 28 call $28D6 ; call QUEUE_EXPLOSION_SOUND_DUPLICATE 22D0: 3E FF ld a,$FF 22D2: 32 12 41 ld ($4112),a ; set the IS_MISSION_COMPLETE flag. 22D5: C9 ret PLAYER_BULLET_TO_ROCKET_COLLISION_DETECTION: 22D6: 3A 1D 41 ld a,($411D) ; read LANDSCAPE_FLAGS 22D9: A7 and a ; test if rockets allowed 22DA: 28 03 jr z,$22DF 22DC: FE 08 cp $08 ; test if rockets allowed 22DE: C0 ret nz 22DF: FD 21 00 45 ld iy,$4500 ; load IY with address of PLAYER_BULLETS 22E3: 06 04 ld b,$04 ; max number of rockets inflight at one time 22E5: 11 20 00 ld de,$0020 ; sizeof(INFLIGHT_ENEMY) 22E8: CD 46 23 call $2346 ; call CHECK_ONE_PLAYER_BULLET_MANY_ROCKETS 22EB: FD 23 inc iy 22ED: FD 23 inc iy 22EF: FD 23 inc iy ; bump IY to next PLAYER_BULLET 22F1: 10 F5 djnz $22E8 ; repeat until all player bullets done 22F3: C9 ret ; ! IMPORTANT ! ; This is the routine that effectively runs the game. ; PLAY_GAME: 22F4: CD CC 13 call $13CC ; call ANIMATION_AND_MOVEMENT 22F7: CD 35 1F call $1F35 ; call SCROLL_AND_SPRITES 22FA: CD 36 20 call $2036 ; call COLLISION_DETECTION 22FD: CD 63 25 call $2563 ; call SPAWN_ENEMIES 2300: C3 52 28 jp $2852 ; jump to a JP $2303... I expect this would have been to confuse hackers. 2303: CD C2 27 call $27C2 ; call LANDSCAPE_CHANGE 2306: CD 9C 13 call $139C ; call GAIN_POINTS_JUST_FOR_STAYING_ALIVE 2309: CD 80 29 call $2980 ; call AMBIENT_SOUND 230C: CD A7 13 call $13A7 ; call CHECK_IF_EXTRA_LIFE_SHOULD_BE_AWARDED 230F: 21 80 43 ld hl,$4380 ; load HL with address of PLAYER_ONE 2312: 7E ld a,(hl) ; read PLAYER_ONE.IsActive flag 2313: 0F rrca ; move flag into carry 2314: 38 19 jr c,$232F ; if player is active, jump to CHECK_IF_MISSION_IS_COMPLETE 2316: 2C inc l 2317: 7E ld a,(hl) ; read PLAYER_ONE.IsExploding flag 2318: 0F rrca ; move flag into carry 2319: D8 ret c ; exit if player is exploding ; Player's finished exploding! Clear player state 231A: 21 80 43 ld hl,$4380 ; load HL with address of PLAYERS array 231D: 11 81 43 ld de,$4381 2320: 36 00 ld (hl),$00 2322: 01 A0 01 ld bc,$01A0 2325: ED B0 ldir ; clear PLAYERS, PLAYER_BOMBS, INFLIGHT_ENEMIES, PLAYER_BULLETS arrays ; advance to next stage of script 2327: 21 0A 40 ld hl,$400A ; load HL with address of SCRIPT_STAGE 232A: 34 inc (hl) ; advance to next stage of script (either PLAYER_ONE_KILLED @ $111D or PLAYER_TWO_KILLED @ $1209) 232B: 2D dec l ; bump HL to point to TEMP_COUNTER_4009 232C: 36 64 ld (hl),$64 ; set temp counter value 232E: C9 ret CHECK_IF_MISSION_IS_COMPLETE: 232F: 3A 12 41 ld a,($4112) ; read IS_MISSION_COMPLETE flag. 2332: FE FF cp $FF ; have we completed our mission? 2334: C0 ret nz ; exit if not ; We've completed our mission. We now need to kick off the MISSION_COMPLETED_SCRIPT at $127E 2335: 21 80 45 ld hl,$4580 ; load HL with address of TEMP_COUNTER_4580 2338: 36 96 ld (hl),$96 ; set value of counter 233A: 2C inc l ; bump HL to point to MISSION_COMPLETE_SCRIPT_STAGE 233B: 36 00 ld (hl),$00 ; 233D: 21 0A 40 ld hl,$400A ; load HL with address of SCRIPT_STAGE 2340: 36 08 ld (hl),$08 ; set SCRIPT_STAGE to 8 2342: 2D dec l 2343: 36 64 ld (hl),$64 2345: C9 ret ; Check if a given player bullet has hit any active rockets. ; ; IY = pointer to PLAYER_BULLET structure CHECK_ONE_PLAYER_BULLET_MANY_ROCKETS: 2346: FD CB 00 46 bit 0,(iy+$00) ; test PLAYER_BULLET.IsActive flag 234A: C8 ret z ; return if flag is not set 234B: DD 21 00 44 ld ix,$4400 ; load IX with address of INFLIGHT_ENEMIES 234F: 0E 04 ld c,$04 ; max number of INFLIGHT_ENEMIES on screen at one time 2351: D9 exx 2352: CD 5C 23 call $235C ; call CHECK_IF_PLAYER_BULLET_HIT_ROCKET 2355: D9 exx 2356: DD 19 add ix,de ; bump IX to next INFLIGHT_ENEMY 2358: 0D dec c ; reduce count of INFLIGHT_ENEMY slots to process 2359: 20 F6 jr nz,$2351 ; repeat until all slots done 235B: C9 ret ; IX = pointer to INFLIGHT_ENEMY structure ; IY = pointer to PLAYER_BULLET structure CHECK_IF_PLAYER_BULLET_HIT_ROCKET: 235C: DD CB 00 46 bit 0,(ix+$00) ; test INFLIGHT_ENEMY.IsActive flag 2360: C8 ret z ; exit if this slot isn't being used 2361: FD 7E 01 ld a,(iy+$01) ; Read PLAYER_BULLET.X 2364: DD 96 03 sub (ix+$03) ; subtract INFLIGHT_ENEMY.X 2367: C6 05 add a,$05 2369: FE 0B cp $0B 236B: D0 ret nc 236C: FD 7E 02 ld a,(iy+$02) ; read PLAYER_BULLET.Y 236F: DD 96 04 sub (ix+$04) ; subtract INFLIGHT_ENEMY.Y 2372: C6 03 add a,$03 2374: FE 07 cp $07 2376: D0 ret nc ; Rocket has been hit by a bullet. 2377: DD 36 00 00 ld (ix+$00),$00 ; clear INFLIGHT_ENEMY.IsActive flag 237B: DD 36 01 01 ld (ix+$01),$01 ; set INFLIGHT_ENEMY.IsExploding flag 237F: DD 36 02 06 ld (ix+$02),$06 ; set INFLIGHT_ENEMY.StageOfLife to 6 (see ROCKET_EXPLOSION_INIT @ $1D2C) 2383: FD 36 00 00 ld (iy+$00),$00 ; clear PLAYER_BULLET.IsActive flag 2387: 11 0A 03 ld de,$030A ; Command ID: 3 = UPDATE_PLAYER_SCORE_COMMAND, Param:$0A = 80 pts 238A: FF rst $38 ; call QUEUE_COMMAND 238B: CD FB 28 call $28FB ; call QUEUE_ROCKET_EXPLOSION_SOUND 238E: C9 ret ; ; Deactivate any player bullets that have hit the landscape. ; PLAYER_BULLET_TO_LANDSCAPE_COLLISION_DETECTION: 238F: DD 21 00 45 ld ix,$4500 ; load IX with address of PLAYER_BULLETS 2393: 11 03 00 ld de,$0003 ; sizeof(PLAYER_BULLET) 2396: 06 04 ld b,$04 ; maximum number of player bullets on screen at one time 2398: D9 exx 2399: CD A2 23 call $23A2 ; call CHECK_IF_PLAYER_BULLET_HIT_LANDSCAPE 239C: D9 exx 239D: DD 19 add ix,de 239F: 10 F7 djnz $2398 23A1: C9 ret ; IX = pointer to PLAYER_BULLET CHECK_IF_PLAYER_BULLET_HIT_LANDSCAPE: 23A2: DD CB 00 46 bit 0,(ix+$00) ; test PLAYER_BULLET.IsActive flag 23A6: C8 ret z ; return if bullet is not active ; find the LANDSCAPE_EXTENT record that defines the safe X coordinate range for our bullet. 23A7: 3A 16 41 ld a,($4116) ; read LANDSCAPE_SCROLL_COUNTER 23AA: 47 ld b,a 23AB: DD 7E 02 ld a,(ix+$02) ; read PLAYER_BULLET.Y 23AE: 90 sub b ; A = PLAYER_BULLET.Y - LANDSCAPE_SCROLL_COUNTER 23AF: E6 F8 and $F8 23B1: 0F rrca 23B2: 0F rrca 23B3: C6 C0 add a,$C0 ; LSB of address of LANDSCAPE_EXTENTS array 23B5: 6F ld l,a 23B6: 26 41 ld h,$41 ; now HL is a pointer to an entry in LANDSCAPE_EXTENTS array ; if (PLAYER.BULLET.X > LANDSCAPE_EXTENT.GroundX) OR (PLAYER.BULLET.X < LANDSCAPE_EXTENT.CeilingX) THEN Deactivate bullet 23B8: 7E ld a,(hl) ; read LANDSCAPE_EXTENT.GroundX 23B9: DD BE 01 cp (ix+$01) ; compare to PLAYER_BULLET.X 23BC: 38 06 jr c,$23C4 ; if PLAYER_BULLET.X > LANDSCAPE_EXTENT.GroundX, deactivate 23BE: 2C inc l 23BF: 7E ld a,(hl) ; read LANDSCAPE_EXTENT.CeilingX 23C0: DD BE 01 cp (ix+$01) ; compare to PLAYER_BULLET.X 23C3: D8 ret c ; if PLAYER_BULLET.X > LANDSCAPE_EXTENT.CeilingX, exit function ; Bullet has hit the landscape 23C4: DD 36 00 00 ld (ix+$00),$00 ; Deactivate bullet 23C8: C9 ret ; ; ; Check if any active player bombs have hit any active UFOs. ; ; PLAYER_BOMB_TO_UFO_COLLISION_DETECTION: 23C9: 3A 1D 41 ld a,($411D) ; read LANDSCAPE_FLAGS 23CC: FE 02 cp $02 ; UFO level? 23CE: C0 ret nz ; exit if not 23CF: FD 21 C0 43 ld iy,$43C0 ; load IY with address of PLAYER_BOMBS array 23D3: 06 02 ld b,$02 ; number of bombs on screen player can have at one time 23D5: 11 20 00 ld de,$0020 ; sizeof(PLAYER_BOMB) and sizeof(INFLIGHT_ENEMY) 23D8: CD E0 23 call $23E0 ; call CHECK_IF_PLAYER_BOMB_HIT_ANY_UFO 23DB: FD 19 add iy,de ; bump IY to point to next PLAYER_BOMB 23DD: 10 F9 djnz $23D8 ; repeat until all bombs checked 23DF: C9 ret ; IY = pointer to PLAYER_BOMB ; DE = sizeof(INFLIGHT_ENEMY) CHECK_IF_PLAYER_BOMB_HIT_ANY_UFO: 23E0: FD CB 00 46 bit 0,(iy+$00) ; read PLAYER_BOMB.IsActive 23E4: C8 ret z ; return if bomb is not active 23E5: DD 21 00 44 ld ix,$4400 ; load IX with address of INFLIGHT_ENEMIES 23E9: 0E 04 ld c,$04 ; max number of INFLIGHT_ENEMIES on screen at one time 23EB: D9 exx 23EC: CD F6 23 call $23F6 ; call CHECK_IF_PLAYER_BOMB_HIT_UFO 23EF: D9 exx 23F0: DD 19 add ix,de ; bump IX to next INFLIGHT_ENEMY 23F2: 0D dec c ; repeat until bomb checked against all INFLIGHT_ENEMIES 23F3: 20 F6 jr nz,$23EB 23F5: C9 ret ; IX = pointer to INFLIGHT_ENEMY (which, if active, will be a UFO) ; IY = pointer to PLAYER_BOMB CHECK_IF_PLAYER_BOMB_HIT_UFO: 23F6: DD CB 00 46 bit 0,(ix+$00) ; test INFLIGHT_ENEMY.IsActive flag 23FA: C8 ret z ; exit if enemy is not active 23FB: FD 7E 03 ld a,(iy+$03) ; read INFLIGHT_ENEMY.X 23FE: DD 96 03 sub (ix+$03) ; subtract PLAYER_BOMB.X 2401: C6 05 add a,$05 2403: FE 0B cp $0B 2405: D0 ret nc 2406: FD 7E 04 ld a,(iy+$04) ; read INFLIGHT_ENEMY.Y 2409: DD 96 04 sub (ix+$04) ; subtract PLAYER_BOMB.Y 240C: C6 06 add a,$06 240E: FE 0D cp $0D 2410: D0 ret nc ; The player's bomb has hit an inflight enemy. DETONATE_PLAYER_BOMB_AND_KILL_INFLIGHT_ENEMY: 2411: DD 36 00 00 ld (ix+$00),$00 ; clear INFLIGHT_ENEMY.IsActive flag 2415: DD 36 01 01 ld (ix+$01),$01 ; set INFLIGHT_ENEMY.IsExploding flag 2419: DD 36 02 06 ld (ix+$02),$06 ; set INFLIGHT_ENEMY.StageOfLife (for rocket, this stage is ROCKET_EXPLOSION_INIT, see $1D2C. For UFOs, this stage is UFO_AND_FIREBALL_EXPLOSION_INIT - see $1DEE) 241D: FD 36 00 00 ld (iy+$00),$00 ; clear PLAYER_BOMB.IsActive flag 2421: FD 36 01 01 ld (iy+$01),$01 ; set PLAYER_BOMB.IsExploding flag 2425: FD 36 02 06 ld (iy+$02),$06 ; set PLAYER_BOMB.StageOfLife to 6 (see PLAYER_BOMB_EXPLOSION_INIT @ $1A6B) 2429: 11 08 03 ld de,$0308 ; Command ID: 3 = UPDATE_PLAYER_SCORE_COMMAND, Param:8 = 100 pts 242C: FF rst $38 ; call QUEUE_COMMAND 242D: CD DE 28 call $28DE ; call QUEUE_PLAYER_BOMB_EXPLOSION_SOUND 2430: CD EB 28 call $28EB ; call QUEUE_UFO_DEATH_SOUND 2433: C9 ret ; ; ; Check if any active player bombs have hit any active ground objects. ; ; PLAYER_BOMB_TO_GROUND_OBJECT_COLLISION_DETECTION: 2434: FD 21 C0 43 ld iy,$43C0 ; load IY with address of PLAYER_BOMBS array 2438: 06 02 ld b,$02 ; Player has 2 bombs max 243A: 11 20 00 ld de,$0020 ; sizeof(PLAYER_BOMB) 243D: CD 45 24 call $2445 2440: FD 19 add iy,de 2442: 10 F9 djnz $243D 2444: C9 ret ; IY = pointer to PLAYER_BOMB structure 2445: FD CB 00 46 bit 0,(iy+$00) ; test PLAYER_BOMB.IsActive flag 2449: C8 ret z ; return if bomb is not active 244A: DD 21 80 42 ld ix,$4280 ; load IX with address of GROUND_OBJECTS array 244E: 0E 08 ld c,$08 ; length of GROUND_OBJECTS array 2450: D9 exx 2451: CD 5B 24 call $245B ; call CHECK_IF_PLAYER_BOMB_HIT_GROUND_OBJECT 2454: D9 exx 2455: DD 19 add ix,de 2457: 0D dec c 2458: 20 F6 jr nz,$2450 245A: C9 ret ; IX = pointer to GROUND_OBJECT structure ; IY = pointer to PLAYER_BOMB structure CHECK_IF_PLAYER_BOMB_HIT_GROUND_OBJECT: 245B: DD CB 00 46 bit 0,(ix+$00) ; test GROUND_OBJECT.IsActive flag 245F: C8 ret z ; exit if this slot isn't used 2460: FD 7E 03 ld a,(iy+$03) ; read PLAYER_BOMB.X 2463: DD 96 03 sub (ix+$03) 2466: C6 07 add a,$07 2468: FE 0E cp $0E 246A: D0 ret nc 246B: FD 7E 04 ld a,(iy+$04) ; read PLAYER_BOMB.Y 246E: DD 96 04 sub (ix+$04) 2471: C6 07 add a,$07 2473: FE 0E cp $0E 2475: D0 ret nc ; Player bomb has hit ground object. Make both bomb and ground object explode. 2476: DD 36 00 00 ld (ix+$00),$00 ; clear GROUND_OBJECT.IsActive flag 247A: DD 36 01 01 ld (ix+$01),$01 ; set GROUND_OBJECT.IsExploding flag 247E: DD 36 02 06 ld (ix+$02),$06 ; set GROUND_OBJECT.StageOfLife to 6 (see GROUND_OBJECT_EXPLOSION_INIT @ $1964) 2482: FD 36 00 00 ld (iy+$00),$00 ; clear PLAYER_BOMB.IsActive 2486: FD 36 01 01 ld (iy+$01),$01 ; set PLAYER_BOMB.IsExploding 248A: FD 36 02 06 ld (iy+$02),$06 ; set PLAYER_BOMB.StageOfLife to 6 (see PLAYER_BOMB_EXPLOSION_INIT @ $1AAC) 248E: CD DE 28 call $28DE ; call QUEUE_PLAYER_BOMB_EXPLOSION_SOUND 2491: C3 6D 22 jp $226D ; jump to AWARD_POINTS_FOR_DESTROYING_GROUND_OBJECT ; ; ; Check if any active player bombs have hit any active ground objects. ; ; PLAYER_BOMB_TO_ROCKET_COLLISION_DETECTION: 2494: 3A 1D 41 ld a,($411D) ; read LANDSCAPE_FLAGS 2497: A7 and a ; check if this is the first level with rockets 2498: 28 03 jr z,$249D 249A: FE 08 cp $08 ; check if this is the second level with rockets 249C: C0 ret nz ; OK, we're on a level that has rockets that can fly. 249D: FD 21 C0 43 ld iy,$43C0 ; load IY with address of PLAYER_BOMBS array 24A1: 06 02 ld b,$02 ; Player has 2 bombs max 24A3: 11 20 00 ld de,$0020 ; sizeof(PLAYER_BOMB) 24A6: CD AE 24 call $24AE ; call CHECK_IF_PLAYER_BOMB_HIT_ANY_ROCKET 24A9: FD 19 add iy,de ; bump IY to point to next PLAYER_BOMB 24AB: 10 F9 djnz $24A6 ; repeat until all player bombs have been checked 24AD: C9 ret ; IY = pointer to PLAYER_BOMB CHECK_IF_PLAYER_BOMB_HIT_ANY_ROCKET: 24AE: FD CB 00 46 bit 0,(iy+$00) ; test PLAYER_BOMB.IsActive flag 24B2: C8 ret z ; return if bomb is not active 24B3: DD 21 00 44 ld ix,$4400 ; load IX with address of INFLIGHT_ENEMIES 24B7: 0E 04 ld c,$04 ; max number of INFLIGHT_ENEMIES on screen at one time 24B9: D9 exx 24BA: CD E5 24 call $24E5 ; call CHECK_IF_PLAYER_BOMB_HIT_ROCKET 24BD: D9 exx 24BE: DD 19 add ix,de ; bump IX to next INFLIGHT_ENEMY 24C0: 0D dec c ; repeat until all INFLIGHT_ENEMIES checked 24C1: 20 F6 jr nz,$24B9 24C3: C9 ret ; ; ; ; ; protection related code... if not interested skip to $24E0. 24C4: 3A B8 40 ld a,($40B8) ; read from PROTECTION_1 24C7: 07 rlca 24C8: 07 rlca 24C9: 07 rlca 24CA: C6 30 add a,$30 24CC: 0F rrca 24CD: 0F rrca 24CE: 67 ld h,a 24CF: C6 8D add a,$8D 24D1: 6F ld l,a ; set HL to $0E9B 24D2: 06 1E ld b,$1E 24D4: 86 add a,(hl) ; calculate checksum to see if code has been tampered with 24D5: 23 inc hl 24D6: 10 FC djnz $24D4 24D8: FE 13 cp $13 24DA: 28 04 jr z,$24E0 ; reset the game 24DC: AF xor a 24DD: 32 05 40 ld ($4005),a ; set SCRIPT_NUMBER ; play EXTRA LIFE AWARDED sound 24E0: 3E 08 ld a,$08 24E2: C3 77 28 jp $2877 ; jump to QUEUE_SOUND_COMMAND ; ; Check if a given player bomb has hit a specific rocket. ; ; IX = pointer to INFLIGHT_ENEMY (which, if active, will be a rocket) ; IY = pointer to active PLAYER_BOMB ; CHECK_IF_PLAYER_BOMB_HIT_ROCKET: 24E5: DD CB 00 46 bit 0,(ix+$00) ; test INFLIGHT_ENEMY.IsActive flag 24E9: C8 ret z ; exit if this rocket is not active 24EA: FD 7E 03 ld a,(iy+$03) ; read PLAYER_BOMB.X 24ED: DD 96 03 sub (ix+$03) ; subtract INFLIGHT_ENEMY.X 24F0: C6 06 add a,$06 24F2: FE 0D cp $0D 24F4: D0 ret nc 24F5: FD 7E 04 ld a,(iy+$04) ; read PLAYER_BOMB.Y 24F8: DD 96 04 sub (ix+$04) ; subtract INFLIGHT_ENEMY.Y 24FB: C6 04 add a,$04 24FD: FE 09 cp $09 24FF: D0 ret nc ; Player bomb has hit rocket. Make both bomb and rocket explode. 2500: DD 36 00 00 ld (ix+$00),$00 ; clear INFLIGHT_ENEMY.IsActive flag 2504: DD 36 01 01 ld (ix+$01),$01 ; set INFLIGHT_ENEMY.IsExploding flag 2508: DD 36 02 06 ld (ix+$02),$06 ; set INFLIGHT_ENEMY.StageOfLife to 6 (see ROCKET_EXPLOSION_INIT @ $1D2C) 250C: FD 36 00 00 ld (iy+$00),$00 ; clear PLAYER_BOMB.IsActive flag 2510: FD 36 01 01 ld (iy+$01),$01 ; set PLAYER_BOMB.IsExploding flag 2514: C3 3B 13 jp $133B ; jump to CHECK_IF_PLAYER_BOMB_HIT_ROCKET_CONTINUED_1 ; Jumped to from $133B CHECK_IF_PLAYER_BOMB_HIT_ROCKET_CONTINUED_2: 2517: CD DE 28 call $28DE ; call QUEUE_PLAYER_BOMB_EXPLOSION_SOUND 251A: CD FB 28 call $28FB ; call QUEUE_ROCKET_EXPLOSION_SOUND 251D: C9 ret ; ; ; Check if any active player bombs have hit the landscape. ; ; PLAYER_BOMB_TO_LANDSCAPE_COLLISION_DETECTION: 251E: DD 21 C0 43 ld ix,$43C0 ; load IX with address of PLAYER_BOMBS array 2522: 06 02 ld b,$02 2524: 11 20 00 ld de,$0020 ; sizeof(PLAYER_BOMB) 2527: D9 exx 2528: CD 31 25 call $2531 ; call CHECK_IF_PLAYER_BOMB_HIT_LANDSCAPE 252B: D9 exx 252C: DD 19 add ix,de ; bump IX to next PLAYER_BOMB 252E: 10 F7 djnz $2527 ; repeat until all PLAYER_BOMBS processed. 2530: C9 ret ; IX = pointer to PLAYER_BOMB struct CHECK_IF_PLAYER_BOMB_HIT_LANDSCAPE: 2531: DD CB 00 46 bit 0,(ix+$00) ; test PLAYER_BOMB.IsActive flag 2535: C8 ret z ; return if bomb is not active 2536: 3A 16 41 ld a,($4116) ; read LANDSCAPE_SCROLL_COUNTER 2539: 47 ld b,a 253A: DD 7E 04 ld a,(ix+$04) ; read PLAYER_BOMB.Y 253D: 90 sub b ; subtract LANDSCAPE_SCROLL_COUNTER 253E: E6 F8 and $F8 2540: 0F rrca 2541: 0F rrca 2542: C6 C0 add a,$C0 ; add LSB of LANDSCAPE_EXTENTS address 2544: 6F ld l,a 2545: 26 41 ld h,$41 ; now HL is a pointer into LANDSCAPE_EXTENTS 2547: 7E ld a,(hl) ; read LANDSCAPE_EXTENT.GroundX 2548: DD BE 03 cp (ix+$03) ; compare to PLAYER_BOMB.X 254B: 38 06 jr c,$2553 ; if LANDSCAPE_EXTENT.GroundX < PLAYER_BOMB.X then bomb has hit landscape 254D: 2C inc l 254E: 7E ld a,(hl) ; read LANDSCAPE_EXTENT.CeilingX 254F: DD BE 03 cp (ix+$03) ; compare to PLAYER_BOMB.X 2552: D8 ret c ; if LANDSCAPE_EXTENT.CeilingX < PLAYER_BOMB.X then no collision, exit ; Bomb has hit the landscape. Make it explode. 2553: DD 36 00 00 ld (ix+$00),$00 ; reset PLAYER_BOMB.IsActive flag 2557: DD 36 01 01 ld (ix+$01),$01 ; set PLAYER_BOMB.IsExploding flag 255B: DD 36 02 06 ld (ix+$02),$06 ; set PLAYER_BOMB.StageOfLife to 6 (see PLAYER_BOMB_EXPLOSION_INIT @ $1AAC) 255F: CD DE 28 call $28DE ; call QUEUE_PLAYER_BOMB_EXPLOSION_SOUND 2562: C9 ret ; ! IMPORTANT ! ; Main routine for spawning ground based and flying enemies ; SPAWN_ENEMIES: 2563: CD 7F 25 call $257F ; call SPAWN_PLAYER_BULLET 2566: CD CC 25 call $25CC ; call TRY_SPAWN_UFO 2569: CD 0C 26 call $260C ; call TRY_SPAWN_INFLIGHT_ROCKET 256C: CD 74 26 call $2674 ; call SPAWN_FIREBALLS 256F: CD B1 26 call $26B1 ; call SPAWN_PLAYER_BOMB 2572: CD FA 26 call $26FA ; call SPAWN_FUEL_TANK 2575: CD 5E 27 call $275E ; call SPAWN_MYSTERY 2578: CD 2C 27 call $272C ; call SPAWN_ROCKET_ON_GROUND 257B: CD 90 27 call $2790 ; call SPAWN_BASE 257E: C9 ret ; ; Test for SHOOT button being pressed. ; If shoot button has been pressed, spawn a player bullet if possible. ; ; SPAWN_PLAYER_BULLET: 257F: 3A 06 40 ld a,($4006) ; read IS_GAME_IN_PLAY 2582: 0F rrca ; move flag into carry 2583: D0 ret nc ; return if game is not in play 2584: 3A 80 43 ld a,($4380) ; read PLAYERS[0].IsActive flag 2587: 0F rrca ; move flag into carry 2588: D0 ret nc ; return if player is not active 2589: 3A 0D 40 ld a,($400D) ; read CURRENT_PLAYER 258C: 0F rrca ; 258D: 38 0E jr c,$259D ; if carry is set then its player 2 in control, goto $259D ; Player 1 258F: 3A 10 40 ld a,($4010) ; read PORT_STATE_8100 2592: CB 5F bit 3,a ; test if IPT_BUTTON1 is pressed 2594: C8 ret z ; return if IPT_BUTTON1 not pressed 2595: 3A 13 40 ld a,($4013) ; read PREV_PORT_STATE_8100 2598: CB 5F bit 3,a ; test if IPT_BUTTON1 was pressed before 259A: C0 ret nz ; return if it was. 259B: 18 0C jr $25A9 ; Player 2 259D: 3A 11 40 ld a,($4011) ; read PORT_STATE_8101 25A0: CB 5F bit 3,a ; test if IPT_BUTTON1 (cocktail) was pressed 25A2: C8 ret z ; return if IPT_BUTTON1 (cocktail) was not pressed 25A3: 3A 14 40 ld a,($4014) ; read PREV_PORT_STATE_8101 25A6: CB 5F bit 3,a ; test if IPT_BUTTON1 (cocktail) was pressed before 25A8: C0 ret nz ; return if it was ; The shoot button has been pressed. ; Look for a vacant PLAYER_BULLET record and re-use it for a new bullet. TRY_SPAWN_PLAYER_BULLET: 25A9: 21 00 45 ld hl,$4500 ; load HL with address of PLAYER_BULLETS array 25AC: 06 04 ld b,$04 ; maximum of 4 bullets on screen at once 25AE: CB 46 bit 0,(hl) ; read PLAYER_BULLET.IsActive flag 25B0: 20 14 jr nz,$25C6 ; if this PLAYER_BULLET record is in use, try next PLAYER_BULLET record. ; we've found a vacant record. Repurpose it for a "new" bullet. ; HL = pointer to PLAYER_BULLET structure. 25B2: CB C6 set 0,(hl) ; set PLAYER_BULLET.IsActive flag 25B4: 2C inc l 25B5: 3A 83 43 ld a,($4383) ; read PLAYERS[0].X 25B8: C6 02 add a,$02 25BA: 77 ld (hl),a ; set PLAYER_BULLET.X 25BB: 2C inc l 25BC: 3A 84 43 ld a,($4384) ; read PLAYERS[0].Y 25BF: D6 07 sub $07 25C1: 77 ld (hl),a ; set PLAYER_BULLET.Y 25C2: CD 03 29 call $2903 ; call QUEUE_PLAYER_BULLET_FIRED_SOUND 25C5: C9 ret 25C6: 2C inc l 25C7: 2C inc l 25C8: 2C inc l ; bump HL to next PLAYER_BULLET record 25C9: 10 E3 djnz $25AE ; repeat until all records in PLAYER_BULLETS scanned 25CB: C9 ret ; ; ; Check if the level contains UFOs. If so, try to spawn one at regular intervals. ; ; TRY_SPAWN_UFO: 25CC: 3A 1D 41 ld a,($411D) ; read LANDSCAPE_FLAGS 25CF: FE 02 cp $02 ; UFOs on this level? 25D1: C0 ret nz ; exit if not ; We only want to spawn a new UFO every 64 cycles of the game, to keep the UFOs spaced apart. 25D2: 3A 5F 42 ld a,($425F) ; read TIMING_VARIABLE 25D5: E6 3F and $3F 25D7: C0 ret nz ; Look for a vacant INFLIGHT_ENEMY record and re-use it for a new UFO. ; If no vacant INFLIGHT_ENEMY records, just exit 25D8: DD 21 00 44 ld ix,$4400 ; load IX with address of INFLIGHT_ENEMIES 25DC: 11 20 00 ld de,$0020 ; sizeof(INFLIGHT_ENEMY) 25DF: 06 04 ld b,$04 ; max number of INFLIGHT_ENEMIES on screen at one time 25E1: DD 7E 00 ld a,(ix+$00) ; read INFLIGHT_ENEMY.IsActive flag 25E4: DD B6 01 or (ix+$01) ; combined with INFLIGHT_ENEMY.IsExploding flag 25E7: 0F rrca ; move result into carry 25E8: 30 05 jr nc,$25EF ; if INFLIGHT_ENEMY is not active and not exploding, this record is free for use. Goto SPAWN_UFO 25EA: DD 19 add ix,de 25EC: 10 F3 djnz $25E1 25EE: C9 ret ; IX = pointer to vacant INFLIGHT_ENEMY record to use for UFO. SPAWN_UFO: 25EF: DD 36 00 01 ld (ix+$00),$01 ; set INFLIGHT_ENEMY.IsActive flag 25F3: DD 36 01 00 ld (ix+$01),$00 ; clear INFLIGHT_ENEMY.IsExploding flag 25F7: DD 36 02 00 ld (ix+$02),$00 ; set INFLIGHT_ENEMY.StageOfLife to 0. 25FB: DD 36 03 88 ld (ix+$03),$88 ; set INFLIGHT_ENEMY.X ; calculate a pseudo-random Y coordinate for the UFO 25FF: ED 5F ld a,r 2601: E6 0F and $0F 2603: C6 09 add a,$09 2605: DD 77 04 ld (ix+$04),a ; set INFLIGHT_ENEMY.Y 2608: C9 ret 2609: C3 79 0E jp $0E79 ; ; Check if the level contains rockets. If so, find a rocket on the ground and launch it. ; ; TRY_SPAWN_INFLIGHT_ROCKET: 260C: 3A 1D 41 ld a,($411D) ; read LANDSCAPE_FLAGS 260F: A7 and a ; test if zero 2610: 28 03 jr z,$2615 ; if zero, then rocket launches are enabled for this level 2612: FE 08 cp $08 ; check if eight 2614: C0 ret nz ; if 8, then rocket launches are enabled for this level ; We only want to spawn a new rocket every 64 cycles of the game, to keep the sky from being too saturated with rockets. 2615: 3A 5F 42 ld a,($425F) ; read TIMING_VARIABLE 2618: E6 3F and $3F ; effectively do A = A modulus 64 261A: C0 ret nz ; if A wasn't a multiple of 64, return ; Scan through GROUND_OBJECTS array for an active object that has an ObjectType == 0 (type "Rocket"). 261B: DD 21 80 42 ld ix,$4280 ; load IX with address of GROUND_OBJECTS array 261F: 11 20 00 ld de,$0020 ; sizeof(GROUND_OBJECT) 2622: 06 08 ld b,$08 ; length of GROUND_OBJECTS array 2624: DD CB 00 46 bit 0,(ix+$00) ; test GROUND_OBJECT.IsActive flag 2628: 20 05 jr nz,$262F ; if ground object is active goto $262F 262A: DD 19 add ix,de 262C: 10 F6 djnz $2624 262E: C9 ret ; Expects: IX = pointer to GROUND_OBJECT 262F: DD 7E 17 ld a,(ix+$17) ; read GROUND_OBJECT.ObjectType 2632: A7 and a ; test if its zero (Rocket) 2633: 20 F5 jr nz,$262A ; if its not a rocket, resume scanning GROUND_OBJECTS array for one. ; We've got an active rocket on the ground. Is this rocket within an area where if it launched, there's a chance ; it might hit the player? (ie: its not going off screen, and its not too far in front of the player) 2635: DD 7E 04 ld a,(ix+$04) ; read GROUND_OBJECT.Y 2638: FE 60 cp $60 263A: 38 EE jr c,$262A 263C: FE E8 cp $E8 263E: 30 EA jr nc,$262A ; We've got a GROUND_OBJECT that's a rocket. We now need to find a free INFLIGHT_ENEMY record to use for our rocket. TRY_LAUNCH_ROCKET: 2640: FD 21 00 44 ld iy,$4400 ; load IY with address of INFLIGHT_ENEMIES 2644: 06 04 ld b,$04 ; max number of INFLIGHT_ENEMIES on screen at one time 2646: FD 7E 00 ld a,(iy+$00) ; read INFLIGHT_ENEMY.IsActive flag 2649: FD B6 01 or (iy+$01) ; combine with INFLIGHT_ENEMY.IsExploding 264C: 0F rrca 264D: 30 05 jr nc,$2654 ; if not active and not exploding, we can use this slot. goto LAUNCH_ROCKET 264F: FD 19 add iy,de ; bump IY to next INFLIGHT_ENEMY record 2651: 10 F3 djnz $2646 ; repeat until all INFLIGHT_ENEMIES scanned. 2653: C9 ret ; ; Delete the GROUND_OBJECT and launch the INFLIGHT_ENEMY rocket. ; ; Expects: ; IX = pointer to GROUND_OBJECT to delete (a stationary rocket) ; IY = pointer to inactive INFLIGHT_ENEMY record (which now becomes an active flying rocket) LAUNCH_ROCKET: ; First, we need to delete the rocket characters from screen.. 2654: DD 36 02 03 ld (ix+$02),$03 ; set GROUND_OBJECT.StageOfLife to 3 (GROUND_OBJECT_DELETE) ; .. then swap in a rocket sprite at the correct coordinates. 2658: DD 7E 03 ld a,(ix+$03) ; read GROUND_OBJECT.X 265B: FD 77 03 ld (iy+$03),a ; set INFLIGHT_ENEMY.X 265E: DD 7E 04 ld a,(ix+$04) ; read GROUND_OBJECT.Y 2661: FD 77 04 ld (iy+$04),a ; set INFLIGHT_ENEMY.Y 2664: FD 36 00 01 ld (iy+$00),$01 ; set INFLIGHT_ENEMY.IsActive 2668: FD 36 01 00 ld (iy+$01),$00 ; clear INFLIGHT_ENEMY.IsExploding 266C: FD 36 02 00 ld (iy+$02),$00 ; set INFLIGHT_ENEMY.StageOfLife 2670: CD 0D 29 call $290D ; just a RET 2673: C9 ret ; ; ; ; SPAWN_FIREBALLS: 2674: 3A 1D 41 ld a,($411D) ; read LANDSCAPE_FLAGS 2677: FE 01 cp $01 ; are we on the fireball level? 2679: C0 ret nz ; exit if not ; We only want to spawn a fireball every 16 ticks of the timing variable 267A: 3A 5F 42 ld a,($425F) ; read TIMING_VARIABLE 267D: E6 0F and $0F 267F: C0 ret nz ; Look for a vacant INFLIGHT_ENEMY record and re-use it for a new fireball. ; If no vacant INFLIGHT_ENEMY records, just exit TRY_SPAWN_FIREBALL: 2680: DD 21 00 44 ld ix,$4400 ; load IX with address of INFLIGHT_ENEMIES 2684: 11 20 00 ld de,$0020 ; sizeof(INFLIGHT_ENEMY) 2687: 06 04 ld b,$04 ; max number of INFLIGHT_ENEMIES on screen at one time 2689: DD 7E 00 ld a,(ix+$00) ; read INFLIGHT_ENEMY.IsActive 268C: DD B6 01 or (ix+$01) ; or with INFLIGHT_ENEMY.IsExploding 268F: 0F rrca ; move combined flags into carry 2690: 30 05 jr nc,$2697 ; if enemy is not active or exploding, goto $2697 2692: DD 19 add ix,de 2694: 10 F3 djnz $2689 2696: C9 ret ; IX = pointer to vacant INFLIGHT_ENEMY record to use for fireball. SPAWN_FIREBALL: 2697: DD 36 00 01 ld (ix+$00),$01 ; set INFLIGHT_ENEMY.IsActive flag 269B: DD 36 01 00 ld (ix+$01),$00 ; clear INFLIGHT_ENEMY.IsExploding flag 269F: DD 36 02 00 ld (ix+$02),$00 ; set INFLIGHT_ENEMY.StageOfLife to 0 (see FIREBALL_INIT @ $1EFF) 26A3: ED 5F ld a,r ; get a pseudo-random number 26A5: E6 7F and $7F ; ensure it's between 0-127 26A7: C6 30 add a,$30 ; add 48 26A9: DD 77 03 ld (ix+$03),a ; set INFLIGHT_ENEMY.X 26AC: DD 36 04 08 ld (ix+$04),$08 ; set INFLIGHT_ENEMY.Y to 8 26B0: C9 ret ; ; Check if player has pushed the BOMB button. ; If bomb button has been pressed, spawn a bomb if possible. ; SPAWN_PLAYER_BOMB: ; can't drop a bomb if in demo mode! 26B1: 3A 06 40 ld a,($4006) ; read IS_GAME_IN_PLAY 26B4: 0F rrca ; move flag into carry 26B5: D0 ret nc ; return if game is not in play 26B6: 3A 80 43 ld a,($4380) 26B9: 0F rrca 26BA: D0 ret nc 26BB: 3A 0D 40 ld a,($400D) ; read CURRENT_PLAYER 26BE: 0F rrca ; if carry flag is set, PLAYER 2 is current player 26BF: 38 0E jr c,$26CF ; jumpt to $26CF if player 2 playing ; Read player 1 bomb button 26C1: 3A 10 40 ld a,($4010) ; read PORT_STATE_8100 26C4: CB 4F bit 1,a ; test if IPT_BUTTON2 is pressed 26C6: C8 ret z ; return if not 26C7: 3A 13 40 ld a,($4013) ; read PREV_PORT_STATE_8100 26CA: CB 4F bit 1,a ; test if IPT_BUTTON2 was already pressed 26CC: C0 ret nz ; return if true 26CD: 18 0C jr $26DB ; try to spawn a player bomb ; Read Player 2 bomb button 26CF: 3A 11 40 ld a,($4011) ; read PORT_STATE_8101 26D2: CB 57 bit 2,a ; test if IPT_BUTTON2 (cocktail) is pressed 26D4: C8 ret z ; return if not 26D5: 3A 14 40 ld a,($4014) ; read PREV_PORT_STATE_8101 26D8: CB 57 bit 2,a ; test if IPT_BUTTON2 (cocktail) was already pressed 26DA: C0 ret nz ; return if true ; Player's pressed the bomb button. Can we spawn a bomb? TRY_SPAWN_PLAYER_BOMB: 26DB: 21 C0 43 ld hl,$43C0 ; load HL with address of PLAYER_BOMBS 26DE: 11 1F 00 ld de,$001F ; sizeof(PLAYER_BOMB)-1 26E1: 06 02 ld b,$02 ; player gets a max of 2 bombs 26E3: 7E ld a,(hl) ; read PLAYER_BOMB.IsActive 26E4: 2C inc l 26E5: B6 or (hl) ; combine with PLAYER_BOMB.IsExploding flag 26E6: 0F rrca ; move result into carry 26E7: 30 04 jr nc,$26ED ; if bomb slot is not active, and not exploding, we can re-use it, goto SPAWN_PLAYER_BOMB 26E9: 19 add hl,de ; bump HL to point to next PLAYER_BOMB record in array 26EA: 10 F7 djnz $26E3 ; repeat until we find a vacant bomb slot or we find all bombs are in use. 26EC: C9 ret ; HL+1 = pointer to PLAYER_BOMB struct SPAWN_PLAYER_BOMB: 26ED: 2D dec l ; align HL to point to start of PLAYER_BOMB struct. 26EE: 36 01 ld (hl),$01 ; set PLAYER_BOMB.IsActive to true 26F0: 2C inc l 26F1: 36 00 ld (hl),$00 ; set PLAYER_BOMB.IsExploding to false 26F3: 2C inc l 26F4: 36 00 ld (hl),$00 ; set PLAYER_BOMB.StageOfLife to 0 26F6: CD 08 29 call $2908 ; call QUEUE_PLAYER_BOMB_DROP_SOUND 26F9: C9 ret ; ; Should we scroll a Fuel Tank onto the screen? ; SPAWN_FUEL_TANK: 26FA: 3A 1A 41 ld a,($411A) ; read NEXT_GROUND_OBJECT_ID 26FD: E6 02 and $02 ; is it a FUEL TANK? 26FF: C8 ret z ; exit if not 2700: DD 21 80 42 ld ix,$4280 ; load IX with address of GROUND_OBJECTS array 2704: 11 20 00 ld de,$0020 ; sizeof(GROUND_OBJECT) 2707: 06 08 ld b,$08 ; length of GROUND_OBJECTS array 2709: DD 7E 00 ld a,(ix+$00) ; read GROUND_OBJECT.IsActive flag 270C: DD B6 01 or (ix+$01) ; combine with GROUND_OBJECT.IsExploding flag 270F: 0F rrca ; move into carry. 2710: 30 05 jr nc,$2717 ; if object is not active nor exploding, its free to re-use, goto FUEL_TANK_INIT 2712: DD 19 add ix,de 2714: 10 F3 djnz $2709 2716: C9 ret FUEL_TANK_INIT: 2717: DD 36 00 01 ld (ix+$00),$01 ; set GROUND_OBJECT.IsActive flag 271B: DD 36 01 00 ld (ix+$01),$00 ; clear GROUND_OBJECT.IsExploding flag 271F: DD 36 02 00 ld (ix+$02),$00 ; set GROUND_OBJECT.StageOfLife to 0 (see GROUND_OBJECT_INIT @ $18E6) 2723: DD 36 17 01 ld (ix+$17),$01 ; set GROUND_OBJECT.ObjectType 2727: AF xor a 2728: 32 1A 41 ld ($411A),a ; clear NEXT_GROUND_OBJECT_ID 272B: C9 ret ; ; Should we scroll a Rocket onto the screen? ; SPAWN_ROCKET_ON_GROUND: 272C: 3A 1A 41 ld a,($411A) ; read NEXT_GROUND_OBJECT_ID 272F: E6 01 and $01 ; is it a ROCKET? 2731: C8 ret z ; exit if not 2732: DD 21 80 42 ld ix,$4280 ; load IX with address of GROUND_OBJECTS array 2736: 11 20 00 ld de,$0020 ; sizeof(GROUND_OBJECT) 2739: 06 08 ld b,$08 ; length of GROUND_OBJECTS array 273B: DD 7E 00 ld a,(ix+$00) ; read GROUND_OBJECT.IsActive flag 273E: DD B6 01 or (ix+$01) ; combine with GROUND_OBJECT.IsExploding flag 2741: 0F rrca ; move into carry. 2742: 30 05 jr nc,$2749 ; if object is not active nor exploding, its free to re-use, goto ROCKET_ON_GROUND_INIT 2744: DD 19 add ix,de ; bump IX to point to next GROUND_OBJECT in array 2746: 10 F3 djnz $273B ; repeat until all records in GROUND_OBJECTS have been scanned. 2748: C9 ret ROCKET_ON_GROUND_INIT: 2749: DD 36 00 01 ld (ix+$00),$01 ; set GROUND_OBJECT.IsActive flag 274D: DD 36 01 00 ld (ix+$01),$00 ; clear GROUND_OBJECT.IsExploding flag 2751: DD 36 02 00 ld (ix+$02),$00 ; ; set GROUND_OBJECT.StageOfLife to 0 (see GROUND_OBJECT_INIT @ $18E6) 2755: DD 36 17 00 ld (ix+$17),$00 ; set GROUND_OBJECT.ObjectType to 0 (Rocket) 2759: AF xor a 275A: 32 1A 41 ld ($411A),a ; clear NEXT_GROUND_OBJECT_ID 275D: C9 ret ; ; Should we scroll a Mystery onto the screen? ; ; SPAWN_MYSTERY: 275E: 3A 1A 41 ld a,($411A) ; read NEXT_GROUND_OBJECT_ID 2761: E6 04 and $04 ; is it a MYSTERY? 2763: C8 ret z ; exit if not ; Find a vacant GROUND_OBJECT record to use to hold our MYSTERY. 2764: DD 21 80 42 ld ix,$4280 ; load IX with address of GROUND_OBJECTS array 2768: 11 20 00 ld de,$0020 ; sizeof(GROUND_OBJECT) 276B: 06 08 ld b,$08 ; length of GROUND_OBJECTS array 276D: DD 7E 00 ld a,(ix+$00) ; read GROUND_OBJECT.IsActive flag 2770: DD B6 01 or (ix+$01) ; combine with GROUND_OBJECT.IsExploding flag 2773: 0F rrca ; move combined flags into carry. 2774: 30 05 jr nc,$277B ; if object is not active nor exploding, its free to re-use, goto MYSTERY_INIT 2776: DD 19 add ix,de ; bump IX to point to next GROUND_OBJECT in array 2778: 10 F3 djnz $276D ; repeat until all records in GROUND_OBJECTS have been scanned. 277A: C9 ret ; IX = pointer to vacant GROUND_OBJECT record to use for a MYSTERY. MYSTERY_INIT: 277B: DD 36 00 01 ld (ix+$00),$01 ; set GROUND_OBJECT.IsActive flag 277F: DD 36 01 00 ld (ix+$01),$00 ; clear GROUND_OBJECT.IsExploding flag 2783: DD 36 02 00 ld (ix+$02),$00 ; ; set GROUND_OBJECT.StageOfLife to 0 (see GROUND_OBJECT_INIT @ $18E6) 2787: DD 36 17 02 ld (ix+$17),$02 ; set GROUND_OBJECT.ObjectType to 2 (Mystery) 278B: AF xor a 278C: 32 1A 41 ld ($411A),a ; clear NEXT_GROUND_OBJECT_ID 278F: C9 ret ; ; Should we scroll a Base (I think it looks more like a drilling rig, personally) onto the screen? ; SPAWN_BASE: 2790: 3A 1A 41 ld a,($411A) ; read NEXT_GROUND_OBJECT_ID 2793: E6 08 and $08 ; is it a BASE? 2795: C8 ret z ; exit of not ; Find a vacant GROUND_OBJECT record to use to hold our BASE. 2796: DD 21 80 42 ld ix,$4280 ; load IX with address of GROUND_OBJECTS array 279A: 11 20 00 ld de,$0020 ; sizeof(GROUND_OBJECT) 279D: 06 08 ld b,$08 ; length of GROUND_OBJECTS array 279F: DD 7E 00 ld a,(ix+$00) ; read GROUND_OBJECT.IsActive flag 27A2: DD B6 01 or (ix+$01) ; combine with GROUND_OBJECT.IsExploding flag 27A5: 0F rrca ; move into carry. 27A6: 30 05 jr nc,$27AD ; if object is not active nor exploding, its free to re-use, goto BASE_INIT 27A8: DD 19 add ix,de ; bump IX to point to next GROUND_OBJECT in array 27AA: 10 F3 djnz $279F ; repeat until all records in GROUND_OBJECTS have been scanned. 27AC: C9 ret ; IX = pointer to vacant GROUND_OBJECT to use for a BASE. BASE_INIT: 27AD: DD 36 00 01 ld (ix+$00),$01 ; set GROUND_OBJECT.IsActive flag 27B1: DD 36 01 00 ld (ix+$01),$00 ; clear GROUND_OBJECT.IsExploding flag 27B5: DD 36 02 00 ld (ix+$02),$00 ; ; set GROUND_OBJECT.StageOfLife to 0 (see GROUND_OBJECT_INIT @ $18E6) 27B9: DD 36 17 03 ld (ix+$17),$03 ; set GROUND_OBJECT.ObjectType to 3 (Base) 27BD: AF xor a 27BE: 32 1A 41 ld ($411A),a ; clear NEXT_GROUND_OBJECT_ID 27C1: C9 ret ; ; !IMPORTANT! ; Used to ensure correct landscape and colour scheme is chosen ; LANDSCAPE_CHANGE: 27C2: CD C9 27 call $27C9 ; call SELECT_NEXT_LANDSCAPE 27C5: CD 04 28 call $2804 ; call SET_LANDSCAPE_AND_BACKGROUND_COLOUR 27C8: C9 ret ; ; Check if player has reached end of current landscape. ; ; If so: ; * Select next landscape that player will fly over. ; * Update progress bar (1ST, 2ND, 3RD.. BASE). ; SELECT_NEXT_LANDSCAPE: 27C9: 2A 18 41 ld hl,($4118) ; load HL with contents of LANDSCAPE_LAYOUT_PTR 27CC: 7E ld a,(hl) ; read byte from landscape 27CD: FE FF cp $FF ; end of level marker? 27CF: C0 ret nz ; return if not end of level ; we've reached the end of the level. Advance to next level if we can 27D0: 21 00 44 ld hl,$4400 ; load HL with address of INFLIGHT_ENEMIES 27D3: 11 01 44 ld de,$4401 27D6: 01 80 00 ld bc,$0080 ; sizeof(INFLIGHT_ENEMIES) 27D9: 36 00 ld (hl),$00 27DB: ED B0 ldir ; clear INFLIGHT_ENEMIES array 27DD: 21 1E 41 ld hl,$411E ; load HL with address of CURRENT_PLAYERS_LEVEL 27E0: 7E ld a,(hl) ; read level 27E1: FE 05 cp $05 ; are we on the (final) "BASE" level? 27E3: 28 01 jr z,$27E6 ; if so, goto $27E6 27E5: 34 inc (hl) ; otherwise, increment level ; get landscape for next level 27E6: 7E ld a,(hl) ; read level 27E7: 47 ld b,a ; effectively multiply level.. 27E8: 87 add a,a 27E9: 80 add a,b ; .. by 3. 27EA: 5F ld e,a 27EB: 16 00 ld d,$00 ; extend A into DE 27ED: 21 D0 29 ld hl,$29D0 ; load HL with address of LANDSCAPE_LAYOUT_METADATA_TABLE table 27F0: 19 add hl,de 27F1: 7E ld a,(hl) 27F2: 32 18 41 ld ($4118),a ; set LANDSCAPE_LAYOUT_PTR_LO 27F5: 23 inc hl 27F6: 7E ld a,(hl) 27F7: 32 19 41 ld ($4119),a ; set LANDSCAPE_LAYOUT_PTR_LO 27FA: 23 inc hl 27FB: 7E ld a,(hl) 27FC: 32 1D 41 ld ($411D),a ; set LANDSCAPE_FLAGS ; update progress bar 27FF: 11 02 07 ld de,$0702 ; Command ID: 7 = HEAD_UP_DISPLAY_COMMAND, Param:2 = DISPLAY_CURRENT_PLAYER_PROGRESS_BAR 2802: FF rst $38 ; call QUEUE_COMMAND 2803: C9 ret ; ; ; Change the landscape and background colours, enable starfield . ; ; SET_LANDSCAPE_AND_BACKGROUND_COLOUR: 2804: 3A 5F 42 ld a,($425F) ; read TIMING_VARIABLE 2807: A7 and a ; test if zero 2808: C0 ret nz ; return if not zero 2809: 21 1B 40 ld hl,$401B ; load HL with address of LANDSCAPE_COLOUR_CHANGE_COUNTER 280C: 34 inc (hl) ; increment counter 280D: 7E ld a,(hl) ; read value of counter 280E: 0F rrca ; move bit zero into carry 280F: D8 ret c ; if counter is not an even number, exit ; change colour of landscape 2810: 21 17 41 ld hl,$4117 ; load HL with address of LANDSCAPE_COLOUR 2813: 7E ld a,(hl) ; read colour value 2814: 3C inc a ; increment it 2815: E6 07 and $07 ; ensure its a value between 0..7 2817: FE 01 cp $01 ; compare to 1 2819: 28 03 jr z,$281E ; if it's 1 then we can't use that colour, goto $281E 281B: 77 ld (hl),a ; update LANDSCAPE_COLOUR 281C: 18 02 jr $2820 281E: 3C inc a ; Adjust invalid colour 1 to 2 281F: 77 ld (hl),a ; update LANDSCAPE_COLOUR 2820: 47 ld b,a ; load B with value of LANDSCAPE_COLOUR ; If DISABLE_STARS = 1 then disable starfield & set background colour to black. ; Else set the background colour and starfield from the STARS_AND_BACKGROUND_TABLE 2821: 3A 11 41 ld a,($4111) ; read DISABLE_STARS flag 2824: 0F rrca ; move flag into carry 2825: 38 13 jr c,$283A ; if flag is set, goto HIDE_STARS_AND_SET_BACKGROUND_TO_BLACK 2827: 78 ld a,b ; load A with LANDSCAPE_COLOUR 2828: 21 42 28 ld hl,$2842 ; load HL with address of STARS_AND_BACKGROUND_TABLE 282B: 16 00 ld d,$00 282D: 87 add a,a ; multiply A by 2 282E: 5F ld e,a ; extend A into DE (or alternatively: DE = A) 282F: 19 add hl,de ; now HL points to an entry in STARS_AND_BACKGROUND_TABLE 2830: 7E ld a,(hl) ; read enable stars flag 2831: 32 04 68 ld ($6804),a ; enable/disable stars 2834: 23 inc hl ; 2835: 7E ld a,(hl) ; read background colour 2836: 32 03 68 ld ($6803),a ; set background colour 2839: C9 ret HIDE_STARS_AND_SET_BACKGROUND_TO_BLACK: 283A: AF xor a 283B: 32 04 68 ld ($6804),a ; disable stars 283E: 32 03 68 ld ($6803),a ; set background to black 2841: C9 ret STARS_AND_BACKGROUND_TABLE: 2842: 01 00 ; enable stars, black background 01 00 ; enable stars, black background 01 00 ; enable stars, black background 01 00 ; enable stars, black background 01 01 ; enable stars, blue background 01 01 ; enable stars, blue background 01 00 ; enable stars, black background 01 00 ; enable stars, black background ; called by $2300. Jumps to succeeding instruction. Was this to make the code harder to trace or waste CPU cycles? ; (I think the former is more likely.) 2852: C3 03 23 jp $2303 ; ; ! IMPORTANT ! ; Process the circular sound command queue @ starting $4243 (CIRC_SOUND_CMD_QUEUE_START) ; PROCESS_CIRC_SOUND_CMD_QUEUE: 2855: 11 41 42 ld de,$4241 ; load de with address of CIRC_SOUND_CMD_QUEUE_PROC_LO 2858: 1A ld a,(de) ; read CIRC_SOUND_CMD_QUEUE_PROC_LO 2859: 6F ld l,a 285A: 26 42 ld h,$42 ; Now HL = pointer to a command or a "done" marker ($FF) ; read command ID from sound command queue. If it's $FF then exit 285C: 7E ld a,(hl) ; read command ID 285D: FE FF cp $FF ; if its a "done" marker we do nothing with it 285F: C8 ret z ; return if done marker ; we don't play any sounds when the game's not in play 2860: 47 ld b,a ; preserve command ID in B 2861: 3A 06 40 ld a,($4006) ; read IS_GAME_IN_PLAY flag 2864: A7 and a ; test flag 2865: 78 ld a,b ; restore command ID from B register 2866: C4 B2 28 call nz,$28B2 ; if game is in play, call EXEC_AUDIO_COMMAND 2869: 36 FF ld (hl),$FF ; overwrite sound command with "done" flag ; have we hit the end of the sound queue? If so, we need to go back to start of queue and resume processing from there. 286B: 7D ld a,l 286C: FE 5E cp $5E 286E: 28 03 jr z,$2873 ; yes, we've reached end - so go to RESET_CIRC_SOUND_CMD_QUEUE_PROC_LO ; bump 2870: 3C inc a 2871: 12 ld (de),a ; update CIRC_SOUND_CMD_QUEUE_PROC_LO 2872: C9 ret ; Reset CIRC_SOUND_CMD_QUEUE_PROC_LO to point to start of queue in memory RESET_CIRC_SOUND_CMD_QUEUE_PROC_LO: 2873: 3E 43 ld a,$43 2875: 12 ld (de),a ; update CIRC_SOUND_CMD_QUEUE_PROC_LO 2876: C9 ret ; Queue a sound command in the CIRC_SOUND_CMD_QUEUE queue. ; ; A = Command to perform ; ; ; Value in A What it does ; ========== ============ ; 1 Halts current sound (I think!) ; 3 Queue rocket exploding sound ; 4 Queue UFO exploding sound ; 5 Queue Player jet exploding sound ; 6 Queue Player bullet fired sound ; 7 Queue low fuel alert sound ; 8 Queue new life awarded sound ; 9 Queue game start music voice 1 (doo doo doo de doo doo doo doo ;-) ) ; $0A Queue game start music voice 2 ; $0E Queue nice little jingle that I don't think is used in game ; $0F Queue another nice little unused jingle ; $12 Queue default ambient sound (reminds me of Paradroid C64) ; $13 Queue player bomb exploding sound ; $20 Queue bomb drop sound ; $21 Queue UFO ambient sound ; $22 Prep before queuing fireball sound (you need to queue $23 as well) ; $23 Queue Fireball sizzling ambient sound ; $24 Queue maze ambient sound ; ; There probably are other sounds/melodies available, but these are the sounds used in game. ; Tinker with the value of A and see what you can find :) ; QUEUE_SOUND_COMMAND: 2877: C5 push bc 2878: D5 push de 2879: E5 push hl 287A: 47 ld b,a ; preserve A in B, as A will be used to read from (DE) 287B: 11 40 42 ld de,$4240 ; load DE with address of CIRC_SOUND_CMD_QUEUE_PTR 287E: 1A ld a,(de) ; get LSB of address into A 287F: 6F ld l,a 2880: 26 42 ld h,$42 ; now HL = $42xx, where xx = value read from CIRC_SOUND_CMD_QUEUE_PTR_LO ; add command ID to sound queue 2882: 70 ld (hl),b ; write command to CIRC_SOUND_CMD_QUEUE 2883: 7D ld a,l ; get LSB of HL into A ; test if we've hit the end of the queue - if so, reset CIRC_SOUND_CMD_QUEUE_PTR_LO 2884: FE 5E cp $5E ; have we hit the end of the circular buffer? 2886: 28 04 jr z,$288C ; yes, we need to go back to start 2888: 3C inc a ; otherwise, bump A to point to next entry in buffer 2889: 12 ld (de),a ; and update CIRC_SOUND_CMD_QUEUE_PTR_LO 288A: 18 03 jr $288F ; and we're out. ; reset CIRC_SOUND_CMD_QUEUE_PTR_LO 288C: 3E 43 ld a,$43 ; LSB of CIRC_SOUND_CMD_QUEUE_START 288E: 12 ld (de),a ; set CIRC_SOUND_CMD_QUEUE_PTR_LO to point to start of buffer ; restore registers 288F: E1 pop hl 2890: D1 pop de 2891: C1 pop bc 2892: C9 ret DISABLE_SOUND: 2893: 3A 42 42 ld a,($4242) ; read IRQTRIGGER_CTRL 2896: F6 10 or $10 ; set bit 4 to disable sound 2898: 32 42 42 ld ($4242),a ; set IRQTRIGGER_CTRL 289B: 32 01 82 ld ($8201),a ; write to i8255 chip 289E: AF xor a 289F: C3 B2 28 jp $28B2 ; jump to EXEC_SOUND_COMMAND ENABLE_SOUND: 28A2: AF xor a 28A3: CD B2 28 call $28B2 ; jump to EXEC_SOUND_COMMAND 28A6: 3A 42 42 ld a,($4242) ; read IRQTRIGGER_CTRL 28A9: E6 EF and $EF ; clear bit 4 to enable sound 28AB: 32 42 42 ld ($4242),a ; IRQTRIGGER_CTRL 28AE: 32 01 82 ld ($8201),a ; write to interrupt trigger port 28B1: C9 ret ; ; Execute a command on the audio CPU. ; ; Expects: ; A = command to execute. See QUEUE_SOUND_COMMAND for list of commands. ; EXEC_AUDIO_COMMAND: 28B2: 32 00 82 ld ($8200),a ; write to AY-3-8910 28B5: 3A 42 42 ld a,($4242) ; read IRQTRIGGER_CTRL 28B8: E6 F7 and $F7 ; clear bit 3 (audio CPU) 28BA: 32 01 82 ld ($8201),a ; write to i8255 chip ; use NOPs to create a delay before writing to i8255 chip again 28BD: 00 nop 28BE: 00 nop 28BF: 00 nop 28C0: 00 nop 28C1: 3A 42 42 ld a,($4242) ; read IRQTRIGGER_CTRL 28C4: F6 08 or $08 ; set bit 3 (audio CPU) 28C6: 32 01 82 ld ($8201),a ; write to i8255 chip 28C9: C9 ret 28CA: 3E 08 ld a,$08 28CC: 18 E4 jr $28B2 QUEUE_EXPLOSION_SOUND_1: 28CE: 3E 01 ld a,$01 28D0: CD 77 28 call $2877 ; call QUEUE_SOUND_COMMAND 28D3: C3 36 29 jp $2936 ; jump to QUEUE_EXPLOSION_SOUND ; Named duplicate because its the exact same code as QUEUE_EXPLOSION_SOUND_1, above. QUEUE_EXPLOSION_SOUND_DUPLICATE: 28D6: 3E 01 ld a,$01 28D8: CD 77 28 call $2877 ; call QUEUE_SOUND_COMMAND 28DB: C3 36 29 jp $2936 ; jump to QUEUE_EXPLOSION_SOUND QUEUE_PLAYER_BOMB_EXPLOSION_SOUND: 28DE: 3E 30 ld a,$30 28E0: CD 77 28 call $2877 ; call QUEUE_SOUND_COMMAND 28E3: 3E 02 ld a,$02 28E5: CD 77 28 call $2877 ; call QUEUE_SOUND_COMMAND 28E8: C3 36 29 jp $2936 ; jump to QUEUE_EXPLOSION_SOUND QUEUE_UFO_DEATH_SOUND: 28EB: 3E 04 ld a,$04 28ED: CD 77 28 call $2877 ; call QUEUE_SOUND_COMMAND 28F0: C3 36 29 jp $2936 ; jump to QUEUE_EXPLOSION_SOUND QUEUE_PLAYER_HIT_OBJECT_SOUND: 28F3: 3E 05 ld a,$05 28F5: CD 77 28 call $2877 ; call QUEUE_SOUND_COMMAND 28F8: C3 36 29 jp $2936 ; jump to QUEUE_EXPLOSION_SOUND QUEUE_ROCKET_EXPLOSION_SOUND: 28FB: 3E 03 ld a,$03 28FD: CD 77 28 call $2877 ; call QUEUE_SOUND_COMMAND 2900: C3 36 29 jp $2936 ; jump to QUEUE_EXPLOSION_SOUND QUEUE_PLAYER_BULLET_FIRED_SOUND: 2903: 3E 06 ld a,$06 2905: C3 77 28 jp $2877 ; call QUEUE_SOUND_COMMAND QUEUE_PLAYER_BOMB_DROP_SOUND: 2908: 3E 20 ld a,$20 290A: C3 77 28 jp $2877 ; call QUEUE_SOUND_COMMAND 290D: C9 ret ; Unused 290E: 3E 0A ld a,$0A 2910: C3 77 28 jp $2877 ; call QUEUE_SOUND_COMMAND ; ; You can change the start music for the game by inputting the following into the MAME debugger: ; ; maincpu.mb@2913 = C3 ; maincpu.mb@2914 = 1D (or 2C if you want another tune) ; maincpu.mb@2915 = 29 ; QUEUE_GAME_START_MUSIC: 2913: 3E 09 ld a,$09 2915: CD 77 28 call $2877 ; call QUEUE_SOUND_COMMAND 2918: 3E 0A ld a,$0A 291A: C3 77 28 jp $2877 ; call QUEUE_SOUND_COMMAND ; ; This is a nice little jingle that I think was *meant* to be played when you gain an extra life OR you complete all the levels. ; Its a shame its not played - as far as I know. No code references it and I've never heard it without hacking. ; QUEUE_UNUSED_MUSIC_1: 291D: 3E 0B ld a,$0B 291F: CD 77 28 call $2877 ; call QUEUE_SOUND_COMMAND 2922: 3E 0C ld a,$0C 2924: CD 77 28 call $2877 ; call QUEUE_SOUND_COMMAND 2927: 3E 0D ld a,$0D 2929: C3 77 28 jp $2877 ; call QUEUE_SOUND_COMMAND ; Another nice tune that is not used. QUEUE_UNUSED_MUSIC_2: 292C: 3E 0E ld a,$0E 292E: CD 77 28 call $2877 ; call QUEUE_SOUND_COMMAND 2931: 3E 0F ld a,$0F 2933: C3 77 28 jp $2877 ; call QUEUE_SOUND_COMMAND QUEUE_EXPLOSION_SOUND: 2936: 3E 13 ld a,$13 2938: CD 77 28 call $2877 ; call QUEUE_SOUND_COMMAND 293B: 3E 14 ld a,$14 293D: CD 77 28 call $2877 ; call QUEUE_SOUND_COMMAND 2940: 3E 15 ld a,$15 2942: C3 77 28 jp $2877 ; call QUEUE_SOUND_COMMAND ; Unused 2945: 3A 42 42 ld a,($4242) ; read IRQTRIGGER_CTRL 2948: F6 40 or $40 294A: B0 or b 294B: 32 42 42 ld ($4242),a ; set IRQTRIGGER_CTRL 294E: 32 01 82 ld ($8201),a 2951: C9 ret ; Unused 2952: 3A 42 42 ld a,($4242) ; read IRQTRIGGER_CTRL 2955: E6 BF and $BF 2957: 32 42 42 ld ($4242),a ; set IRQTRIGGER_CTRL 295A: 32 01 82 ld ($8201),a 295D: AF xor a 295E: 32 1C 40 ld ($401C),a 2961: C9 ret QUEUE_DEFAULT_AMBIENT_SOUND: 2962: 3E 12 ld a,$12 2964: C3 77 28 jp $2877 ; call QUEUE_SOUND_COMMAND QUEUE_UFO_AMBIENT_SOUND: 2967: 3E 21 ld a,$21 2969: C3 77 28 jp $2877 ; call QUEUE_SOUND_COMMAND QUEUE_LOW_FUEL_SOUND: 296C: 3E 07 ld a,$07 296E: C3 77 28 jp $2877 ; call QUEUE_SOUND_COMMAND QUEUE_FIREBALL_SIZZLING_AMBIENT_SOUND: 2971: 3E 22 ld a,$22 2973: CD 77 28 call $2877 ; call QUEUE_SOUND_COMMAND 2976: 3E 23 ld a,$23 2978: C3 77 28 jp $2877 ; call QUEUE_SOUND_COMMAND QUEUE_MAZE_AMBIENT_SOUND: 297B: 3E 24 ld a,$24 297D: C3 77 28 jp $2877 ; call QUEUE_SOUND_COMMAND ; ; Play ambient "background" sounds appropriate for the current level ; AMBIENT_SOUND: 2980: 3A 5F 42 ld a,($425F) ; read TIMING_VARIABLE 2983: E6 3F and $3F ; 2985: C0 ret nz ; return if TIMING_VARIABLE modulus 64 !=0 2986: 3A 05 41 ld a,($4105) ; read CURRENT_PLAYER_FUEL 2989: FE 50 cp $50 298B: DC 6C 29 call c,$296C ; make "low fuel" sound ; determine what level player is on 298E: 3A 1D 41 ld a,($411D) ; read LANDSCAPE_FLAGS 2991: FE 00 cp $00 ; level 1? 2993: CC 62 29 call z,$2962 ; call QUEUE_DEFAULT_AMBIENT_SOUND 2996: FE 01 cp $01 ; level 3? (Yes, 3! Bit flags are not in step with the levels they are used on) 2998: CC 71 29 call z,$2971 ; call QUEUE_FIREBALL_SIZZLING_AMBIENT_SOUND 299B: FE 02 cp $02 ; level 2? 299D: 28 0D jr z,$29AC ; call TRY_PLAY_UFO_SOUND 299F: FE 04 cp $04 ; level 5? 29A1: CC 7B 29 call z,$297B ; call QUEUE_MAZE_AMBIENT_SOUND 29A4: FE 08 cp $08 29A6: CC 62 29 call z,$2962 ; call QUEUE_DEFAULT_AMBIENT_SOUND 29A9: C3 62 29 jp $2962 ; jump to QUEUE_DEFAULT_AMBIENT_SOUND TRY_PLAY_UFO_SOUND: 29AC: 3A 5F 42 ld a,($425F) ; read TIMING_VARIABLE 29AF: E6 7F and $7F ; Only play when TIMING_VARIABLE == 128 29B1: CA 67 29 jp z,$2967 29B4: C9 ret ; ; Sets CURRENT_PLAYER_FUEL_DRAIN_COUNTER depending on the current level. ; ; The fuel counter effectively determines the fuel drain rate of the current player's jet. ; The lower the fuel counter is set to, the faster the fuel drains from the jet. ; ; Expects: ; HL = $4106 (address of CURRENT_PLAYER_FUEL_DRAIN_COUNTER) SET_CURRENT_PLAYER_FUEL_DRAIN_COUNTER: 29B5: 3A 00 41 ld a,($4100) ; read CURRENT_PLAYER_MISSIONS_COMPLETED 29B8: A7 and a ; test if on "1ST" level 29B9: 28 09 jr z,$29C4 ; if on "1ST" level, goto $29C4 29BB: 3D dec a 29BC: 28 0C jr z,$29CA ; if on "2ND" level, goto $29CA ; player on 3RD level or higher, fastest fuel drain 29BE: 36 06 ld (hl),$06 ; set CURRENT_PLAYER_FUEL_DRAIN_COUNTER to 6 (fastest drain rate in game) 29C0: 2D dec l ; bump HL to point to CURRENT_PLAYER_FUEL 29C1: C3 2E 17 jp $172E ; player on 1ST level - slowest fuel drain 29C4: 36 0A ld (hl),$0A ; set CURRENT_PLAYER_FUEL_DRAIN_COUNTER to 10 (slowest drain rate in game) 29C6: 2D dec l ; bump HL to point to CURRENT_PLAYER_FUEL 29C7: C3 2E 17 jp $172E ; player on 2ND level - faster fuel drain 29CA: 36 08 ld (hl),$08 ; set CURRENT_PLAYER_FUEL_DRAIN_COUNTER to 8 (median between slowest and fastest) 29CC: 2D dec l ; bump HL to point to CURRENT_PLAYER_FUEL 29CD: C3 2E 17 jp $172E ; ; Byte 0: LSB of pointer to a landscape layout. Sets LANDSCAPE_LAYOUT_PTR_LO ; 1: MSB of pointer to a landscape layout. Sets LANDSCAPE_LAYOUT_PTR_HI ; 2: Flags used to determine how the landscape looks, what enemies appear, what ambient sounds play ; LANDSCAPE_LAYOUT_METADATA_TABLE: 29D0: E2 29 00 ; pointer to $29E2 (LEVEL_1_LANDSCAPE_LAYOUT), flags = 0 D3 2D 02 ; pointer to $2DD3 (LEVEL_2_LANDSCAPE_LAYOUT). flags = 2 C4 31 01 ; pointer to $31C4 (LEVEL_3_LANDSCAPE_LAYOUT). flags = 1 65 34 08 ; pointer to $3465 (LEVEL_4_LANDSCAPE_LAYOUT). flags = 8 56 38 04 ; pointer to $3856 (LEVEL_5_LANDSCAPE_LAYOUT). flags = 4 47 3C 10 ; pointer to $3C47 (BASE_LANDSCAPE_LAYOUT), flags = 16 ; See docs @ $15C5 (READ_LANDSCAPE_LAYOUT) to understand how layout is structured. LEVEL_1_LANDSCAPE_LAYOUT: 29E2: C0 33 BC 31 00 00 B0 32 AB 31 00 00 A0 33 9B 30 29F2: 00 00 93 30 8B 33 00 00 83 30 7B 31 00 00 70 32 2A02: 68 32 00 00 60 34 6B 2D 00 00 73 2D 7B 2E 00 00 2A12: 80 36 80 36 00 01 83 2C 83 30 00 00 80 36 80 36 2A22: 00 01 80 36 80 36 00 00 80 36 80 36 00 01 80 36 2A32: 80 36 00 00 80 36 80 36 00 01 80 36 80 36 00 01 2A42: 80 36 80 36 00 01 80 35 80 35 00 00 80 36 80 36 2A52: 00 04 83 35 7B 30 00 00 73 30 6B 31 00 00 60 32 2A62: 60 2C 00 00 6B 2C 73 2C 00 00 7B 2F 83 2F 00 00 2A72: 8B 2E 93 2D 00 00 98 2E A0 2E 00 00 AB 2D B0 2F 2A82: 00 00 BB 2C C3 2C 00 00 C8 36 C8 2E 00 00 D3 2D 2A92: DB 2E 00 00 E0 36 E0 36 00 01 E0 36 E0 36 00 01 2AA2: D8 32 D8 35 00 00 DB 2C E0 36 00 00 E0 36 E0 36 2AB2: 00 02 E0 36 E0 36 00 02 E0 36 D8 34 00 00 E0 36 2AC2: E0 36 00 01 E0 36 E0 36 00 01 E0 36 E0 36 00 04 2AD2: D8 32 D8 35 00 00 D0 30 D0 2D 00 00 DB 2F E0 36 2AE2: 00 00 E0 36 E0 36 00 00 E0 36 E0 36 00 01 E0 36 2AF2: E0 36 00 00 E0 36 E0 36 00 01 E0 36 E0 36 00 00 2B02: E0 36 E0 36 00 01 E0 36 E0 36 00 04 E0 36 E0 36 2B12: 00 02 D8 30 D3 30 00 00 D0 2C D8 2E 00 00 E0 36 2B22: E0 36 00 01 E0 36 DB 33 00 00 D0 33 CB 33 00 00 2B32: C8 36 C8 36 00 01 C0 31 C0 36 00 00 C0 36 C0 36 2B42: 00 01 B9 34 C0 35 00 00 C0 36 C0 36 00 01 B8 34 2B52: B8 33 00 00 B8 36 B8 36 00 01 B8 36 B8 36 00 01 2B62: B0 33 A8 34 00 00 B0 36 B0 36 00 04 B0 36 B0 36 2B72: 00 02 A8 33 A0 32 00 00 A0 36 A0 36 00 01 A0 36 2B82: A0 36 00 01 98 34 98 30 00 00 98 2C 98 33 00 00 2B92: 98 36 98 36 00 01 98 36 98 36 00 01 90 34 90 33 2BA2: 00 00 90 36 90 36 00 01 90 36 90 36 00 01 8B 32 2BB2: 83 31 00 00 7B 33 73 34 00 00 7B 2D 83 2E 00 00 2BC2: 8B 2D 93 2F 00 00 9B 2C A3 2E 00 00 AB 2C B3 2D 2BD2: 00 00 B8 36 B8 36 00 01 B8 36 B8 36 00 01 B3 32 2BE2: AB 33 00 00 A0 34 A0 34 00 00 A8 36 A8 36 00 01 2BF2: A3 32 9B 32 00 00 90 34 98 35 00 00 98 36 98 36 2C02: 00 01 93 30 8B 33 00 00 83 32 7B 30 00 00 73 33 2C12: 6B 31 00 00 60 34 6B 2E 00 00 70 35 70 2E 00 00 2C22: 78 36 78 36 00 01 7B 2D 83 2D 00 00 88 36 88 36 2C32: 00 02 8B 2F 93 2D 00 00 9B 2E A0 35 00 00 A0 36 2C42: A0 36 00 01 A3 2C A3 30 00 00 9B 33 90 33 00 00 2C52: 90 36 90 36 00 01 90 36 90 36 00 04 88 30 88 2C 2C62: 00 00 90 36 90 36 00 01 88 34 93 2E 00 00 9B 2D 2C72: A3 2F 00 00 AB 2C B3 2E 00 00 BB 2F C3 2C 00 00 2C82: C8 36 CB 2E 00 00 D0 36 D0 36 00 01 D0 35 C8 34 2C92: 00 00 D0 36 D0 36 00 02 D0 36 D0 36 00 02 CB 31 2CA2: C3 33 00 00 C0 36 C0 36 00 01 C0 36 BB 32 00 00 2CB2: B0 30 B0 2E 00 00 B8 36 B3 33 00 00 AB 32 AB 2C 2CC2: 00 00 B3 2C BB 2C 00 00 C3 2C C8 36 00 00 C8 36 2CD2: C8 36 00 01 C8 36 C8 35 00 00 C8 36 C0 34 00 00 2CE2: CB 2C D3 2C 00 00 DB 2C DB 30 00 00 D8 36 D8 36 2CF2: 00 02 DB 2D E0 36 00 00 E0 36 E0 36 00 04 D8 34 2D02: D8 30 00 00 D8 36 D8 36 00 01 D3 30 CB 32 00 00 2D12: C8 36 C8 36 00 01 C8 36 C8 36 00 04 CB 2C D0 35 2D22: 00 00 CB 30 C8 36 00 00 C0 33 BB 33 00 00 B3 31 2D32: AB 33 00 00 A0 34 A8 35 00 00 A8 36 A8 36 00 01 2D42: A8 36 A8 36 00 01 A8 36 A8 36 00 04 A8 36 A8 36 2D52: 00 02 A8 35 A8 2C 00 00 B0 36 B0 36 00 01 B0 36 2D62: B0 36 00 01 B0 36 B0 36 00 01 B0 36 B0 36 00 00 2D72: A8 34 A8 31 00 00 A8 36 A8 36 00 02 A8 36 A8 36 2D82: 00 01 A8 36 A8 36 00 04 AB 2F B3 2E 00 00 BB 2D 2D92: C0 35 00 00 C0 36 C0 36 00 02 BB 31 B3 32 00 00 2DA2: AB 31 A0 34 00 00 A3 31 9B 32 00 00 93 33 90 35 2DB2: 00 00 8B 32 80 34 00 00 8B 2C 93 2E 00 00 9B 2F 2DC2: A3 2C 00 00 AB 2D B3 2E 00 00 BB 2D C3 2F 00 00 2DD2: FF LEVEL_2_LANDSCAPE_LAYOUT: 2DD3: C8 2E D0 36 28 63 28 5E 00 D0 36 D0 2D 28 5E 30 2DE3: 63 00 D8 36 D8 36 30 5E 30 5E 02 D8 2F E0 36 38 2DF3: 63 38 5E 00 E0 36 D8 31 38 5E 40 63 00 D0 30 C8 2E03: 30 48 62 48 62 00 C0 30 B8 31 48 5D 50 62 00 B8 2E13: 36 B8 35 50 5E 5F 62 00 B0 34 B8 2C 5F 5D 5F 60 2E23: 00 C0 35 B8 34 50 60 48 60 00 C0 2D C8 2C 48 62 2E33: 50 62 00 D0 36 D0 2C 50 60 50 62 00 D8 2D D8 31 2E43: 5F 62 5F 60 00 D0 32 C8 31 5F 62 5F 60 00 C0 32 2E53: C0 2F 50 60 48 60 00 C8 2F C8 33 48 62 48 60 00 2E63: C8 2C D0 2C 40 60 38 60 00 D0 30 D0 2D 38 62 40 2E73: 62 00 D0 31 C8 34 48 62 48 60 00 C8 30 C0 30 40 2E83: 60 38 61 00 B8 30 B0 30 38 62 40 63 00 B0 2C B0 2E93: 30 40 60 38 61 00 B0 36 B0 2C 30 60 30 62 00 B8 2EA3: 2C B8 30 38 62 38 60 00 B8 2C C0 2C 30 60 30 62 2EB3: 00 C8 36 C0 30 38 62 40 5C 00 C0 2C C8 35 38 60 2EC3: 30 61 00 C0 30 C0 2C 28 5E 28 60 00 C0 31 B8 30 2ED3: 28 62 30 62 00 B0 32 B0 36 38 62 40 62 00 B0 2E 2EE3: B8 2D 48 62 50 62 00 C0 2E C8 2D 5F 62 5F 3A 00 2EF3: D0 36 D0 36 5F 3A 5F 60 01 D0 36 D0 36 50 3A 50 2F03: 3A 02 D0 36 D0 36 5F 62 5F 3A 01 D0 36 D0 36 5F 2F13: 3A 5F 3A 02 D0 36 D0 2E 5F 3A 5F 3A 00 D8 36 D8 2F23: 36 5F 3A 5F 60 04 D8 36 D8 36 50 60 48 60 04 D0 2F33: 32 D0 36 40 60 38 60 00 D0 36 D0 36 30 3A 30 3A 2F43: 01 C8 31 C0 32 30 3A 30 3A 00 C0 36 C0 36 30 3A 2F53: 30 3A 01 B8 31 B0 32 30 3A 30 60 00 B0 36 B0 36 2F63: 28 3A 28 3A 00 B0 36 B0 36 28 3A 28 3A 00 B0 2E 2F73: B8 2D 30 62 30 3A 00 C0 36 C0 2E 30 3A 30 3A 00 2F83: C8 36 C8 36 38 62 40 62 04 C8 36 C8 36 48 62 48 2F93: 3A 01 C8 2E D0 36 48 3A 50 62 00 D0 36 D0 36 5F 2FA3: 62 5F 3A 04 D0 36 D0 2D 5F 3A 5F 60 00 D8 36 D8 2FB3: 36 50 60 48 60 01 D8 36 D8 36 40 60 38 60 02 D0 2FC3: 31 C8 32 30 60 28 60 00 C0 31 B8 30 28 62 30 62 2FD3: 00 B0 32 B0 36 38 62 40 62 00 B0 2E B8 2D 48 62 2FE3: 50 62 00 C0 2E C8 2D 5F 62 5F 3A 00 D0 36 D0 36 2FF3: 5F 3A 5F 60 01 D0 36 D0 36 50 3A 50 3A 01 D0 36 3003: D0 36 5F 62 5F 3A 02 D0 36 D0 36 5F 3A 5F 3A 02 3013: D0 36 D0 2E 5F 3A 5F 3A 00 D8 36 D8 36 5F 3A 5F 3023: 60 01 D8 36 D8 36 50 60 48 60 01 D0 32 D0 36 40 3033: 60 38 60 00 D0 36 D0 36 30 3A 30 3A 04 C8 31 C0 3043: 32 30 3A 30 3A 00 C0 36 C0 36 30 3A 30 3A 01 B8 3053: 31 B0 32 30 3A 30 60 00 B0 36 B0 36 28 3A 28 3A 3063: 00 B0 36 B0 36 28 3A 28 3A 00 B0 2E B8 2D 30 62 3073: 30 3A 00 C0 36 C0 2E 30 3A 30 3A 00 C8 36 C8 36 3083: 38 62 40 62 04 C8 36 C8 36 48 62 48 3A 04 C8 2E 3093: D0 36 48 3A 50 62 00 D0 36 D0 36 5F 62 5F 3A 01 30A3: D0 36 D0 2D 5F 3A 5F 60 00 D8 36 D8 36 50 60 48 30B3: 60 04 D8 36 D8 36 40 60 38 60 04 D0 31 C8 32 30 30C3: 60 28 60 00 C8 2C D0 2C 28 62 30 62 00 D8 2C E0 30D3: 36 38 62 40 62 00 E0 36 E0 36 48 62 50 62 01 E0 30E3: 36 D8 34 5F 5C 50 60 00 E0 36 E0 36 48 60 40 60 30F3: 02 E0 36 E0 36 38 60 30 60 01 E0 36 E0 36 28 5E 3103: 30 62 01 D8 34 E0 36 36 5C 30 60 00 E0 36 E0 36 3113: 28 5E 30 5C 04 E0 36 E0 36 28 5E 30 62 01 E0 36 3123: E0 36 38 62 40 62 04 D8 30 D0 30 48 62 57 5C 00 3133: C8 30 C0 30 48 60 40 60 00 B8 30 B0 34 38 60 30 3143: 60 00 B8 2C C0 2C 28 3A 28 3A 00 C8 2C D0 2C 28 3153: 3A 28 3A 00 D8 2C D8 34 28 3A 28 3A 00 E0 36 E0 3163: 36 28 3A 30 5C 01 E0 36 E0 36 28 3A 28 3A 02 D8 3173: 30 D0 34 28 3A 28 3A 00 D8 2C E0 36 28 3A 28 3A 3183: 00 E0 36 E0 36 28 3A 28 3A 01 E0 36 E0 36 28 3A 3193: 28 3A 01 E0 36 E0 36 28 3A 28 3A 01 E0 36 D8 34 31A3: 28 3A 28 3A 00 E0 36 E0 36 28 3A 28 3A 04 E0 36 31B3: D8 30 28 3A 28 3A 00 D0 30 C8 30 28 3A 28 60 00 31C3: FF LEVEL_3_LANDSCAPE_LAYOUT: 31C4: C8 36 CB 2E 00 00 D3 2C DB 2E 00 00 E0 36 E0 36 31D4: 00 01 DB 30 D3 33 00 00 D0 35 CB 32 00 00 C3 33 31E4: B8 30 00 00 BB 2F C3 2D 00 00 CB 2E D3 2C 00 00 31F4: DB 2E E0 36 00 00 E0 36 E0 36 00 01 E0 36 E0 36 3204: 00 02 E0 36 E0 36 00 01 DB 30 D0 32 00 00 D3 2E 3214: DB 2C 00 00 DB 30 D3 32 00 00 C8 34 D3 2F 00 00 3224: D8 36 D8 36 00 02 D3 30 CB 33 00 00 C3 30 BB 33 3234: 00 00 B8 2F B8 30 00 00 B8 2E C3 2D 00 00 CB 2C 3244: D3 2F 00 00 D8 36 D8 36 00 01 D8 36 DB 2E 00 00 3254: E0 36 E0 36 00 04 E0 36 E0 36 00 02 E0 36 DB 33 3264: 00 00 D3 30 C8 30 00 00 C0 33 B8 34 00 00 C3 2F 3274: CB 2D 00 00 D3 2C DB 2F 00 00 E0 36 E0 36 00 02 3284: D8 32 D8 35 00 00 DB 2C E0 36 00 00 E0 36 E0 36 3294: 00 04 E0 36 E0 36 00 04 E0 36 D8 34 00 00 E0 36 32A4: E0 36 00 02 E0 36 E0 36 00 01 E0 36 E0 36 00 01 32B4: D8 32 D8 35 00 00 D0 30 D0 2D 00 00 DB 2F E0 36 32C4: 00 00 E0 36 E0 36 00 04 E0 36 E0 36 00 04 E0 36 32D4: E0 36 00 01 E0 36 E0 36 00 02 E0 36 E0 36 00 01 32E4: DB 30 D3 31 00 00 CB 32 C3 33 00 00 BB 31 BB 2C 32F4: 00 00 C3 2D CB 2E 00 00 D0 2C D8 2E 00 00 E0 36 3304: E0 36 00 02 E0 36 DB 33 00 00 D0 33 CB 33 00 00 3314: C8 36 CB 2E 00 00 D3 2C DB 2E 00 00 E0 36 E0 36 3324: 00 04 DB 30 D3 33 00 00 D0 35 CB 32 00 00 C3 33 3334: B8 30 00 00 BB 2F C3 2D 00 00 CB 2E D3 2C 00 00 3344: DB 2E E0 36 00 00 E0 36 E0 36 00 02 E0 36 E0 36 3354: 00 01 E0 36 E0 36 00 01 DB 30 D0 32 00 00 D3 2E 3364: DB 2C 00 00 DB 30 D3 32 00 00 C8 34 D3 2F 00 00 3374: D8 36 D8 36 00 02 D3 30 CB 33 00 00 C3 30 BB 33 3384: 00 00 B8 2F B8 30 00 00 B8 2E C3 2D 00 00 CB 2C 3394: D3 2F 00 00 D8 36 D8 36 00 00 D8 36 DB 2E 00 00 33A4: E0 36 E0 36 00 04 E0 36 E0 36 00 02 E0 36 DB 33 33B4: 00 00 D3 30 C8 30 00 00 C8 36 CB 2E 00 00 D3 2C 33C4: DB 2E 00 00 E0 36 E0 36 00 01 DB 30 D3 33 00 00 33D4: D0 35 CB 32 00 00 C3 33 B8 30 00 00 BB 2F C3 2D 33E4: 00 00 CB 2E D3 2C 00 00 DB 2E E0 36 00 00 E0 36 33F4: E0 36 00 01 E0 36 E0 36 00 01 E0 36 E0 36 00 02 3404: DB 30 D0 32 00 00 D3 2E DB 2C 00 00 DB 30 D3 32 3414: 00 00 C8 34 D3 2F 00 00 D8 36 D8 36 00 02 D3 30 3424: CB 33 00 00 C3 30 BB 33 00 00 B8 2F B8 30 00 00 3434: B8 2E C3 2D 00 00 CB 2C D3 2F 00 00 D8 36 D8 36 3444: 00 00 D8 36 DB 2E 00 00 E0 36 E0 36 00 02 E0 36 3454: E0 36 00 04 E0 36 DB 33 00 00 D3 30 C8 30 00 00 3464: FF LEVEL_4_LANDSCAPE_LAYOUT: 3465: 60 D1 60 D1 00 00 60 D1 60 D1 00 00 60 D1 60 D1 3475: 00 00 60 D1 60 D1 00 00 70 D1 70 D1 00 01 60 D1 3485: 60 D1 00 01 60 D1 60 D1 00 00 78 D1 78 D1 00 01 3495: 60 D1 60 D1 00 01 48 D1 48 D1 00 00 58 D1 58 D1 34A5: 00 01 48 D1 48 D1 00 00 58 D1 58 D1 00 01 58 D1 34B5: 58 D1 00 01 68 D1 68 D1 00 01 68 D1 68 D1 00 02 34C5: 78 D1 78 D1 00 01 78 D1 78 D1 00 01 88 D1 88 D1 34D5: 00 01 88 D1 88 D1 00 02 98 D1 98 D1 00 01 98 D1 34E5: 98 D1 00 01 90 D1 90 D1 00 04 90 D1 90 D1 00 04 34F5: 90 D1 90 D1 00 01 90 D1 90 D1 00 00 A0 D1 A0 D1 3505: 00 01 80 D1 80 D1 00 00 90 D1 90 D1 00 01 70 D1 3515: 70 D1 00 00 80 D1 80 D1 00 01 60 D1 60 D1 00 00 3525: 70 D1 70 D1 00 01 50 D1 50 D1 00 00 60 D1 60 D1 3535: 00 01 50 D1 50 D1 00 00 48 D1 48 D1 00 00 58 D1 3545: 58 D1 00 01 50 D1 50 D1 00 00 50 D1 50 D1 00 00 3555: 60 D1 60 D1 00 01 58 D1 58 D1 00 00 68 D1 68 D1 3565: 00 01 60 D1 60 D1 00 00 70 D1 70 D1 00 01 68 D1 3575: 68 D1 00 00 78 D1 78 D1 00 01 70 D1 70 D1 00 00 3585: 80 D1 80 D1 00 01 80 D1 80 D1 00 00 80 D1 80 D1 3595: 00 00 80 D1 80 D1 00 04 80 D1 80 D1 00 02 80 D1 35A5: 80 D1 00 04 80 D1 80 D1 00 01 80 D1 80 D1 00 01 35B5: 78 D1 78 D1 00 01 70 D1 70 D1 00 02 68 D1 68 D1 35C5: 00 04 60 D1 60 D1 00 01 60 D1 60 D1 00 01 58 D1 35D5: 58 D1 00 00 58 D1 58 D1 00 00 60 D1 60 D1 00 01 35E5: 48 D1 48 D1 00 00 48 D1 48 D1 00 00 50 D1 50 D1 35F5: 00 01 48 D1 48 D1 00 01 58 D1 58 D1 00 01 48 D1 3605: 48 D1 00 00 50 D1 50 D1 00 01 48 D1 48 D1 00 00 3615: 60 D1 60 D1 00 01 50 D1 50 D1 00 02 40 D1 40 D1 3625: 00 00 60 D1 60 D1 00 01 40 D1 40 D1 00 00 60 D1 3635: 60 D1 00 01 40 D1 40 D1 00 00 50 D1 50 D1 00 01 3645: 40 D1 40 D1 00 00 50 D1 50 D1 00 01 60 D1 60 D1 3655: 00 00 60 D1 60 D1 00 02 60 D1 60 D1 00 00 60 D1 3665: 60 D1 00 02 60 D1 60 D1 00 00 60 D1 60 D1 00 02 3675: 70 D1 70 D1 00 01 60 D1 60 D1 00 01 60 D1 60 D1 3685: 00 00 78 D1 78 D1 00 01 60 D1 60 D1 00 01 48 D1 3695: 48 D1 00 00 58 D1 58 D1 00 01 48 D1 48 D1 00 00 36A5: 58 D1 58 D1 00 01 58 D1 58 D1 00 01 68 D1 68 D1 36B5: 00 01 68 D1 68 D1 00 02 78 D1 78 D1 00 01 78 D1 36C5: 78 D1 00 01 88 D1 88 D1 00 01 88 D1 88 D1 00 02 36D5: 98 D1 98 D1 00 01 98 D1 98 D1 00 01 90 D1 90 D1 36E5: 00 04 90 D1 90 D1 00 04 90 D1 90 D1 00 01 90 D1 36F5: 90 D1 00 00 A0 D1 A0 D1 00 01 80 D1 80 D1 00 00 3705: 90 D1 90 D1 00 01 70 D1 70 D1 00 00 80 D1 80 D1 3715: 00 01 60 D1 60 D1 00 00 70 D1 70 D1 00 01 50 D1 3725: 50 D1 00 00 60 D1 60 D1 00 01 50 D1 50 D1 00 00 3735: 48 D1 48 D1 00 00 58 D1 58 D1 00 01 50 D1 50 D1 3745: 00 00 50 D1 50 D1 00 00 60 D1 60 D1 00 01 58 D1 3755: 58 D1 00 00 68 D1 68 D1 00 01 60 D1 60 D1 00 00 3765: 70 D1 70 D1 00 01 68 D1 68 D1 00 00 78 D1 78 D1 3775: 00 01 70 D1 70 D1 00 00 80 D1 80 D1 00 01 80 D1 3785: 80 D1 00 00 80 D1 80 D1 00 00 80 D1 80 D1 00 04 3795: 80 D1 80 D1 00 02 80 D1 80 D1 00 04 80 D1 80 D1 37A5: 00 01 80 D1 80 D1 00 01 78 D1 78 D1 00 01 70 D1 37B5: 70 D1 00 02 68 D1 68 D1 00 04 60 D1 60 D1 00 01 37C5: 60 D1 60 D1 00 01 58 D1 58 D1 00 00 58 D1 58 D1 37D5: 00 00 60 D1 60 D1 00 01 48 D1 48 D1 00 00 48 D1 37E5: 48 D1 00 00 50 D1 50 D1 00 01 48 D1 48 D1 00 01 37F5: 58 D1 58 D1 00 01 48 D1 48 D1 00 00 50 D1 50 D1 3805: 00 01 48 D1 48 D1 00 00 60 D1 60 D1 00 01 50 D1 3815: 50 D1 00 02 40 D1 40 D1 00 00 60 D1 60 D1 00 01 3825: 40 D1 40 D1 00 00 60 D1 60 D1 00 01 40 D1 40 D1 3835: 00 00 50 D1 50 D1 00 01 60 D1 60 D1 00 00 60 D1 3845: 60 D1 00 00 60 D1 60 D1 00 00 60 D1 60 D1 00 00 3855: FF LEVEL_5_LANDSCAPE_LAYOUT: 3856: 60 D1 60 D1 47 D1 47 D1 02 60 D1 60 D1 47 D1 47 3866: D1 02 60 D1 60 D1 37 D1 37 D1 00 60 D1 60 D1 37 3876: D1 37 D1 00 60 D1 60 D1 37 D1 37 D1 00 50 D1 50 3886: D1 37 D1 37 D1 02 50 D1 50 D1 37 D1 37 D1 00 60 3896: D1 60 D1 37 D1 37 D1 00 60 D1 60 D1 37 D1 37 D1 38A6: 00 60 D1 60 D1 37 D1 37 D1 00 60 D1 60 D1 47 D1 38B6: 47 D1 00 60 D1 60 D1 47 D1 47 D1 00 60 D1 60 D1 38C6: 47 D1 47 D1 02 60 D1 60 D1 47 D1 47 D1 02 60 D1 38D6: 60 D1 47 D1 47 D1 02 60 D1 60 D1 2F D1 2F D1 02 38E6: 60 D1 60 D1 2F D1 2F D1 00 60 D1 60 D1 2F D1 2F 38F6: D1 00 40 D1 40 D1 2F D1 2F D1 00 40 D1 40 D1 2F 3906: D1 2F D1 00 40 D1 40 D1 2F D1 2F D1 00 40 D1 40 3916: D1 2F D1 2F D1 00 40 D1 40 D1 2F D1 2F D1 00 A0 3926: D1 A0 D1 2F D1 2F D1 00 A0 D1 A0 D1 2F D1 2F D1 3936: 00 A0 D1 A0 D1 2F D1 2F D1 00 A0 D1 A0 D1 2F D1 3946: 2F D1 00 A0 D1 A0 D1 2F D1 2F D1 02 A0 D1 A0 D1 3956: 2F D1 2F D1 02 A0 D1 A0 D1 37 D1 37 D1 02 A0 D1 3966: A0 D1 8F D1 8F D1 02 A0 D1 A0 D1 8F D1 8F D1 02 3976: A0 D1 A0 D1 8F D1 8F D1 02 A0 D1 A0 D1 8F D1 8F 3986: D1 00 A0 D1 A0 D1 8F D1 8F D1 00 A0 D1 A0 D1 8F 3996: D1 8F D1 00 D8 D1 D8 D1 8F D1 8F D1 00 D8 D1 D8 39A6: D1 8F D1 8F D1 00 D8 D1 D8 D1 8F D1 8F D1 00 D8 39B6: D1 D8 D1 C7 D1 C7 D1 00 D8 D1 D8 D1 C7 D1 C7 D1 39C6: 02 D8 D1 D8 D1 C7 D1 C7 D1 02 D8 D1 D8 D1 C7 D1 39D6: C7 D1 02 D8 D1 D8 D1 C7 D1 C7 D1 02 D8 D1 D8 D1 39E6: C7 D1 C7 D1 00 D8 D1 D8 D1 3F D1 3F D1 00 D8 D1 39F6: D8 D1 3F D1 3F D1 00 D8 D1 D8 D1 3F D1 3F D1 00 3A06: D8 D1 D8 D1 3F D1 3F D1 00 D8 D1 D8 D1 3F D1 3F 3A16: D1 00 D8 D1 D8 D1 3F D1 3F D1 00 50 D1 50 D1 3F 3A26: D1 3F D1 00 50 D1 50 D1 3F D1 3F D1 00 50 D1 50 3A36: D1 3F D1 3F D1 00 50 D1 50 D1 3F D1 3F D1 00 50 3A46: D1 50 D1 3F D1 3F D1 00 D8 D1 D8 D1 3F D1 3F D1 3A56: 00 D8 D1 D8 D1 3F D1 3F D1 00 D8 D1 D8 D1 3F D1 3A66: 3F D1 00 D8 D1 D8 D1 3F D1 3F D1 00 D8 D1 D8 D1 3A76: 3F D1 3F D1 00 D8 D1 D8 D1 3F D1 3F D1 00 D8 D1 3A86: D8 D1 3F D1 3F D1 00 D8 D1 D8 D1 C7 D1 C7 D1 00 3A96: D8 D1 D8 D1 C7 D1 C7 D1 00 D8 D1 D8 D1 C7 D1 C7 3AA6: D1 00 D8 D1 D8 D1 C7 D1 C7 D1 00 D8 D1 D8 D1 C7 3AB6: D1 C7 D1 00 D8 D1 D8 D1 C7 D1 C7 D1 00 D8 D1 D8 3AC6: D1 C7 D1 C7 D1 00 D8 D1 D8 D1 4F D1 4F D1 00 D8 3AD6: D1 D8 D1 4F D1 4F D1 00 D8 D1 D8 D1 4F D1 4F D1 3AE6: 00 D8 D1 D8 D1 4F D1 4F D1 00 D8 D1 D8 D1 4F D1 3AF6: 4F D1 00 60 D1 60 D1 4F D1 4F D1 00 60 D1 60 D1 3B06: 4F D1 4F D1 00 60 D1 60 D1 4F D1 4F D1 00 60 D1 3B16: 60 D1 37 D1 37 D1 00 60 D1 60 D1 37 D1 37 D1 00 3B26: 48 D1 48 D1 37 D1 37 D1 00 48 D1 48 D1 37 D1 37 3B36: D1 00 48 D1 48 D1 37 D1 37 D1 00 58 D1 58 D1 37 3B46: D1 37 D1 00 58 D1 58 D1 37 D1 37 D1 00 58 D1 58 3B56: D1 47 D1 47 D1 00 58 D1 58 D1 47 D1 47 D1 00 58 3B66: D1 58 D1 47 D1 47 D1 00 58 D1 58 D1 47 D1 47 D1 3B76: 00 D8 D1 D8 D1 47 D1 47 D1 00 D8 D1 D8 D1 47 D1 3B86: 47 D1 00 D8 D1 D8 D1 47 D1 47 D1 00 D8 D1 D8 D1 3B96: 47 D1 47 D1 00 D8 D1 D8 D1 47 D1 47 D1 00 D8 D1 3BA6: D8 D1 C7 D1 C7 D1 00 D8 D1 D8 D1 C7 D1 C7 D1 00 3BB6: D8 D1 D8 D1 C7 D1 C7 D1 00 D8 D1 D8 D1 C7 D1 C7 3BC6: D1 00 D8 D1 D8 D1 C7 D1 C7 D1 00 D8 D1 D8 D1 C7 3BD6: D1 C7 D1 00 D8 D1 D8 D1 C7 D1 C7 D1 00 D8 D1 D8 3BE6: D1 C7 D1 C7 D1 00 D8 D1 D8 D1 3F D1 3F D1 00 D8 3BF6: D1 D8 D1 3F D1 3F D1 00 D8 D1 D8 D1 3F D1 3F D1 3C06: 00 D8 D1 D8 D1 3F D1 3F D1 00 D8 D1 D8 D1 3F D1 3C16: 3F D1 00 D8 D1 D8 D1 3F D1 3F D1 00 50 D1 50 D1 3C26: 3F D1 3F D1 00 60 D1 60 D1 3F D1 3F D1 00 60 D1 3C36: 60 D1 3F D1 3F D1 00 60 D1 60 D1 3F D1 3F D1 00 3C46: FF ; Final level BASE_LANDSCAPE_LAYOUT: 3C47: C8 D1 C8 D1 00 00 C8 D1 C8 D1 00 00 90 D1 90 D1 3C57: 00 00 50 D1 50 D1 00 00 90 D1 90 D1 00 00 C8 D1 3C67: C8 D1 00 00 C8 D1 C8 D1 00 00 98 D1 98 D1 00 00 3C77: 98 D1 98 D1 00 00 98 D1 98 D1 00 00 C8 D1 C8 D1 3C87: 00 00 48 1B 48 1F 00 00 48 1E 48 11 00 00 48 1D 3C97: 48 19 00 00 C8 D1 C8 D1 00 00 98 D1 98 D1 00 00 3CA7: 98 D1 98 D1 00 00 98 D1 98 D1 00 00 C8 D1 C8 D1 3CB7: 00 00 C8 D1 C8 D1 00 00 C8 D1 C8 D1 00 08 C8 D1 3CC7: C8 D1 00 00 C8 D1 C8 D1 00 00 C8 D1 C8 D1 00 00 3CD7: 70 D1 70 D1 00 00 70 D1 70 D1 00 00 C8 D1 C8 D1 3CE7: 00 00 C8 D1 C8 D1 00 00 FF