Technical Notes on Manic Miner: Neighbours - Allana Truman (MM:N-AT) ==================================================================== As well as incorporating John Elliott's Manic Miner patch [http://www.seasip.demon.co.uk/Jsw/mmdiffs.html], I have hacked the MM game-engine in five ways: * to make the Jet Set Willy item-collection sound (new subroutine at 37714-37737); * to play as a different character in Room 13, and to fix the bug which corrupts Room 7 when you fall off the bottom of the screen (new subroutine at 37738-37760); * to play the in-game tune at half-speed (new subroutine at 37761-37765); * not to OR the colour-attribute of a vertical guardian with background; * to alter the Room 19 swordfish-routine. ---------------------------------------------- Making the Jet Set Willy item-collection sound ---------------------------------------------- The Manic Miner game-engine first checks the colour-attribute of the character-square on which an item is to be drawn. If this square has white ink, then it adds 100 to the score and marks the item as collected (by setting its colour-attribute to 0), otherwise it draws the item. It calls the following subroutine to increment the score: 36741: CALL 37118 This is as good a time as any to make the item-collection sound, so I decided to replace this call with a call to a new subroutine (located at 37714, which is the next free address after John Elliott's MM patch) to implement the item-collection sound from Jet Set Willy: 36741: CALL 37714 Item-sound subroutine (37714-37737): 37714: LD A,(32883) ; get current room's border-colour (32768 + 627-512) 37717: LD C,128 ; FOR C= 128 TO 2 STEP -2 37719: OUT (254),A ; Port 254: Bits 0-2 = border; Bit 4 = loudspeaker 37721: XOR 00011000 ; toggle Bits 3 and 4 of A 37723: LD E,A 37724: LD A,144 37726: SUB C ; LET A= 144 - C 37727: LD B,A 37728: LD A,E 37729: DJNZ 37729 ; FOR B= A TO 1 STEP -1: NEXT B 37731: DEC C 37732: DEC C 37733: JR NZ,37719 ; NEXT C 37735: JP 37118 ; jump across to score-incrementing subroutine The CPU returns to the main item-handling routine when it executes the RET instruction at the end of the score-incrementing subroutine, and carries on like nothing happened. Note for trainee machine-code programmers: >>> The subroutine at 37714 overwrites registers A, B, C and E, but this doesn't matter because neither the score-incrementing subroutine nor the main item-handling routine need the values in these registers to be preserved. There /would/ be a problem if the item-sound subroutine overwrote registers H or L, because the main item-handling routine uses the HL register-pair to pass a value to the score-incrementing subroutine, and that value would be overwritten if the item-sound subroutine were to write to H or L. If this were the case, we would use a PUSH HL instruction to save the value in HL on the stack, and a POP HL instruction to restore this value to HL afterwards. <<< To reuse the item-sound subroutine: 1. Copy 37714-37737 (24 bytes) to your game. 2. POKE 36742,82: POKE 36743,147 ------------------------------------------------------------------------ Overriding the player's sprite, and fixing the bug which corrupts Room 7 ------------------------------------------------------------------------ In MM:N-AT, the routine which draws the player's sprite has been modified in two ways: 1. Just as in Jet Set Willy you play as a different character in Room 29 ("The Nightmare Room"), so in MM:N-AT you play as a different character in Room 13; 2. I have fixed the bug where erroneous blocks appear in the Room 7 screen-layout if you fall off the bottom of the screen or jump off the top. These two changes are right next to each other in the player-sprite-drawing code, so let us first study this code in the original Manic Miner, in order to understand how Room 7 gets corrupted. 37503: LD A,(32872) ; Willy's pixel-position (32768 + 616-512) 37506: LD IXH,131 37509: LD IXL,A ; IX is an index into the X,Y lookup-table (33536 to 33791) 37511: LD A,(32874) ; Willy's direction (32768 + 618-512): 0=right, 1=left 37514: AND 1 37516: RRCA ; Willy's direction now in MSB of A, i.e. 0=right, 128=left 37517: LD E,A 37518: LD A,(32873) ; Willy's sprite (32768 + 617-512): {0,1,2,3} 37521: AND 00000011 37523: RRCA 37524: RRCA 37525: RRCA ; Willy's sprite now 0SS0000, i.e. multiplied by 32: {0,32,64,96} 37526: OR E ; combine it with Willy's direction to form offset of sprite in sprite-page 37527: LD E,A 37528: LD D,130 ; use Sprite-Page 130 for the player, i.e. 33280; DE holds address of sprite 37530: LD B,16 ; it's going to loop 16 times: once for each pixel-row in the sprite 37532: LD A,(32876) ; Willy's colour-position, low byte (32768 + 619-512) 37535: AND 00011111 ; extract Willy's horizontal position 37537: LD C,A Now it's about to enter the loop which draws each of the 16 pixel-rows. This loop uses IX to look up the address to poke each byte to, storing this address in HL. The valid ranges are {33536 <= IX < 33791} (the lookup-table) and {24576 <= HL <= 28671} (the secondary pixel-buffer). Note that IX must be an even number, since each entry in the lookup-table is 16 bits. 37538: LD A,(IX+0) ; low byte of address to poke pixel-row to 37541: LD H,(IX+1) ; high byte of address to poke pixel-row to 37544: OR C ; add Willy's horizontal position to this address 37545: LD L,A ; HL now holds the complete address to poke the pixel-row to 37546: LD A,(DE) ; next left pixel-row of the sprite 37547: OR (HL) ; superimpose it with any pixels already on the screen 37548: LD (HL),A ; poke it to the secondary pixel-buffer 37549: INC HL ; move right one character 37550: INC DE ; 37551: LD A,(DE) ; next right pixel-row of the sprite 37552: OR (HL) ; 37553: LD (HL),A ; ditto for the right side of the sprite 37554: INC IX ; 37556: INC IX ; point to next entry in the lookup-table 37558: INC DE ; point to next pixel-row of the sprite 37559: DJNZ 37538 ; decrement B; if B > 0 then go back to the start of the loop 37561: RET The problem when Willy is falling off the bottom of the screen is that IX >= 33792, which is pointing off the end of the lookup-table, into the first few instructions of Manic Miner! So it tries to interpret the codes for these instructions as though they were entries in the lookup table, thus generating all kinds of erroneous addresses to poke to! Some of these addresses happen to be in the screen-layout of Room 7. I can think of three solutions to this bug: 1. Test IX each time round the loop, breaking out of the loop if IX >= 33792. This seems woefully inefficient and could even slow the game down, considering that it would have to execute a test and a conditional branch 16 times every time-frame! 2. Test IX before the loop, and skip over the loop if IX > 33760 (33760 is the last safe value of IX going into the loop, because it will be 33760 + 15*2 = 33790 on the final pass before being incremented to 33792 at the end of that pass). This means that Willy would disappear as soon as his feet did! 3. Test IX before the loop. If IX > 33760 then decrease the initial value in B (16 by default) so that the loop terminates before attempting to draw pixel-rows off the bottom of the screen. Hence the initial value in B should be (33792 - IX)/2 = (256 - IXL)/2. I have written a subroutine to implement the third solution. This subroutine also selects the horizontal-guardian sprite from Room 0 (Allana) as the player sprite for Room 13. To do this, I replaced the code from the original Manic Miner... 37527: LD E,A 37528: LD D,130 ; use Sprite-Page 130 for the player, i.e. 33280 37530: LD B,16 ; it's going to loop 16 times: once for each pixel-row in the sprite ...with the following code: 37527: LD B,16 ; set default-value first - it will be adjusted if necessary 37529: CALL 37738 37738: LD E,A ; precondition: A holds offset of sprite in sprite-page 37739: LD D,130 37741: LD A,(33799) ; get current room-number 37744: CP 13 ; if in Room 13... 37746: JR NZ,37750 ; 37748: LD D,179 ; ...then use Sprite-Page 179, i.e. 45824 (Room 0 horizontal guardian) 37750: LD A,IXL ; get low byte of IX 37752: CP 225 ; 37754: RET C ; return if IXL < 225 (i.e. if IX <= 33760) 37755: NEG ; 256 - IXL 37757: SRL A ; shift right to divide by 2 37759: LD B,A ; set initial loop-counter 37760: RET To reuse this player-sprite-drawing subroutine: 1. Copy 37527-37531 (5 bytes) to your game. 2. Copy 37738-37760 (23 bytes) to your game. 3. POKE 37745,r to change the player-sprite in Room r. 4. If you don't want to change the player-sprite, get rid of the code at 37741-37749, e.g. by shunting the code at 37750-37760 down over it: FOR a= 37741 TO 37751: POKE a, PEEK (a+9): NEXT a -------------------------------------- Playing the in-game tune at half-speed -------------------------------------- Usually, each note is held for two time-frames, due to the following code which converts the time-frame counter (0 to 255) to the number of the note to play (0 to 63, two time-frames each): 34881: AND 01111110 34883: RRCA To hold each note for four time-frames, I replaced the above code with the following: 34881: CALL 37761 37761: AND 11111100 37763: RRCA 37764: RRCA 37765: RET To reuse this subroutine: 1. Copy 34881-34883 (3 bytes) to your game. 2. Copy 37761-37765 (5 bytes) to your game. ---------------------------- Colouring vertical guardians ---------------------------- The standard Manic Miner engine ORs the colour-attribute of a vertical guardian with the background colour-attribute - not the INK; just PAPER, BRIGHT and FLASH. So if you try to have a red-paper vertical guardian on a blue-paper background, it will actually be a magenta-paper vertical guardian because 010 OR 001 = 011. The following subroutine is used to colour vertical guardians in Manic Miner: 36447: LD (HL),A ; write VG colour-attribute to screen-buffer 36448: LD A,(32800) ; get background colour-attribute of current room 36451: AND 11111000 ; ignore INK (lower three bits) 36453: OR (HL) ; OR VG colour-attribute with background colour-attribute 36454: LD (HL),A ; colour top-left character-square (again) 36455: LD DE,31 36458: INC HL ; one character-column to the right 36459: LD (HL),A ; colour top-right character-square 36460: ADD HL,DE ; one character-row down (+32), one column left (-1) 36461: LD (HL),A ; colour middle-left character-square 36462: INC HL 36463: LD (HL),A ; colour middle-right character-square 36464: ADD HL,DE 36465: LD (HL),A ; colour bottom-left character-square 36466: INC HL 36467: LD (HL),A ; colour bottom-right character-square 36468: RET This subroutine is called by the Skylab code... 36583: CALL 36447 ...by the vertical-guardian code... 36697: CALL 36477 ...and jumped-to from the Eugene code (but we're not going to bother about that because Eugene has black PAPER, so we /do/ want him to inherit the background paper-colour). MM:N-AT bypasses the ORing of the VG colour-attribute with the background colour-attribute by entering the above subroutine at 36454: 36583: CALL 36454 ; in Skylab code 36697: CALL 36454 ; in vertical-guardian code These changes can be applied to the standard Manic Miner engine (Bug-Byte edition, NOT the Software Projects edition) with the following POKEs: POKE 36584,102 (Skylabs) POKE 36698,102 (vertical guardians) ------------------------- Room 19 swordfish-routine ------------------------- On completing Room 19, unless the "6031769" teleport-mode is enabled,* the portal turns into a swordfish, the player is drawn three columns above the swordfish, and the player's legs are colour-erased from the column below the portal. The swordfish-routine in the original Manic Miner is as follows: --- * a restriction which can be circumvented using POKE 36924,0 36912: LD A,(33882) ; if DEMO 36915: OR A ; = 0 36916: JP NZ,37008 ; then jump to 37008 (normal cavern-completion routine; restart at Room 0) 36919: LD A,(33885) ; if CHEAT 36922: CP 7 ; = 7 ("6031769" teleport-mode enabled) 36924: JR Z,37008 ; then jump to 37008 (normal cavern-completion routine; restart at Room 0) 36926: LD C,0 ; no collision-detection when sprite-drawing subroutine is called 36928: LD DE,33376 ; address of Sprite 3 of player (facing right, rightmost frame) 36931: LD HL,16467 ; pixel-address of character-square (2,19) in video-RAM 36934: CALL 36852 ; call sprite-drawing subroutine 36937: LD DE,45792 ; address of swordfish-sprite (Special Graphic in Room 0) 36940: LD HL,16563 ; pixel-address of character-square (5,19) in video-RAM 36943: CALL 36852 ; call sprite-drawing subroutine 36946: LD HL,22611 ; colour-address of character-square (2,19) in video-RAM 36949: LD DE,31 36952: LD (HL),47 ; set colour-attribute at (2,19) to white ink on cyan paper (player above portal) 36954: INC HL 36955: LD (HL),47 ; set colour-attribute at (2,20) to white ink on cyan paper 36957: ADD HL,DE ; one character-row down (+32), one column left (-1) 36958: LD (HL),39 ; set colour-attribute at (3,19) to white ink on green paper 36960: INC HL 36961: LD (HL),39 ; set colour-attribute at (3,20) to white ink on green paper 36963: ADD HL,DE 36964: INC HL 36965: ADD HL,DE 36966: LD (HL),69 ; set colour-attribute at (5,19) to cyan ink on black paper, BRIGHT 1 (swordfish) 36968: INC HL 36969: LD (HL),69 ; set colour-attribute at (5,20) to cyan ink on black paper, BRIGHT 1 36971: ADD HL,DE 36972: LD (HL),70 ; set colour-attribute at (6,19) to yellow ink on black paper, BRIGHT 1 36974: INC HL 36975: LD (HL),71 ; set colour-attribute at (6,20) to white ink on black paper, BRIGHT 1 36977: ADD HL,DE 36978: LD (HL),0 ; set colour-attribute at (7,19) to black ink on black paper (erase player's legs) 36980: INC HL 36981: LD (HL),0 ; set colour-attribute at (7,20) to black ink on black paper MM:N-AT modifies the above code to: * not draw the player above the swordfish; * change the position of the `swordfish' from (5,19) to (6,21); * edit the colour-attributes of the `swordfish'; * erase not only the two squares directly below the portal, but also the square to the left of them. The altered code is as follows: 36928: NOP 36929: NOP 36930: NOP 36931: NOP 36932: NOP 36933: NOP 36934: NOP 36935: NOP 36936: NOP 36937: LD DE,45792 ; address of swordfish-sprite (Special Graphic in Room 0) 36940: LD HL,16597 ; pixel-address of character-square (6,21) in video-RAM 36943: CALL 36852 ; call sprite-drawing subroutine 36946: LD HL,22741 ; colour-address of character-square (6,21) in video-RAM 36949: LD DE,31 36952: LD (HL),66 ; set colour-attribute at (6,21) to red ink on black paper, BRIGHT 1 36954: INC HL 36955: LD (HL),70 ; set colour-attribute at (6,22) to yellow ink on black paper, BRIGHT 1 36957: ADD HL,DE 36958: LD (HL),66 ; set colour-attribute at (7,21) to red ink on black paper, BRIGHT 1 36960: INC HL 36961: LD (HL),70 ; set colour-attribute at (7,22) to yellow ink on black paper, BRIGHT 1 36963: ADD HL,DE 36964: DEC HL 36965: JR 36975 ; minimise possible effects of MM-editors overwriting 36967/70/73 ;-) 36967: NOP 36968: NOP 36969: NOP 36970: NOP 36971: NOP 36972: NOP 36973: NOP 36974: NOP 36975: LD (HL),64 ; set colour-attribute at (8,20) to black ink on black paper, BRIGHT 1 36977: INC HL 36978: LD (HL),64 ; set colour-attribute at (8,21) to black ink on black paper, BRIGHT 1 36980: INC HL 36981: LD (HL),64 ; set colour-attribute at (8,22) to black ink on black paper, BRIGHT 1