; Venture for the Atari 2600 VCS ; ; Copyright 19?? Coleco ; Written by ???? ; ; Reverse-Engineered by Manuel Polik (cybergoth@arcor.de) ; and Robert Mundschau (rmundschau@sbcglobal.net) ; Compiles with DASM ; ; Build command: (needs vcs.h) ; dasm venture.asm -f3 -lventure.list -sventure.sym -oventure.bin ; ; History ; 08.11.2K3 - Started ; 12.11.2K3 - Worked a few things from Initialisation out ; 17.11.2K3 - Halfway finished display ; 07.01.2K4 - Expanded all graphics data to include visual comments. ; - Worked on commenting the initialization code. ; 11.01.2K4 - Worked on InitBigRoom ; 16.01.2K4 - Completing vertical blank. ; 22.01.2K4 - Display Kernal is finished. ; 23.01.2K4 - Begin at kernalDone. ; 02.02.2K4 - Collisions ; 02.08.2K4 - Object collsions and movement. ; 02.11.2K4 - Completed direction changes and movement. processor 6502 include vcs.h ;---------------------------- ; GAME CONSTANTS: ; TIA_BASE_READ_ADDRESS = $30 SCREEN_BOTTOM_LINE = $50 ; The region of the screen available for rendering rooms is $50 pairs of scanlines ; tall. This does not include the portion of the screen with the score and lives. ; indicator. SPRITE_NOT_VISIBLE = $51 ; When a sprite is not to be displayed anymore, the program ; set the Y coordinate of the sprite to this constant value. SPRITE_STATUS_DEAD = $F2 ; This is the Status value used to indicate a game object is dead. PLAYER_SHOT_RANGE = $1E ; How far the player's shot will fly if it doesn't hit anything. ;------------------------ ; RAM Address Equates: ; Zoomed Room Only. Big Room sprite0_Shape = $80;$81 ; Shape * Player * * Hall Monster Small * sprite1_Shape = $82;$83 ; Shape * Monster * or * Item * * Hall Monster Small * sprite2_Shape = $84;$85 ; Shape * Item * or * Monster * * Hall Monster Small * sprite3_Shape = $86;$87 ; Shape * Monster * * Hall Monster Small * sprite4_Shape = $88;$89 ; Shape * Monster * * Hall Monster Small * sprite5_Shape = $8A;$8B ; Shape * Hall Monster Big * * Hall Monster Small * unusedRAM1 = $8C ; These 2 bytes of RAM are not used by the original code and are unusedRAM2 = $8D ; therefore available for hacking. sprite7_Shape = $8E;$8F ; Used in the moving wall room only, to timeshare the treasure and ; large hall monster on one sprite. roomLockMask = $90 ; contains the bit mask to test/set bits in roomLockedFlags. roomLockedFlags = $91 ; The lower nybble of this byte are flags, one for each room on the ; current level. When the player completes a room, its corresponding ; bit is set in this byte. roomDoneFlag = $92 ; bit 7 is cleared when entering a new room. When the item is collected ; bit 7 is set. Then if the room is exited safely, it is locked. There ; may be another purpose to this byte I haven't determined yet. mirroredRoom = $93 ; reflection of room switched in the 6th bit sprite0_Ypos = $94 ; Ypos sprite1_Ypos = $95 ; Ypos sprite2_Ypos = $96 ; Ypos sprite3_Ypos = $97 ; Ypos sprite4_Ypos = $98 ; Ypos sprite5_Ypos = $99 ; Ypos ballYpos = $9A ; ypos of ball sprite7_Ypos = $9B ; Used in the moving wall room only, to timeshare the ; treasure and large hall monster on one sprite. sprite0_Status = $9C ; The direction and speed of each game object. The upper nybble is the sprite1_Status = $9D ; direction, and it corresponds to the values generated by a joystick. sprite2_Status = $9E ; The 3 lowest order bits hold the object's speed 0 - 7. 0 is immobile sprite3_Status = $9F ; like an item. 7 is the fastest speed in the game. sprite4_Status = $A0 sprite5_Status = $A1 ball_Status = $A2 sprite7_Status = $A3 sprite0_Color = $A4 ; Color sprite1_Color = $A5 ; Color sprite2_Color = $A6 ; Color sprite3_Color = $A7 ; Color sprite4_Color = $A8 ; Color sprite5_Color = $A9 ; Color sprite0_Height = $AA ; negative signed Height sprite1_Height = $AB ; negative signed Height sprite2_Height = $AC ; negative signed Height sprite3_Height = $AD ; negative signed Height sprite4_Height = $AE ; negative signed Height sprite5_Height = $AF ; negative signed Height spriteColorSwap = $B0 ; Used in the moving wall room only, to timeshare the ; treasure and large hall monster on one sprite. spriteHeightSwap = $B1 sprite0_Timer = $B2 ; Timer value tracked for each sprite. For Items and the player it sprite1_Timer = $B3 ; is intialized to zero. sprite2_Timer = $B4 ; sprite3_Timer = $B5 ; sprite4_Timer = $B6 ; sprite5_Timer = $B7 ; ball_RangeTimer = $B8 ; This location counts down to zero to count out the range of the player's shot. sprite7_Timer = $B9 ; Set to $20 when the large hall monster activates. The HM remains frozen visible ; in the corner until this timer reaches zero. live0GFX = $BA ; 1st live, highest bit set shows live live1GFX = $BB ; 1st live, highest bit set shows live live2GFX = $BC ; 1st live, highest bit set shows live flickerOffset = $BD ; 0 or 3, swappwed every frame. Draw either sprites 0, 1, and 2 ; or sprites 3, 4, and 5. roomOffset = $BE ; stores PF offset for current room gameStateFlags1 = $BF ; Bit7 = 1 = all movement disabled. Bit6 = 1 = In Zoomed-Room. 0 = Big Room. ; Bit0 = 1 means the reset button was being pressed last frame, used ; to debounce the switch it seems. P1spritewidth = $C0 ; width of player 1 in kernel (normal/4width) ;unknown = $C1 ;unknown = $C2 PFcollisionFlags = $C3 ; A set of flags to keep track of what objects collided with other objects ; during the previously rendered frame. randomNumPtr = $C4 ; This location is used to generate pseudo-random numbers by acting as an index to ; read a byte from the program ROM as a "random" number. After each random number is ; read, this location is incremented. LastPlayerDirection = $C5 ; Every time the player pushes the joystick the last direction pushed ; is stored here so that even if the player stops pushing we can ; recall the last direction traveled to fire the shot. livesCounter = $C6 ; Starts of with 3 lives score_1_2 = $C7 ; These 2 bytes hold the players score divided by 100 in BCD format. score_3_4 = $C8 ; shotReadyFlag = $C9 ; When bit7 is set the player can fire. P1repositionEven = $CA ; line to reposition GRP1 even frame. P1repositionOdd = $CB ; line to reposition GRP1 odd frames. ; These values never change within a room. ; The result is an invisble barrier to some monsters. pauseTimer = $CC ; When bit7 in gameStateFlags is set to pause the gameplay, then this ; byte contains a timer decremented each frame. When the timer transitions ; from 1 to zero, bit7 in gameStateFlags is cleared to unpause gameplay. ; If pauseTimer is intialized to zero when bit7 of gameStateFlags is set, then ; the game is over, and reset must be pressed to unfreeze the game. ; unknown = $CD currentLevel = $CE ; Initialized to 0-3 based on the difficulty switches. Incremented everytime the ; player completes a level. There is nothing to prevent it wrapping back to zero. ; Weird things may happen if that were to occur. sprite0_Xpos = $CF ; X-Position sprite1_Xpos = $D0 ; X-Position sprite2_Xpos = $D1 ; X-Position sprite3_Xpos = $D2 ; X-Position sprite4_Xpos = $D3 ; X-Position sprite5_Xpos = $D4 ; X-Position ballXpos = $D5 ; xpos of ball spriteXposSwap = $D6 ; Used in the moving wall room only, to timeshare ; the treasure and large hall monster on one sprite. playerObjIndex = $D7 ; This location stores the number of the sprite (0-5) that corresponds ; to the player. It is always 0 or 6. A value of 6 means the player ; is represented by the ball. pointsForKill = $D8 ; This byte holds the number of points/100 in BCD that the player ; will score for killing a monster in a ZoomedRoom. It begins at ; 1-4 based on difficulty switches and increments each level up to 9 max. ; The player scores double the number of points for picking up an item. HMspriteNumber = $D9 ; Bit 7 set to disable large hall monster, else the number within is ; the hall monster sprite 0-5. It seems to be restricted to 3 or 4. $80 ; means the large hall monster is inactive. currentRoom = $DA ; The number of the room the player is currently in 0 to 9. SpriteSwapFlag = $DB ; Only in the moving walls room. Set to 1 to indicate the HM sprite ; is swapped onto the screen, and the treasure sprite is swapped out. ; This byte is 0 when the item is swapped in and the Hall ; monster is swapped out. HallMonsterTimer = $DC ; This counter is decremented every 256 frames. When it reaches zero, ; the big hall monster appears. FrameCounter = $DD ; 8-bit frame counter. The engine alternates drawing 3 of 6 ; sprites on even and odd frames. sound1Active = $DE ; This byte holds an offset into a table of sound generation values. ; When this byte > 0 the sound is on. When it decrements to zero ; the sound turns off. soundUnknown1 = $DF noteTimer1 = $E0 soundDuration = $E1 ;unknown = $E2 InitStatusforHM = $E3 ; Holds the value used to initialize the status byte of the large hall monster ; whenever we enter a new zoomed room. At the start of a game it is set from the ; state of the difficulty switches. The speed increments once per level completed. ;unknown = $E4 difficulty = $E5 ; Difficulty 0-3 pointsScored = $E6 ; The number of points scored this frame/100 and in BCD format. Cleared to zero ; at the beginning of each frame. P0ShapeThisFrameLo = $E7 ; 16-bit Pointer to GRP0 shape to be rendered this frame. (odd/even) P0ShapeThisFrameHi = $E8 P1ReposThisFrame = $E9 ; desired repositioning line for current frame desiredYposP0 = $EA ; y position for next P0 sprite drawn this frame. Note that a Y ; position of 0 corresponds to scanline 17 of 192 in the kernel. activelyDrawP0 = $EB ; Set to 0 when P0 is not being drawn. Contains the negative non-zero ; count of lines remaining to be drawn for P0. preloadHeightP0 = $EC ; preloaded height for the P0 sprite in the next frame. If all ; sprites were the same height this byte would not be needed. saveCXP0FB = $ED ; Mid-kernal saving of collision register information. saveCXP1FB = $EE saveCXBLPF = $EF saveCXPPMM = $F0 spritePointer1 = $ED;$EE ; Points to a sprite shape for the score. spritePointer2 = $EF;$F0 ; Points to a sprite shape for the score. spritePointer3 = $F1;$F2 ; Points to a sprite shape for the score. resetShotFlag = $F1 ; RE-USED from spritePointer3. Set to 0 at the beginning of collision detection. ; If the shot hits a sprite, wall, or edge of the playfield this location it incremented ; to indicate the shot should be reset for firing by the player. currentShapeP1 = $F2 ; RE-USED from spritePointer3. Points to GRP1 shape spritePointer4 = $F3;$F4 ; Points to a sprite shape for the score. desiredYposP1 = $F4 ; RE-USED from spritePointer4: y position for next P1 sprite activelyDrawP1 = $F5 ; Set to 0 when P1 is not being drawn. Otherwise it contains the negative non-zero ; count of lines remaining to be drawn for P1. preloadHeightP1 = $F6 ; preloaded height for next P1 sprite. Used twice per frame, once for ; each P1 instance in the frame. savePFindex = $F7 ; Used to save the current index to the PF grpahics while in the ; kernel. tempVar3 = $F7 ; Various temporary data tempVar1 = $F8 ; Various temporary data tempVar2 = $F9 ; Various temporary data HMTimerSetup = $FA ; This location holds the value used to reset the HM timer every time ; the player enters a zoomed room. It starts at 6 - diff and decrements once ; per level to a minimum of 3. The large hall monster appears after the ; the player has been in a room for HMTimerSetup*256 Frames. ORG $F000 Mainloop ; Control passes to the mainloop on cycle 11 of the first visible scanline. However, because of ; the way this loop is structured, nothing is drawn on the first scanline. This prevents us from ; seeing the HMOVE move bar in the first digit of the displayed score. ; Diplay score and remaining lives ; ; The kernel does three sprites per frame: ; shape flickerOffset is done with GRP0 ; shape flickerOffset+1 is done with GRP1 ; shape flickerOffset+2 is done with GRP1 LDA #$07 ;11+2 = [13] STA tempVar1 ; StatusDisplay LDY tempVar1 LDX charset,Y ; X contains the '0' shape LDA (spritePointer1),Y STA GRP0 ; Draw 1st digit LDA #$00 STA PF1 ; Clear PF1 for left half of screen. STA WSYNC ; Sync Cycles = 0 LDA (spritePointer2),Y ; 5 STA GRP1 ; Draw 2nd digit ; 3 LDA (spritePointer3),Y ; 5 STA GRP0 ; Draw 3rd digit ; 3 LDA (spritePointer4),Y ; 5 LDY tempVar1 ; 3 STA GRP1 ; Draw 4th digit ; 3 STX GRP0 ; Draw 0 ; 3 STX GRP1 ; Draw 0 ; 3 STX GRP0 ; (Req'd for VDEL) ; 3 LDA tempVar2 ; 3 STA PF1 ; display remaining lives ; 3 DEC tempVar1 ; 5 BPL StatusDisplay ; The loop repeats 8 times. ; 2 ; Total Cycles = 51 of scanline 9 of 192 ; Position sprites for room display. LDX flickerOffset ; 3 LDA sprite0_Xpos,X ; Get xpos of 1st sprite ; 4 LDX #$00 ; 2 STX NUSIZ0 ; Injected cleanup code after ; 3 STX VDELP0 ; status display. ; 3 STX VDELP1 ; 3 STX PF1 ; 3 STX GRP0 ; 3 STX GRP1 ; 3 JSR PosObject ; Position P0 sprite. ; 6 = 33 + 51 = 84 ; We call PosObject on cycle 8 of scanline 10, ; control returns on cycle 6 of scanline 12. ; cycle 6 LDX flickerOffset ; 3 LDA sprite1_Xpos,X ; Get xpos of top P1 sprite ; 4 LDX #$01 ; 2 JSR PosObject ; Position P1 sprite for first display ; 6 = cycle 21 ; within the frame. ; We call PosObject on cycle 21 of scanline 12. ; control returns on cycle 6 of scanline 14 ; *** There are 67 wasted cycles on scanline 14. *** STA WSYNC ; Sync to end of scanline 14 STA HMOVE ; Fine Position ; cycle 3, line 15 of 192. ; Set P0 color. LDX flickerOffset STX spritePointer2 ; Store offset (temporary?). Is this ever used? LDA sprite0_Color,X STA COLUP0 ; Set the color of P0 for the entire frame. ; Set the width of the P1 sprite for this screen. LDA P1spritewidth STA NUSIZ1 ; P1 either normal or quad width STY WSYNC ; End scanline 15 of 192. STA HMCLR ; Clear horizontal motion ; 3 INY ; Y = 0. Since it came back from ; 5 ; PosObject set to $FF. ;----- ; When control falls through from above we are just setting up to draw the room below ; the already rendered score and lives display. This kernal uses P1 twice per frame. ; The second time P1 needs to be used we come to this point through the ; setUpForNextP1 label to finish the initialization of P1 after it has already ; been repositioned horizontally over the last for scanlines. ;----- setUpForNextP1 ; Control passes from above on cycle 5 of ; [5]/[14] - line 2 ; scanline 16. The cycle for control entering ; through the the setUpForNextP1 is cycle 14. INX ; X = flickeroffset + 1 or + 2 depending ; 16 - worst case ; which instance of P1 we are setting up. LDA sprite0_Color,X ; 20 STA COLUP1 ; Set color for P1 ; 23 LDA sprite0_Ypos,X ; 27 STA desiredYposP1 ; Set Y pos for GRP1 ; 30 BPL P1SpriteNotOnYet ; Draw immediately on the first line? ; 32/33 LDA sprite0_Height,X ; Y: ; 36 - worst case STA activelyDrawP1 ; Set current height ; 39 P1SpriteNotOnYet ; [33]/[39] - line 2 LDA sprite0_Height,X ; ; 43 - worst case STA preloadHeightP1 ; Preload height for P1 ; 47 TXA ; 49 ASL ; 51 TAX ; sprite # * 2 ; 53 LDA sprite0_Shape,X ; 57 STA currentShapeP1 ; 60 LDA sprite0_Shape+1,X ; 64 STA currentShapeP1+1 ; Set shape for GRP1 ; 67 LDX savePFindex ; x-> PF offset, depending on current room ; 70 ; Draw first kernel line. Drawing of the room graphics begins on scanline 17 of 192. ; ------ ; NOTE: Venture is a 2 scanline kernel. That is, there is a minimum horizontal ; resolution for all graphics of 2 scanlines. Also, all sprites and missles move ; vertically in increments of 2 scanlines. So there is no use of VDELPx in the ; drawing of the current room. ; ; Its important to note that pixels for P0 are in alignment with the 2 line kernal, but ; visible pixels for P1 or the Ball are offset from P0 pixels by 1 scanline. NextLine SUBROUTINE LDA activelyDrawP0 ; Are we drawing P0? ; 73 STA WSYNC ; 76 cycles! ; Note: in the worst case, zero cycles remain. The worst case is when, ; the kernal is repositioning P1 for its second appearance in the ; current frame. BNE .DrawP0 ; Yes: We are already drawing P0 so go do it. ; 2/3 CPY desiredYposP0 ; No: Start drawing P0? ; 5 BNE .SkipStartP0 ; No: Skip start ; 7/8 LDA preloadHeightP0 ; Yes: ; 10 STA activelyDrawP0 ; Activate the drawing of P0 for as many ; 13 JMP .Continue1 ; frames as its preloaded height indicates. ; 16 .DrawP0 ; [3] - Line 1 LDA (P0ShapeThisFrameLo),Y ; Load next byte of P0 graphic data and ; 8 STA GRP0 ; set shape of P0 for the current scanline. ; 11 INC activelyDrawP0 ; scanline reduce remaining lines to draw for ; 16 ; P0, and simultaneously increment the P0 ; graphics index. All sprite graphic data ends ; with a zero byte to turn the sprite off. .Continue1 ; With P0 taken care of proceed ; [16] - Line 1 LDA pf1shapesleft,X ; to draw the Playfield graphics. ; 20 STA PF1 ; ; 23 LDA pf2shapesleft,X ; ; 27 STA PF2 ; Draw left half of PF ; 30 BIT mirroredRoom ; is Room mirrored? ; 33 BVS .SkipPF ; Y: Skip changing PF ; 35/36 NOP ; ; 37 NOP ; ; 38 NOP ; Waste cycles ; 40 LDA pf2shapesright,X ; 44 STA PF2 ; Draw right PF2 - asymetrical playfield ; 47 LDA pf1shapesright,X ; 51 STA $0100+PF1 ; Draw right PF1, wasting an additional ; 55 ; cycle to stay in sync at .Continue2 .Continue2 ; [55] - line 1 STX savePFindex ; Save a copy of the PF graphics ; 58 ; index in case we jump to the ; P1 repositioning kernel. LDA activelyDrawP1 ; Draw P1? ; 61 BNE .DrawP1 ; Y: Goto draw ; 63/64 CPY desiredYposP1 ; Start drawing P1? ; 66 BNE .SkipStartP1 ; N: Skip start ; 68/69 LDA preloadHeightP1 ; ; 71 STA activelyDrawP1 ; Store height of sprite ; 74 CPY ballYpos ; Draw Ball? ; 01 - begin 2nd line JMP .Continue3 ; 04 .SkipStartP0 ; [8] - line 1 NOP ; 10 LDA flickerOffset ; Waste cycles ; 13 JMP .Continue1 ; 16 .SkipStartP1 ; [69] - line 1 NOP ; 71 LDA flickerOffset ; Waste Cycles ; 74 CPY ballYpos ; Draw Ball? ; 01 - begin 2nd line JMP .Continue3 ; 04 .WasterRTS RTS .SkipPF ; 36 - line 1 JSR .WasterRTS ; 47 STX savePFindex ; 50 NOP ; Waste cycles ; 52 JMP .Continue2 ; 55 perfect sync! .SkipBall ; [7] - line 2 LDA #$00 ; 9 STA ENABL ; Disable Ball ; 12 BEQ .Continue4 ; 15 .SkipPFAgain1 ; [40] - line 2 JSR .WasterRTS ; 52 JMP .Continue5 ; 55 .DrawP1 ; [64] - line 1 INC activelyDrawP1 ; reduce remaining height ; 69 LDA (currentShapeP1),Y ; Load shape ; 74 CPY ballYpos ; Draw Ball? ; 01 - begin 2nd line ; Second kernel line. Draws P1, Ball and the PF. STA GRP1 ; Set shape ; 04 .Continue3 ; [04] - line 2 BNE .SkipBall ; No: Don't draw ball. ; 6/7 LDA #$02 ; Yes: Draw Ball! ; 8 STA ENABL ; Enable Ball ; 11 NOP ; ; 13 NOP ; Waste cycles ; 15 .Continue4 ; [15] - line 2 LDA pf1shapesleft,X ; 19 STA PF1 ; 22 LDA pf2shapesleft,X ; 26 STA PF2 ; Draw left half of PF ; 29 TYA ; A -> Kernal Line-Pair Counter ; 31 CMP PFshapechanges,X ; change PF shape? ; 35 BNE .KeepPFShape ; ; 37/38 BVS .SkipPFAgain1 ; Mirrored? V flag is still intact! ; 39/40 LDA pf2shapesright,X ; No: ; 43 STA $010F ; Draw right PF2(!) + 1 cycle waste ; 47 LDA pf1shapesright,X ; 51 STA PF1 ; Draw right PF1 ; 54 - out of sync? .Continue5 ; [55] - line 2 INY ; 2 line kernal, line-pair done. ; 57 CPY P1ReposThisFrame ; Reposition GRP1 now? ; 60 BEQ RepositionP1Kernel ; ; 62/63 INX ; Advance PF offset to next line ; 64 JMP NextLine ; Continue with next normal line .KeepPFShape ; [38] - line 2 BVS .SkipPFAgain2 ; Mirrored? V flag is still intact! ; 40/41 LDA pf2shapesright,X ; No: ; 44 STA PF2 ; Draw right PF2(!) ; 47 LDA pf1shapesright,X ; 51 STA PF1 ; Draw right half of PF ; 54 .SkipPFAgain2 ; [41]/[54] - line 2 INY ; 2 line kernal, line-pair done. ; 43/56 CPY #SCREEN_BOTTOM_LINE ; Done With Kernel? ; 46/59 BEQ .BailOut ; Y: Quit Kernel ; 48/49, 61/62 JMP NextLine ; N: Continue with next normal line ; 51/64 .BailOut ; [49]/[62] - line 2 JMP kernelDone ; 52/65 ;======================================================================================== SUBROUTINE ; These small routines are part of the RepositionP1Kernel ; which has its etry point below. .SkipStartP0 ; [07] - Line 1 INC tempVar2 ; Waste time? ; 12 JMP .Continue1 ; 15 ;-------- .1skipcalc ; [58] - Line 1 BCC .Continue2 ; This branch is always taken. ; 61 ;-------- .TurnBallOff ; [06] - Line 2 BNE .Continue3 ; This branch is always taken. ; 09 ;------- ; NOTE: P1ReposThisFrame values ALWAYS coincide with a PFshapechange value for the room ; being displayed. The kernel will not work if the value in P1ReposThisFrame does not ; match a value in PFshapechange for the current room. Repositioning must not be set ; to occur on or between the kernal line pairs Y=$4E or Y=$4F (the last line), or the ; kernel will draw to many lines. ; ; ALSO: The RepositionP1Kernal only has enough cycles to draw a reflected playfield, ; so the 4 scanlines where it occurs in the frame must be symetrical even if the room ; otherwise is not a mirrored room! ; ; Basically the entire kernel from above is repeated again TWICE(!) except it repositions ; P1 horizontally rather than drawing P1. The kernel still must draw the PF, P0, and Ball ; while it repositions P1. ; ;-------- RepositionP1Kernel ; [63] - Line 2 INX ; Increment the PF graphics index. ; 65 LDA pf1shapesleft,X ; Get ahead of ourselves and begin load ; 69 STA PF1 ; loading the PF graphics for the next ; 72 ; line 1. LDA activelyDrawP0 ; Is P0 visible this kernel line-pair? ; 75 BNE .DrawP0 ; Yes: Skip ahead to draw it. ; 01/02 - Begin Line 1! CPY desiredYposP0 ; No: Should we be drawing it? ; 04 BNE .SkipStartP0 ; No: skip out. ; 06/07 LDA preloadHeightP0 ; Yes: Initialize P0 drawing values. ; 09 STA activelyDrawP0 ; 12 JMP .Continue1 ; 15 .DrawP0 ; [02] LDA (P0ShapeThisFrameLo),Y ; 07 STA GRP0 ; load P0 graphics for the line. ; 10 INC activelyDrawP0 ; count the line drawn for P0. ; 15 .Continue1 ; [15] - Line 1 LDA pf2shapesleft,X ; ; 19 STA PF2 ; 22 ; Note: we are losing the PF graphics index stored only in X. ; Later in the routine we will restore it from the value ; in P1ReposThisFrame that brought us to this alternate kernal. LDX flickerOffset ; load the desired X position for P1 ; 25 LDA sprite2_Xpos,X ; which is for either sprite 2 or ; 29 ; sprite 5 according to flickerOffset, ; which in turn is set according to if ; the frame is even or odd. TAX ; 31 AND #$0F ; 33 STA tempVar2 ; 36 TXA ; 38 LSR ; 40 LSR ; 42 LSR ; 44 LSR ; 46 TAX ; X = (Xpos >> 4) ; 48 CLC ; 50 ADC tempVar2 ; A = (Xpos & $0F) + (Xpos >> 4) ; 53 CMP #$10 ; 55 BCC .1skipcalc ; 57/58 SBC #$0F ; 59 INX ; X = (Xpos >> 4) + 1 ; 61 .Continue2 ; [61] - Line 1 ASL ; 63 ASL ; 65 ASL ; 67 ASL ; 69 EOR #$70 ; 71 STA HMP1 ; 74 LDA #$00 ; 00 - Begin Line 2 CPY ballYpos ; Should Ball be drawn: ; 03 BNE .TurnBallOff ; No: skip out to waste time. ; 05/06 LDA #$02 ; Yes: Turn the ball on. ; 07 NOP ; 09 .Continue3 ; [09] - Line 2 STA ENABL ; Enable/Disable according to what is in A. ; 12 .1loop ; [12] - Line 2 DEX BPL .1loop NOP STA RESP1 ; Set rough position for P1. STA WSYNC ; end the second line in thefirst kernal pair. ; --------------- ; We are now beginning the 2nd kernalline-pair of two in ; the P1 repositioning routine. ; NOTE: No changes are made to the PF registers for this or the next ; scanline. So 4 scanlines have identical and reflected PF ; graphics when P1 is being repositioned. ; --------------- SUBROUTINE ; Let's reuse local labels. ; [00] - Begin line 1 INY ; 02 LDA activelyDrawP0 ; Are we drawing P0? ; 05 BNE .DrawP0 ; Yes: then branch to draw P0. ; 07/08 CPY desiredYposP0 ; No: Should we start drawing P0? ; 10 BNE .Continue1 ; No: Skip ahead. ; 12/13 LDA preloadHeightP0 ; Yes: Initialize the P0 ; 15 STA activelyDrawP0 ; graphics index/counter ; 18 JMP .Continue1 ; ; 21 .DrawP0 ; [08] - line 1 LDA (P0ShapeThisFrameLo),Y ; Draw P0 and increment the ; 13 STA GRP0 ; index/counter for P0. ; 16 INC activelyDrawP0 ; 21 .Continue1 ; [13]/[21] - line 1 LDX flickerOffset ; Initialize to point at the sprite ; 24 - worst case timing. INX ; data for the next instance of P1. ; 26 LDA CXP0FB ; We are recycling P1 in the frame so ; 29 STA saveCXP0FB ; we preserve collision information ; 32 LDA CXP1FB ; for the first half of the kernel. ; 35 STA saveCXP1FB ; 38 LDA CXBLPF ; 41 STA saveCXBLPF ; 44 LDA CXPPMM ; 47 STA saveCXPPMM ; 50 INC savePFindex ; Increment the PF graphics index?! ; 55 ; NOTE: This means the PF graphics ; will be different next scanline. ; For the 4 scanlines within this ; alternate kernel the PF grpahics were ; mirrored and unchanging even if the ; rest of the room is asymetrical. LDA #$00 ; Deactivate P1 in case it was being ; 57 STA activelyDrawP1 ; drawn before we began repositioning. ; 60 CPY ballYpos ; Test if the ball should be turned on ; 63 BNE .ballOff ; No: skip ahead to disable the Ball ; 65/66 LDA #$02 ; Yes: Load the value to enable the ; 67 ; Ball. .ballOff ; [66]/[67] - Line 1 STA WSYNC ; sync for HMOVE. ; 70 - worst case timing. ; [00] - line 2 STA HMOVE ; fine adjustment of P1 position. ; 03 STA ENABL ; enable/disble ball from value in A. ; 06 STA CXCLR ; clear all collision registers. ; 09 INY ; Nothing more will be drawn on this ; 11 ; scanline and the kernel line-pair is ; ending so we increment the kernel ; line-pair counter in Y. JMP setUpForNextP1 ; We jump back to the top of the kernel ; 14 ; which will complete the initialization ; of the second P1, by ;====================================================================================== ; Control comes to kernelDone when the kernel line-pair counter in Y reaches $50. ; At this point we have drawn 16 + 80*2 scanliens = 176 visible scanlines!? ; Control arrives on cycle 52 or 65 of the last visible scanline. ;------------ kernelDone SUBROUTINE ; [52]/[65] - line 2 LDA #$02 ; 67 - worst case STA WSYNC ; sync to end of last visible line. ; 70 - end kernel. STA VBLANK ; Turn on VBLANK, to begin overscan LDX #$30 ; Set the overscan timer. STX TIM64T ; $30 = 48 -> 48 * 64 = 3072 cycles ; 3072 / 76 = 40.41 scanlines worth ; = 41 overscan scanlines per frame. ; ================================ ; Total lines per frame: ; 3 vertical sync ; 43 vertical blank ; 176 visible scanlines. ; + 41 overscan scanlines. ; ------ ; 263 total scanlines per frame. (I think) ; ================================= LDA SpriteSwapFlag ; If the sprites are swapped, then swap them back and clear the BEQ .1skip ; swapped flag. (sprite swapping occurs only in moving wall room) LDX HMspriteNumber JSR ItemHMDataSwap DEC SpriteSwapFlag .1skip LDA SWCHB ; Read the console switches to check for a reset. LSR ; Shift the RESET switch state flag into carry. BCS .noReset ; If C=1, then the reset switch is not pressed, so skip ahead. LDA gameStateFlags1 ; RESET IS PRESSED: So we pause the game by setting bit 7 in ORA #$81 ; gameStateFlags1, and record the reset switch is closed by setting STA gameStateFlags1 ; bit0. The game will remain paused and not reset until the reset ; switch is released (see below). .noReset LDA gameStateFlags1 ; If bit7 is set the gameplay is paused either because the player just BPL gameNotPaused ; entered a new room, the game is over, or reset is being held down. LSR ; Test to see if the reset button was being pressed. BCC gameNotReset ; NO: skip ahead the game is paused, but not being reset. LDA SWCHB ; YES: Check if the player has released the reset button because LSR ; the game does not reset until the reset button is pressed BCC .2skip ; and released. LDX #$80 ; YES: The reset switch is now released, so we reset the game. Note JSR InitGame ; that setting X=$80 before calling InitGame, causes Initgame to BEQ .2skip ; clears only RAM from $80 to $FB, which is different than the powerup ; reset which clears all TIA registers along with the RAM. gameNotReset ; Control comes to this point when the gameplay is paused, but the ; reset switch is not being pressed. LDA pauseTimer ; If (pauseTimer == $00) then BEQ .2skip ; remain paused the player is dead and game play stops! DEC pauseTimer ; else if (pauseTimer > 1) then BNE .2skip ; remain paused, but decrement the timer. ; else the timer in pauseTimer is expired and gameplay resumes. ; Fall through to resume gameplay. DEC $CD ; not sure what $CD is??? BPL .3skip ; but when it is negative, then LDA gameStateFlags1 AND #$7F STA gameStateFlags1 ; the game is unpaused. LDA roomLockedFlags CMP #$0F ; If all 4 bits are set, then all rooms on the level are done. BEQ levelCompleted ; YES: go to the next level. BIT gameStateFlags1 BVS .2skip LDA #$27 STA sound1Active BNE .2skip ; branch always taken. ;------------------- levelCompleted LDA #$FF STA pauseTimer ; The game always pauses at the end of a level. ROR gameStateFlags1 LDA #$02 STA $CD BNE .2skip ; branch always taken. .3skip DEC $CD BMI LF265 LDX pointsForKill ; Increase the number of points scored for picking up an item CPX #$09 ; Unless it is already at 900 points which is the max. BEQ .4skip INC pointsForKill .4skip INC currentLevel LDX currentLevel LDA #$06 STA HMTimerSetup ; Reset to 6 the same as at powerup. CPX #$04 BPL LF25F INC $E2 INC $E4 INC difficulty CPX #$02 BNE LF25F INC InitStatusforHM LF25F LDA #$00 STA roomLockedFlags BEQ LF26D LF265 DEC livesCounter BPL LF26D INC livesCounter BEQ .2skip LF26D JSR LF8E8 .2skip JMP FinishOverscan ;------------------- gameNotPaused SUBROUTINE LDX flickerOffset ; We look at flicker offset to determine which sprites were active BEQ postFrame012 ; during the last frame, and need to be processed during overscan. JMP postFrame345 postFrame012 ; Control comes to this point following a frame where sprites 0, 1, and 2 ; were being displayed. STX PFcollisionFlags ; = 0 LDX playerObjIndex LDA sprite0_Status,X ; Isolate the last joystick information for the player. AND #$F0 CMP #$F0 BNE .1skip ; If any direction is being pressed, then skip ahead. LDA $C1 BEQ LF2AE LDA $C2 STA LastPlayerDirection BNE LF2AE .1skip STA LastPlayerDirection ; This set of tests makes no sense, yet. ??? CMP #$A0 ; Left and Up? BEQ .diagonal CMP #$90 ; Left and Down? BEQ .diagonal CMP #$60 ; Right and Up? BEQ .diagonal CMP #$50 ; Right and Down? BNE .straight .diagonal STA $C2 LDA #$06 ; Seems to be some sort of timer for player changes of direction. ??? STA $C1 .straight LDA $C1 BEQ LF2AE DEC $C1 LF2AE LDA sprite0_Xpos,X STA tempVar1 LDA sprite0_Ypos,X STA tempVar2 LDA currentRoom CMP #$08 BMI .zoomedRoomCalcIndex .bigRoomCalcIndex ; A = currentRoom = 8 or 9 LDX #$01 STX roomLockMask ; In big rooms roomLockMask holds the room-lock bitmask used to check if ; the room-locked flag for the room corresponding to each door is set or not. SEC SBC #$08 ; A = 0 or 1 ASL ASL ASL ASL ; A = A * 16 = 0 or 16 TAX LDY #$08 ; 8 doors in each big room to check. BNE .nextDoor .zoomedRoomCalcIndex ; A = currentRoom = { 0, 1, 2, 3, 4, 5, 6, or 7 } ASL ASL ADC #$01 ; A = currentRoom * 4 + 1 = { 1, 5, 9, 13, 17, 21, 25, 29 } TAX LDY #$02 ; 2 doors in each zoomed room to check. ; Just a reminder of the meaning of bits 6 and 7 in the byte containing the Y-coordinate ; if each door: ; %00 = Vertical orientation approach from left moving right to pass the through door. ; %01 = Vertical orientation approach from right moving left to pass the through door. ; %10 = Horizontal orientation approach from bottom moving up to pass the through door. ; %11 = Horizontal orientation approach from top moving down to pass the through door. ; .nextDoor LDA doorYPositions,X ; Get door y-coor and flags in bits 7 and 6. BMI .horizontalDoor ; I bit7 is set, then the door is oriented horizontally. .verticalDoor ; Bit7=0 means the door is oriented vertically. ASL ; Multiply the Y value by 2 and move bit 6 to bit 7. bit7 is discarded. STA tempVar3 ; Save a copy to remember the bit6 flag. AND #$7E ; Mask out the bit6, and preserve only the Y value. SEC ; no borrow. SBC tempVar2 ; Subtract the player's Y position. BPL .positive1 ; A positive result requires no adjustment, but a negative result EOR #$FF ; is inverted. (-1) becomes 0, (-2) becomes 1, (-3) becomes 2. .positive1 CMP #$02 ; Acc must be 0 or 1 for the subtraction of 2 to yield a negative. BPL .doorNotTouched ; If the doorY - playerY was not 1,0,-1, or -2, then door not touched. ; So a verticalDoor will trigger over a range of 4 pixels. ;----------------- ; Its not clear why the door Y positions are not set to produce ; values 0 to 3 rather than 1 to -2 to indicate door contact. ; changing the code and data would free ROM bytes. ;------------------ LDA tempVar3 ; Test the bit6 flag from the Door-Y-coordinate byte saved earlier. BMI .approachFromRight ; If the bit is set, the program expects the player to approach the ; door from the right .approachFromLeft ; If bit6 is clear, the program expects the player to approach the ; door from the left side. LDA doorXPositions,X ; playerY was in range for the door so fetch the door X coordinate. ; NOTE: The X-coor does not get multiplied by 2. SEC SBC tempVar1 ; DoorX - PlayerX >= 0? BPL .doorNotTouched ; Yes: player is too far left to trigger the door. ; NOTE: C=0 if the result of the previous subtraction is negative? ADC #$05 ; No: If DoorX - PlayerX + 5 < 0 ? BMI .doorNotTouched ; YES: player is too far right to trigger the door. BPL .inDoorZone1 ; NO: player is in the activation zone of the door. ;----------------------- .approachFromRight ; Alternately, if bit 6 is set, then we expect the player to approach ; the door in question from the right. LDA doorXPositions,X ; STA tempVar3 ; LDA tempVar1 SEC SBC tempVar3 ; Calculate PlayerX - DoorX >= 0 ? BPL .doorNotTouched ; YES: Player is too far to the right of the door to activate it. ADC #$05 ; No: Calulate PlayerX - DoorX + 5 < 0 ? BMI .doorNotTouched ; Yes: The player is too far left to activate the door. ; No: Fall through, the player is in the door's activation zone. .inDoorZone1 LDA roomLockMask ; Is the room locked blocking player entry? BIT roomLockedFlags BEQ doorActivated ; No: Activate the door to enter the new room. LDA doorXPositions,X ; Yes: The room is locked, bounce off the door. Please notice in the STA ballXpos ; region testing above a condition of DoorX=PlayerX will not activate the JMP postFrame345 ; door, thus we can set the PlayerX from DoorX safely. ;------------------------ .horizontalDoor ; We repeat the same logic for a horizontal door as we did for a vertical ; door above, except the roles of X and Y are reversed in the calculations. ASL STA tempVar3 LDA doorXPositions,X SEC SBC tempVar1 BPL .positive2 EOR #$FF .positive2 CMP #$02 BPL .doorNotTouched LDA tempVar3 BMI .approachFromTop ; Test bit 6 for Y-table byte to determine the expected approach vector. .approachFromBottom LDA tempVar2 SEC SBC tempVar3 BPL .doorNotTouched ADC #$05 BMI .doorNotTouched BPL .inDoorZone2 .approachFromTop AND #$7E STA tempVar3 SEC SBC tempVar2 BPL .doorNotTouched ADC #$05 BMI .doorNotTouched .inDoorZone2 LDA roomLockMask ; Check if the room is locked? BIT roomLockedFlags BEQ doorActivated ; No: Enter the room. LDA tempVar3 ; Yes: Bounce off. STA ballYpos BNE postFrame345 .doorNotTouched INX ; Increment the Door data index by 2. INX DEY ; Decrement count of doors remaining to check in Y. BEQ postFrame345 ; If Y reaches 0, then no door was being touched. TYA ; Every other time through the loop (because each room has 2 doors), LSR ; The room Lock Mask is advanced to the next room's lock flag position. BCS .sameRoom ASL roomLockMask ; -> here is the key to the locking of doors. This mask shifts 1 bit every ; that maps to once per room even in the zoomed rooms. .sameRoom JMP .nextDoor ;-------------------------------- ; doorActivated ; Control comes to this routine when it is detected that the player is within the activation ; zone of a door. The X register holds the index to the Door that was activated. BIT gameStateFlags1 ; Test Bit6 in Game State flags to see if we are entering a Big Room BVC .enterZoomedRoom ; or a Zoomed Room. .enterBigRoom LDA #$08 ; Activate the sound effect sequence for entering a Big Room. STA sound1Active DEX ; Decrementing the index in X points the door index to the equivalent ; door data for the door in the room being entered. LDA doorXPositions,X ; In BigRooms the player is represented by the ball. So we set the STA ballXpos ; position of the ball to match the door position of the door just LDA doorYPositions,X ; passed through. ASL AND #$7E STA ballYpos LDA roomLockedFlags ; Decide if the Zoomed Room we are leaving should be locked? BIT roomDoneFlag BPL .doNotLock ; If the item was not picked up, the room is not done. ORA roomLockMask ; otherwise we set the locked flag corresponding to the room. .doNotLock STA roomLockedFlags TXA ; Because of the way the Door data table is arranged we calculate LSR ; whether the Big Room being entered is Level 1 (room 8) or Level 2 LSR ; (room 9) by dividing the Door Data index in X by 16. After which LSR ; the remaining value is 0 or 1. To which we add 8 to get 8 or 9. LSR CLC ADC #$08 BNE .goNewRoom ; This branch is always taken. Register A = the new value for ; currentRoom .enterZoomedRoom INX ; Incrementing the index in X points the door index to the equivalent ; door data for the door in the room being entered. LDA doorXPositions,X ; Position the player at the door entered. STA sprite0_Xpos LDA doorYPositions,X ASL AND #$7E STA sprite0_Ypos LDA #$F9 ; Set the height for the player sprite. STA sprite0_Height LDA #$50 CLC SBC sprite0_Ypos STA sprite0_Shape LDA #$FF STA sprite0_Shape+1 ; Set the player graphics pointer adjusted for the current Y position. TXA LSR LSR ; Calculate the new room number from the door data index. .goNewRoom STA currentRoom ; Save the new room number. JSR InitRoom ; Initialize the new Room. JMP FinishOverscan ; We are entering a new room so skip collision detection and monster ; movement. ;------------------------- postFrame345 SUBROUTINE ; Control comes to this point during overscan for every odd frame, and on any even frame when ; the player does not activate a door. This portion of overscan deals with checking for ; collisions. If the player passed through a doorway, then collisions are impossible for ; the current frame. ; It takes 2 frames to collect collision data for all 6 sprites and two for the Ball. ; ; The bits in collsionFlags are between sprites and the playfield. ; bit7 = sprite 0 ; bit6 = sprite 1 ; bit5 = sprite 2 ; bit4 = Ball even frame ; bit3 = sprite 3 ; bit2 = sprite 4 ; bit1 = sprite 5 ; bit0 = ball odd frame ASL PFcollisionFlags ; Make room for next collision flag. New flags are shifted in from the LDA saveCXP0FB ; right. The flag is currently cleared. ORA CXP0FB ; P0 could be before or after the P1 repositioning, so we OR the two BPL .1skip ; collision flags together. INC PFcollisionFlags ; Set the flag, a collision occurred. .1skip ; No collision, the flag remains cleared. ASL PFcollisionFlags ; Repeat for the two instances of P1 and the ball. LDA P1ReposThisFrame CMP #$50 BNE .2skip LDA CXP1FB BCS .3skip .2skip LDA saveCXP1FB .3skip BPL .4skip INC PFcollisionFlags .4skip ASL PFcollisionFlags LDA CXP1FB BPL .5skip INC PFcollisionFlags .5skip LDA flickerOffset BEQ .6skip ASL PFcollisionFlags LDA spritePointer2 ORA CXBLPF BPL .7skip INC PFcollisionFlags .7skip ASL PFcollisionFlags .6skip LDA currentRoom ; Are we in a large room or a zoomed room. CMP #$08 BMI zoomRoomCollisions ; Zoomed room, jump ahead. ; Big Room, fall through. bigRoomCollisions SUBROUTINE BIT saveCXP0FB ; In a Big Room the player is the Ball. So we check to see if the BVS playerHit ; ball touched any of the 3 sprites in the frame. If it did, then BIT saveCXP1FB ; the player loses a life. BVS playerHit BIT CXP0FB BVS playerHit BIT CXP1FB BVS playerHit JMP updateScore ; Player not killed this frame. playerHit JMP playerKilled ; Player died. zoomRoomCollisions SUBROUTINE LDX flickerOffset ; Are we dealing with sprites 0, 1, and 2, or sprites 3, 4, and 5? BNE sprites345 .sprites012 LDX #$07 ; Setting LDX to 7 will point at the SpriteSwap data locations. JSR playerCollisionTest ; Did player collide with the large Hall Monster? BCS playerHit ; Yes: Ouch! ; No: Fall through to next test. LDY currentRoom ; Note: We are in an even frame in which P0 is the player's sprite. BIT saveCXPPMM ; So we check if P0 is touching either instance of P1 in the frame. BPL .1skip ; No: P0 is not touching the 1st instance of P1, so skip to next test. CPY #$03 ; Is this the moving walls room (#3)? BEQ playerHit ; YES: then the player touched a wall and is dead. LDA RepositionTable,Y ; Else: If bit7 of the P1 reposition value is clear, then sprite 1 is BPL playerHit ; a monster and the player is hit. Otherwise, sprite 1 is the item LDX #$01 ; and the player picks it up. BNE .item .1skip BIT CXPPMM ; Check for contact between sprite0 (player) and sprite2 BPL shotCheck ; If no contact then branch to next collision test. CPY #$03 ; Yes the player is touching sprite 2. If we are in the moving walls BEQ playerHit ; room then the player has hit a wall and dies. LDA RepositionTable,Y ; If bit7 of the Y reposition value for the current room is set, then BMI playerHit ; sprite2 is a monster and the player is hit. Else sprite2 is the item LDX #$02 ; and we fall through to pick it up. .item BNE touchingItem ; ALWAYS jump to the routine that handles picking up an item. The number ; of the sprite (1 or 2) that is the item is in the X register. ;---------- ; Collision detection for odd frames when we display sprites 3, 4 and 5. sprites345 SUBROUTINE LDX #$04 ; We are going to test for colllisions with sprite 4 a little later. LDY currentRoom ; Are we in the moving walls room? CPY #$03 ; BNE .1skip ; No: Then the humming noise is not active and we skip ahead. ; Yes: Fall through to update humming sound effect. LDA HMspriteNumber ; Is the large hall monster active? BPL .2skip ; Yes: Skip ahead because Roar sound overrides humming wall sound. LDA sprite2_Xpos ; No: Set the volume for the humming sound effect in the moving wall LSR ; room. It is based on the oscillating position of the moving LSR ; walls. AND #$07 STA AUDV1 .2skip ; X is currently 4, but if the Hall monster is active then we need to test LDX #$05 ; sprite 5 as well as 3 and 4 for collision with the player sprite 0. .1skip ; Control skips to this point to test only sprites 3 and 4. .1loop JSR playerCollisionTest ; Is Sprite0 touching sprite X? BCC .3skip ; NO: Skip ahead. CPX #$04 ; YES: Then, is it sprite 4 or 5? BPL playerKilled ; YES: The player is killed. CPY #$03 ; NO: Then, are we in Room 3? (moving wall room?) BEQ touchingItem ; YES: Player is touching sprite 3 in the moving wall room which is the treasure. BNE playerKilled ; No: Player is touching sprite 3 in another room and the player dies. .3skip DEX ; Decrement sprite counter in X. CPX #$02 ; When X reaches 2, we are finished testing for collisions. BNE .1loop ;--------------- ; This code executes for both even and odd frames to update the player's shot. ; Y = currentRoom ; shotCheck SUBROUTINE CPY #$03 ; If we are in the Moving Walls room the player can not shoot, so we skip ahead to update. BEQ updateScore ; the player's score. BIT shotReadyFlag ; If the shotReadyFlag is set, then the shot is not in flight and requires no update, BMI updateScore ; skip ahead to update the player's score. ; The shot is in flight we will therefore check for its collision with the monsters in the ; current room. LDX #$01 ; The item is either sprite 1 or 2, we test bit7 in the RepositionTable for the room to LDY currentRoom ; (this seems like a wasted instruction, Y should already contain currentRoom) LDA RepositionTable,Y ; see which is the item, and set X to point at the other which must be a monster. BPL .1skip LDX #$02 .1skip JSR shotCollisionTest ; X is sprite1 or 2, depending on which is the monster in this room. BCS monsterKilled ; C=1 so monster is dead. LDX #$03 JSR shotCollisionTest ; X = sprite3 BCS monsterKilled ; C=1 so monster is dead. LDX #$04 JSR shotCollisionTest ; X = sprite4 BCC updateScore ; C=0 so monster is alive jump ahead, else monster killed and we fall through. ;------- Fall through possible --------------------- monsterKilled SUBROUTINE ; Control is passed to this routine when it has been detected that the player's shot has hit a monster. ; X holds the number of the sprite that corresponds to the monster hit. LDA #SPRITE_STATUS_DEAD CMP sprite0_Status,X ; If the monster is already a corpse do not give the player more points. BEQ .1skip LDY roomDoneFlag ; If the player has not yet picked up the room's item, do not award points for BEQ .1skip ; killing the monster. LDY pointsForKill ; Give the player points for the kill STY pointsScored .1skip STA sprite0_Status,X ; Set the status of the monster sprite to SPRITE_STATUS_DEAD, which seems to mean dead??? LDA #$20 STA sound1Active ; Activate the kill monster sound. ASL STA sprite0_Timer,X ; Set the monster sprite Timer value to $40. So the corpse is displayed for 128 frames. LDA #$F6 SEC SBC sprite0_Ypos,X TAY TXA ASL TAX STY sprite0_Shape,X ; Change the image pointer for the monster to point at the corpse image. LDA #$80 ; Reset the shot, and make it ready to fire. STA shotReadyFlag BNE updateScore ; Branch is always taken. ;--------------------------------------------------------- touchingItem SUBROUTINE ; This routine is called when the player collides with the room's item. ; X = the sprite that represents the item in the current room. LDA #SPRITE_NOT_VISIBLE STA sprite0_Ypos,X ; Make the item sprite invisible, by moving it off the bottom of the screen. CLC SED LDA pointsForKill ADC pointsForKill STA pointsScored ; The player scores double the points for picking up the item as for killing CLD ; a monster. DEC roomDoneFlag ; Set bit7 -> $00 - 1 = $FF, to indicate the room's item was collected. LDA #$2A ; Activate the sound effect for collecting an item. STA sound1Active BNE updateScore ; This branch is always taken. ;---------------------------------------------------------- playerKilled SUBROUTINE ; This rountine is called when the player dies. LDA gameStateFlags1 ORA #$80 STA gameStateFlags1 ; Set bit7 to pause the gameplay. LDA #$19 STA sound1Active ; Activate the death noise. LDX #$FF STX pauseTimer ; Freeze game play when the death sound plays. (~ 4 sec) INX ; X = 0 STX AUDV1 ; Silence any currently active sound. INX ; X = 1 STX $CD ; = 1 ??? I still don't know what $CD is for!!!! BNE playerDiedSkip ; This branch is always taken. ;---------------------------- updateScore SUBROUTINE SED CLC LDA score_3_4 ADC pointsScored ; Add the points scored this frame to the player's total score. STA score_3_4 LDA score_1_2 ADC #$00 STA score_1_2 CLD LDX flickerOffset ; Odd or even frame? BNE .1skip ; On odd frames we skip ahead to possibly update the sprite positions. ; On even frames we update the timers that control how fast game objects ; have their positions updated. ASL live0GFX ; This timer cycles once every 16 frames. 8 pairs of even/odd frames. BCC .2skip ; The pattern of values for this timer is: INC live0GFX ; $01, $02, $04, $08, $10, $20, $40, $80, -> $01 ... .2skip ASL live1GFX ; This timer cycles once every 10 frames. 5 pairs of even/odd frames. BCC .3skip ; The pattern of values for this timer is: LDA #$08 ; $08, $10, $20, $40, $80, -> $08 ... STA live1GFX .3skip ASL live2GFX ; This timer cycles once every 6 frames. 3 pairs of even/odd frames. BCC .1skip ; The pattern of values for this timer is: LDA #$20 ; $20, $40, $80, -> $02 ... STA live2GFX .1skip LDX #$00 ; Again we check is this is an even or odd frame. LDA flickerOffset BNE oddFrame BIT gameStateFlags1 ; Check if we are in a Zoomed Room or a Big Room. BVS evenFrame ; For Big Rooms X must be 6 for both odd and even frames and never 0. playerDiedSkip JMP FinishOverscan ; Since the player died we do not need to update sprite positions this frame. ;- - - - - - - - - - - - - - - SUBROUTINE evenFrame ; For even frames, X is initialized to 6 (ball) going into the loop. For odd frames X is 0. LDX #$06 ; If the player is in a large room then X is set to 6 (ball) for both odd and even frames. oddFrame nextSprite STX tempVar3 ; Save the loop/object index in TempVar3. LDA #$00 STA resetShotFlag ; = 0 Clear the flag, if the shot touches something we will set this flag to remember to ; reset it back to the player ready to be fired again. STA tempVar2 ; = 0 CPX #$06 ; If X = 6 (ball) or X = 7 (Large HM), then skip ahead. BCS .1skip LDY currentRoom ; Or if the currentRoom is the Moving Wall room, then skip ahead. CPY #$03 BEQ .1skip TXA ; Otherwise, if the loop/object index is < 3, then we change tempVar2 CMP #$03 ; from 0 to (loop/object index) - 3 = [-1|-2|-3]. If loop/object index is >=3 BCC .2skip ; then we set tempVar2 to loop/object index = [3|4|5] SBC #$03 .2skip STA tempVar2 .1skip ; Here is a summary of the possible values of tempVar2 as a function of loop/object index in X ; Index = tempVar2 = ; 0 0 -> P0 ; 1 1 -> P1 first instance in even frame. ; 2 2 -> P1 second instance in even frame. ; 3 0 -> P0 ; 4 1 -> P1 first instance in odd frame. ; 5 2 -> P1 second instance in odd frame. ; 6 0 -> Ball ; 7 0 -> Large Hall Monster (HM) ; This value is used later on to constrict the vertical movement of P1 sprites. LDA sprite0_Status,X ; STA tempVar1 ; Okay so the Status has been copied into tempVar1 LDY flickerOffset ; Is the frame odd or even? BEQ PFtestFinished ; EVEN: Skip ahead because the PF collision flags are only ready to be tested on odd frames. CMP #SPRITE_STATUS_DEAD BNE .notDead ASL PFcollisionFlags ; The sprite is dead so we don't care if it is touching the PF. Just throw the flag away JMP .3skip ; Jumping to .3skip below is okay because we know that the dead sprite can not be the player. .notDead AND #$07 ; Isolate the lower 3 bits of the living sprites status. TAY ; Y -> 0=immobile 1=veryslow 2=dead 3=slow 4=monstersL1; 5=monstersL2; 6=player|Large HM|L3; 7=L4 LDX whichGFXTable,Y ; Use that 3 bit status value to read an index of 0, 1, or 2 into X. LDA live0GFX,X ; Load the GFX timer pointed at by X. LDX tempVar3 ; Restore the sprite index into X. AND eventMaskTable,Y ; A Logical AND of the GFX timer and the eventMask. BEQ .4skip ; No update event for this sprite so skip ahead. CPX HMspriteNumber ; Is this sprite the large HM? BNE .5skip ; NO: skip ahead. ; YES: fall through LDA #$02 ; I believe this is to restrict updates of the large hall monster to every other frame pair BIT FrameCounter ; because sometimes it is being swapped every other frame pair. ??? BNE .5skip .4skip ASL PFcollisionFlags ; No action this frame pair for this sprite, discard its PF collision flag. JMP objectDone .5skip CPX #$07 ; The large HM can pass through walls it has no PF collision flag to be checked, skip ahead. BEQ .3skip ASL PFcollisionFlags ; Test the PF collision flag for the current sprite by moving it into Carry. BCC .6skip ; If it is clear, then no PF collision occured in the last frame pair for the sprite. LDA sprite0_Ypos,X ; PF COLLISION! Make sure the sprite is not dead. CMP #SPRITE_NOT_VISIBLE BEQ .3skip ; YES: The sprite is dead, so skip ahead and ignore the PF collision. JSR bounceOffWall ; No: the sprite is alive, jump to a subroutine to bounce off the wall. INC resetShotFlag ; Record that a collision with a wall occurred, so the shot will be returned to the player. BNE PFtestFinished ; This branch is always taken. ;- - - - - - - - .6skip CPX playerObjIndex ; Are we updating the player's sprite? [could be optimized since playerObjIndex is always 0] BNE .3skip ; NO: skip ahead to next case. ; YES: We are updating the player sprite, fall through. LDA sprite0_Status,X AND #$07 ; Get the player's speed. STA sprite0_Status,X ; Save only the speed discarding the previous direction value. LDA SWCHA ; Read the player's joystick and save its value as the new direction. AND #$F0 ORA sprite0_Status,X STA sprite0_Status,X ; In the player's status byte. STA tempVar1 ; Overwrite, the copy of the old status in tempVar1 with the new status. BNE PFtestFinished ; This branch is always taken. ;- - - - - - - - - .3skip ; The object is not the player, and did not hit a wall... LDA sprite0_Timer,X ; Is the timer for this object expired? BEQ PFtestFinished ; Yes: it expired on an earlier frame, so skip ahead. ; NO: Is the timer actively counting down? BMI .timerExpired ; NO: bit7 being set means the timer function is disabled for this object. DEC sprite0_Timer,X ; YES: decrement the timer until the corpse will disappear. ; Is the timer expired now? BNE PFtestFinished ; NO: not yet, skip ahead. ; YES: fall through to take the action related to the timer expiring. .timerExpired CPX #$06 ; Was it the Ball range timer that expired? BNE .8skip ; NO: skip ahead ; YES: Its the ball reaching its maximum range. INC resetShotFlag ; Set the flag to reset the shot to the player later on. BNE PFtestFinished ; branch always taken. .8skip LDA sprite0_Status,X ; Is it dead? CMP #SPRITE_STATUS_DEAD BNE .9skip ; NO: skip ahead the timer is active for some other reason than timing display of a corpse. LDA #SPRITE_NOT_VISIBLE ; YES: Make the sprite that is currently a corpse disappear. STA sprite0_Ypos,X BNE PFtestFinished ; Branch always taken. ;- - - - - - - - - - .9skip JSR changeDirection ; If the sprite is not dead and its timer has expired, then it changes the direction it is STA tempVar1 ; traveling in. ; At this point we have determined what direction the object should move this frame. ; So at PFtestFinished the position of the object is updated for the chosen direction. PFtestFinished SUBROUTINE ; ; ;tempVar1 contains the direction the object will travel in its upper nybble encoded as a joystick position. ; Joystick values reference table: ; -------------------------------- ; %0111 = Right ; %1011 = Left ; %1101 = Down ; %1110 = UP ; ;tempVar3 contains the index (0-7) to the object being moved. ; ASL tempVar1 ; Test RIGHT direction bit. LDX tempVar3 BCS .1skip ; RIGHT bit set, so no movement to the right LDA sprite0_Xpos,X ; Get the current X-pos of the object in preparation to move right. CPX #$06 ; If the object being updated is the Ball, then we must subtract 4 from the horizontal BCC .2skip ; position of the ball to compensate for the difference in horizontal size between SBC #$04 ; a sprite and the ball .2skip CMP #$9C ; If the object is at the right hand side of the screen, then we will not move BNE .3skip ; it any further to the right. INC resetShotFlag ; If is assumed that only the ball could reach the edge of the play area, since sprites BNE .4skip ; are constrained by the room walls. This branch is always taken. .3skip INC sprite0_Xpos,X ; Move the object to the right by 1 pixel. .4skip ASL tempVar1 ; The player moved RIGHT, so the LEFT bit must be set. We throw it away and skip ahead. BCS .5skip ; Notice that bad things happen if the LEFT bit is actually clear! ;- - - - - - - - - .1skip ASL tempVar1 ; Test LEFT direction bit. BCS .5skip ; The bit is set, so the object is not moving to the left, skip ahead. LDA sprite0_Xpos,X ; Load the object's current horizontal position. CMP #$01 ; If it is 1, then the object is at the left edge of the screen, then we assume it is the BNE .6skip ; shot, and we fall through to reset the shot to the player rather than skip ahead and... INC resetShotFlag BNE .5skip .6skip DEC sprite0_Xpos,X ; ...move the object to the left. ;- - - - - - - - - .5skip ASL tempVar1 ; Test the DOWN direction bit. BCS .7skip ; The bit is set, so the object is not moving down, skip ahead. LDA #$4E ; $4E is the bottom edge of the screen. CPX #$06 ; Are we updating the Ball? BEQ .8skip ; YES: Jump to ball handling code. ; NO: We are processing a sprite, so we need to compensate for the height of the sprite ; moving downward to know if it reached the bottom of the screen. BIT gameStateFlags1 ; Are we in a Big Room or a Zoomed Room? BVS .9skip ; V=1: We are in a Zoomed Room skip ahead. LDA #$4F ; V=0: In Big Rooms we extend the bottom of the screen by 1 scan line pair. .9skip ; I presume this is needed to allow enough room for the HM under the bottom wall. ; Why not just set A to $AE and be done with it? {chance for optimization?} ; RECALL: a summary of the possible values of tempVar2 as a function of loop/object index in X ; Index = tempVar2 = ; 0 0 -> P0 ; 1 1 -> P1 first instance in even frame. ; 2 2 -> P1 second instance in even frame. ; 3 0 -> P0 ; 4 1 -> P1 first instance in odd frame. ; 5 2 -> P1 second instance in odd frame. ; 6 0 -> Ball ; 7 0 -> Large Hall Monster (HM) LDY tempVar2 ; If the object is 7 (large HM), 0 (P0 even frame), or 3 (P0 odd frame). BEQ .10skip ; YES: There is no restriction on the vertical movement of the ball, large HM, or P0 so we ; skip ahead to avoid any vertical movement boundary test. ; NO: The object is either 1, 2, 4, or 5 and represented on the screen by P1. Since ; P1 is used twice per frame, we must restrict the vertical movement of these ; objects so that they do not overlap on the screen. ; Is this object the first or second instance of P1 in the frame? CPY #$01 ; FIRST INSTANCE: Fall through to find the restriction on vertical movement BNE .10skip ; SECOND INSTANCE: Skip ahead because the downward movement restriction for the ; second instance of P1 in any frame is the bottom of the screen. LDA P1repositionEven ; For a FIRST INSTANCE P1 object set the bottom of its movement vertical movement range to SEC ; the P1 reposition line minus 1. SBC #$01 .10skip ; At this point A holds the lowest vertical position the object is permitted to go to. CLC ; Since the object's position is measured from its upper left hand corner, we subtract ADC sprite0_Height,X ; the height of the object from the lowest vertical position allowed. ; NOTE: The sprite Height values are negative so we add them to achieve subtraction. .8skip ; NOTE: The Ball is free to travel vertically, and has a height of 1, so the above adjustments ; for all other objects are skipped in the case of the ball. CMP sprite0_Ypos,X ; Has the object reached the boundary in A BNE .11skip ; NO: skip ahead. INC resetShotFlag ; YES: Reset, the shot! What if a sprite hits the boundary? Does the shot get reset? No, in fact BNE .done ; a test below will ignore the reset flag if the ball is not the object being updated this time ; through the loop. Skip ahead since the object can not be traveling up if it is traveling down. .11skip INC sprite0_Ypos,X ; Move the object down 1 pixel. TXA ; Multiply the index in X by 2 to index into the table of word sized graphics data pointers. ASL TAX DEC sprite0_Shape,X ; Decrement the object's shape pointer by 1 to adjust for the increase in the object's Y pos. JMP .done ; Skip ahead since the object can not be traveling up if it is traveling down. ;- - - - - - - - .7skip ASL tempVar1 ; Test the UP direction bit. BCS .done ; If the bit is set, then we are finished updating the current object's position. CPX #$06 ; Else, Move the object up. Is this the ball? BNE .12skip ; NO: skip ahead. LDA #$00 ; YES: So set the upper movement boundary at 0. BEQ .13skip ; Branch always taken. .12skip LDA #$FF ; For all sprites the upper movement boundary is -1 = $FF. LDY tempVar2 ; Unless its the second instance of P1 in the frame, which has a lower upper movement CPY #$02 ; boundary. BCC .13skip ; LDA P1repositionEven ; Set the upper movement boundary to the P1 reposition + 1 for second instances of P1. ADC #$01 .13skip ; At this point, the Accumulator has the upper movement boundary for the object. CMP sprite0_Ypos,X ; Compare the boundary to the Y position of the object. BNE .14skip ; Skip ahead if the object can move up. INC resetShotFlag ; Otherwise, set the shot reset flag in case this is the ball being updated. LDA sprite0_Status,X ; Turn off the UP direction bit for this object. ORA #$10 STA sprite0_Status,X BNE .done .14skip DEC sprite0_Ypos,X ; Move the object up 1. TXA ASL TAX INC sprite0_Shape,X ; Also update the graphics pointer. .done ; At the .done label the object position is updated, all that remains is to LDX tempVar3 ; Check if the shot reset flag is set. If it is, then check if we are updating LDA resetShotFlag ; the ball. If we are, then reset the shot, otherwise jump ahead. BEQ .15skip CPX #$06 ; Ball? BMI .16skip ; No - normal sprite, objects 0 through 5. BNE .15skip ; No - large hall monster, object 7. BIT gameStateFlags1 ; YES: its the ball, but are we in a Big Room or a Zoomed Room? BVC .15skip ; BIG ROOM: In a big room the player is the ball, so reseting the shot has no meaning. ; and we skip ahead. LDY #$80 ; ZOOMED ROOM: Reset the shot to the player. STY shotReadyFlag STY ball_Status .15skip JMP objectDone ; Object update complete, .16skip LDY #$01 ; If a sprite touches the outer edge of the playfield, then we set the timer to STY sprite0_Timer,X ; have it change directions next frame. If we didn't it could become stuck. (I think) BNE .15skip ;------------------------------- ; It appears that when the timer expires anytime the sprite is a live monster, this routine is called to ; change the direction of the monster. ; ; INPUTS: ; X = the object being updated (0-7) ; tempVar3 = X (backup copy) ; changeDirection SUBROUTINE LDY #$01 CPX #$06 ; Is this the shot? BEQ .1skip ; YES: The shot can not change direction in mid-flight, so skip ahead. ; NO: Is is the large Hall Monster (object 7)? BMI .sprites0to5 ; NO: skip ahead ; YES: Its the timer for the Large Hall monster. STY sprite7_Timer ; Reset the Large Hall monster timer to 1. BPL .homeOnPlayer ; Skip ahead to the Large Hall monster player tracking routine. .sprites0to5 LDA currentRoom CMP #$03 ; Is this the moving walls room? BNE .3skip ; NO: skip to process the object as a item or monster ; YES: fall through to the code for updating the direction of the oscillating walls.. .movingwalls ; Update the direction of a moving wall. LDY #$18 ; Set the object timer to $18 for even numbered moving wall objects. TXA ROR ; Object # even or odd? BCC .2skip LDY #$0E ; Set the object timer to $0E for odd numbered moving wall objects. .2skip STY sprite0_Timer,X JSR bounceOffWall ; Invert the current direction of the moving wall object by 180 degrees. ORA #$06 ; Set the wall speed bits. BNE .done ;- - - - - - - - .3skip LDY randomNumPtr LDA $F000,Y ; Get a "random" value. EOR FrameCounter ; Scramble it with the frame counter. AND #$30 ORA #$08 ; Produce a value of $08, $18, $28, $38 as the next object timer value. STA sprite0_Timer,X ; Save the new timer value. When the object timer reaches zero the object ; will change direction again. LDA $F002,Y ; Get a "random" value. EOR FrameCounter ; Scramble it with the frame counter. INY ; Increment the "random" number pointer so it points at a new number next time. STY randomNumPtr AND #$07 ; Based on the difficulty, there is a random chance the monster will home in on the player. CMP difficulty BMI .homeOnPlayer EOR $F003,Y ; Scramble the bits again with a "random" number AND #$07 ; Reduce the random value to a random direction 0-7 TAY LDA sprite0_Status,X AND #$0F ORA monsterDirTable,Y ; Combine the new random direction for the object with its existing speed. BNE .done ; This branch is always taken. ;- - - - - - - - - .homeOnPlayer LDY sprite0_Xpos,X ; Fetch the X-pos of the object homing on the player. LDA #$50 ; Initialize the new direction to $50 which is RIGHT & DOWN BIT gameStateFlags1 ; Are we in a Big Room or a Zoomed Room? BVC .bigroom CPY sprite0_Xpos ; Compare the X position of the object to the X position of the player sprite. BVS .4skip ; V=1 from BIT instruction above! So this branch is always taken! Nice trick. .bigroom ; Compare the X position of the object to the X position of the player as the ball. CPY ballXpos .4skip BCC .5skip ; If the player is to the RIGHT of the object, then skip ahead. BNE .6skip ; Else, if they are equal, set the RIGHT bit. If the player is left of the ADC #$40 ; object, then set the RIGHT bit and clear the LEFT bit. .6skip ADC #$40 .5skip ; The RIGHT and LEFT direction bits are set to home in on the player. LDY sprite0_Ypos,X ; Now we perform a similar function for the UP and DOWN bits. BIT gameStateFlags1 BVC .7skip ; big room or zoomed room? V is still set from BIT test earlier! CPY sprite0_Ypos BVS .8skip .7skip CPY ballYpos .8skip BMI .9skip BNE .10skip ADC #$10 .10skip ADC #$10 .9skip AND #$F0 ; Combine the new object direction with the current object speed. STA tempVar3 LDA sprite0_Status,X AND #$0F ORA tempVar3 STX tempVar3 .done STA sprite0_Status,X ; Save the new direction and speed for the object. .1skip RTS objectDone ; This is the bottom of the loop to test each object for collisions and for ; timer expirations to change direction or disappear if a corpse. INX ; Increment the object counter in X. LDA flickerOffset ; Is this an even frame? BEQ FinishOverscan ; YES: then only the ball/shot is updated on an even frame, exit the loop. CPX #$08 ; NO: its an odd frame when all 7 game objects are updated. Are all 7 done? BEQ FinishOverscan ; YES: all seven are done, exit the loop. JMP nextSprite ; NO: update the next object. ; ----------------------------------- ; -- This is the powerup entry point. ; ----------------------------------- ; Start SUBROUTINE SEI CLD ; Clear interrupts & decimal flag LDX #$FF TXS ; Init Stack INX JSR InitGame LDA #$80 STA gameStateFlags1 ; = $80 -> the game play is paused. ASL STA pauseTimer ; = $00 -> until someone pushes reset. FinishOverscan SUBROUTINE DEC FrameCounter BNE .2skip DEC HallMonsterTimer BNE .2skip ; Until the large hall monster timer reaches zero we skip ahead. BIT gameStateFlags1 ; Test bit6 in gameStateFlags1 to see if we are in a BigRoom or a ; Zoomed Room. BVC .2skip ; V=0 means its a big room, so no large hall monster ever appears. BMI .2skip ; N=1 means the gameplay is paused (temporarily). LDX #$03 ; determine which sprite the large hall monster is represented by LDA sprite4_Ypos ; for this room? CMP #SPRITE_NOT_VISIBLE BNE .3skip LDX #$04 .3skip STX HMspriteNumber ; Save the index to the large sprite monster and simultaneously ; clear bit 7 to indicate the monster is active. LDA currentRoom ; If this is the moving wall room then, we face a special case CMP #$03 ; because the room has 7, not 6 sprites like all other rooms. BEQ .1skip ; If this is the moving wall room, then skip over this step. LDA #$07 ORA sprite0_Status,X ; For any room except the Moving Wall room set the three lower STA sprite0_Status,X ; bits of the sprite0_Status,X. I am guessing the three set bits ; mean "home in on player"? .1skip LDA #$0C STA AUDV1 ; Set the volume for the large hall monster's roar! LDA #$20 STA sprite7_Timer ; Some large hall monster flag? Perhaps the timer after appearing ; until it starts to move? .2skip LDX #$00 STX flickerOffset ; Default to draw sprites 0, 1, and 2. STX SpriteSwapFlag ; We are activating the hall monster, but the sprites aren't swapped ; yet so set flag to 0. LDA FrameCounter ; Check if Frame is even or odd. LSR BCC .4skip ; Frame counter even, then skip ahead. LDX #$03 STX flickerOffset ; else, frame counter odd then prepare to draw sprites 3, 4 and 5. TAY ; Y = FrameCounter / 2 LDX HMspriteNumber BMI .4skip ; If the large hall monster is off (HMSpriteNumber=$80), ; then skip ahead. LDA #$08 STA AUDC1 LDA #$1F STA AUDF1 ; Set the ROAR! sound effect values for voice channel 1. LDA sprite0_Ypos,X CMP #SPRITE_NOT_VISIBLE BEQ LF746 TYA LSR BCS .4skip LF746 JSR ItemHMDataSwap ; For the moving walls room we swap the item and big Hall Monster ; sprite data every 2 frames. INC SpriteSwapFlag ; SpriteSwapFlag is set to 1 when the sprite is displaying the Hall ; Monster, 0 for the treasure. .4skip LDA #$02 ; Load A with 2 to set the vertical sync flag in a moment. .OverScanWait LDX INTIM ; Wait here for the overscan timer to expire and then fall through BNE .OverScanWait ; to VerticalSync. VerticalSync SUBROUTINE ; A = 2, X = 0 STX WSYNC STA VSYNC ; Begin vertical sync. STX PF1 ; Clear the playfield registers. NOTE: PF0 is never used so it STX PF2 ; doesn't need to be cleared. STX ENABL ; Turn off the ball in case it was on during the last visible ; scanline of the previous frame. STA WSYNC ; Begin second line of vertical sync. STA CXCLR ; Clear all collision registers. LDX #$08 ; This loop sets 4 pointers in RAM to the graphics to draw the first 4 LDY #$01 ; digits of the score ;spritePointer1 = $ED:$EE ; Points to a sprite shape for 1st digit ;spritePointer2 = $EF:$F0 ; Points to a sprite shape for 2nd digit ;spritePointer3 = $F1:$F2 ; Points to a sprite shape for 3rd digit ;spritePointer4 = $F3:$F4 ; Points to a sprite shape for 4th digit .1loop ; First Pass Second Pass LDA $0100+score_1_2,Y ; Y=1 ; Y=0 DEX ; *** Since this loop only executes 2 times we could eliminate DEX ; *** these DEX instructions, and load X=4 above, and X=0 at the DEX ; *** end of the loop and ave 2 bytes ROM. DEX ; ; X=4 ; X=0 ROR ; AND #$78 ; ; A=([$C8]>>1) & %01111000 ; A=([$C7]>>1) & %01111000 STA spritePointer1,X ; third digit of score ; first digit of score LDA $0100+score_1_2,Y ASL ASL ASL AND #$78 ; A=([$C8]<<3) & %01111000 ; A=([$C7]<<3) & %01111000 STA spritePointer2,X ; fourth digit of score ; second digit of score LDA #>charset STA spritePointer1+1,X ; Set high bytes of 16-bit sprite pointers. STA spritePointer2+1,X DEY BPL .1loop STA WSYNC ; Sync to end of 3rd line of vertical sync. STX VSYNC ; Turn off vertical sync. X=0 from the loop above. VerticalBlank SUBROUTINE STX pointsScored ; = 0 LDA #$32 STA TIM64T ; Set timer for vertical blank period. ; 50 * 64 = 3200 cycles ; 3200 / 76 cycles per scanline = 42.1 lines ; = 43 scanlines of vertical blank. ; In this next part we check the player's fire buttton and if he can fire. LDA currentRoom ; If the current room is the Moving wall room, then skip ahead. CMP #$03 ; because shooting is disabled in that room. BEQ .1skip BIT shotReadyFlag ; If bit7 of shotReadyFlag is clear then the player's BPL .2skip ; player's shot is in flight or we are in a big room, ; so we skip ahead. LDA INPT4 ; The player's shot is available to shoot and the gun is BMI .1skip ; enabled in the current room so we test the fire button on the ; player's joystick. If the button is not pressed we skip ahead to ; draw the bullet in the ready position. If the BIT gameStateFlags1 ; ??? Purpose of this test is unknown, it seems to me all BMI .2skip ; possible cases for the player's shot have been covered ??? ; Possibly the lock on all movement while tones play as the player ; enters a new room? ; The player is shooting, initialize the shot's in-flight variables. STX shotReadyFlag ; = 0 Clear bit7 of shotReadyFlag to indicate the player's ; shot is in flight. LDA LastPlayerDirection ; ??? ORA #$07 STA ball_Status ; Set the shot direction to the player's last direction and set ; the speed of the shot to #$07. LDA #PLAYER_SHOT_RANGE STA ball_RangeTimer ; Set the countdown for the shot's range of flight. LDA #$0F STA sound1Active ; =$0F ??? .1skip ; Control comes to this point to draw the ball relative to the player orientation. ; If we fell through from above, the ball will be fired in the latest direction ; pushed by the player, if the direction and fire button changed since the last ; frame, then the shot goes in the new direction. LDA LastPlayerDirection ROR ROR ROR ROR AND #$0F ; Move the 4 bits that hold the joystick current position ; to the lower nybble of X to index the Joystick Table. TAX LDA ShotToPlayerPosTable,X TAY AND #$0F ; lower nybble is ball Y relative to player's CLC ; current facing. ADC sprite0_Ypos STA ballYpos TYA ROR ROR ROR ROR AND #$0F ; upper nybble is ball X relative to the player's CLC ; current facing + 1 (see adjustment below) ADC sprite0_Xpos STA ballXpos DEC ballXpos ; subtract an additional 1 from the ball X-pos so that when ; the facing is left the ball is 1 pixel left of the player. .2skip ; the ball is in flight or the player is the ball as in a big room. ; So do nothing for now. ; Get ready to Draw the score digits and player life indicator. LDX #$00 ; Turn off both sprite graphics to stop drawing them in case STX GRP0 ; they were on for the last scanline of the previous frame. STX GRP1 STX activelyDrawP0 ; Set the current heights to zero because we are not STX activelyDrawP1 ; drawing game sprites at the start of a frame. LDA #$01 JSR PosObject ; A = 1 = horizontal position, and X = 0 = P0 LDX #$01 LDA #$09 STA COLUP0 ; Set the color for the score digits. STA COLUP1 ; Set the color for the score digits. JSR PosObject ; A = 9 = horizontal position, and X = 1 = P1 ;----- LDY P1repositionEven ; Set P1ReposThisFrame based on whether the current frame is LDX flickerOffset ; odd or even. BEQ .3skip LDY P1repositionOdd ; It seems strange that we waste two bytes of RAM to hold these .3skip ; values since they are constants. Maybe there was a plan at STY P1ReposThisFrame ; one point to make them dynamic. At any rate this is a good ; place to save 2 bytes of RAM. ;----- LDY #$00 ; NOTE: X is either 0 or 3. LDA sprite0_Ypos,X BPL .4skip ; If the sprites Y position is positive, then that sprite is no LDY sprite0_Height,X ; no longer being displayed. So height is set to zero. .4skip STY activelyDrawP0 STA desiredYposP0 ;----- LDA sprite0_Height,X ; Copy the height for P0 into preload variable. STA preloadHeightP0 TXA ASL ; Double the index in X to manipulate the word sized sprite pointer TAX LDA sprite0_Shape,X ; Make a copy of the sprite pointer to be used for P0 for this STA P0ShapeThisFrameLo ; frame. LDA sprite0_Shape+1,X STA P0ShapeThisFrameHi LDX #$04 ; Set the horizontal position of the ball for this frame. LDY ballXpos INY ; note that 1 is added to the balll position to adjust for the TYA ; fact that horizontal positioning of balls and missles using ; the same code as for positioning sprites will result in the ; ball and missles being 1 pixel to the left of sprites positioned ; using the same value in A. JSR PosObject ; X = 4 = Ball. A = ballXPos + 1 STA WSYNC STA HMOVE LDA roomOffset ; Make a copy of the offset to the PF graphics for the currentRoom STA savePFindex ; for the next frame. LDX livesCounter LDA livesPFshape,X STA tempVar2 ; Prepare shape of remaining lives LDA #$03 ; Set last minute sprite control register values to display the STA NUSIZ0 ; score digits. P0 and P1 set to 3 copies close. STA NUSIZ1 STA VDELP0 ; Turn on vertical delay for P0 and P1 so the 48-pixel sprite STA VDELP1 ; routine to draw the score will work. ; NOTE: I should investigate if VDEL needs to be used for ; this game since the last two digits of the score are ; always zero. LDX sound1Active ; A value of zero means the sound effect is off. so skip ahead. BEQ .5skip LDA audioDataTable,X STA AUDC0 LSR LSR LSR LSR STA soundDuration ; ?Its not clear why we are saving the upper nybble from the audio ; data table in soundDuration STX soundUnknown1 ; Its even less clear why we are saving the index in soundUnknown1 LDA #$00 STA sound1Active ; Turn off current sound? BEQ .1jump ; Branch always! .5skip LDX noteTimer1 ; If the noteTimer is zero, nothing happens. Otherwise it is BEQ WaitVBLANK ; decremented. If it decrements to zero, then fall through to DEC noteTimer1 ; take action. Otherwise, skip ahead to change nothing. BNE WaitVBLANK .1jump INC soundUnknown1 ; I am guessing the sound data is stored in pairs of bytes. LDX soundUnknown1 ; more work is needed CPX #$29 BNE .6skip DEC soundUnknown1 DEC soundUnknown1 .6skip LDA audioDataTable,X ASL STA AUDV0 BNE .7skip STA soundDuration ; = 0 .7skip LSR LSR LSR LSR STA AUDF0 LDA soundDuration STA noteTimer1 WaitVBLANK LDY INTIM ; Waiting for the vbank timer to expire. BNE WaitVBLANK STY WSYNC STY VBLANK ;[3] Turn VBLANK off STA HMCLR ;[6] Clear all horizontal move registers. JMP Mainloop ;[11] Begin darwing the visible screen. ; Positions an object horizontally ; Inputs: A = Desired position. ; X = Desired object to be positioned. ; scanlines: If control comes on or before cycle 31 then 2 scanlines are consumed. ; If control comes after cycle 31 then 3 scanlines are consumed. ; Outputs: Y = $FF ; control is returned on cycle 6 of the next scanline. PosObject SUBROUTINE TAY ;[00]+2 = 02 AND #$0F ;[02]+2 = 04 STA tempVar2 ;[04]+3 = 07 TYA ;[07]+2 = 09 LSR ; LSR LSR LSR ;[09]+8 = 17 TAY ;[17]+2 = 19 CLC ;[19]+2 = 21 ADC tempVar2 ;[21]+3 = 24 CMP #$10 ;[24]+2 = 26 BCC .1skip ;[26]+2 = 28 longest path only SBC #$0F ;[28]+2 = 30 INY ;[30]+2 = 32 .1skip ASL ASL ASL ASL ;[32]+8 = 40 EOR #$70 ;[40]+2 = 42 STA WSYNC ;[42]+3 = 45 -> 76 - 45 = 31 cycles. STA HMP0,X ; Why is this store done twice? STA HMP0,X INY .1loop DEY BPL .1loop STA RESP0,X STA WSYNC RTS ; --------------------------------------------------- ; X will contain either zero or $80 when InitGame is called. ; X=$00 will wipe the registers and RAM clean. ; x=$80 will erase only the contents of RAM leaving the registers untouched. InitGame SUBROUTINE LDY #$00 ClearRAM STY $00,X INX CPX #$FB BNE ClearRAM ; Clear RAM, leave last 4 byte intact to preserve any return addresses ; on the stack. STY AUDV0 ; Silence! INY STY difficulty ; Assume difficulty STY CTRLPF ; Reflect Playfield LDA #$80 STA live0GFX ; Show live 1 GFX STA live1GFX ; Show live 1 GFX STA live2GFX ; Show live 1 GFX LDA #$03 ; STA livesCounter ; Init live Counter = 3 ASL ; 3 * 2 STA HMTimerSetup ; = 6. ADC #$F0 ; STA InitStatusforHM ; InitStatusforHM -> $F6 ???? LDA SWCHB ; Check Difficulty BPL .1skip INC InitStatusforHM ; Increase InitStatusforHM ???? .1skip ROL ROL ROL AND #$03 STA difficulty ; Get basic difficulty from switches STA currentLevel ; Gameplay starts at level 0 through 3 depending on the difficulty switches. STA pointsForKill ; The initial points for killing a monster is the difficulty+1 (*100) INC pointsForKill CLC ; ADC #$F4 ; STA $E2 ; ??? STA $E4 ; ??? LF8E8 LDA currentLevel ; If we fell through from above currentLevel has the difficulty. AND #$01 ; If the player 2 difficulty switch is set to B, then start at room 9, ; else start in room 8. CLC ; ADC #$08 STA currentRoom LDA #$4C ; In either case the player as the Ball begins the game in the same STA ballYpos ; starting location. LDA #$44 STA ballXpos InitRoom SUBROUTINE ; Or is this initLevel, and InitRoom is really below? ; ??? Is this the entry point for screen transtions? Sharing common code with the ; primary intialization? LDY currentRoom ; Since bit 6 will always be off from currentRoom we use its value STY mirroredRoom ; to disable mirroredRoom by default, then test to see if LDA #$40 ; mirroredRoom should be enabled below. STA pauseTimer ; ?Set the relative position of the shot to the player? ASL ; A=$80 STA HMspriteNumber ; HMspriteNumber = $80 means the large hall monster is not active. LDA pfroomoffsets,Y ; Get a copy of the offset to the current room graphics. STA roomOffset CMP #$59 ; If the roomOffset is < $59 then the room is not a mirrored room. BCC .2skip LDA #$40 STA mirroredRoom ; else, it is a mirrored room. .2skip LDX #$00 STX $CD ; = 0 STX sprite7_Timer ; = 0 CPY #$08 ; Here we are testing if the currentRoom is 8 or 9 which are the BCC .3skip ; special rooms with 6 small hall monsters. In which case we jump JMP InitBigRoom ; to a separate routine to continue room init. .3skip ; We are not dealing with room 8 or 9. It is one of the zoomed-in ; rooms 0 through 7. STX P1spritewidth ; X is 0 so we are defaulting to single width sprites, not quad ; which are used in the moving walls room (currentRoom=3). STX sprite0_Timer ; = 0 STX playerObjIndex ; = 0 STX $C1 ; = 0 STX ball_Status ; = 0 The ball is not in flight, nor is it ready to be shot. STX roomDoneFlag ; = 0 INX STX sound1Active ; = 1 STX spriteXposSwap ; = 1 Starting position for large HM STX sprite7_Ypos ; = 1 upper left hand corner. LDA #$F7 STA spriteHeightSwap ; = -9 default height of Large HM is 9. LDA InitStatusforHM STA sprite7_Status ; = $F6 if P0diff = novice, else = $F7 LDX HMTimerSetup STX HallMonsterTimer ; = 6 at game start, and -1 for each level completed down to a minimum of 3. CPX #$03 ; HMTimerSetup will never fall below 3, thus setting the minimum BEQ .4skip ; time that the large HM will take to appear at maximum difficulty. DEC HMTimerSetup .4skip LDA #$53 STA sprite7_Shape ; = $53 LDA #$80 STA shotReadyFlag ; = $80 Upon entering a zoomed room the player's shot is ready ; to be fired. LDA #$C0 STA gameStateFlags1 ; = $C0 LDA #$F6 ; STA sprite0_Status ; = $F6 -> F means no current direction of travel. 6 is the speed from 0 (immobile) to 7 (fastest) LDA #$06 STA AUDF1 ; = 6 LDX #$00 .2loop LDA MonsterColorTable,Y STA sprite1_Color,X LDA $E4 STA sprite1_Status,X INX TXA STA sprite0_Timer,X CPX #$04 BNE .2loop LDA RepositionTable,Y STA P1repositionEven LDA #$50 ; This matters in the collision detection routines elsewhere! STA P1repositionOdd LDA #$43 ; Set the player's color for zoomed in rooms = Red. STA sprite0_Color LDX #$00 STX FrameCounter LDA currentRoom ; Load Y with currentRoom * 4 to index the table of sprite positions. ASL ASL TAY .3loop STX tempVar3 LDA ZoomSpriteXYTable,Y AND #$F0 CLC ROR STA tempVar1 ROR ROR SEC ADC tempVar1 STA sprite1_Xpos,X ; Multiply the X nybble by 10. LDA ZoomSpriteXYTable,Y AND #$0F STA tempVar2 ASL ASL ADC tempVar2 SEC SBC #$02 STA sprite1_Ypos,X ; Multiply the Y nybble by 5 and subtract 2. STA tempVar2 LDA #$F7 ; Set the height of the monster and item sprites to 9. STA sprite1_Height,X ; NOTE: Each sprite graphics data sequence ends with $00 which turns ; the sprite display off so each graphic is visible for 8 lines. TXA ASL ; X = X * 2 TAX LDA #$FE ; The next step is to set the graphics pointers for the Monsters STA sprite7_Shape+1 ; and Treasure. STA sprite1_Shape+1,X ; Set the high byte of the 16-bit sprite graphics pointer to point LDA currentRoom ; at the graphics table. ASL ; A = currentRoom * 2 ASL ; A = currentRoom * 4 ASL ; A = currentRoom * 8 ADC currentRoom ; A = currentRoom * 9 ASL ; A = currentRoom * 18 which is 2 * standard sprite height of 9. SBC tempVar2 ; The Y position of the sprite is subtracted from the pointer so CLC ; that ,Y will index the shape correctly. ADC # $01 ... Never (used for items) ; %001 = 1 1 %10000000 $08, $10, $20, $40, $80, -> $08 ... Every 5th frame pair (6/sec) ; %010 = 2 0 %10001000 $01, $02, $04, $08, $10, $20, $40, $80, -> $01 ... Every 4th frame pair (7.5/sec) ; %011 = 3 2 %10000000 $20, $40, $80, -> $02 ... Every 3rd frame pair (10/sec) ; %100 = 4 0 %10101010 $01, $02, $04, $08, $10, $20, $40, $80, -> $01 ... Every 2nd frame pair (15/sec) ; %101 = 5 1 %11010000 $08, $10, $20, $40, $80, -> $08 ... Every 3 of 5 frame pairs (18/sec) ; %110 = 6 2 %11000000 $20, $40, $80, -> $02 ... Every 2 of 3 frame pairs (20/sec) ; %111 = 7 2 %11100000 $20, $40, $80, -> $02 ... Every frame pair (30/sec) whichGFXTable .byte $00 ; | | Which timer is used. .byte $01 ; | X| .byte $00 ; | | .byte $02 ; | X | .byte $00 ; | | .byte $01 ; | X| .byte $02 ; | X | .byte $02 ; | X | eventMaskTable .byte $00 ; | | The event mask logically AND'ed with the timer identified in the previous table. .byte $80 ; |X | .byte $88 ; |X X | .byte $80 ; |X | .byte $AA ; |X X X X | .byte $D0 ; |XX X | .byte $C0 ; |XX | .byte $E0 ; |XXX | ;------------------------------------------------ HallMonsterGraphics .byte $70 ; | XXX | hall monster small .byte $A8 ; |X X X | .byte $F8 ; |XXXXX | .byte $88 ; |X X | .byte $00 ; | | .byte $18 ; | XX | hall monster big .byte $3C ; | XXXX | .byte $5A ; | X XX X | .byte $66 ; | XX XX | .byte $3C ; | XXXX | .byte $42 ; | X X | .byte $24 ; | X X | .byte $18 ; | XX | .byte $00 ; | | ;------------------------------------------------ SpriteImagesTable .byte $38 ; | XXX | zombie .byte $54 ; | X X X | .byte $6C ; | XX XX | .byte $39 ; | XXX X| .byte $7E ; | XXXXXX | .byte $98 ; |X XX | .byte $24 ; | X X | .byte $66 ; | XX XX | .byte $00 ; | | .byte $3F ; | XXXXXX| chest .byte $21 ; | X X| .byte $3F ; | XXXXXXX| .byte $42 ; | X X | .byte $FE ; |XXXXXXX | .byte $82 ; |X X | .byte $82 ; |X X | .byte $FE ; |XXXXXXX | .byte $00 ; | | .byte $18 ; | XX | ghoul .byte $BD ; |X XXXX X| .byte $A5 ; |X X X X| .byte $FF ; |XXXXXXXX| .byte $3C ; | XXXX | .byte $24 ; | X X | .byte $24 ; | X X | .byte $66 ; | XX XX | .byte $00 ; | | .byte $18 ; | XX | helmet .byte $BD ; |X XXXX X| .byte $FF ; |XXXXXXXX| .byte $42 ; | X X | .byte $24 ; | X X | .byte $18 ; | XX | .byte $00 ; | | .byte $00 ; | | .byte $00 ; | | .byte $00 ; | | .byte $C6 ; |XX XX | snake .byte $CA ; |XX X X | .byte $2A ; | X X X | .byte $4A ; | X X X | .byte $51 ; | X X X| .byte $21 ; | X X| .byte $00 ; | | .byte $00 ; | | .byte $08 ; | X | apple .byte $10 ; | X | .byte $7C ; | XXXXX | .byte $FE ; |XXXXXXX | .byte $FE ; |XXXXXXX | .byte $7C ; | XXXXX | .byte $38 ; | XXX | .byte $00 ; | | .byte $00 ; | | .byte $F8 ; |XXXXX | Horizontal wall (quad wide) .byte $F8 ; |XXXXX | .byte $F8 ; |XXXXX | .byte $F8 ; |XXXXX | .byte $00 ; | | .byte $80 ; |X | vertical wall (quad wide) .byte $80 ; |X | .byte $80 ; |X | .byte $80 ; |X | .byte $80 ; |X | .byte $80 ; |X | .byte $80 ; |X | .byte $80 ; |X | .byte $80 ; |X | .byte $80 ; |X | .byte $80 ; |X | .byte $80 ; |X | .byte $00 ; | | Its important that the height of the two walls together is 18 just like ; the monster Monster/Item graphics for all other rooms because the ; graphics pointers are set as 18 * currentRoom for all zoomed-rooms. .byte $44 ; | X X | ettin .byte $AA ; |X X X X | .byte $44 ; | X X | .byte $FE ; |XXXXXXX | .byte $BA ; |X XXX X | .byte $BA ; |X XXX X | .byte $28 ; | X X | .byte $6C ; | XX XX | .byte $00 ; | | .byte $99 ; |X XX X| chess piece .byte $DB ; |XX XX XX| .byte $7E ; | XXXXXX | .byte $34 ; | XX X | .byte $2C ; | X XX | .byte $24 ; | X X | .byte $66 ; | XX XX | .byte $FF ; |XXXXXXXX| .byte $00 ; | | .byte $24 ; | X X | goblin .byte $3C ; | XXXX | .byte $DB ; |XX XX XX| .byte $7E ; | XXXXXX | .byte $7E ; | XXXXXX | .byte $3C ; | XXXX | .byte $42 ; | X X | .byte $C3 ; |XX XX| .byte $00 ; | | .byte $3C ; | XXXX | vial .byte $18 ; | XX | .byte $7E ; | XXXXXX | .byte $DF ; |XX XXXXX| .byte $FF ; |XXXXXXXX| .byte $7E ; | XXXXXX | .byte $3C ; | XXXX | .byte $00 ; | | .byte $00 ; | | .byte $00 ; | | .byte $7B ; | XXXX XX| dragon .byte $3A ; | XXX X | .byte $0A ; | X X | .byte $3E ; | XXXXX | .byte $5E ; | X XXXX | .byte $92 ; |X X X | .byte $1B ; | XX XX| .byte $00 ; | | .byte $99 ; |X XX X| crown .byte $DB ; |XX XX XX| .byte $FF ; |XXXXXXXX| .byte $AB ; |X X X XX| .byte $FF ; |XXXXXXXX| .byte $00 ; | | .byte $00 ; | | .byte $00 ; | | .byte $00 ; | | .byte $24 ; | X X | Spider .byte $A5 ; |X X X X| .byte $5A ; | X XX X | .byte $3C ; | XXXX | .byte $5A ; | X XX X | .byte $A5 ; |X X X X| .byte $24 ; | X X | .byte $00 ; | | .byte $00 ; | | .byte $00 ; | | .byte $00 ; | | .byte $07 ; | XXX| key .byte $FD ; |XXXXXX X| .byte $A7 ; |X X XXX| .byte $00 ; | | .byte $00 ; | | .byte $00 ; | | .byte $00 ; | | .byte $7C ; | XXXXX | diamond .byte $82 ; |X X | .byte $FE ; |XXXXXXX | .byte $44 ; | X X | .byte $28 ; | X X | .byte $10 ; | X | .byte $00 ; | | .byte $00 ; | | .byte $00 ; | | .byte $72 ; | XXX X | corpse .byte $93 ; |X X XX| .byte $ED ; |XXX XX X| .byte $5F ; | X XXXXX| .byte $5C ; | X XxX | .byte $7D ; | XXXXX X| .byte $56 ; | X X XX | .byte $DD ; |XX XXX X| .byte $00 ; | | charset .byte $3C ; | XXXX | $FF00 .byte $66 ; | XX XX | $FF01 .byte $66 ; | XX XX | $FF02 .byte $66 ; | XX XX | $FF03 .byte $66 ; | XX XX | $FF04 .byte $66 ; | XX XX | $FF05 .byte $66 ; | XX XX | $FF06 .byte $3C ; | XXXX | $FF07 .byte $7E ; | XXXXXX | $FF08 .byte $18 ; | XX | $FF09 .byte $18 ; | XX | $FF0A .byte $18 ; | XX | $FF0B .byte $18 ; | XX | $FF0C .byte $38 ; | XXX | $FF0D .byte $18 ; | XX | $FF0E .byte $08 ; | X | $FF0F .byte $7E ; | XXXXXX | $FF10 .byte $60 ; | XX | $FF11 .byte $60 ; | XX | $FF12 .byte $3C ; | XXXX | $FF13 .byte $06 ; | XX | $FF14 .byte $06 ; | XX | $FF15 .byte $46 ; | X XX | $FF16 .byte $3C ; | XXXX | $FF17 .byte $3C ; | XXXX | $FF18 .byte $46 ; | X XX | $FF19 .byte $06 ; | XX | $FF1A .byte $06 ; | XX | $FF1B .byte $1C ; | XXX | $FF1C .byte $06 ; | XX | $FF1D .byte $46 ; | X XX | $FF1E .byte $3C ; | XXXX | $FF1F .byte $0C ; | XX | $FF20 .byte $0C ; | XX | $FF21 .byte $7E ; | XXXXXX | $FF22 .byte $4C ; | X XX | $FF23 .byte $4C ; | X XX | $FF24 .byte $2C ; | X XX | $FF25 .byte $1C ; | XXX | $FF26 .byte $0C ; | XX | $FF27 .byte $3C ; | XXXX | $FF28 .byte $46 ; | X XX | $FF29 .byte $06 ; | XX | $FF2A .byte $06 ; | XX | $FF2B .byte $3C ; | XXXX | $FF2C .byte $60 ; | XX | $FF2D .byte $60 ; | XX | $FF2E .byte $7E ; | XXXXXX | $FF2F .byte $3C ; | XXXX | $FF30 .byte $66 ; | XX XX | $FF31 .byte $66 ; | XX XX | $FF32 .byte $66 ; | XX XX | $FF33 .byte $7C ; | XXXXX | $FF34 .byte $60 ; | XX | $FF35 .byte $62 ; | XX X | $FF36 .byte $3C ; | XXXX | $FF37 .byte $30 ; | XX | $FF38 .byte $30 ; | XX | $FF39 .byte $30 ; | XX | $FF3A .byte $18 ; | XX | $FF3B .byte $0C ; | XX | $FF3C .byte $06 ; | XX | $FF3D .byte $42 ; | X X | $FF3E .byte $3E ; | XXXXX | $FF3F .byte $3C ; | XXXX | $FF40 .byte $66 ; | XX XX | $FF41 .byte $66 ; | XX XX | $FF42 .byte $66 ; | XX XX | $FF43 .byte $3C ; | XXXX | $FF44 .byte $66 ; | XX XX | $FF45 .byte $66 ; | XX XX | $FF46 .byte $3C ; | XXXX | $FF47 .byte $3C ; | XXXX | $FF48 .byte $46 ; | X XX | $FF49 .byte $06 ; | XX | $FF4A .byte $3E ; | XXXXX | $FF4B .byte $66 ; | XX XX | $FF4C .byte $66 ; | XX XX | $FF4D .byte $66 ; | XX XX | $FF4E .byte $3C ; | XXXX | $FF4F .byte $3C ; | XXXX | $FF50 .byte $5A ; | X XX X | $FF51 .byte $FF ; |XXXXXXXX| $FF52 .byte $DB ; |XX XX XX| $FF53 .byte $66 ; | XX XX | $FF54 .byte $3C ; | XXXX | $FF55 .byte $00 ; | | $FF56 ;----------------- ; This table contains a set of values for each room in the game. When the ; kernal line counter in Y reaches each value in the table, the graphics ; index in X is incremented by 1 to change the PF graphics for the next and ; subsequent scanlines. ; ; IF ( Y = PFshapchanges[X] ), THEN X = X + 1; ; ; Changing the values in this table will distort the wall thicknesses and ; heights of the displayed room walls. ; ; If we change the kernal logic so all rooms use the same height wall sections, ; then most if not all of this ROM code be reclaimed for other uses. ; ; For some reason, the pfshapchange sequences for level 1 rooms are terminated ; with $00, but the sequences for level 2 rooms are terminated with $FF. PFshapechanges ; Room 0: (pfroomoffsets = $00) .byte $01 ; | X| .byte $0B ; | X XX| .byte $0F ; | XXXX| .byte $19 ; | XX X| .byte $1B ; | XX XX| .byte $2F ; | X XXXX| .byte $31 ; | XX X| .byte $41 ; | X X| .byte $4D ; | X XX X| .byte $00 ; | | ; Room 1: (pfroomoffsets = $0A) .byte $01 ; | X| .byte $11 ; | X X| .byte $13 ; | X XX| .byte $29 ; | X X X| .byte $2B ; | X X XX| .byte $3D ; | XXXX X| .byte $41 ; | X X| .byte $4D ; | X XX X| .byte $00 ; | | ; Room 2: (pfroomoffsets = $13) .byte $01 ; | X| .byte $0D ; | XX X| .byte $11 ; | X X| .byte $1D ; | XXX X| .byte $21 ; | X X| .byte $23 ; | X XX| .byte $4D ; | X XX X| .byte $00 ; | | ; Room 8: (pfroomoffsets = $1B) .byte $03 ; | XX| .byte $05 ; | X X| .byte $07 ; | XXX| .byte $09 ; | X X| .byte $0B ; | X XX| .byte $0D ; | XX X| .byte $0F ; | XXXX| .byte $11 ; | X X| .byte $13 ; | X XX| .byte $1B ; | XX XX| .byte $1D ; | XXX X| .byte $1F ; | XXXXX| .byte $23 ; | X XX| .byte $25 ; | X X X| .byte $27 ; | X XXX| .byte $2B ; | X X XX| .byte $2D ; | X XX X| .byte $2F ; | X XXXX| .byte $31 ; | XX X| .byte $37 ; | XX XXX| .byte $39 ; | XXX X| .byte $3B ; | XXX XX| .byte $3D ; | XXXX X| .byte $3F ; | XXXXXX| .byte $43 ; | X XX| .byte $45 ; | X X X| .byte $47 ; | X XXX| .byte $00 ; | | ; Room 9: (pfroomoffsets = $37) .byte $03 ; | XX| .byte $05 ; | X X| .byte $07 ; | XXX| .byte $09 ; | X X| .byte $0B ; | X XX| .byte $0D ; | XX X| .byte $0F ; | XXXX| .byte $11 ; | X X| .byte $13 ; | X XX| .byte $15 ; | X X X| .byte $17 ; | X XXX| .byte $19 ; | XX X| .byte $1B ; | XX XX| .byte $1D ; | XXX X| .byte $1F ; | XXXXX| .byte $21 ; | X X| .byte $25 ; | X X X| .byte $29 ; | X X X| .byte $2B ; | X X XX| .byte $2F ; | X XXXX| .byte $31 ; | XX X| .byte $33 ; | XX XX| .byte $35 ; | XX X X| .byte $37 ; | XX XXX| .byte $39 ; | XXX X| .byte $3B ; | XXX XX| .byte $3D ; | XXXX X| .byte $3F ; | XXXXXX| .byte $41 ; | X X| .byte $43 ; | X XX| .byte $45 ; | X X X| .byte $47 ; | X XXX| .byte $49 ; | X X X| .byte $FF ; |XXXXXXXX| ; Room 3: (pfroomoffsets = $59) .byte $01 .byte $0D .byte $1B .byte $1F .byte $21 .byte $25 .byte $29 .byte $2D .byte $2F .byte $33 .byte $41 .byte $4D .byte $00 ; Room 4: (pfroomoffsets = $66) .byte $01 .byte $09 .byte $0D .byte $17 .byte $19 .byte $21 .byte $23 .byte $2B .byte $2D .byte $4D .byte $FF ; Room 5: (pfroomoffsets = $71) .byte $01 .byte $0C .byte $15 .byte $1F .byte $21 .byte $37 .byte $4D .byte $FF ; Room 6: (pfroomoffsets = $79) .byte $01 .byte $1B .byte $1D .byte $33 .byte $35 .byte $4D .byte $FF ; Room 7: (pfroomoffsets = $80) .byte $01 .byte $09 .byte $0B .byte $11 .byte $13 .byte $1B .byte $1D .byte $25 .byte $29 .byte $31 .byte $33 .byte $3B .byte $3D .byte $43 .byte $45 .byte $4D .byte $FF ballToPixelMaskTable ; This table is to check for ball collisions with individual pixels of monster sprites. .byte $C0,$60,$30,$18,$0C,$06,$03,$03 ; Offsets for PF shapes for the 10 different rooms. It is also the same offset used to ; index the pfshapechages table that defines the vertical height of each section of wall ; within a room. ; From InitGame we know that rooms with offsets >= $59 are mirrored rooms. pfroomoffsets .byte $00,$0A,$13,$59,$66,$71,$79,$80,$1B,$37 .word Start .word Start .word Start