LIST OFF ; *** Y A R ' S R E V E N G E *** ; Copyright 1982 Atari, Inc. ; Designer: Howard Scott Warshaw ; Analyzed, labeled and commented ; by Dennis Debro ; Last Update: Sept. 21, 2005 ; ; - This was HSW first game published by Atari. ; - The shield flickers. It's sort of hard to tell but it's drawn ~ every ; other frame. The neutral zone is shown on the alternate frames. ; - This game uses a lot of state flags. ; - It seems RAM locations $A1, $B9 - $BC, $F3 - $FD are not used (17 bytes ; free). RAM locations $FE - $FF are used to RTS to the kernel that should ; be used for display. ; - The Qotile missile is the only object that has it's game speeds adjusted ; for PAL. ; - The game colors and kernel heights are adjusted for PAL. processor 6502 ; ; NOTE: You must compile this with vcs.h version 105 or greater. ; TIA_BASE_READ_ADDRESS = $30 ; set the read address base so this runs on ; the real VCS and compiles to the exact ; ROM image include vcs.h include macro.h ;============================================================================== ; T I A - C O N S T A N T S ;============================================================================== HMOVE_L7 = $70 HMOVE_L6 = $60 HMOVE_L5 = $50 HMOVE_L4 = $40 HMOVE_L3 = $30 HMOVE_L2 = $20 HMOVE_L1 = $10 HMOVE_0 = $00 HMOVE_R1 = $F0 HMOVE_R2 = $E0 HMOVE_R3 = $D0 HMOVE_R4 = $C0 HMOVE_R5 = $B0 HMOVE_R6 = $A0 HMOVE_R7 = $90 HMOVE_R8 = $80 ; values for ENAMx and ENABL DISABLE_BM = %00 ENABLE_BM = %10 ; values for RESMPx LOCK_MISSILE = %10 UNLOCK_MISSILE = %00 ; values for REFPx: NO_REFLECT = %0000 REFLECT = %1000 ; values for NUSIZx: ONE_COPY = %000 TWO_COPIES = %001 TWO_MED_COPIES = %010 THREE_COPIES = %011 TWO_WIDE_COPIES = %100 DOUBLE_SIZE = %101 THREE_MED_COPIES = %110 QUAD_SIZE = %111 MSBL_SIZE1 = %000000 MSBL_SIZE2 = %010000 MSBL_SIZE4 = %100000 MSBL_SIZE8 = %110000 ; values for CTRLPF: PF_PRIORITY = %100 PF_REFLECT = %01 PF_NO_REFLECT = %00 ; values for SWCHB P1_DIFF_MASK = %10000000 P0_DIFF_MASK = %01000000 BW_MASK = %00001000 SELECT_MASK = %00000010 RESET_MASK = %00000001 VERTICAL_DELAY = 1 ; SWCHA joystick bits: MOVE_RIGHT = %01111111 MOVE_LEFT = %10111111 MOVE_DOWN = %11011111 MOVE_UP = %11101111 NO_MOVE = %11111111 LIST ON ;============================================================================ ; A S S E M B L E R - S W I T C H E S ;============================================================================ NTSC = 0 PAL = 1 COMPILE_VERSION = NTSC ; change this to compile for different ; regions ;============================================================================ ; U S E R - C O N S T A N T S ;============================================================================ ROMTOP = $F000 NTSC_OVERSCAN_TIME = 34 NTSC_VBLANK_TIME = 42 PAL_VBLANK_TIME = 54 PAL_OVERSCAN_TIME = 43 IF COMPILE_VERSION = NTSC VBLANK_TIME = NTSC_VBLANK_TIME OVERSCAN_TIME = NTSC_OVERSCAN_TIME H_KERNEL = 192 YAR_YMIN = 4 YAR_YMAX = 174 SHIELD_YMAX = 63 YAR_XMIN = 5 NUM_TOP_BLANK_SCANLINES = 39 NUM_BOTTOM_SCANLINES = 113 LINES_BETWEEN_SCORES = 35 BOTTOM_STATUS_LINES = 41 ; frame delay values FRAME_DELAY_0 = 255 ; move every frame FRAME_DELAY_HALF = 127 ; move 1 out of 2 frames FRAME_DELAY_THIRD = 85 ; move 1 out of 3 frames FRAME_DELAY_FOURTH = 63 ; move 1 out of 4 frames FRAME_DELAY_FIFTH = 51 ; move 1 out of 5 frames ; NTSC color constants BLACK = $00 WHITE = $0E RED = $30 ORANGE = $40 DK_PINK = $50 DK_BLUE = $70 BLUE = $80 DK_GREEN = $D0 BROWN = $F0 ELSE VBLANK_TIME = PAL_VBLANK_TIME OVERSCAN_TIME = PAL_OVERSCAN_TIME H_KERNEL = 227 YAR_YMIN = 6 YAR_YMAX = 206 SHIELD_YMAX = 99 YAR_XMIN = 6 NUM_TOP_BLANK_SCANLINES = 48 NUM_BOTTOM_SCANLINES = 139 LINES_BETWEEN_SCORES = 51 BOTTOM_STATUS_LINES = 51 ; frame delay values FRAME_DELAY_0 = 255 ; move every frame FRAME_DELAY_HALF = 148 ; move 1 out of 2 frames FRAME_DELAY_THIRD = 99 ; move 1 out of 3 frames FRAME_DELAY_FOURTH = 74 ; move 1 out of 4 frames FRAME_DELAY_FIFTH = 60 ; move 1 out of 5 frames ; PAL color constants BLACK = $00 WHITE = $0E BROWN = $20 RED = $40 DK_PINK = $60 BLUE = $90 DK_BLUE = BLUE DK_GREEN = $E0 ENDIF ZORLON_CANNON_XMIN = 6 XMAX = 160 SHIELD_YMIN = 2 H_FONT = 7 H_YAR = 9 H_SWIRL = 9 H_QOTILE = 10 H_SHIELD_CELL = 7 NUM_SHIELD_BYTES = 15 ; number of bytes used for the shield INIT_NUM_LIVES = 4 MAX_LIVES = 9 NUM_DEATH_SPRITES = 5 SELECT_DELAY = 31 INIT_QOTILE_X = 150 INIT_YAR_Y = 100 NEUTRAL_ZONE_MASK = %11111110 ; point values (BCD) DESTROY_CELL_POINT_VALUE = $69 DESTROY_QOTILE_POINT_VALUE = $10 ; game state values GAME_OVER = %10000000 SHOW_SELECT_SCREEN = %01000000 SWIRL_TRIPLE_FREQ = %00100000 SWIRL_SEEK_YAR = %00010000 SHOW_COPYRIGHT = %00001000 GAME_SELECTION_MASK = %00000111 ULTIMATE_YARS_GAME = 6 ; Qotile status values SWIRL_ACTIVE = %10000000 SWIRL_TRAVELING = %01000000 SUPPRESS_YAR_SHOT = %00100000 SOLID_SHIELD = %00010000 ; playfield control status values QOTILE_MISSILE_ACTIVE = %10000000 YAR_HIT_PLAYFIELD = %01000000 ; Zorlon Cannon status values CANNON_NOT_MOVING = %10000000 ZORLON_CANNON_ACTIVE = %01000000 ZORLON_CANNON_FIRED = %00100000 YAR_MISSILE_FIRED = %00010000 ; game board status values ZORLON_CANNON_BOUNCED = %10000000 TWO_PLAYER_GAME = %01000000 SWIRL_VERT_LOCK = %00100000 SWIRL_HORIZ_LOCK = %00010000 RESTART_LEVEL = %00001000 ONE_PLAYER_GAME = %00000100 PLAYER_TWO_SHIELD_RESTORED = %00000010 ACTIVE_PLAYER_MASK = %00000001 ; Yar status values LOSING_LIFE = %10000000 IMPLODING_ANIMATION = %01000000 EXPLODING_ANIMATION = %00100000 ; kernel status values YAR_FLAP_UP = %01000000 RESET_QOTILE_POSITION = %00100000 RESET_YAR_POSITION = %00010000 SHIELD_TRAVEL_UP = %00001000 KERNEL_ID_MASK = %00000111 ; explosion status values EXPLOSION_ACTIVE = %10000000 REDUCE_EXPLOSION_ZONE = %00100000 EXPLOSION_TIMER = %00011111 ; Yar sound flag values MISSILE_HIT_SHIELD_SND = %10000000 YAR_BOUNCE_SOUND = %01000000 EATING_SHIELD_SOUND = %00000001 ID_SHIELD_KERNEL = 0 ID_NEUTRAL_ZONE_KERNEL = 2 ID_EXPLOSION_KERNEL = 4 ID_STATUS_KERNEL = 6 ;============================================================================ ; Z P - V A R I A B L E S ;============================================================================ gameState = $80 currentShieldGraphics = $81 ; $81 - $90 neutralZoneMask = $91 qotileMissileSpeedIndex = $92 yarColor = $93 qotileStatus = $94 kernelStatus = $95 zorlonCannonStatus = $96 yarStatus = $97 zorlonCannonVertPos = $98 zorlonCannonHorizPos = $99 shieldVertPos = $9A shieldSectionHeight = $9B shieldGraphicIndex = $9C yarGraphicIndex = $9D lives = $9E yarVertPos = $9F yarHorizPos = $A0 yarMoving = $A2 ; 0 = Yar not moving yarGraphicPtrs = $A3 ; $A3 - $A4 yarMissileVertPos = $A5 yarMissileHorizPos = $A6 yarSoundFlags = $A7 explosionStatus = $A8 qotileGraphicIndex = $A9 qotileVertPos = $AA qotileHorizPos = $AB qotileGraphicPtrs = $AC ; $AC - $AD qotileMissileVertPos = $AE qotileMissileHorizPos = $AF travelingSwirlSound = $B0 explosionFrequency = $B1 tempCharHolder = $B2 ;-------------------------------------- yarRotationValue = tempCharHolder ;-------------------------------------- saveY = yarRotationValue ;-------------------------------------- dividedBy8 = saveY tempHorizPos = $B3 tempVertPos = $B4 playfieldControlStatus = $B5 qotileMissileFrameDelay = $B6 statusLoopCount = $B7 ;-------------------------------------- explosionUpperLimit = statusLoopCount explosionLowerLimit = $B8 graphicPointers = $BD ; $BD - $C8 tempShieldGraphics = $C9 ; $C9 - $D8 reserveNeutralZoneMask = $D9 reserveQotileMissileIdx = $DA reservedYarColor = $DB reservedQotileStatus = $DC attractModeColors = $DD swirlMissedYar = $DE yarLeftBounces = $DF playerScores = $E0 ; $E0 - $E7 ;-------------------------------------- player1Score = playerScores player2Score = player1Score+4 qotileColor = $E8 gameTimer = $E9 loopCount = $EA ;-------------------------------------- swirlMotion = loopCount ;-------------------------------------- joystickValue = swirlMotion ;-------------------------------------- saveX = joystickValue ;-------------------------------------- neutralZonePtr = gameTimer ; $E9 - $EA ;-------------------------------------- explosionGraphicPtr = neutralZonePtr gameBoardStatus = $EB scoreDataPtr = $EC ; $EC - $ED $ED always 0 -- used for kernel playerScorePtr = $EE ; $EE - $EF $EF always 0 trons = $F0 easterEggTrigger = $F1 colorMask = $F2 ;============================================================================ ; R O M - C O D E (Part 1) ;============================================================================ SEG Bank0 org ROMTOP ShieldKernel SUBROUTINE inx ; 2 increment scan line ldy yarGraphicIndex ; 3 cpx yarVertPos ; 3 sta WSYNC ;-------------------------------------- bcc .drawYarSprite ; 2³ dey ; 2 beq .drawYarSprite ; 2³ sty yarGraphicIndex ; 3 .drawYarSprite lda (yarGraphicPtrs),y ; 5 sta GRP1 ; 3 = @17 lda #0 ; 2 sta PF2 ; 3 = @22 cpx shieldVertPos ; 3 bit shieldVertPos ; 3 bcs .prepareShieldDraw ; 2³ bpl SkipShieldDraw ; 2 .prepareShieldDraw ldy shieldSectionHeight ; 3 dey ; 2 bpl .skipShieldIdxReduction; 2³ ldy #H_SHIELD_CELL ; 2 dec shieldGraphicIndex ; 5 .skipShieldIdxReduction sty shieldSectionHeight ; 3 ldy shieldGraphicIndex ; 3 bmi SkipShieldDraw ; 2³ lda currentShieldGraphics,y; 4 sta PF2 ; 3 = @61 SkipShieldDraw inx ; 2 lda #0 ; 2 ldy qotileGraphicIndex ; 3 cpx qotileVertPos ; 3 sta WSYNC ;-------------------------------------- sta PF2 ; 3 = @03 bcc .drawQotileSprite ; 2³ dey ; 2 beq .drawQotileSprite ; 2³ sty qotileGraphicIndex ; 3 .drawQotileSprite lda (qotileGraphicPtrs),y ; 5 sta GRP0 ; 3 = @20 cpx shieldVertPos ; 3 bcc .skipShieldDraw2 ; 2³ ldy shieldSectionHeight ; 3 dey ; 2 bmi .prepareShieldDraw2 ; 2³ nop ; 2 lda #0 ; 2 beq .skipShieldIdxReduction2; 3 unconditional branch .prepareShieldDraw2 ldy #H_SHIELD_CELL ; 2 dec shieldGraphicIndex ; 5 .skipShieldIdxReduction2 sty shieldSectionHeight ; 3 ldy shieldGraphicIndex ; 3 bmi .skipShieldDraw2 ; 2³ lda currentShieldGraphics,y; 4 sta PF2 ; 3 .skipShieldDraw2 cpx #H_KERNEL ; 2 bcc ShieldKernel ; 2³ jmp Overscan ; 3 NeutralZoneKernel SUBROUTINE inx ; 2 ldy yarGraphicIndex ; 3 cpx yarVertPos ; 3 sta WSYNC ;-------------------------------------- bcc .drawYarSprite ; 2³ dey ; 2 beq .drawYarSprite ; 2³ sty yarGraphicIndex ; 3 .drawYarSprite lda (yarGraphicPtrs),y ; 5 sta GRP1 ; 3 = @17 txa ; 2 ldx #ENABL ; 2 txs ; 2 point stack to ENABL tax ; 2 tay ; 2 eor (neutralZonePtr),y ; 5 and neutralZoneMask ; 3 sta PF2 ; 3 = @38 and #$F7 ; 2 sta COLUPF ; 3 = @43 ldy #0 ; 2 txa ; 2 sbc zorlonCannonVertPos ; 3 bmi .drawZorlonCannon ; 2³ cmp #6 ; 2 bcs .drawZorlonCannon ; 2³ tya ; 2 .drawZorlonCannon php ; 3 = @61 enable/disable ball sty PF2 ; 3 = @64 PrepareNextNeutralZoneLine inx ; 2 ldy qotileGraphicIndex ; 3 cpx qotileVertPos ; 3 sta WSYNC ;-------------------------------------- bcc .drawQotileSprite ; 2³ dey ; 2 beq .drawQotileSprite ; 2³ sty qotileGraphicIndex ; 3 .drawQotileSprite lda (qotileGraphicPtrs),y ; 5 sta GRP0 ; 3 = @17 txa ; 2 eor #$FF ; 2 tay ; 2 eor (neutralZonePtr),y ; 5 and neutralZoneMask ; 3 beq .skipNeutralZoneDraw ; 2³ sta PF2 ; 3 = @36 jmp .drawMissiles ; 3 .skipNeutralZoneDraw lda #$5A ; 2 sta COLUPF ; 3 = @39 .drawMissiles cpx yarMissileVertPos ; 3 php ; 3 = @45 enable/disable missile 1 cpx qotileMissileVertPos ; 3 php ; 3 = @51 enable/disable missile 0 lda #0 ; 2 sta PF2 ; 3 = @56 cpx #H_KERNEL ; 2 bcc NeutralZoneKernel ; 2³ ldx #$FF ; 2 txs ; 2 restore stack to point to beginning jmp Overscan ; 3 ExplosionKernel SUBROUTINE sta WSYNC ;-------------------------------------- inx ; 2 increment scan line lda #1 ; 2 sta GRP0 ; 3 = @07 sta COLUP0 ; 3 = @10 cpx explosionUpperLimit ; 3 bcc .skipPlayfieldDraw ; 2³ cpx explosionLowerLimit ; 3 bcc .drawExplosion ; 2³ .skipPlayfieldDraw lda #0 ; 2 sta PF0 ; 3 = @25 sta PF1 ; 3 = @28 sta PF2 ; 3 = @31 sta COLUBK ; 3 = @34 beq PrepareToDrawYar ; 3+1 unconditional branch .drawExplosion txa ; 2 tay ; 2 lda (explosionGraphicPtr),y; 5 sta PF0 ; 3 = @33 sta PF1 ; 3 = @36 sta PF2 ; 3 = @39 sta COLUPF ; 3 = @42 lda gameTimer ; 3 sta COLUBK ; 3 = @48 PrepareToDrawYar inx ; 2 increment scan line ldy yarGraphicIndex ; 3 cpx yarVertPos ; 3 sta WSYNC ;-------------------------------------- bcc .drawYar ; 2³ dey ; 2 beq .drawYar ; 2³ sty yarGraphicIndex ; 3 .drawYar lda (yarGraphicPtrs),y ; 5 sta GRP1 ; 3 = @17 cpx #H_KERNEL ; 2 bne ExplosionKernel ; 2³+1 jmp Overscan ; 3 StatusKernel ldx #6 ; 2 sta WSYNC ;-------------------------------------- .coarseMovePlayers dex ; 2 bpl .coarseMovePlayers ; 2³ nop ; 2 sta RESP0 ; 3 = @39 coarse move GRP0 to pixel 117 sta RESP1 ; 3 = @42 coarse move GRP1 to pixel 126 lda #HMOVE_R1 ; 2 sta HMP0 ; 3 = @47 place GRP0 to pixel 118 lda gameTimer ; 3 bne .skipColorMaskSet ; 2³ branch if cycling colors lda #$F7 ; 2 sta colorMask ; 3 .skipColorMaskSet sta WSYNC ;-------------------------------------- sta HMOVE ; 3 lda #NO_REFLECT ; 2 sta REFP0 ; 3 = @08 sta REFP1 ; 3 = @11 sta statusLoopCount ; 3 lda #DK_BLUE+12 ; 2 and colorMask ; 3 sta COLUP0 ; 3 = @22 sta COLUP1 ; 3 = @25 ldx #10 ; 2 lda #>NumberFonts ; 2 .setGraphicPointersMSB sta graphicPointers+1,x ; 4 dex ; 2 dex ; 2 bpl .setGraphicPointersMSB ; 2³ lda #Qotile sta qotileGraphicPtrs+1 lda #NEUTRAL_ZONE_MASK sta neutralZoneMask sta reserveNeutralZoneMask ldx #4 ; assume Qotile missile moves 1/5 of time jsr CheckGameDifficultyValue beq .setQotileMissileSpeed ; branch if playing Ultimate Yars dex ; reduce so missile moves 1/4 of the time .setQotileMissileSpeed stx qotileMissileSpeedIndex ; set Qotile missile speed index stx reserveQotileMissileIdx ; set reserved missile speed index jmp RestoreShieldGraphics CheckToRotateSolidShield lda #SOLID_SHIELD bit qotileStatus ; check Qotile status beq .skipShieldRotation ; branch if not showing solid shield lda #3 and gameTimer ; see if game timer value divisible by 4 bne .skipShieldRotation ; branch if not time to rotate shield ldx #NUM_SHIELD_BYTES-1 clc .rotateSolidShield rol currentShieldGraphics+1,x ror currentShieldGraphics,x dex dex bpl .rotateSolidShield lda currentShieldGraphics+NUM_SHIELD_BYTES adc #0 sta currentShieldGraphics+NUM_SHIELD_BYTES .skipShieldRotation bit gameState bvs .turnOffSounds ; branch if showing select screen bpl PlayAudioChannel0Sounds ; branch if game in progress .turnOffSounds lda #0 sta AUDV0 sta AUDV1 jmp .donePlayingAudioSounds PlayAudioChannel0Sounds bit explosionStatus ; check explosion status bmi CheckToReduceExplosionZone ; branch if explosion kernel taking place lda yarSoundFlags ; get Yar sound flag value ror ; shift D0 to carry bcc .playYarBuzzingSound lda #3 sta AUDF0 lda #10 sta AUDV0 bne .jmpToSetAudioChannel0 ; unconditional branch .playYarBuzzingSound bit qotileStatus bmi PlaySwirlSounds ; branch if Swirl active lda #14 sta AUDC0 and qotileColor sta AUDV0 lda #7 sta AUDF0 bne PlayAudioChannel1Sounds ; unconditional branch PlaySwirlSounds bvc .playSwirlActiveSound ; branch if Swirl not traveling lda gameTimer ; get current game timer value ror ; shift D0 into carry bcs PlayAudioChannel1Sounds ; branch if this is an odd frame ldx travelingSwirlSound beq PlayAudioChannel1Sounds inx stx AUDF0 stx travelingSwirlSound lda #12 sta AUDV0 lda #8 .jmpToSetAudioChannel0 bne .setAudioChannel0 ; unconditional branch .playSwirlActiveSound lda gameTimer ; get current game timer value sta AUDF0 lda #5 sta AUDV0 bne .setAudioChannel0 ; unconditional branch CheckToReduceExplosionZone lda explosionStatus ; get the explosion status and #REDUCE_EXPLOSION_ZONE ; mask to see if time to reduce zone beq .skipReduceExplosionZone ; branch if not time to shrink zone lda gameTimer ; get game timer value ror ; shift D0 into carry bcc PlayAudioChannel1Sounds ; branch if this is an even frame lda gameTimer ; get game timer value sta AUDF0 ; set audio freq for explosion lda #8 sta AUDC0 ; set audio channel for explosion lda explosionLowerLimit ; get lower limit for explosion graphics lsr ; divide the value by 4 lsr sta AUDV0 ; to set volume of explosion inc explosionUpperLimit ; gradually shink the explosion zone dec explosionLowerLimit ldx explosionUpperLimit ; check to see if the explosion sequence cpx explosionLowerLimit ; is done bcc PlayAudioChannel1Sounds lda kernelStatus ora #RESET_QOTILE_POSITION | RESET_YAR_POSITION | ID_STATUS_KERNEL sta kernelStatus ldy #0 bit CXPPMM ; check player/missile collisions bpl .dontShowEasterEgg ; branch if the players didn't collide lda #32 bit yarVertPos ; don't show Easter Egg if Yar's vertical bpl .dontShowEasterEgg ; position is less than 128 bne .dontShowEasterEgg lda easterEggTrigger ; get Easter Egg trigger value beq .dontShowEasterEgg ; branch if Easter Egg not found jmp .jmpIntoVerticalSync .dontShowEasterEgg sty easterEggTrigger ; reset Easter Egg trigger (i.e. y = 0) sty explosionStatus ; clear explosion status jmp RestoreShieldGraphics .skipReduceExplosionZone lda gameTimer ; get current game timer asl ; multiply the value by 4 asl sta AUDF0 ; set the frequency for explosion sound lda #13 sta AUDV0 ; set volume for explosion sound .setAudioChannel0 sta AUDC0 PlayAudioChannel1Sounds bit yarStatus ; check Yar's status bmi PlayYarDeathSounds ; branch if Yar losing life bit yarSoundFlags ; check Yar sound flags bmi .playShieldCellDestroyedSound; branch if missile hit shield bvc CheckToPlayCannonSound ; branch if not playing Yar "bounce" sound lda #4 sta AUDV1 sta AUDF1 bne .setAudioChannel1 ; unconditional branch .playShieldCellDestroyedSound lda #12 sta AUDV1 sta AUDF1 bne .setAudioChannel1 ; unconditional branch CheckToPlayCannonSound lda #ZORLON_CANNON_FIRED bit zorlonCannonStatus beq PlayYarMovingSound ; branch if Zorlon Cannon not fired lda zorlonCannonHorizPos ; set volume based on Zorlon Cannon sta AUDV1 ; horizontal position lda #13 sta AUDF1 bne .setAudioChannel1 ; unconditional branch PlayYarMovingSound ldy #12 ; default frequency for non moving Yar ldx yarMoving ; see if Yar is moving beq .checkToPlayNeutralZoneSound ; branch if Yar not moving ldy #10 ; frequency for moving Yar .checkToPlayNeutralZoneSound bit playfieldControlStatus bvc .setFrequencyForYar ; branch if Yar not hitting playfield ldy #4 ; frequency for Yar in neutral zone .setFrequencyForYar sty AUDF1 lda #3 sta AUDV1 bne .setAudioChannel1 ; unconditional branch PlayYarDeathSounds bvs .playYarExplodingSound ; branch if Yar is exploding lda #13 sta AUDV1 lda gameTimer ; get current game timer value ora #$1C sta AUDF1 lda #5 bne .setAudioChannel1 ; unconditional branch .playYarExplodingSound lda #EXPLODING_ANIMATION bit yarStatus ; check Yar's status bne .setExplosionFrequency lda #$0F bit gameTimer beq .setImplosionFrequency lda #5 .setImplosionFrequency sta AUDV1 lda #3 sta AUDF1 lda #13 bne .setAudioChannel1 ; unconditional branch .setExplosionFrequency lda gameTimer ; get the current game timer value ora #$0C sta AUDV1 ; set volume for explosion ldx explosionFrequency ; get explosion frequency value inx ; increment frequency value stx AUDF1 ; set explosion frequency stx explosionFrequency lda #8 .setAudioChannel1 sta AUDC1 .donePlayingAudioSounds bit yarStatus ; check Yar's status bmi DoYarDeathAnimation ; branch if Yar is losing life jmp VerticalSync DoYarDeathAnimation lda #$FF sta zorlonCannonVertPos sta colorMask lda #~(CANNON_NOT_MOVING | ZORLON_CANNON_ACTIVE | ZORLON_CANNON_FIRED) sta RESMP1 ; lock Yar missile to Yar and zorlonCannonStatus ora #CANNON_NOT_MOVING sta zorlonCannonStatus lda #LOCK_MISSILE sta RESMP0 ; lock Qotile missile to Qotile lda #HMOVE_0 sta yarMoving ; set to Yar not moving sta HMP1 ; do not move Yar sta yarLeftBounces ; clear left bounce number bvc VerticalSync ; branch if not showing implosion animation lda yarStatus and #EXPLODING_ANIMATION bne CheckToDoYarExplosionAnimation lda gameTimer and #$0F beq .doYarExplosionAnimation bne VerticalSync CheckToDoYarExplosionAnimation lda gameTimer ; get current game timer and #3 ; update Yar death sprites every 4 frames bne VerticalSync .doYarExplosionAnimation lda yarGraphicPtrs ; get current LSB for Yar graphic pointer clc adc #H_YAR ; increment value by Yar height to get sta yarGraphicPtrs ; next animation frame cmp #YarSprites sta yarGraphicPtrs+1 lda kernelStatus and #~KERNEL_ID_MASK ora #YAR_FLAP_UP | ID_STATUS_KERNEL sta kernelStatus lda #1 sta gameTimer lda #14 sta explosionFrequency VerticalSync .waitTime lda INTIM bne .waitTime sta WSYNC ; wait for next scan line sta CXCLR ; clear collision register lda #0 sta yarSoundFlags ; clear sound flags each frame sta swirlMissedYar ; reset value for this frame lda #RESET_YAR_POSITION bit kernelStatus beq StartNewFrame ; branch if not resetting Yar's position lda #INIT_YAR_Y sta yarVertPos lda #~YAR_HIT_PLAYFIELD and playfieldControlStatus ; clear the Yar hit playfield status sta playfieldControlStatus StartNewFrame lda #%00000010 sta WSYNC ; wait for next scan line sta VSYNC ; start vertical sync (D1 = 1) sta VBLANK ; disable TIA (D1 = 1) lda #RESET_YAR_POSITION bit kernelStatus beq .skipSetYarFineMotion ; branch if not resetting Yar's position lda #HMOVE_L1 sta HMP1 lda #5 sta yarHorizPos .skipSetYarFineMotion bit zorlonCannonStatus ; check Zorlon Cannon status bpl .skipSetCannonFineMotion ; branch if Zorlon Cannon in flight lda #HMOVE_0 sta HMBL lda #1 sta zorlonCannonHorizPos .skipSetCannonFineMotion lda #RESET_QOTILE_POSITION and kernelStatus beq .coarseMoveZorlonCannon ; branch if not resetting Qotile position lda shieldVertPos ; get the shield's vertical position clc adc #53 ; add in Qotile offset from the shield sta qotileVertPos ; set Qotile vertical position lda #INIT_QOTILE_X sta qotileHorizPos ; set Qotile horizontal position IF COMPILE_VERSION = NTSC lda #>Qotile ; set Qotile graphic pointers to point to sta qotileGraphicPtrs+1 ; Qotile sprite ENDIF lda #YarDeathSprites sta yarGraphicPtrs+1 bne .jmpToSetYarAnimationState ; unconditional branch .rotateYarForDeathAnimation lda #~NO_MOVE sta joystickValue ; set joystick value to not moving beq RotateYarForDeath ; unconditional branch CheckForJoystickMovement bit gameState bmi .jmpToSetYarAnimationState ; branch if game not in progress bvs .jmpToSetYarAnimationState ; branch is showing select screen lda SWCHA ; read joystick values eor #$FF ; flip the bits tax ; move joystick values to x bne .determineRotationValue ; branch if player moving joystick sta joystickValue .jmpDetermineToFireYarMissile jmp DetermineToFireYarMissile .determineRotationValue lda gameBoardStatus ; get current game board status ror ; shift active player flag to carry txa ; move joystick values to accumulator bcs .maskJoystickValues ; branch if player2 is active lsr ; shift player 1 joystick values to lower lsr ; nybbles lsr lsr .maskJoystickValues and #$0F sta joystickValue beq .jmpDetermineToFireYarMissile; branch if joystick not moved tax ; move joystick values to x lda YarRotationIndexTable,x ; get Yar rotation index tay ; move rotation index to y jmp SetYarRotationAnimation ; could used bpl to save a byte RotateYarForDeath lda gameTimer ; get current game timer and #3 ; see if value divisible by 4 bne DetermineToFireYarMissile lda lives ; get lives/Yar rotation values and #$0F ; mask to keep rotation values tay ; move rotation value to y dey dey SetYarRotationAnimation tya and #$0F sta yarRotationValue lsr tay lda lives ; get number of lives and #$F0 ; mask out old rotation value ora yarRotationValue ; or in new rotation value sta lives ; and set them lda YarRotationPointers,y clc adc #YarSprites adc #0 sta yarGraphicPtrs+1 lda kernelStatus ora #YAR_FLAP_UP sta kernelStatus tya asl IF COMPILE_VERSION = NTSC and #8 ENDIF eor #8 sta REFP1 DetermineToFireYarMissile ldx #0 lda joystickValue ; get current joystick value beq .setYarMovingState ; branch if joystick not moved ldx #3 ; any non-zero value would do .setYarMovingState stx yarMoving ; set joystick not moved lda #YAR_MISSILE_FIRED bit zorlonCannonStatus bne UpdateYarMissilePosition ; branch if missile fired lda yarHorizPos ; get Yar's horizontal position clc adc #4 ; add in offset to set missile new sta yarMissileHorizPos ; horizontal position jmp UpdateYarPosition UpdateYarMissilePosition lda zorlonCannonStatus ; get Zorlon Cannon status and #$0F ; mask upper nybbles tay ; move to y to look up movement value lda ObjectMotionTable,y sta HMM1 ; set fine motion of Yar's missile and #$0F ; mask upper nybbles cmp #8 bmi .changeYarMissileVertPos ora #$F0 ; make the value negative (i.e move up) .changeYarMissileVertPos clc adc yarMissileVertPos tax ; move missile vertical position to x beq .turnOffYarMissile IF COMPILE_VERSION = NTSC cpx #H_KERNEL ELSE cpx #H_KERNEL + 1 ENDIF bcc .setYarMissileVerticalPosition .turnOffYarMissile lda #~YAR_MISSILE_FIRED sta RESMP1 and zorlonCannonStatus sta zorlonCannonStatus sec bcs UpdateYarPosition ; unconditional branch .setYarMissileVerticalPosition stx yarMissileVertPos lda ObjectMotionTable,y jsr DetermineHorizontalOffset adc yarMissileHorizPos tax beq .turnOffYarMissile cpx #XMAX+1 bcs .turnOffYarMissile stx yarMissileHorizPos UpdateYarPosition lda gameTimer ; get current game timer ror ; shift D0 into carry bcc SetYarAnimationState ; branch if this is an even frame ldx yarMoving ; see if joystick was moved beq SetYarAnimationState ; branch if Yar not moving lda lives ; get lives/Yar rotation values and #$0F ; keep rotation values tay ; move rotation values to y lda ObjectMotionTable,y sta tempHorizPos sta HMP1 ; set Yar fine motion and #$0F ; mask to get vertical motion value cmp #8 bmi .updateYarVerticalPosition ora #$F0 ; negate value so Yar moves down .updateYarVerticalPosition clc adc yarVertPos tax ; move Yar's vertical position to x cpx #YAR_YMIN ; see if Yar has reached the top of screen bcs .checkToWrapYarToTop ; branch if not at top of screen ldx #YAR_YMAX ; wrap Yar to bottom of the screen bne .setYarVerticalPosition ; unconditional branch .checkToWrapYarToTop cpx #YAR_YMAX ; see if Yar reached the bottom of screen bcc .setYarVerticalPosition ; branch if not at the bottom ldx #YAR_YMIN ; wrap Yar to top of the screen .setYarVerticalPosition stx yarVertPos IF COMPILE_VERSION = NTSC stx VDELP1 ; VDEL Yar if on an odd scan line ELSE txa ; move Yar vertical position to accumulator eor #1 ; flip D0 sta VDELP1 ; to set vertical delay bit ENDIF lda tempHorizPos jsr DetermineHorizontalOffset adc yarHorizPos tax ; move Yar horizontal position to x cpx #XMAX - 8 ; see if Yar at the right screen border bcc .checkYarLeftBorderBoundary .dontMoveYar lda #HMOVE_0 sta HMP1 beq SetYarAnimationState ; unconditional branch .checkYarLeftBorderBoundary cpx #YAR_XMIN ; compare with horizontal min bcs .setYarHorizontalPosition ; branch if Yar not at left screen border lda #4 cmp trons ; see if Yar has more than 3 trons bcs .dontMoveYar inc yarLeftBounces ; increment times Yar bounces left jsr CheckToActivateZorlonCannon dec yarLeftBounces ; reduce times Yar bounces left beq .dontMoveYar .setYarHorizontalPosition stx yarHorizPos SetYarAnimationState bit yarStatus ; check Yar's status bmi CheckToActivateSwirl ; branch if losing life lda #3 and gameTimer ; see if game timer is divisible by 4 beq FlapYarWings ; flap Yar's wings every 4th frame ldx yarMoving ; check to see if Yar moving beq CheckToActivateSwirl ; branch if Yar not moving cmp #2 bne CheckToActivateSwirl FlapYarWings lda yarGraphicPtrs ; get LSB pointer to Yar graphics bit kernelStatus ; check Yar wing flapping state bvc .flapYarWingsDown ; branch if Yar wings down clc adc #5 * H_YAR bne .setYarFlapAnimation ; unconditional branch .flapYarWingsDown sec sbc #5 * H_YAR .setYarFlapAnimation sta yarGraphicPtrs ; set new Yar sprite pointer lda kernelStatus eor #YAR_FLAP_UP ; flip yar's wing flapping state sta kernelStatus CheckToActivateSwirl bit explosionStatus ; check explosion status bvs .jmpToCheckUpdateQotileMissile; branch if reducing explosion zone lda qotileStatus ; get Qotile status bmi AnimateSwirl ; branch if Swirl active tay ; move Qotile status to y lda gameState ; get current game state bmi .jmpToCheckUpdateQotileMissile; branch if game not in progress ldx qotileColor cpx #RED+4 beq .activateSwirl lda #SWIRL_TRIPLE_FREQ bit gameState beq .jmpToCheckUpdateQotileMissile; branch if Swirl not firing at triple freq cpx #BLUE+6 beq .activateSwirl cpx #DK_GREEN+8 bne .jmpToCheckUpdateQotileMissile .activateSwirl tya ; move Qotile status to accumulator ora #SWIRL_ACTIVE sta qotileStatus ; set flag to show Swirl activated StartSwirlAnimation IF COMPILE_VERSION = NTSC lda #>SwirlSprites sta qotileGraphicPtrs+1 ENDIF lda #YarSprites sta yarGraphicPtrs+1 lda #%11110000 ora kernelStatus sta kernelStatus lda #SUPPRESS_YAR_SHOT | SOLID_SHIELD and qotileStatus sta qotileStatus lda #$E2 sta COLUPF sta RESMP1 ; lock Yar missile to Yar sprite lda #0 sta explosionStatus ; clear explosion status bits sta yarStatus ; clear Yar status bits sta COLUBK ; color background BLACK lda #5 sta shieldVertPos sta gameTimer clc adc #53 sta qotileVertPos jmp VerticalSync DetermineShieldCellRemoved lda tempHorizPos sec sbc #XMAX-32 ; Shield is last PF2 of non-reflective PF bcc .notRemovingShieldCell lsr ; divide value by 4 for bit masking index lsr ; (i.e. PF is 4 pixel res) tax ; set bit masking index lda tempVertPos ; get vertical position of item sec sbc shieldVertPos ; subtract the shield's vertical position bcc .notRemovingShieldCell ; don't remove cell if out of range lsr ; divide the value by 8 (i.e. height of lsr ; shield cell) lsr cmp #16 bpl .notRemovingShieldCell sta dividedBy8 ; save value lda #NUM_SHIELD_BYTES sec sbc dividedBy8 tay RemoveShieldCell lda currentShieldGraphics,y ; get the shield graphic value and ShieldCellMasking,x beq .notRemovingShieldCell ; branch if cell already removed eor #$FF ; flip the bits and currentShieldGraphics,y ; and value to remove cell from shield sta currentShieldGraphics,y stx saveX ; save off x and y register values sty saveY lda #DESTROY_CELL_POINT_VALUE ldy #2 jsr IncrementScore ; increment score for removing sheild cell ldx saveX ; restore x and y values ldy saveY sec rts .notRemovingShieldCell clc ; clear carry to show cell wasn't removed rts CheckToActivateZorlonCannon lda gameState ; get current game state and #GAME_SELECTION_MASK ; mask values to keep game selection lsr ; divide game selection by 4 lsr adc #0 ror eor yarLeftBounces ror bcs .doneZorlonCannonActivation bit zorlonCannonStatus bvs .doneZorlonCannonActivation ; branch if Zorlon Cannon active lda #ZORLON_CANNON_ACTIVE ; set flag to activate Zorlon Cannon ora zorlonCannonStatus sta zorlonCannonStatus lda #~ZORLON_CANNON_BOUNCED and gameBoardStatus ; clear Zorlon Cannon bounced state sta gameBoardStatus lda trons ; get number of trons clc ; huh??? sbc #5-1 ; reduce tron count by 5 -- carry clear sta trons .doneZorlonCannonActivation rts SwapPlayerData lda lives ; get number of lives ldy #3 ; set index to point to nubmer of lives sta (playerScorePtr),y ; store lives in MSB of last score byte bit gameBoardStatus bvc .doneSwapPlayerData ; branch if not a two player game lda #